Spring5
导航
概述
Spring是一个轻量级,非入侵式的控制反转(IOC)和面向切面(AOP)编程的免费框架。
此话一出,我当时就只看得懂两个字:免费!
好吧,我们先看看什么是IoC
一、IoC(Inversion of Control)
1.1 什么是IoC
控制反转:我理解的控制反转就是控制权反转。
说理论难理解,直接上代码就明白了,我们先来模拟一下很久以前是怎么开发的:
先创建一个maven项目,然后目录结构为maven的经典结构:
假设有一个实体类User,依次创建Dao层和Service的接口和实现类。
UserDao:
public interface UserDao {
List<User> getAllUsers();
}
UserDaoImpl:
public class UserDaoImpl implements UserDao {
@Override
public List<User> getAllUsers() {
System.out.println("从数据库获取数据");
return null;
}
}
UserService:
public interface UserService {
List<User> getAllUsers();
}
UserServiceImpl:
public class UserServiceImpl implements UserService {
private UserDao userImpl = new UserDaoImpl();
@Override
public List<User> getAllUsers() {
userImpl.getAllUsers();
return null;
}
}
测试:
@Test
public void test01() {
UserService userService = new UserServiceImpl();
userService.getAllUsers();
}
输出:
现在思考一下,假如我们现在有了新的需求,如模拟从不同数据库中获取数据,于是我们又写了一个dao层的实现类:
public class UserDaoMysqlImpl implements UserDao {
@Override
public List<User> getAllUsers() {
System.out.println("从MySQL中获取数据");
return null;
}
}
如果我们需要使用它,那么还需要修改service层:
public class UserServiceImpl implements UserService {
private UserDao userImpl = new UserDaoMysqlImpl();
@Override
public List<User> getAllUsers() {
userImpl.getAllUsers();
return null;
}
}
测试:
@Test
public void test01() {
UserService userService = new UserServiceImpl();
userService.getAllUsers();
}
结果:
那要是我们再增加一个需求,模拟从Oracle中获取数据:
public class UserDaoOracleImpl implements UserDao {
@Override
public List<User> getAllUsers() {
System.out.println("从Oracle中获取数据");
return null;
}
}
那么还是一样的要去修改service层…………
然后再多一个需求…………
这样就很麻烦,这个时候是程序主动创建对象,控制权在程序员手中,耦合度很高。
解决办法:
给service层添加一个set方法,让程序被动接受对象,控制权就在用户手中了:
UerService:
public interface UserService {
List<User> getAllUsers();
void setUserImpl(UserDao userImpl);
}
UserServiceImpl:
public class UserServiceImpl implements UserService {
private UserDao userImpl = new UserDaoImpl();
public void setUserImpl(UserDao userImpl) {
this.userImpl = userImpl;
}
@Override
public List<User> getAllUsers() {
userImpl.getAllUsers();
return null;
}
}
测试:
public class TestDemo {
@Test
public void test01() {
UserService userService = new UserServiceImpl();
userService.setUserImpl(new UserDaoOracleImpl());
userService.getAllUsers();
}
}
结果:
新增再多功能都不怕了,这个时候我们想set什么就set什么:
public class UserDaoHeadImpl implements UserDao {
@Override
public List<User> getAllUsers() {
System.out.println("从脑子里中获取数据");
return null;
}
}
public class TestDemo {
@Test
public void test01() {
UserService userService = new UserServiceImpl();
userService.setUserImpl(new UserDaoHeadImpl());
userService.getAllUsers();
}
}
这就是控制反转的一种实现方式DI(依赖注入)。
1.2 初识Spring
1.3 Hello Spring
我们先看看如何用用Spring写一个Hello Spring,再看看如何利用Spring简化上一节的操作。
-
导包:
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.3.19</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.16.20</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13.2</version> <scope>test</scope> </dependency>
-
创建一个Hello类,给他一个set方法(必须要,依赖注入就是靠set方法)
@Data public class Hello { private String str; }
-
写 XML 的配置元数据
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="hello" class="vip.yangsf.entity.Hello"> <property name="str" value="Hello Spring"/> </bean> </beans>
-
使用
@Test public void test01() { // 获取Spring的上下文对象 ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); // 从Spring容器中获取对象 Hello hello = (Hello) context.getBean("hello"); System.out.println(hello.getStr()); }
结果:
这就是Spring最简单的使用。
来看看怎么优化第一节的程序:
1,2步不用变,只需要把这个程序套到Spring里面去就行了。
-
编写xml配置元数据
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 这里相当于创建了两个对象 --> <bean id="mysqlImpl" class="vip.yangsf.dao.user.impl.UserDaoMysqlImpl"/> <bean id="oracleImpl" class="vip.yangsf.dao.user.impl.UserDaoOracleImpl"/> <bean id="userServiceImpl" class="vip.yangsf.service.user.impl.UserServiceImpl"> <!-- ref引用容器中的对象 --> <property name="userImpl" ref="oracleImpl"/> </bean> </beans>
-
使用
@Test public void test02() { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); UserServiceImpl userServiceImpl = (UserServiceImpl) context.getBean("userServiceImpl"); userServiceImpl.getAllUsers(); }
结果:
如果要换成MySQL,只需要在配置文件中将第12行左右property中ref中的值改为mysqlImpl即可。
降低了耦合度,我们的程序不需要再做改变,只需要修改配置文件即可,对象由Spring来创建,管理,装配!程序由主动创建对象变为被动接收对象,体现了IoC的思想。
1.4 IoC创建对象的方式
我们做点试验,把刚刚的Hello类改一下:
@Data
public class Hello {
private String str;
public Hello() {
System.out.println("无参构造 Hello被创建");
}
public Hello(String str) {
System.out.println("有参构造 Hello被创建");
this.str = str;
}
}
再来一个Hello02类:
@Data
public class Hello2 {
private String str;
public Hello2() {
System.out.println("无参构造 Hello被创建");
}
}
applicationContext.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="hello" class="vip.yangsf.entity.Hello">
<constructor-arg index="0" value="hello01"/>
</bean>
<bean id="hello02" class="vip.yangsf.entity.Hello2"/>
</beans>
测试:
@Test
public void test01() {
// 获取Spring的上下文对象
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// 从Spring容器中获取对象
Hello hello = (Hello) context.getBean("hello");
Hello hello1 = (Hello) context.getBean("hello");
System.out.println(hello.getStr());
System.out.println(hello == hello1);
}
结果:
结论:
-
默认使用无参构造,并且在get其中一个bean时,所有的bean实例都会创建。
-
使用有参构造来创建(三种方式)
-
通过参数位置(下标)来创建
<bean id="hello" class="vip.yangsf.entity.Hello"> <constructor-arg index="0" value="hello01"/> </bean>
-
通过参数类型创建
<bean id="hello" class="vip.yangsf.entity.Hello"> <constructor-arg type="java.lang.String" value="hello01"/> </bean>
-
通过参数名创建
<bean id="hello" class="vip.yangsf.entity.Hello"> <property name="str" value="hello"/> </bean>
-
-
一个bean不管get多少次,在容器中都只有一个。
1.5 Spring配置
还是刚刚的程序
-
取别名:
alias:
<alias name="hello" alias="abc"/>
使用:
Hello hello = (Hello) context.getBean("abc");
name:
不仅可以用alias标签取别名,还可以用bean的name属性取别名,一次可以取多个,可以用空格,逗号,分号分隔
<bean id="hello" class="vip.yangsf.entity.Hello" name="h1 h2,h3;h4"/>
-
import:
将其他配置文件引入。
虽然classPathXmlApplicationContext可以一次读取多个配置文件,但是把所有的beans导入一个applicationContext.xml更方便:
beans.xml:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="hello02" class="vip.yangsf.entity.Hello2"/> <alias name="hello" alias="abc"/> </beans>
applicationContext.xml:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="hello" class="vip.yangsf.entity.Hello" name="h1 h2,h3;h4"> <constructor-arg type="java.lang.String" value="hello01"/> </bean> <import resource="beans.xml"/> </beans>
1.6 依赖注入
Dependency Injection
实现IoC思想的一种方式。
1.6.1 测试环境搭建
我们现在已经会了一点,但是在复杂的环境下,还有很多需要注意的点。
-
创建一个maven项目,把包建好。
-
创建一个Student实体类,需要设置set方法。
@Data public class Student { private String id; private String name; private Address address; private String[] books; private List<String> hobbies; private Map<String, String> scores; private Set<String> friends; private Properties info; } @Data class Address { private String address; }
1.6.2 构造器注入
也就是1.4说过的那三种方法,不再赘述。
1.6.3 set方法注入(重点)
简单的set注入我们已经会了:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="student" class="vip.yangsf.entity.Student">
<property name="name" value="set方法注入"/>
</bean>
</beans>
测试:
@Test
public void test01() {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Student student = context.getBean("student", Student.class);
System.out.println(student.getName());
}
结果:
那Map、Set、Address……这些怎么注入呢?
直接看答案:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="student" class="vip.yangsf.entity.Student">
<property name="id">
<null/>
</property>
<property name="name" value="set方法注入"/>
<property name="address" ref="address"/>
<property name="books">
<array>
<value>语文书</value>
<value>数学书</value>
<value>历史书</value>
</array>
</property>
<property name="hobbies">
<list>
<value>敲代码</value>
<value>玩游戏</value>
<value>打篮球</value>
</list>
</property>
<property name="scores">
<map>
<entry key="语文" value="120"/>
<entry key="数学" value="150"/>
<entry key="历史" value="90"/>
</map>
</property>
<property name="friends">
<set>
<value>张三</value>
<value>李四</value>
<value>王五</value>
</set>
</property>
<property name="info">
<props>
<prop key="职务">班长</prop>
<prop key="性别">男</prop>
</props>
</property>
</bean>
<bean id="address" class="vip.yangsf.entity.Address">
<property name="address" value="四川省"/>
</bean>
</beans>
测试:
@Test
public void test01() {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Student student = context.getBean("student", Student.class);
System.out.println(student);
}
结果:
Student(id=null, name=set方法注入, address=Address(address=四川省), books=[语文书, 数学书, 历史书], hobbies=[敲代码, 玩游戏, 打篮球], scores={语文=120, 数学=150, 历史=90}, friends=[张三, 李四, 王五], info={性别=男, 职务=班长})
1.6.4 拓展方式注入
使用之前要导入xml约束,如:
xmlns:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
p命名空间注入:
p指的就是property
拿之前的Hello类来做实验:
@Data
public class Hello {
private String str;
public Hello() {
System.out.println("无参构造 Hello被创建");
}
public Hello(String str) {
System.out.println("有参构造 Hello被创建");
this.str = str;
}
}
xml前后对比:
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 前: -->
<bean id="hello" class="vip.yangsf.entity.Hello">
<property name="str" value="hello"/>
</bean>
<!-- 后: -->
<bean id="hello" class="vip.yangsf.entity.Hello" p:str="hello"/>
</beans>
c命名空间注入:
c指的就是构造器constructor
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 前: -->
<bean id="hello" class="vip.yangsf.entity.Hello">
<property name="str" value="hello"/>
</bean>
<!-- 后: -->
<bean id="hello" class="vip.yangsf.entity.Hello" p:str="hello"/>
</beans>
可以测试一下
@Test
public void test01() {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Hello hello = context.getBean("hello", Hello.class);
System.out.println(hello.getStr());
}
1.7 bean作用域
看看官网的图:
-
singleton 单例模式:
Spring默认为单例模式,即这个bean对象只有一个
<bean id="hello" class="vip.yangsf.entity.Hello" c:str="hello" scope="singleton"/>
-
prototype 原型模式:
即每次get都会创建一个新的对象
<bean id="hello" class="vip.yangsf.entity.Hello" c:str="hello" scope="prototype"/>
其余的几个是在web开发中使用的,日后再议。
1.8 Bean自动装配
1.8.1 环境搭建
为了方便,就没有写规范:
类:
@Data
public class People {
private Dog dog;
private Cat cat;
private String name;
}
@Data
class Dog {
private String name;
}
@Data
class Cat {
private String name;
}
xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean class="vip.yangsf.entity.Dog" id="dog" p:name="dog"/>
<bean class="vip.yangsf.entity.Cat" id="cat" p:name="cat"/>
<bean class="vip.yangsf.entity.People" id="people"
p:name="people" p:cat-ref="cat" p:dog-ref="dog"/>
</beans>
test:
@Test
public void test() {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
People people = context.getBean("people", People.class);
System.out.println(people);
}
结果:
People(dog=Dog(name=dog), cat=Cat(name=cat), name=people)
1.8.1 自动装配xml配置
刚刚的代码,People里面还有Dog和Cat,一个个设置值太麻烦,于是,自动装配出现了。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean class="vip.yangsf.entity.Dog" id="dog" p:name="dog"/>
<bean class="vip.yangsf.entity.Cat" id="cat" p:name="cat"/>
<!-- <bean class="vip.yangsf.entity.People" id="people"-->
<!-- p:name="people" p:cat-ref="cat" p:dog-ref="dog"/>-->
<bean class="vip.yangsf.entity.People" id="people" autowire="byName">
<property name="name" value="autowiredByName"/>
</bean>
</beans>
通过容器中对象的id自动装配,但id必须与被装配对象中的set后面的名字相同才行。
还有第二种方式,通过类型自动装配:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean class="vip.yangsf.entity.Dog" id="dog" p:name="dog"/>
<bean class="vip.yangsf.entity.Cat" id="cat" p:name="cat"/>
<!-- <bean class="vip.yangsf.entity.People" id="people"-->
<!-- p:name="people" p:cat-ref="cat" p:dog-ref="dog"/>-->
<!-- <bean class="vip.yangsf.entity.People" id="people" autowire="byName">-->
<!-- <property name="name" value="autowiredByName"/>-->
<!-- </bean>-->
<bean class="vip.yangsf.entity.People" id="people" autowire="byType">
<property name="name" value="autowiredByName"/>
</bean>
</beans>
通过容器中对象的类型自动装配,但类型必须与被装配对象中的属性类型相同才行。
用注解更方便。
二、 使用注解
2.1 注解实现自动装配
接着上回,我们还可以使用注解进行自动装配。
步骤:
-
导入约束
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <context:annotation-config/> </beans>
-
配置注解
<context:annotation-config/>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
<bean id="dog" class="vip.yangsf.entity.Dog" p:name="dog"/>
<bean id="cat" class="vip.yangsf.entity.Cat" p:name="cat"/>
<bean id="people" class="vip.yangsf.entity.People" p:name="people"/>
</beans>
-
写注解:
注解可以写在属性上,可以写在setter上,还可以写在构造方法上,一般写在属性上:
@Data public class People { @Autowired private Dog dog; @Autowired private Cat cat; private String name; }
-
test
@Test public void test() { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); People people = context.getBean("people", People.class); System.out.println(people); }
输出:
People(dog=Dog(name=dog), cat=Cat(name=cat), name=people)
补充:
-
@Autowired有一个参数required,是布尔值,@Autowired(required=true)默认为true
为true时表示注入时该bean必须存在,不然报错
为false时即使不存在也不报错
-
会先通过类型搜索bean,不行就通过id搜索bean
-
配合@Qualifier()使用:
@Data public class People { @Autowired @Qualifier("dog123") private Dog dog; @Autowired private Cat cat; private String name; }
表示通过byName的方式自动装配,Qualifier里面的值对应beans里面的id。
-
还可以用@Resource注解来自动装配,效果一样,但@Resource是先byName再byType,还可以指定name或者type或者都指定。
@Data public class People { @Resource(name = "dog123",type = Dog.class) private Dog dog; @Autowired private Cat cat; private String name; }
2.2 使用注解开发
-
先看看结构:
-
application.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <context:component-scan base-package="vip.yangsf"/> </beans>
扫描vip/yangsf包下的所有component以及隐式启用
<context:annotation-config/>
-
把类放入beans:
之前的做法(User为例)
<bean id="user" class="vip.yangsf.entity.User"> <property name="id" value="123"/> <property name="name" value="root"/> <property name="pwd" value="123456"/> </bean>
使用注解(get/set方法自行添加):
@Component public class User { @Value("123") private int id; @Value("root") private String name; @Value("123456") private String pwd; }
@Component的作用就是,
<bean id="user" class="vip.yangsf.entity.User">
相同作用不同语义的还有三个
dao层的@Repository
@Repository public class UserDaoImpl implements UserDao { @Override public List<User> getUsers() { return null; } }
service层的@Service
@Service public class UserServiceImpl implements UserService { @Override public List<User> getUsers() { return null; } }
controller层的@Controller
@Controller public class UserController { }
作用都是把类注册到Spring中。
-
作用域
@Component @Scope("singleton") @Data public class User { @Value("123") private int id; @Value("root") private String name; @Value("123456") private String pwd; }
用@Scope(“singleton”)设置单例模式,当然也可以@Scope(“prototype”)。
2.3 注解与xml
- xml更万能,易于维护,但比较繁琐。
- 注解更方便,但不够万能。
最佳实践:
- xml用来管理bean
- 注解用来注入属性(@Value)
2.4 使用Java配置xml
完全可以用一些注解来代替xml的配置。
把刚刚的xml删掉,换成配置类。
-
新建一个Config类:
@Configuration @ComponentScan("vip.yangsf") public class MyConfig { @Bean public String getString() { return "hello annotation"; } }
Configuration表示这是一个配置类,并且这个注解还有和Component一样的效果,会在Spring中注册一个bean。
@ComponentScan(“xxx”)会扫描xxx路径下的Component,相当于
<context:component-scan base-package="vip.yangsf"/>
@Bean就是在Spring中注册了一个bean ,id就是方法名,class就是返回类型。
当然,既然要替代xml,其他的注解也能用,比如@import等等
-
测试
@Test public void test() { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class); System.out.println(context.getBean("user")); System.out.println(context.getBean("getString")); }
获取Spring上下文对象由ClassPathXmlApplicationContext变成了AnnotationConfigApplicationContext,其他的没啥区别
结果:
三、 代理模式
不直接访问某个对象,而是通过代理类来提供某个对象的访问。相当于一个中介。
3.1 静态代理
代理类和委托类在运行前就写死了,就是静态代理。
模拟一个静态代理,需要以下几步:
-
抽象角色,一般为接口或者抽象类。
// 租房 public interface Rent { void rent(); }
-
真实角色,具体被代理的角色
public class Host implements Rent{ @Override public void rent() { System.out.println("房东卖房"); } }
-
代理角色,代理真实角色,在代理前后我们会对其做一些处理
public class Proxy implements Rent { Rent rent; // 默认代理自己 public Proxy() { this.rent = new Proxy(); } public Proxy(Rent rent) { this.rent = rent; } @Override public void rent() { seeHouse(); rent.rent(); hetong(); } public void seeHouse() { System.out.println("中介带你看房"); } public void hetong() { System.out.println("中介和你签合同"); } }
-
客户端访问
public class Client { public static void main(String[] args) { Rent host = new Host(); Proxy proxy = new Proxy(host); proxy.rent(); } }
整个过程我们没有接触到房东,一直都是和中介接触。
3.2 动态代理
动态代理就比刚刚的静态代理牛逼多了,静态代理只能写死了代理一个接口,而动态代理的代理类是动态生成的,可以代理一切。
我们可以根据不同的接口,生成不同的代理类。运用Java的反射机制,我们可以做到这一点:
-
抽象角色,被代理的接口,只能是接口。
还是先用刚刚的例子
// 租房 public interface Rent { void rent(); }
-
被代理的真实类
public class Host implements Rent { @Override public void rent() { System.out.println("房东卖房"); } }
-
代理类我们动态生成,如何根据不同的接口生成不同的代理类?
可以通过java.lang.reflect.Proxy类下的
方法来生成代理类。
ClassLoader loader:类加载器,用来加载被代理的类。
Class<?>[] interfaces:被代理对象的所有接口,即要生成的代理类需要实现的接口。
InvocationHandler h:调用的策略,需要传入一个实现了InvocationHandler接口的类,设置代理类调用方法的策略。
假如我要代理房东,那么房东的代理类这样生成:
Rent host = new Host(); Proxy.newProxyInstance(host.getClass().getClassLoader(),host.getClass().getInterfaces(), 策略??);
我们倒是知道了要用rent的类加载器来加载被代理的类,也知道代理类要实现Rent接口,但调用策略如何创建?
public class ProxyInvocationHandler implements InvocationHandler{ private Object target; public void setTarget(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object result = method.invoke(target, args); return result; } }
Object proxy:刚刚生成的代理类。
Method method:调用的方法。
Object[] args:调用的方法的参数。
target:被代理的对象。
于是:
Rent host = new Host(); ProxyInvocationHandler proxyInvocationHandler = new ProxyInvocationHandler(); proxyInvocationHandler.setTarget(host); // 有了策略过后 Rent proxy = Proxy.newProxyInstance(rent.getClass().getClassLoader(),rent.getClass().getInterfaces(), proxyInvocationHandler); //就获得了代理类
-
把获得代理类方法和调用策略类合并成一个工具类(完整代码):
public class ProxyInvocationHandler implements InvocationHandler { // 被代理的类 private Object target; // 动态设置被代理的类 public void setTarget(Object target) { this.target = target; } // 动态生成代理类 public Object getProxy() { return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this); } // 调用策略 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { log(method.getName()); Object result = method.invoke(target, args); return result; } // 添加一个自制日志 public void log(String s) { System.out.println("调用:" + s + " 方法。"); } }
-
客户端访问
public class Client { public static void main(String[] args) { Rent host = new Host(); ProxyInvocationHandler pih = new ProxyInvocationHandler(); pih.setTarget(host); Rent proxy = (Rent) pih.getProxy(); proxy.rent(); } }
结果:
3.3 动态代理模拟代理UserService(动态代理练习)
-
被代理的接口
public interface UserService { // 增 int addUser(); // 删 int delete(); // 改 int update(); // 查 List<User> select(); }
-
被代理的真实类
public class UserServiceImpl implements UserService{ @Override public int addUser() { System.out.println("增"); return 0; } @Override public int delete() { System.out.println("删"); return 0; } @Override public int update() { System.out.println("改"); return 0; } @Override public List<User> select() { System.out.println("查"); return null; } }
-
工具类不变,还是刚才那个
-
客户端访问
public class Client { public static void main(String[] args) { UserService userService = new UserServiceImpl(); ProxyInvocationHandler proxyInvocationHandler = new ProxyInvocationHandler(); proxyInvocationHandler.setTarget(userService); UserService proxy = (UserService) proxyInvocationHandler.getProxy(); proxy.addUser(); proxy.delete(); proxy.update(); proxy.select(); } }
结果:
有了代理模式的基础,走向AOP就容易的多。
四、AOP(Aspect Oriented Programming)
4.1 什么是AOP
面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
----百度百科
再来一波概念:
- Aspect:切面,即一个横跨多个核心逻辑的功能,或者称之为系统关注点;
- Joinpoint:连接点,即定义在应用程序流程的何处插入切面的执行;
- Pointcut:切入点,即一组连接点的集合;
- Advice:通知,指特定连接点上执行的动作;
- Introduction:引介,指为一个已有的Java对象动态地增加新的接口;
- Weaving:织入,指将切面整合到程序的执行流程中;
- Interceptor:拦截器,是一种实现通知的方式;
- Target Object:目标对象,即真正执行业务的核心逻辑对象;
- AOP Proxy:AOP代理,是客户端持有的通知后的对象引用。
直接懵逼,AOP概念就是很抽象,实际上很简单,就是刚刚实现的动态代理,AOP就是简化了动态代理。
我们先来一个不用Spring的:
就拿刚刚的UserService举例
被代理接口,实现类就是刚刚的UserService和UserServiceImpl,我们先手写一个传说中的切面(模拟):
public class AfterLog {
public void after() {
System.out.println("==== after ====");
}
}
public class AfterLog {
public void after() {
System.out.println("==== after ====");
}
}
再次手敲一个工具类:
public class ProxyInvocationHandler implements InvocationHandler {
// 被代理的类
private Object target;
// 为了方便就这样导入切面了
AfterLog afterLog = new AfterLog();
BeforeLog beforeLog = new BeforeLog();
// 动态设置被代理的类
public void setTarget(Object target) {
this.target = target;
}
// 动态生成代理类
public Object getProxy() {
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(), this);
}
// 调用策略
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
beforeLog.before();
Object result = method.invoke(target, args);
afterLog.after();
return result;
}
}
当我们运行时,调用方法前都会执行before() 调用完后都会执行 after()
我们把切面 切入了 切入点。
降低了代码的耦合度,核心业务不需要再做修改,添加功能只需横向开发,添加切面。
那用Spring呢?
4.2 Spring种使用AOP
工具类不再需要,service和其实现类不变,现在将我们的切面变成真正的Spring中的切面:
先导个包包:
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
通知(advice):五种通知方式:
于是:
方式一:使用Spring的API接口
public class AfterLog implements AfterReturningAdvice {
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println(target.getClass().getName() + "的" + method.getName() + "方法执行完毕,返回了:" + returnValue);
}
}
实现AfterReturningAdvice接口表示在调用方法后执行。
public class BeforeLog implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println(target.getClass().getName() + "的" + method.getName() + "方法被执行");
}
}
实现MethodBeforeAdvice接口表示在调用方法前执行。
然后去Spring上下文中注册:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="userService" class="vip.yangsf.service.impl.UserServiceImpl"/>
<bean id="beforeLog" class="vip.yangsf.log.BeforeLog"/>
<bean id="afterLog" class="vip.yangsf.log.AfterLog"/>
<aop:config>
<aop:pointcut id="pointcut" expression="execution(* vip.yangsf.service.impl.UserServiceImpl.*(..))"/>
<aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
<aop:advisor advice-ref="beforeLog" pointcut-ref="pointcut"/>
</aop:config>
</beans>
注意点:
- 引入aop的xml约束
- 注册bean不必多说
- aop配置 pointcut 表示切入点,也就是可以被切面切入的地方, expression后面表示可以被切入的方法,第一个*表示返回值,然后是类,然后第二个*是方法名,后面的(…)表示参数。
- advisor表示环绕,pointcut-ref引入到哪个切入点,advice-ref引入切面
方式二:自定义切面类实现AOP
定义一个切面:
public class DiyPointcut {
public void before() {
System.out.println("======before======");
}
public void after() {
System.out.println("======after======");
}
}
xml中配置:
<bean class="vip.yangsf.diy.DiyPointcut" id="diyPointcut"/>
<aop:config>
<aop:aspect ref="diyPointcut">
<aop:pointcut id="point" expression="execution(* vip.yangsf.service.impl.UserServiceImpl.*(..))"/>
<aop:before method="before" pointcut-ref="point"/>
<aop:after method="after" pointcut-ref="point"/>
</aop:aspect>
</aop:config>
先注册切面,然后配置切面。
测试代码和刚刚一样:
虽然第二种实现获取方法信息那些不好获取,但是更容易理解。
手动配置了这俩例后,相信已经对AOP有一定的理解了,下一步,注解!
4.3 使用注解实现AOP
XML已经配置烦了,来用注解放双手。
包还是刚才那个包,不用导其他的了。
先来观察一下我们的配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="userService" class="vip.yangsf.service.impl.UserServiceImpl"/>
<bean id="beforeLog" class="vip.yangsf.log.BeforeLog"/>
<bean id="afterLog" class="vip.yangsf.log.AfterLog"/>
<aop:config>
<aop:pointcut id="pointcut" expression="execution(* vip.yangsf.service.impl.UserServiceImpl.*(..))"/>
<aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
<aop:advisor advice-ref="beforeLog" pointcut-ref="pointcut"/>
</aop:config>
<bean class="vip.yangsf.diy.DiyPointcut" id="diyPointcut"/>
<bean class="vip.yangsf.diy.AnnotationPointcut" id="annotationPointcut"/>
<aop:config>
<aop:aspect ref="diyPointcut">
<aop:before method="before" pointcut-ref="pointcut"/>
<aop:after method="after" pointcut-ref="pointcut"/>
</aop:aspect>
</aop:config>
</beans>
我们有两个切面和一个切入点,两个切面,现在我们用注解做这件事情:
-
定义一个config类:
@Configuration @EnableAspectJAutoProxy @ComponentScan("vip.yangsf") public class MyConfig { }
@Configuration:表示这是一个配置类
@EnableAspectJAutoProxy:开启注解版的AOP功能
@ComponentScan(“vip.yangsf”):扫描这个包下面的所有@Component
-
定义切面:
先观察原来的切面:
public class BeforeLog implements MethodBeforeAdvice { @Override public void before(Method method, Object[] args, Object target) throws Throwable { System.out.println(target.getClass().getName() + "的" + method.getName() + "方法被执行"); } }
public class AfterLog implements AfterReturningAdvice { @Override public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable { System.out.println(target.getClass().getName() + "的" + method.getName() + "方法执行完毕,返回了:" + returnValue); } }
public class DiyPointcut { public void before() { System.out.println("======before======"); } public void after() { System.out.println("======after======"); } }
然后我们进行改造:
有以下注解可以选择:
- @Before:这种拦截器先执行拦截代码,再执行目标代码。如果拦截器抛异常,那么目标代码就不执行了;
- @After:这种拦截器先执行目标代码,再执行拦截器代码。无论目标代码是否抛异常,拦截器代码都会执行;
- @AfterReturning:和@After不同的是,只有当目标代码正常返回时,才执行拦截器代码;
- @AfterThrowing:和@After不同的是,只有当目标代码抛出了异常时,才执行拦截器代码;
- @Around:能完全控制目标代码是否执行,并可以在执行前后、抛异常后执行任意拦截代码,可以说是包含了上面所有功能。
使用Around最省事:
@Aspect @Component public class DiyPointcut { @Around("execution(* vip.yangsf.service.impl.UserServiceImpl.*(..))") public Object around(ProceedingJoinPoint pjp) throws Throwable { System.out.println(pjp.getTarget().getClass().getName() + "的" + pjp.getSignature().getName() + "方法被执行"); System.out.println("======before======"); // 执行方法,获得返回值 Object proceed = pjp.proceed(); System.out.println("======after======"); System.out.println(pjp.getTarget().getClass().getName() + "的" + pjp.getSignature().getName() + "方法执行完毕,返回了:" + proceed); return proceed; } }
测试访问一下:
@Test public void test01() { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class); UserService userServiceImpl = context.getBean("userServiceImpl", UserService.class); userServiceImpl.addUser(); }
结果:
成功!
五、Spring整合Mybatis
这里介绍两种方式:xml整合Mybatis和注解整合Mybatis
首先要导包,根据自己的版本导入相应的包:
我的环境:
jdk:8
mysql:8.0.28
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.9</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.19</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.7</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.9.1</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.19</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.20</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
整合mybatis很重要的一个包:mybatis-spring 这里我是Spring5所以导入mybatis-spring2.0以上版本。
先来看看不用spring,mybatis应该怎么写:
-
首先有个表和实体类,使用mybatis还要一个mybatis的配置文件(官网就有模板):
mybatis-config.xml:
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <properties resource="db.properties"/> <typeAliases> <package name="vip.yangsf.pojo"/> </typeAliases> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="${driver}"/> <property name="url" value="${url}"/> <property name="username" value="${username}"/> <property name="password" value="${pwd}"/> </dataSource> </environment> </environments> <mappers> <package name="vip.yangsf.mapper"/> </mappers> </configuration>
user表:
实体类:
@Data public class User { private int id; private String name; private String pwd; }
-
然后写接口
public interface UserMapper { List<User> getUsers(); }
-
写UserMapper.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="vip.yangsf.mapper.UserMapper"> <select id="getUsers" resultType="vip.yangsf.pojo.User"> select * from mybatis.user </select> </mapper>
-
测试一下
@Test public void test01() throws IOException { SqlSession sqlSession = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader("mybatis-config.xml")).openSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); mapper.getUsers().forEach(System.out::println); }
通过SqlSessionFactoryBuilder来读取配置文件创建SqlSession。
结果:
5.1 xml整合Mybatis
我们将刚刚mybatis的配置移到Spring中,怎么移呢?
-
表和实体类肯定不变,我们先让mybatis-config.xml消失(可以留下settings)。mybatis-config.xml被SqlSessionFactoryBuilder拿来创建SqlSessionFactory,所以我们可以直接在Spring中利用SqlSessionFactoryBean来创建SqlSessionFactory:
<bean id="datasource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/> <property name="username" value="root"/> <property name="password" value="123456"/> <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8"/> </bean> <bean class="org.mybatis.spring.SqlSessionFactoryBean" id="sqlSessionFactory"> <property name="dataSource" ref="datasource"/> <property name="configLocation" value="classpath:mybatis-config.xml"/> <property name="mapperLocations" value="classpath:vip/yangsf/mapper/*.xml"/> <property name="typeAliasesPackage" value="vip.yangsf.pojo"/> </bean>
datasource是数据源的配置,然后在SqlSessionFactoryBean中引用它,可以mybatis配置文件中的干掉这一坨:
<dataSource type="POOLED"> <property name="driver" value="${driver}"/> <property name="url" value="${url}"/> <property name="username" value="${username}"/> <property name="password" value="${pwd}"/> </dataSource>
然后SqlSessionFactoryBean中的configLocation的属性,可以引用mybatis的配置文件。
一般在mybatis的配置文件中留下settings:
<settings> <setting name="logImpl" value="STDOUT_LOGGING"/> </settings>
mapperLocations表示mapper所在的地方,可以干掉
<mappers> <package name="vip.yangsf.mapper"/> </mappers>
typeAliasesPackage给某个包下的类取别名。所以mybatis的配置文件可能只剩下了:
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <settings> <setting name="logImpl" value="STDOUT_LOGGING"/> </settings> </configuration>
-
SqlSessionFactory有了,我们就可以获得SqlSession了,在spring中,可以通过SqlSessionTemplate来创建SqlSession(spring中SqlSessionTemplate就是SqlSession)。
<bean class="org.mybatis.spring.SqlSessionTemplate" id="sqlSessionTemplate"> <constructor-arg name="sqlSessionFactory" ref="sqlSessionFactory"/> </bean>
传入一个sqlSessionFactory来创建SqlSession。
完整Spring配置:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <bean id="datasource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/> <property name="username" value="root"/> <property name="password" value="123456"/> <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8"/> </bean> <bean class="org.mybatis.spring.SqlSessionFactoryBean" id="sqlSessionFactory"> <property name="dataSource" ref="datasource"/> <property name="configLocation" value="classpath:mybatis-config.xml"/> <property name="mapperLocations" value="classpath:vip/yangsf/mapper/*.xml"/> <property name="typeAliasesPackage" value="vip.yangsf.pojo"/> </bean> <bean class="org.mybatis.spring.SqlSessionTemplate" id="sqlSessionTemplate"> <constructor-arg name="sqlSessionFactory" ref="sqlSessionFactory"/> </bean> </beans>
-
测试一下
@Test public void test01() throws IOException { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); SqlSessionTemplate sqlSession = context.getBean("sqlSessionTemplate", SqlSessionTemplate.class); UserMapper mapper = sqlSession.getMapper(UserMapper.class); mapper.getUsers().forEach(System.out::println); }
结果:
原理都是一样的。
5.2 注解整合Mybatis
思路都是一样的,表和实体类、mapper.xml不变,新建一个config类来注册bean。
-
获得SqlSessionFactory
首先要获得datasource:
@Bean public DriverManagerDataSource datasource() { DriverManagerDataSource dataSource = new DriverManagerDataSource(); dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver"); dataSource.setUsername("root"); dataSource.setPassword("123456"); dataSource.setUrl("jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8"); return dataSource; }
然后是
@Bean public SqlSessionFactory sqlSessionFactory() throws Exception { SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean(); factoryBean .setDataSource(datasource()); factoryBean.setConfigLocation(new PathMatchingResourcePatternResolver().getResource("classpath:mybatis-config.xml")); factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:com/yangsf/mapper/*.xml")); factoryBean.setTypeAliasesPackage("com.yangsf.entity"); return factoryBean.getObject(); }
-
获得SqlSessionTemplate
@Bean public SqlSessionTemplate sqlSession() throws Exception { return new SqlSessionTemplate(sqlSessionFactory()); }
-
完整配置类:
@Configuration @ComponentScan("com.yangsf") public class MyConfig { @Bean public DriverManagerDataSource datasource() { DriverManagerDataSource dataSource = new DriverManagerDataSource(); dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver"); dataSource.setUsername("root"); dataSource.setPassword("123456"); dataSource.setUrl("jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8"); return dataSource; } @Bean public SqlSessionFactory sqlSessionFactory() throws Exception { SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean(); factoryBean .setDataSource(datasource()); factoryBean.setConfigLocation(new PathMatchingResourcePatternResolver().getResource("classpath:mybatis-config.xml")); factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:com/yangsf/mapper/*.xml")); factoryBean.setTypeAliasesPackage("com.yangsf.entity"); return factoryBean.getObject(); } @Bean public SqlSessionTemplate sqlSession() throws Exception { return new SqlSessionTemplate(sqlSessionFactory()); } }
-
使用
@Test public void test01() { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class); SqlSession sqlSession = context.getBean("sqlSession", SqlSession.class); UserMapper mapper = sqlSession.getMapper(UserMapper.class); mapper.getUsers().forEach(System.out::println); }
结果:
六、事务
在执行sql时,有些业务要求一系列操作必须全部执行,不能只执行一部分。把多条语句作为一个整体进行操作,就是事务。
事务的ACID原则:
- A:Atomic,原子性,将所有SQL作为原子工作单元执行,要么全部执行,要么全部不执行;
- C:Consistent,一致性,事务完成后,所有数据的状态都是一致的,即A账户只要减去了100,B账户则必定加上了100;
- I:Isolation,隔离性,如果有多个事务并发执行,每个事务作出的修改必须与其他事务隔离;
- D:Duration,持久性,即事务完成后,对数据库数据的修改被持久化存储。
Spring中怎么开启事务呢?
有多种方式,这里说两种。
首先,说说注解
方式一:注解
public interface UserMapper {
int addUser(User user);
int deleteUser(@Param("id") int id);
List<User> getUsers();
List<User> selectUser();
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.lq.mapper.UserMapper">
<select id="getUsers" resultType="com.lq.pojo.User">
select * from mybatis.user
</select>
<insert id="addUser">
insert into mybatis.user(id, name, pwd) VALUES (#{id}, #{name}, #{pwd})
</insert>
<delete id="deleteUser">
deletes from mybatis.user where id=#{id}
</delete>
</mapper>
这里delete语句故意写错,让事务回滚。
@Service
public class UserMapperImpl implements UserMapper{
@Autowired
private SqlSessionTemplate sqlSession;
public void setSqlSession(SqlSessionTemplate sqlSession) {
this.sqlSession = sqlSession;
}
@Override
public int addUser(User user) {
return sqlSession.getMapper(UserMapper.class).addUser(user);
}
@Override
public int deleteUser(int id) {
return sqlSession.getMapper(UserMapper.class).deleteUser(id);
}
@Override
public List<User> getUsers() {
return sqlSession.getMapper(UserMapper.class).getUsers();
}
@Override
public List<User> selectUser() {
addUser(new User(123, "abb", "123456"));
deleteUser(3);
return getUsers();
}
}
实现类,模拟service层
一些要注册的bean:
@Configuration
@ComponentScan("com.lq")
public class LqConfig {
@Bean
public DriverManagerDataSource datasource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUsername("root");
dataSource.setPassword("123456");
dataSource.setUrl("jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8");
return dataSource;
}
@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean .setDataSource(datasource());
factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:com/lq/mapper/*.xml"));
factoryBean.setTypeAliasesPackage("com.lq.pojo");
return factoryBean.getObject();
}
@Bean
public SqlSessionTemplate sqlSession() throws Exception {
return new SqlSessionTemplate(sqlSessionFactory());
}
@Bean
public DataSourceTransactionManager transactionManager() {
return new DataSourceTransactionManager(datasource());
}
}
最重要的就是最后一个
DataSourceTransactionManager
然后再spring配置中开启注解支持(需要导入tx的xml约束)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.lq"/>
<tx:annotation-driven/>
</beans>
如果不开启事务:
@Test
public void lqTest01() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring-transaction.xml");
UserMapper mapper = context.getBean("userMapperImpl", UserMapper.class);
List<User> users = mapper.selectUser();
users.forEach(System.out::println);
}
调用selectUser方法,即使delete方法报错,delete方法之前的add方法也会执行成功,数据会成功添加。
这个时候只需要在UserMapperImpl中的selectUser方法上添加一个@Transactional注解。
@Transactional
public List<User> selectUser() {
addUser(new User(123, "abb", "123456"));
deleteUser(3);
return getUsers();
}
完美解决问题。
方式二: xml配置
把@Configuration配置类中的那些bean在xml中配置无须多说。关键是如何用xml代替@Transactional注解:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="datasource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8"/>
</bean>
<bean class="org.mybatis.spring.SqlSessionFactoryBean" id="sqlSessionFactory">
<property name="dataSource" ref="datasource"/>
<property name="mapperLocations" value="classpath:com/lq/mapper/*.xml"/>
<property name="typeAliasesPackage" value="com.lq.pojo"/>
</bean>
<bean class="org.mybatis.spring.SqlSessionTemplate" id="sqlSessionTemplate">
<constructor-arg name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="datasource"/>
</bean>
<bean class="com.lq.mapper.UserMapperImpl" id="userMapperImpl">
<property name="sqlSession" ref="sqlSessionTemplate"/>
</bean>
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="txPointCut" expression="execution(* com.lq.mapper.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
</aop:config>
</beans>
最重要的是这段
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="txPointCut" expression="execution(* com.lq.mapper.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
</aop:config>
其实还是aop的思想,在不改变原来代码的情况下,开启事务。