本文最后更新于:1 年前
Spring-01
1.Spring简介
Spring是一个开源框架,它由[Rod Johnson](https://baike.baidu.com/item/Rod Johnson)创建。它是为了解决企业应用开发的复杂性而创建的。
目前是JavaEE开发的灵魂框架。他可以简化JavaEE开发,可以非常方便整合其他框架,无侵入的进行功能增强。
Spring的核心就是 控制反转(IoC)和面向切面(AOP) 。
2.IOC控制反转
2.1 概念
控制反转,之前对象的控制权在类手上,现在反转后到了Spring手上。
2.2 入门案例
①导入依赖
导入SpringIOC相关依赖
1 2 3 4 5
| <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.1.9.RELEASE</version> </dependency>
|
②编写配置文件
在resources目录下创建applicationContext.xml文件,文件名可以任意取。但是建议叫applicationContext。
内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <?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 http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean class="com.sangeng.dao.impl.StudentDaoImpl" id="studentDao" > </bean>
</beans>
|
③创建容器从容器中获取对象并测试
1 2 3 4 5 6 7 8 9 10
| public static void main(String[] args) {
ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml"); StudentDao studentDao = (StudentDao) app.getBean("studentDao"); System.out.println(studentDao.getStudentById(1)); }
|
2.3 Bean的常用属性配置
2.3.1 id
bean的唯一标识,同一个Spring容器中不允许重复
2.3.2 class
全类名,用于反射创建对象
2.3.3 scope
scope主要有两个值:singleton和prototype
如果设置为singleton则一个容器中只会有这个一个bean对象。默认容器创建的时候就会创建该对象。
如果设置为prototype则一个容器中会有多个该bean对象。每次调用getBean方法获取时都会创建一个新对象。
3.DI依赖注入
依赖注入可以理解成IoC的一种应用场景,反转的是对象间依赖关系维护权。
3.1 set方法注入
在要注入属性的bean标签中进行配置。前提是该类有提供属性对应的set方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
| package com.sangeng.domain;
public class Student {
private String name; private int id; private int age;
private Dog dog;
public Dog getDog() { return dog; }
public void setDog(Dog dog) { this.dog = dog; }
@Override public String toString() { return "Student{" + "name='" + name + '\'' + ", id=" + id + ", age=" + age + '}'; }
public Student() {
}
public Student(String name, int id, int age) { this.name = name; this.id = id; this.age = age; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <bean class="com.sangeng.domain.Dog" id="dog"> <property name="name" value="小白"></property> <property name="age" value="6"></property> </bean>
<bean class="com.sangeng.domain.Student" id="student" >
<property name="name" value="东南枝"></property> <property name="age" value="20"></property> <property name="id" value="1"></property> <property name="dog" ref="dog"></property> </bean>
|
3.2 有参构造注入
在要注入属性的bean标签中进行配置。前提是该类有提供对应的有参构造。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public class Student {
private String name; private int id; private int age;
private Dog dog;
public Student(String name, int id, int age, Dog dog) { this.name = name; this.id = id; this.age = age; this.dog = dog; } }
|
1 2 3 4 5 6 7
| <bean class="com.sangeng.domain.Student" id="student2" > <constructor-arg name="name" value="自挂东南枝"></constructor-arg> <constructor-arg name="age" value="20"></constructor-arg> <constructor-arg name="id" value="30"></constructor-arg> <constructor-arg name="dog" ref="dog"></constructor-arg> </bean>
|
3.3 复杂类型属性注入
实体类如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @Data @NoArgsConstructor @AllArgsConstructor public class User { private int age; private String name; private Phone phone; private List<String> list; private List<Phone> phones; private Set<String> set; private Map<String, Phone> map; private int[] arr; private Properties properties; }
|
1 2 3 4 5 6 7 8 9 10
| @Data @NoArgsConstructor @AllArgsConstructor public class Phone { private double price; private String name; private String password; private String path;
}
|
配置如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| <?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 http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean class="com.sangeng.domain.Phone" id="phone"> <property name="price" value="3999"></property> <property name="name" value="黑米"></property> <property name="password" value="123"></property> <property name="path" value="qqqq"></property> </bean> <bean class="com.sangeng.domain.User" id="user"> <property name="age" value="10"></property> <property name="name" value="大队长"></property> <property name="phone" ref="phone"></property> <property name="list"> <list> <value>三更</value> <value>西施</value> </list> </property>
<property name="phones"> <list> <ref bean="phone"></ref> </list> </property>
<property name="set"> <set> <value>setEle1</value> <value>setEle2</value> </set> </property>
<property name="map"> <map> <entry key="k1" value-ref="phone"></entry> <entry key="k2" value-ref="phone"></entry> </map> </property>
<property name="arr"> <array> <value>10</value> <value>11</value> </array> </property>
<property name="properties"> <props> <prop key="k1">v1</prop> <prop key="k2">v2</prop> </props> </property> </bean> </beans>
|
4.Lombok
①导入依赖
1 2 3 4 5
| <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.16</version> </dependency>
|
②增加注解
1 2 3 4 5 6 7 8 9 10
| @Data @NoArgsConstructor @AllArgsConstructor public class Phone { private double price; private String name; private String password; private String path;
}
|
5.SPEL
我们可以再配置文件中使用SPEL表达式。写法如下:
1 2
| <property name="age" value="#{20}"/> <property name="car" value="#{car}"/>
|
注意:SPEL需要写到value属性中,不能写到ref属性。
6.配置文件
6.1 读取properties文件
我们可以让Spring读取properties文件中的key/value,然后使用其中的值。
①设置读取properties
在Spring配置文件中加入如下标签:指定要读取的文件的路径。
1
| <context:property-placeholder location="classpath:filename.properties">
|
其中的classpath表示类加载路径下。
我们也会用到如下写法:classpath:**.properties 其中的* * 表示文件名任意。
注意:context命名空间的引入是否正确
②使用配置文件中的值
在我们需要使用的时候可以使用${key}来表示具体的值。注意要再value属性中使用才可以。例如:
1
| <property name="propertyName" value="${key}"/>
|
6.2 引入Spring配置文件
我们可以在主的配置文件中通过import标签的resource属性,引入其他的xml配置文件
1
| <import resource="classpath:applicationContext-book.xml"/>
|
7. 低频知识点
7.1 bean的配置
7.1.1 name属性
我们可以用name属性来给bean取名。例如:
1 2 3 4 5 6
| <bean class="com.alibaba.druid.pool.DruidDataSource" id="dataSource" name="dataSource2,dataSource3"> <property name="driverClassName" value="${jdbc.driver}"></property> <property name="url" value="${jdbc.url}"></property> <property name="username" value="${jdbc.username}"></property> <property name="password" value="${jdbc.password}"></property> </bean>
|
获取的时候就可以使用这个名字来获取了
1 2 3 4 5 6 7
| public static void main(String[] args) {
ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml"); DruidDataSource dataSource = (DruidDataSource) app.getBean("dataSource3"); System.out.println(dataSource);
}
|
7.1.2 lazy-init
可以控制bean的创建时间,如果设置为true就是在第一次获取该对象的时候才去创建。
1 2 3 4 5 6
| <bean class="com.alibaba.druid.pool.DruidDataSource" lazy-init="true" id="dataSource" name="dataSource2,dataSource3"> <property name="driverClassName" value="${jdbc.driver}"></property> <property name="url" value="${jdbc.url}"></property> <property name="username" value="${jdbc.username}"></property> <property name="password" value="${jdbc.password}"></property> </bean>
|
7.1.3 init-method
可以用来设置初始化方法,设置完后容器创建完对象就会自动帮我们调用对应的方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| @Data @NoArgsConstructor @AllArgsConstructor public class Student {
private String name; private int id; private int age; public void init(){ System.out.println("对学生对象进行初始化操作"); }
}
|
1
| <bean class="com.sangeng.domain.Student" id="student" init-method="init"></bean>
|
注意:配置的初始化方法只能是空参的。
7.1.4 destroy-method
可以用来设置销毁之前调用的方法,设置完后容器销毁对象前就会自动帮我们调用对应的方法。
1
| <bean class="com.sangeng.domain.Student" id="student" destroy-method="close"></bean>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| @Data @NoArgsConstructor @AllArgsConstructor public class Student {
private String name; private int id; private int age;
public void init(){ System.out.println("对学生对象进行初始化操作"); }
public void close(){ System.out.println("对象销毁之前调用,用于释放资源"); } }
|
注意:配置的方法只能是空参的。
7.1.5 factory-bean&factory-method
当我们需要让Spring容器使用工厂类来创建对象放入Spring容器的时候可以使用factory-bean和factory-method属性。
7.1.5.1 配置实例工厂创建对象
配置文件中进行配置
1 2 3 4 5 6
| <bean class="com.sangeng.factory.CarFactory" id="carFactory"></bean>
<bean factory-bean="carFactory" factory-method="getCar" id="car"></bean>
|
创建容器获取对象测试
1 2 3 4
| ClassPathXmlApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
Car c = (Car) app.getBean("car"); System.out.println(c);
|
7.1.5.2 配置静态工厂创建对象
配置文件中进行配置
1 2
| <bean class="com.sangeng.factory.CarStaticFactory" factory-method="getCar" id="car2"></bean>
|
创建容器获取对象测试
1 2 3 4
| ClassPathXmlApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
Car c = (Car) app.getBean("car2"); System.out.println(c);
|
Spring-02
1.注解开发
为了简化配置,Spring支持使用注解代替xml配置。
2.Spring常用注解
2.0 注解开发准备工作
如果要使用注解开发必须要开启组件扫描,这样加了注解的类才会被识别出来。Spring才能去解析其中的注解。
1 2
| <context:component-scan base-package="com.sangeng"/>
|
2.1 IOC相关注解
2.1.1 @Component,@Controller,@Service ,@Repository
上述4个注解都是加到类上的。
他们都可以起到类似bean标签的作用。可以把加了该注解类的对象放入Spring容器中。
实际再使用时选择任意一个都可以。但是后3个注解是语义化注解。
如果是Service类要求使用@Service。
如果是Dao类要求使用@Repository
如果是Controllerl类(SpringMVC中会学习到)要求使用@Controller
如果是其他类可以使用@Component
例如:
配置文件如下:
1 2 3 4 5 6 7 8 9
| <?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 http://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="com.sangeng"></context:component-scan>
</beans>
|
类如下:
1 2 3 4 5 6 7 8
| @Repository("userDao") public class UserDaoImpl implements UserDao {
public void show() { System.out.println("查询数据库,展示查询到的数据"); } }
|
1 2 3 4 5 6 7 8 9 10 11 12
| @Data @NoArgsConstructor @AllArgsConstructor @Component("phone") public class Phone { private double price; private String name; private String password; private String path;
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| @Service("userService") @Data @NoArgsConstructor @AllArgsConstructor public class UserServiceImpl implements UserService {
private UserDao userDao;
private int num;
private String str;
public void show() { userDao.show(); } }
|
测试类如下:
1 2 3 4 5 6 7 8 9 10 11 12 13
| public class Demo { public static void main(String[] args) { ClassPathXmlApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml"); UserDao userDao = (UserDao) app.getBean("userDao"); Phone phone = (Phone) app.getBean("phone"); UserService userService = (UserService) app.getBean("userService"); System.out.println(phone); System.out.println(userService); System.out.println(userDao); } }
|
2.2 DI相关注解
如果一个bean已经放入Spring容器中了。那么我们可以使用下列注解实现属性注入,让Spring容器帮我们完成属性的赋值。
2.2.1 @Value
主要用于String,Integer等可以直接赋值的属性注入。不依赖setter方法,支持SpEL表达式。
例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| @Service("userService") @Data @NoArgsConstructor @AllArgsConstructor public class UserServiceImpl implements UserService { private UserDao userDao; @Value("199") private int num; @Value("三更草堂") private String str; @Value("#{19+3}") private Integer age;
public void show() { userDao.show(); } }
|
2.2.2 @AutoWired
Spring会给加了该注解的属性自动注入数据类型相同的对象。
例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| @Service("userService") @Data @NoArgsConstructor @AllArgsConstructor public class UserServiceImpl implements UserService {
@Autowired private UserDao userDao;
@Value("199") private int num; @Value("三更草堂") private String str;
@Value("#{19+3}") private Integer age;
public void show() { userDao.show(); } }
|
required属性代表这个属性是否是必须的,默认值为true。如果是true的话Spring容器中如果找不到相同类型的对象完成属性注入就会出现异常。
2.2.3 @Qualifier
如果相同类型的bean在容器中有多个时,单独使用@AutoWired就不能满足要求,这时候可以再加上@Qualifier来指定bean的名字从容器中获取bean注入。
例如:
1 2 3
| @Autowired @Qualifier("userDao2") private UserDao userDao;
|
注意:该注解不能单独使用。单独使用没有作用
2.3 xml配置文件相关注解
@Configuration
标注在类上,表示当前类是一个配置类。我们可以用注解类来完全替换掉xml配置文件。
注意:如果使用配置类替换了xml配置,spring容器要使用:AnnotationConfigApplicationContext
例如:
1 2 3 4
| @Configuration public class ApplicationConfig { }
|
@ComponentScan
可以用来代替context:component-scan标签来配置组件扫描。
basePackages属性来指定要扫描的包。
注意要加在配置类上。
例如:
1 2 3 4 5
| @Configuration @ComponentScan(basePackages = "com.sangeng") public class ApplicationConfig { }
|
@Bean
可以用来代替bean标签,主要用于第三方类的注入。
使用:定义一个方法,在方法中创建对应的对象并且作为返回值返回。然后在方法上加上@Bean注解,注解的value属性来设置bean的名称。
例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| @Configuration @ComponentScan(basePackages = "com.sangeng") public class ApplicationConfig {
@Bean("dataSource") public DruidDataSource getDataSource(){ DruidDataSource druidDataSource = new DruidDataSource(); druidDataSource.setDriverClassName("com.mysql.jdbc.Driver"); druidDataSource.setUsername("root"); druidDataSource.setUrl("jdbc:mysql://localhost:3306/mybatis_db"); druidDataSource.setPassword("root"); return druidDataSource; }
}
|
注意事项:如果同一种类型的对象在容器中只有一个,我们可以不设置bean的名称。
具体写法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| @Configuration @ComponentScan(basePackages = "com.sangeng") public class ApplicationConfig {
@Bean public DruidDataSource getDataSource(){ DruidDataSource druidDataSource = new DruidDataSource(); druidDataSource.setDriverClassName("com.mysql.jdbc.Driver"); druidDataSource.setUsername("root"); druidDataSource.setUrl("jdbc:mysql://localhost:3306/mybatis_db"); druidDataSource.setPassword("root"); return druidDataSource; }
}
|
获取方式如下:
1 2 3 4 5 6 7
| public static void main(String[] args) { AnnotationConfigApplicationContext app = new AnnotationConfigApplicationContext(ApplicationConfig.class);
DataSource bean = app.getBean(DataSource.class); System.out.println(userService); }
|
@PropertySource
可以用来代替context:property-placeholder,让Spring读取指定的properties文件。然后可以使用@Value来获取读取到的值。
使用:在配置类上加@PropertySource注解,注解的value属性来设置properties文件的路径。
然后在配置类中定义成员变量。在成员变量上使用@Value注解来获取读到的值并给对应的成员变量赋值。
例如:
1 2 3 4
| jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/mybatis_db jdbc.username=root jdbc.password=root
|
读取文件并且获取值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| @Configuration @ComponentScan(basePackages = "com.sangeng") @PropertySource("jdbc.properties") public class ApplicationConfig {
@Value("${jdbc.driver}") private String driverClassName; @Value("${jdbc.url}") private String url; @Value("${jdbc.username}") private String username; @Value("${jdbc.password}") private String password;
@Bean public DruidDataSource getDataSource(){ DruidDataSource druidDataSource = new DruidDataSource(); druidDataSource.setDriverClassName(driverClassName); druidDataSource.setUsername(username); druidDataSource.setUrl(url); druidDataSource.setPassword(password); return druidDataSource; }
}
|
注意事项:使用@Value获取读到的properties文件中的值时使用的是${key},而不是#{key}。
3.如何选择
①SSM
自己项目中的类的IOC和DI都使用注解,对第三方jar包中的类,配置组件扫描时使用xml进行配置。
②SpringBoot
纯注解开发
Spring-03
1. AOP
1.1 概念
AOP为Aspect Oriented Programming的缩写,意为:面向切面编程。他是一种可以在不修改原来的核心代码的情况下给程序动态统一进行增强的一种技术。
SpringAOP: 批量对Spring容器中bean的方法做增强,并且这种增强不会与原来方法中的代码耦合。
1.2 快速入门
1.2.1 需求
要求让_08_SpringAOP模块中service包下所有类的所有方法在调用前都输出:方法被调用了。
1.2.2 准备工作
①添加依赖
需要添加SpringIOC相关依赖和AOP相关依赖。
1 2 3 4 5 6 7 8 9 10 11 12
| <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.1.9.RELEASE</version> </dependency>
<dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.13</version> </dependency>
|
②相关bean要注入容器中
开启组件扫描
1
| <context:component-scan base-package="com.sangeng"></context:component-scan>
|
加@Service注解
1 2 3 4 5 6 7 8
| @Service public class PhoneService {
public void deleteAll(){ System.out.println("PhoneService中deleteAll的核心代码"); } }
|
1 2 3 4 5 6 7 8 9
| @Service public class UserService {
public void deleteAll(){ System.out.println("UserService中deleteAll的核心代码"); } }
|
1.2.3 实现AOP
①开启AOP注解支持
使用aop:aspectj-autoproxy标签
1 2 3 4 5 6 7 8 9 10 11
| <?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" 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/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <context:component-scan base-package="com.sangeng"></context:component-scan> <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
|
②创建切面类
创建一个类,在类上加上@Component和@Aspect
使用@Pointcut注解来指定要被增强的方法
使用@Before注解来给我们的增强代码所在的方法进行标识,并且指定了增强代码是在被增强方法执行之前执行的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| @Component @Aspect public class MyAspect {
@Pointcut("execution(* com.sangeng.service.*.*(..))") public void pt(){}
@Before("pt()") public void methodbefore(){ System.out.println("方法被调用了"); }
}
|
1.2.4 测试
1 2 3 4 5 6 7
| public static void main(String[] args) { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); PhoneService phoneService = applicationContext.getBean(PhoneService.class); UserService userService = applicationContext.getBean(UserService.class); phoneService.deleteAll();
}
|
1.3 AOP核心概念
Joinpoint(连接点):所谓连接点是指那些可以被增强到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点
Pointcut(切入点):所谓切入点是指被增强的连接点(方法)
Advice(通知/ 增强):所谓通知是指具体增强的代码
Target(目标对象):被增强的对象就是目标对象
Aspect(切面):是切入点和通知(引介)的结合
Proxy (代理):一个类被 AOP 增强后,就产生一个结果代理类
1.4 切点确定
1.4.1 切点表达式
可以使用切点表达式来表示要对哪些方法进行增强。
写法:execution([修饰符] 返回值类型 包名.类名.方法名(参数))
- 访问修饰符可以省略,大部分情况下省略
- 返回值类型、包名、类名、方法名可以使用星号* 代表任意
- 包名与类名之间一个点 . 代表当前包下的类,两个点 .. 表示当前包及其子包下的类
- 参数列表可以使用两个点 .. 表示任意个数,任意类型的参数列表
例如:
1 2 3 4 5 6 7
| execution(* com.sangeng.service.*.*(..)) 表示com.sangeng.service包下任意类,方法名任意,参数列表任意,返回值类型任意 execution(* com.sangeng.service..*.*(..)) 表示com.sangeng.service包及其子包下任意类,方法名任意,参数列表任意,返回值类型任意 execution(* com.sangeng.service.*.*()) 表示com.sangeng.service包下任意类,方法名任意,要求方法不能有参数,返回值类型任意 execution(* com.sangeng.service.*.delete*(..)) 表示com.sangeng.service包下任意类,要求方法不能有参数,返回值类型任意,方法名要求已delete开头
|
1.4.2 切点函数@annotation
我们也可以在要增强的方法上加上注解。然后使用@annotation来表示对加了什么注解的方法进行增强。
写法:**@annotation(注解的全类名)**
例如:
定义注解如下
1 2 3 4
| @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface InvokeLog { }
|
给需要增强的方法增加注解
1 2 3 4 5 6 7 8
| @Service public class PhoneService {
@InvokeLog public void deleteAll(){ System.out.println("PhoneService中deleteAll的核心代码"); } }
|
切面类中使用@annotation来确定要增强的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| @Component @Aspect public class MyAspect {
@Pointcut("@annotation(com.sangeng.aspect.InvokeLog)") public void pt(){}
@Before("pt()") public void methodbefore(){ System.out.println("方法被调用了"); } }
|
1.5 通知分类
@Before:前置通知,在目标方法执行前执行
@AfterReturning: 返回后通知,在目标方法执行后执行,如果出现异常不会执行
@After:后置通知,在目标方法之后执行,无论是否出现异常都会执行
@AfterThrowing:异常通知,在目标方法抛出异常后执行
@Around:环绕通知,围绕着目标方法执行
理解不同通知执行时机。(下面的伪代码是用来理解单个通知的执行时机的,不能用来理解多个通知情况下的执行顺序。如果需要配置多个通知我们会选择使用Around通知,更加的清晰并且好用)
1 2 3 4 5 6 7 8 9 10 11 12 13
| public Object test() { before(); try { Object ret = 目标方法(); afterReturing(); } catch (Throwable throwable) { throwable.printStackTrace(); afterThrowing(); }finally { after(); } return ret; }
|
环绕通知非常特殊,它可以对目标方法进行全方位的增强。
例如:
1 2 3 4 5 6 7 8 9 10 11 12 13
| @Around("pt()") public void around(ProceedingJoinPoint pjp){ System.out.println("目标方法前"); try { pjp.proceed(); System.out.println("目标方法后"); } catch (Throwable throwable) { throwable.printStackTrace(); System.out.println("目标方法出现异常"); }finally { System.out.println("finally中进行增强"); } }
|
1.6 获取被增强方法相关信息
我们实际对方法进行增强时往往还需要获取到被增强代码的相关信息,比如方法名,参数,返回值,异常对象等。
我们可以在除了环绕通知外的所有通知方法中增加一个JoinPoint类型的参数。这个参数封装了被增强方法的相关信息。我们可以通过这个参数获取到除了异常对象和返回值之外的所有信息。
例如:
1 2 3 4 5 6 7
| @Before("pt()") public void methodbefore(JoinPoint jp){ Object[] args = jp.getArgs(); Object target = jp.getTarget(); MethodSignature signature = (MethodSignature) jp.getSignature(); System.out.println("Before方法被调用了"); }
|
案例:
需求:要求让所有service包下类的所有方法被调用前都输出全类名,方法名,以及调用时传入的参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| @Component @Aspect public class PrintLogAspect {
@Pointcut("execution(* com.sangeng.service..*.*(..))") public void pt(){}
@Before("pt()") public void printLog(JoinPoint joinPoint){ MethodSignature signature = (MethodSignature) joinPoint.getSignature(); String className = signature.getDeclaringTypeName(); String methodName = signature.getName(); Object[] args = joinPoint.getArgs();
System.out.println(className+"=="+methodName+"======"+ Arrays.toString(args)); } }
|
如果需要获取被增强方法中的异常对象或者返回值则需要在方法参数上增加一个对应类型的参数,并且使用注解的属性进行配置。这样Spring会把你想获取的数据赋值给对应的方法参数。
例如:
1 2 3 4
| @AfterReturning(value = "pt()",returning = "ret") public void AfterReturning(JoinPoint jp,Object ret){ System.out.println("AfterReturning方法被调用了"); }
|
1 2 3 4
| @AfterThrowing(value = "pt()",throwing = "t") public void AfterThrowing(JoinPoint jp,Throwable t){ System.out.println("AfterReturning方法被调用了"); }
|
相信你肯定觉得上面的获取方式特别的麻烦难以理解。就可以使用下面这种万能的方法。
直接在环绕通知方法中增加一个ProceedingJoinPoint类型的参数。这个参数封装了被增强方法的相关信息。
该参数的proceed()方法被调用相当于被增强方法被执行,调用后的返回值就相当于被增强方法的返回值。
例如:
1 2 3 4 5 6 7 8 9 10 11 12 13
| @Around(value = "pt()") public Object around(ProceedingJoinPoint pjp) { Object[] args = pjp.getArgs(); Object target = pjp.getTarget(); MethodSignature signature = (MethodSignature) pjp.getSignature(); Object ret = null; try { ret = pjp.proceed(); } catch (Throwable throwable) { throwable.printStackTrace(); } return ret; }
|
1.7 AOP应用案例
1.7.1 需求
现有AI核心功能代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public class AIController { public String getAnswer(String question){ String str = question.replace("吗", ""); str = str.replace("?","!"); return str; }
public String fortuneTelling(String name){ String[] strs = {"女犯伤官把夫克,旱地莲花栽不活,不是吃上两家饭,也要刷上三家锅。","一朵鲜花头上戴,一年四季也不开,一心想要花开时,采花之人没到来。","此命生来脾气暴,上来一阵双脚跳,对你脾气啥都好,经常与人吵和闹。"}; int index = name.hashCode() % 3;
return strs[index]; } }
|
现在为了保证数据的安全性,要求调用方法时fortuneTelling传入的姓名是经过加密的。我们需要对传入的参数进行解密后才能使用。并且要对该方法的返回值进行加密后返回。
PS:后期也可能让其他方法进行相应的加密处理。
字符串加密解密直接使用下面的工具类即可:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
| import javax.crypto.Cipher; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import java.security.SecureRandom;
public class CryptUtil { private static final String AES = "AES";
private static int keysizeAES = 128;
private static String charset = "utf-8";
public static String parseByte2HexStr(final byte buf[]) { final StringBuffer sb = new StringBuffer(); for (int i = 0; i < buf.length; i++) { String hex = Integer.toHexString(buf[i] & 0xFF); if (hex.length() == 1) { hex = '0' + hex; } sb.append(hex.toUpperCase()); } return sb.toString(); }
public static byte[] parseHexStr2Byte(final String hexStr) { if (hexStr.length() < 1) return null; final byte[] result = new byte[hexStr.length() / 2]; for (int i = 0;i< hexStr.length()/2; i++) { int high = Integer.parseInt(hexStr.substring(i * 2, i * 2 + 1), 16); int low = Integer.parseInt(hexStr.substring(i * 2 + 1, i * 2 + 2), 16); result[i] = (byte) (high * 16 + low); } return result; }
private static String keyGeneratorES(final String res, final String algorithm, final String key, final Integer keysize, final Boolean bEncode) { try { final KeyGenerator g = KeyGenerator.getInstance(algorithm); if (keysize == 0) { byte[] keyBytes = charset == null ? key.getBytes() : key.getBytes(charset); g.init(new SecureRandom(keyBytes)); } else if (key == null) { g.init(keysize); } else { byte[] keyBytes = charset == null ? key.getBytes() : key.getBytes(charset); SecureRandom random = SecureRandom.getInstance("SHA1PRNG"); random.setSeed(keyBytes); g.init(keysize, random); } final SecretKey sk = g.generateKey(); final SecretKeySpec sks = new SecretKeySpec(sk.getEncoded(), algorithm); final Cipher cipher = Cipher.getInstance(algorithm); if (bEncode) { cipher.init(Cipher.ENCRYPT_MODE, sks); final byte[] resBytes = charset == null? res.getBytes() : res.getBytes(charset); return parseByte2HexStr(cipher.doFinal(resBytes)); } else { cipher.init(Cipher.DECRYPT_MODE, sks); return new String(cipher.doFinal(parseHexStr2Byte(res))); } } catch (Exception e) { e.printStackTrace(); } return null; }
public static String AESencode(final String res) { return keyGeneratorES(res, AES, "aA11*-%", keysizeAES, true); }
public static String AESdecode(final String res) { return keyGeneratorES(res, AES, "aA11*-%", keysizeAES, false); }
public static void main(String[] args) { System.out.println( "加密后:" + AESencode("将要加密的明文") ); System.out.println( "解密后:" + AESdecode("730CAE52D85B372FB161B39D0A908B8CC6EF6DA2F7D4E595D35402134C3E18AB") ); } }
|
1.7.2 实现
①导入依赖
1 2 3 4 5 6 7 8 9 10 11 12
| <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.1.9.RELEASE</version> </dependency>
<dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.13</version> </dependency>
|
②开启AOP注解支持
1 2 3 4 5 6 7 8 9 10 11 12 13
| <?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" 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/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="com.sangeng"></context:component-scan> <aop:aspectj-autoproxy></aop:aspectj-autoproxy> </beans>
|
③自定义注解
1 2 3 4 5 6 7 8 9 10 11 12 13
| package com.sangeng.aspect;
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target;
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Crypt {
}
|
④在目标方法上增加注解
注意:目标对象一定要记得注入Spring容器中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| @Controller public class AIController {
@Crypt public String fortuneTelling(String name){ System.out.println(name); String[] strs = {"女犯伤官把夫克,旱地莲花栽不活,不是吃上两家饭,也要刷上三家锅。","一朵鲜花头上戴,一年四季也不开,一心想要花开时,采花之人没到来。","此命生来脾气暴,上来一阵双脚跳,对你脾气啥都好,经常与人吵和闹。"}; int index = name.hashCode() % 3;
return strs[index]; } }
|
⑤定义切面类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| package com.sangeng.aspect;
import com.sangeng.util.CryptUtil; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component;
@Component @Aspect public class CryptAspect {
@Pointcut("@annotation(com.sangeng.aspect.Crypt)") public void pt(){
}
@Around("pt()") public Object crypt(ProceedingJoinPoint pjp) { Object[] args = pjp.getArgs(); String arg = (String) args[0]; String s = CryptUtil.AESdecode(arg); args[0] = s; Object proceed = null; String ret = null; try { proceed = pjp.proceed(args); ret = (String) proceed; ret = CryptUtil.AESencode(ret); } catch (Throwable throwable) { throwable.printStackTrace(); } return ret; }
}
|
1.8 xml配置AOP
①定义切面类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| public class MyAspect {
public void before(JoinPoint joinPoint){ System.out.println("before"); }
public void afterReturning(JoinPoint joinPoint,Object ret){ System.out.println("afterReturning:"+ret); }
public void after(JoinPoint joinPoint){ System.out.println("after"); }
public void afterThrowing(JoinPoint joinPoint,Throwable e){ String message = e.getMessage(); System.out.println("afterThrowing:"+message); }
public Object around(ProceedingJoinPoint pjp){ Object[] args = pjp.getArgs(); MethodSignature signature = (MethodSignature) pjp.getSignature(); Object target = pjp.getTarget(); Object ret = null; try { ret = pjp.proceed(); System.out.println(ret); } catch (Throwable throwable) { throwable.printStackTrace(); System.out.println(throwable.getMessage()); }
return ret; } }
|
②目标类和切面类注入容器
在切面类和目标类上加是对应的注解。注入如果是使用注解的方式注入容器要记得开启组件扫描。
当然你也可以在xml中使用bean标签的方式注入容器。
1 2 3 4
| @Component public class MyAspect { }
|
1 2 3 4
| @Service public class UserService { }
|
③配置AOP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| <?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" 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/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="com.sangeng"></context:component-scan>
<aop:config> <aop:pointcut id="pt1" expression="execution(* com.sangeng.service..*.*(..))"></aop:pointcut> <aop:pointcut id="pt2" expression="@annotation(com.sangeng.aspect.InvokeLog)"></aop:pointcut> <aop:aspect ref="myAspect"> <aop:before method="before" pointcut-ref="pt1"></aop:before> <aop:after method="after" pointcut-ref="pt1"></aop:after> <aop:after-returning method="afterReturning" pointcut-ref="pt1" returning="ret"></aop:after-returning> <aop:after-throwing method="afterThrowing" pointcut-ref="pt2" throwing="e"></aop:after-throwing> </aop:aspect> </aop:config> </beans>
|
1.9 多切面顺序问题
在实际项目中我们可能会存在配置了多个切面的情况。这种情况下我们很可能需要控制切面的顺序。
我们在默认情况下Spring有它自己的排序规则。(按照类名排序)
默认排序规则往往不符合我们的要求,我们需要进行特殊控制。
如果是注解方式配置的AOP可以在切面类上加**@Order注解来控制顺序。@Order中的属性越小优先级越高。**
如果是XML方式配置的AOP,可以通过调整配置顺序来控制。
例如:
下面这种配置方式就会先使用CryptAspect里面的增强,在使用APrintLogAspect里的增强
1 2 3 4 5 6 7 8 9 10 11 12
| @Component @Aspect @Order(2) public class APrintLogAspect { } @Component @Aspect @Order(1) public class CryptAspect { }
|
1.10 AOP原理-动态代理
实际上Spring的AOP其实底层就是使用动态代理来完成的。并且使用了两种动态代理分别是JDK的动态代理和Cglib动态代理。
所以我们接下去来学习下这两种动态代理,理解下它们的不同点。
1.10.1 JDK动态代理
JDK的动态代理使用的java.lang.reflect.Proxy这个类来进行实现的。要求被代理(被增强)的类需要实现了接口。并且JDK动态代理也只能对接口中的方法进行增强。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| public static void main(String[] args) { AIControllerImpl aiController = new AIControllerImpl(); ClassLoader cl = Demo.class.getClassLoader(); Class<?>[] interfaces = AIControllerImpl.class.getInterfaces(); AIController proxy = (AIController) Proxy.newProxyInstance(cl, interfaces, new InvocationHandler() { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if(method.getName().equals("getAnswer")){ System.out.println("增强"); } Object ret = method.invoke(aiController, args); return ret; } }); String answer = proxy.getAnswer("三连了吗?"); System.out.println(answer); }
|
1.10.2 Cglib动态代理
使用的是org.springframework.cglib.proxy.Enhancer类进行实现的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| public class CglibDemo { public static void main(String[] args) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(AIControllerImpl.class); enhancer.setCallback(new MethodInterceptor() { @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { if ("getAnswer".equals(method.getName())){ System.out.println("被增强了"); } Object ret = methodProxy.invokeSuper(o, objects); return ret; } }); AIControllerImpl proxy = (AIControllerImpl) enhancer.create();
System.out.println(proxy.fortuneTelling("你好吗?")); } }
|
1.10.3 总结
JDK动态代理要求被代理(被增强)的类必须要实现接口,生成的代理对象相当于是被代理对象的兄弟。
Cglib的动态代理不要求被代理(被增强)的类要实现接口,生成的代理对象相当于被代理对象的子类对象。
Spring的AOP默认情况下优先使用的是JDK的动态代理,如果使用不了JDK的动态代理才会使用Cglib的动态代理。
1.11 切换默认动态代理方式
有的时候我们需要修改AOP的代理方式。
我们可以使用以下方式修改:
如果我们是采用注解方式配置AOP的话:
设置aop:aspectj-autoproxy标签的proxy-target-class属性为true,代理方式就会修改成Cglib
1
| <aop:aspectj-autoproxy proxy-target-class="true"/>
|
如果我们是采用xml方式配置AOP的话:
设置aop:config标签的proxy-target-class属性为true,代理方式就会修改成Cglib
1 2
| <aop:config proxy-target-class="true"> </aop:config>
|
Spring-04
1.Spring整合Junit
①导入依赖
1 2 3 4 5 6 7 8 9 10 11 12
| <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency>
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.1.9.RELEASE</version> </dependency>
|
② 编写测试类
在测试类上加上
**@RunWith(SpringJUnit4ClassRunner.class)**注解,指定让测试运行于Spring环境
@ContextConfiguration注解,指定Spring容器创建需要的配置文件或者配置类
1 2 3 4
| @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = "classpath:配置文件1.xml")
public class SpringTest {}
|
③注入对象进行测试
在测试类中注入要测试的对象,定义测试方法,在其中使用要测试的对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = "classpath:配置文件1.xml")
public class SpringTest { @Autowired private UserService userService; @Test public void testUserService() { userService.findById(10); } }
|
2.Spring整合Mybatis
我们如果想把Mybatis整合到Spring中需要使用一个整合包mybatis-spring
官方文档:http://mybatis.org/spring/zh/index.html
①导入依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.1.9.RELEASE</version> </dependency>
<dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.0.4</version> </dependency>
<dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.4</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency>
<dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.16</version> </dependency>
|
②往容器中注入整合相关对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder> <bean class="com.alibaba.druid.pool.DruidDataSource" id="dataSource"> <property name="url" value="${jdbc.url}"></property> <property name="username" value="${jdbc.username}"></property> <property name="password" value="${jdbc.password}"></property> <property name="driverClassName" value="${jdbc.driver}"></property> </bean>
<bean class="org.mybatis.spring.SqlSessionFactoryBean" id="sessionFactory"> <property name="dataSource" ref="dataSource"></property> <property name="configLocation" value="classpath:mybatis-config.xml"></property> </bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer" id="mapperScannerConfigurer"> <property name="basePackage" value="com.sangeng.dao"></property> </bean>
|
mybatis配置文件mybatis-config.xml如下:
1 2 3 4 5 6 7 8 9
| <?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> <typeAliases> <package name="com.sangeng.domain"></package> </typeAliases> </configuration>
|
③从容器中获取Mapper对象进行使用
1 2
| @Autowired private UserDao userDao;
|
3.Spring声明式事务
3.1 事务回顾
3.1.1 事务的概念
保证一组数据库的操作,要么同时成功,要么同时失败
3.1.2 四大特性
隔离性
多个事务之间要相互隔离,不能互相干扰
原子性
指事务是一个不可分割的整体,类似一个不可分割的原子
一致性
保障事务前后这组数据的状态是一致的。要么都是成功的,要么都是失败的。
持久性
指事务一旦被提交,这组操作修改的数据就真的的发生变化了。即使接下来数据库故障也不应该对其有影响。
3.2 实现声明式事务
如果我们自己去对事务进行控制的话我们就需要值原来核心代码的基础上加上事务控制相关的代码。而在我们的实际开发中这种事务控制的操作也是非常常见的。所以Spring提供了声明式事务的方式让我们去控制事务。
只要简单的加个注解(或者是xml配置)就可以实现事务控制,不需要事务控制的时候只需要去掉相应的注解即可。
3.2.0 案例环境准备
①数据初始化
1 2 3 4 5 6 7 8 9 10
| CREATE DATABASE /*!32312 IF NOT EXISTS*/`spring_db` /*!40100 DEFAULT CHARACTER SET utf8 */; USE `spring_db`; DROP TABLE IF EXISTS `account`; CREATE TABLE `account` ( `id` INT(11) NOT NULL AUTO_INCREMENT, `name` VARCHAR(50) DEFAULT NULL, `money` DOUBLE DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=INNODB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8; INSERT INTO `account`(`id`,`name`,`money`) VALUES (1,'三更',100),(2,'草堂',100);
|
②Spring整合Mybatis
③创建Service和Dao
1 2 3 4 5 6 7 8 9
| public interface AccountService {
public void transfer(Integer outId,Integer inId,Double money); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| @Service public class AccountServiceImpl implements AccountService {
@Autowired private AccoutDao accoutDao;
public void transfer(Integer outId, Integer inId, Double money) { accoutDao.updateMoney(inId,money); accoutDao.updateMoney(outId,-money); } }
|
1 2 3 4
| public interface AccoutDao {
void updateMoney(@Param("id") Integer id,@Param("updateMoney") Double updateMoney); }
|
AccoutDao.xml如下:
1 2 3 4 5 6 7 8 9
| <?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.sangeng.dao.AccoutDao">
<update id="updateMoney"> update account set money = money + #{updateMoney} where id = #{id} </update> </mapper>
|
3.2.1 注解实现
①配置事务管理器和事务注解驱动
在spring的配置文件中添加如下配置:
1 2 3 4 5 6
| <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean>
<tx:annotation-driven transaction-manager="txManager"/>
|
②添加注解
在需要进行事务控制的方法或者类上添加@Transactional注解就可以实现事务控制。
1 2 3 4 5 6 7 8
| @Transactional public void transfer(Integer outId, Integer inId, Double money) { accoutDao.updateMoney(inId,money);
accoutDao.updateMoney(outId,-money); }
|
注意:如果加在类上,这个类的所有方法都会受事务控制,如果加在方法上,就是那一个方法受事务控制。
注意,因为声明式事务底层是通过AOP实现的,所以最好把AOP相关依赖都加上。
1 2 3 4 5
| <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.6</version> </dependency>
|
3.2.2 xml方式实现
①配置事务管理器
1 2 3
| <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean>
|
②配置事务切面
1 2 3 4 5 6 7 8 9 10 11
| <tx:advice transaction-manager="txManager" id="txAdvice"> <tx:attributes> <tx:method name="trans*"/> </tx:attributes> </tx:advice>
<aop:config> <aop:pointcut id="pt" expression="execution(* com.sangeng.service..*.*(..))"></aop:pointcut> <aop:advisor advice-ref="txAdvice" pointcut-ref="pt"></aop:advisor> </aop:config>
|
注意,因为声明式事务底层是通过AOP实现的,所以最好把AOP相关依赖都加上。
1 2 3 4 5
| <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.6</version> </dependency>
|
3.3 属性配置
3.3.1 事务传播行为propagation
当事务方法嵌套调用时,需要控制是否开启新事务,可以使用事务传播行为来控制。
测试案例:
1 2 3 4 5 6 7 8 9 10 11
| @Service public class TestServiceImpl { @Autowired AccountService accountService;
@Transactional public void test(){ accountService.transfer(1,2,10D); accountService.log(); } }
|
1 2 3 4 5 6 7 8 9
| public class AccountServiceImpl implements AccountService { @Transactional public void log() { System.out.println("打印日志"); int i = 1/0; }
}
|
属性值 |
行为 |
REQUIRED(必须要有) |
外层方法有事务,内层方法就加入。外层没有,内层就新建 |
REQUIRES_NEW(必须要有新事务) |
外层方法有事务,内层方法新建。外层没有,内层也新建 |
SUPPORTS(支持有) |
外层方法有事务,内层方法就加入。外层没有,内层就也没有 |
NOT_SUPPORTED(支持没有) |
外层方法有事务,内层方法没有。外层没有,内层也没有 |
MANDATORY(强制要求外层有) |
外层方法有事务,内层方法加入。外层没有。内层就报错 |
NEVER(绝不允许有) |
外层方法有事务,内层方法就报错。外层没有。内层就也没有 |
例如:
1 2 3 4 5 6 7
| @Transactional(propagation = Propagation.REQUIRES_NEW) public void transfer(Integer outId, Integer inId, Double money) { accoutDao.updateMoney(inId,money); accoutDao.updateMoney(outId,-money); }
|
3.3.2 隔离级别isolation
Isolation.DEFAULT 使用数据库默认隔离级别
Isolation.READ_UNCOMMITTED
Isolation.READ_COMMITTED
Isolation.REPEATABLE_READ
Isolation.SERIALIZABLE
1 2 3 4 5 6 7
| @Transactional(propagation = Propagation.REQUIRES_NEW,isolation = Isolation.READ_COMMITTED) public void transfer(Integer outId, Integer inId, Double money) { accoutDao.updateMoney(inId,money); accoutDao.updateMoney(outId,-money); }
|
3.3.3 只读readOnly
如果事务中的操作都是读操作,没涉及到对数据的写操作可以设置readOnly为true。这样可以提高效率。
1 2 3 4 5
| @Transactional(readOnly = true) public void log() { System.out.println("打印日志"); int i = 1/0; }
|