目录
PlatformTransactionManager平台事务管理器接口
通过springAOP实现自动事务,默认情况下不再需要对每个方法手动打注解
概述
什么是spring
一站式轻量级分层框架,非侵入式
spring是非侵入式的框架
侵入式的概念
侵入式:实现特定接口,继承特定类,改变原有类的结构,即为侵入式,如struts2
非侵入式:对类原本结构没有影响,仍然增强了JavaBean的功能
spring的核心
核心是IOC控制反转和AOP面向切面
spring的优势
IOC,解耦开发
AOP,方便权限拦截,运行监控
声明式事务管理
方便集成其它框架
降低日常开发难度
注意
因为当下项目已经全面普及springboot,前后端分离,因此我们不再关注以前ssm项目中常见的xml配置文件,比如applicationContext.xml,只关注spring本身
IOC控制反转
概述
在没有spring的情况下,如果需要使用一个类的对象,需要手动的new Xxx(),那么当前类就和Xxx类形成了强耦合
于是有人想到用BeanFactory模式,通过BeanFactory获取对象,这样当前类就和Xxx解耦,比如
private XxDao xxDao = DaoFactory.getInstance().createDao("...dao.impl.XxDaoImpl", XxDao.class);
spring提供了更好的解决方案,即IOC控制反转,将对象的管理权反转给了spring,开发者不再需要手动创建Bean,而是直接从spring容器获取
核心
IOC的核心是spring容器,spring创建对象并负责管理它们完整的生命周期
IOC需要依赖注入(DI)的支持:创建A对象,A会用到其他的类,这时就需要依赖注入支持
容器
ApplicationContext
即时加载,加载applicationContext.xml(容器启动)时候就会创建对象
ClassPathXmlApplicationContext:加载类路径下 Spring 的配置文件.
FileSystemXmlApplicationContext:加载本地磁盘下 Spring 的配置文件
DI,dependency injection依赖注入
概念
依赖:指一个类使用到了另一个类,A类依赖B类,即A类使用到了B类
依赖注入:将类和类的依赖关系告诉spring容器
注入方式
构造方法:通过构造方法将另一个类的对象传入当前类
set方法:通过set方法将另一个类的对象传入当前类
注解注入:通过注解获取另一个类的对象
注解方式只能注入spring容器管理的类,因此要配置扫描包,springboot默认会扫描启动类所在包及其子包下的Bean,可以自定义配置
@ComponentScan(value = {"com.xxx.app.service.*", "com.xxx.app.controller.*"})
public class UserServiceImpl implements UserService {
//注解注入
@Autowired
private UserDao userDao;
//使用构造方法实现依赖注入
public UserServiceImpl(UserDao userDao){
this.userDao = userDao;
}
//使用set方法实现依赖注入
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
}
循环依赖
定义:A依赖B,B依赖A
spring如何解决循环依赖
构造器的循环依赖:无法处理,直接抛出BeanCurrentlylnCreationException异常
单例模式下的setter循环依赖:通过“三级缓存”处理循环依赖
非单例循环依赖:无法处理
spring生成Bean的方式
通过无参构造创建Bean
使用BeanFactory:静态和非静态工厂
Bean属性注入(Bean属性赋值)
构造方法:通过构造方法给属性赋值
set方法:通过set方法给属性赋值
spel
springBean生命周期
实例化Bean
对于BeanFactory容器,当客户向容器请求一个尚未初始化的bean时,或初始化bean的时候需要注入另一个尚未初始化的依赖时,容器就会调用createBean进行实例化。
对于ApplicationContext容器,当容器启动结束后,通过获取BeanDefinition对象中的信息,实例化所有的bean
属性赋值(依赖注入)
实例化后的对象被封装在BeanWrapper对象中,紧接着,Spring根据BeanDefinition中的信息 以及 通过BeanWrapper提供的设置属性的接口完成属性设置与依赖注入
Aware接口回调
Spring会检测该对象是否实现了xxxAware接口,如果Bean实现了Aware接口,容器会回调相应的方法,将容器相关的信息注入到Bean中
如果这个Bean实现了BeanNameAware接口,会调用它实现的setBeanName(String beanId)方法,传入Bean的名字
如果这个Bean实现了BeanClassLoaderAware接口,调用setBeanClassLoader()方法,传入ClassLoader对象的实例
如果这个Bean实现了BeanFactoryAware接口,会调用它实现的setBeanFactory()方法,传递的是Spring工厂自身
如果这个Bean实现了ApplicationContextAware接口,会调用setApplicationContext(ApplicationContext)方法,传入Spring上下文
BeanPostProcessor前置处理器
容器会调用所有实现了BeanPostProcessor接口的类的postProcessBeforeInitialization方法,对Bean进行前置处理
InitializingBean(初始化)
如果Bean实现了InitializingBean接口,执行afeterPropertiesSet()方法
或在Bean定义中(配置)指定init-method方法
BeanPostProcessor后置处理器
如果这个Bean实现了BeanPostProcessor接口,将会调用postProcessAfterInitialization(Object obj, String s)方法
由于这个方法是在Bean初始化结束时调用的,所以可以被应用于内存或缓存技术
在这一步后,Bean就被创建完成,可以开始使用这个Bean了
destroy-method销毁
当容器关闭时,会调用Bean的销毁方法
如果Bean实现了DisposableBean这个接口,会调用其实现的destroy()方法
或在Bean定义中指定destroy-method方法
springBean的作用域
singleton:默认作用域,单例bean,每个容器中只有一个bean的实例
prototype:为每一个bean请求创建一个实例
request:为每一个request请求创建一个实例,在请求完成以后,bean会失效并被垃圾回收器回收
session:与request范围类似,同一个session会话共享一个实例,不同会话使用不同的实例
global-session:全局作用域,所有会话共享一个实例。如果想要声明让所有会话共享的存储变量的话,那么这全局变量需要存储在global-session中
springIOC注解
刚刚我们说,IOC是将对象的控制从开发者反转给spring,我们通过spring提供的注解来通知spring管理哪些类,将哪些类以哪种作用域向容器中注入Bean
类注解
@Component
向spring容器注入当前Bean
@Component("user"),相当于xml配置了<bean name="user"...></bean>,现在是否理解xml中的注入?其实就是通过xml的方式向spring容器注入了一个Bean
Spring 中提供@Component 的三个衍生注解,功能和@Component一样
@Controller 控制层
@Service 业务层
加在接口的实现类上,不加在接口上
对于有多个实现类的接口,要指定name,自动装配要使用@Resource(name="xxx")
@Repository 持久层
说明:为了区别于类型,通常配置name属性为开头小写
实现类通常配置name=接口名,存在多个实现类则根据区别配置不同name
@Configuration
标记这是一个配置类,一般会配合@Bean,@Scope,@Lazy使用,等价于原本的xml配置文件注入Bean
@Configuration
public class AppConfig {
@Bean
public TransferService transferService() {
return new TransferServiceImpl();
}
}
相当于xml
<beans>
<bean id="transferService" class="com.acme.TransferServiceImpl"/>
</beans>
@Scope
@Scope作用在类上,表示给定容器中Bean的作用域
@Scope(scopeName="prototype");
@Scope具有以下几种作用域
singleton 单实例的(单例)(默认),全局有且仅有一个实例
prototype 多实例的(多例),每次获取Bean的时候会有一个新的实例
reqeust 同一次请求,每一次HTTP请求都会产生一个新的bean,同时该bean仅在当前HTTP request内有效
session 同一个会话级别,每一次HTTP请求都会产生一个新的bean,同时该bean仅在当前HTTP session内有效
@Scope注意点
1.尽量不要直接使用字符串配置,使用spring提供的参数
ConfigurableBeanFactory.SCOPE_PROTOTYPE
ConfigurableBeanFactory.SCOPE_SINGLETON
WebApplicationContext.SCOPE_REQUEST
WebApplicationContext.SCOPE_SESSION
2.每一个Bean实例,都会有一个initMethod() 和 destroyMethod() 方法,我们可以在Bean 类中自行定义,也可以使用 Spring 默认提供的这两个方法
也可以通过@Bean的属性指定@Bean(initMethod = "initUser", destroyMethod = "destroyUser")
3.思考这么一个场景:当Controller中定义了非静态成员变量,如果不设置成多例,那么多次调用操作同一个变量,会不断的改变变量的值,与我们调用方法的预期不符
属性注解
@Value
用于注入普通类型
@Value(value="zs"),value=可以省略
作用在指定成员的成员变量上或者对应的set()上,区别在于
作用成员变量上,通过反射Field注入
作用在set()上,通过set()注入
可以获取配置文件的数据进行注入,比如.properties
@AutoWire
@Autowire默认按照类型(by-type)装配
默认情况下要求依赖对象必须存在,如果找到多个,再按照名称装配
@Autowire
private StudentService studentService;
如果允许依赖对象为null,需设置required属性为false,即
@Autowire(required=false)
private StudentService studentService;
如果使用按照名称(by-name)装配,需结合@Qualifier注解使用,即
@Autowire
@Qualifier("studentService") //这里的参数是bean的name,studentService
private StudentService studentService;
@Resource
默认按照名称(by-name)装配,名称可以通过name属性指定
当按照名称(by-name)装配未匹配时,按照类型(by-type)装配,此时就是用成员的类型去容器中bean的类型匹配
@Resource(name="studentService") //用这里的参数studentService去容器和bean的name匹配
当显式指定name属性后,只能按照名称(by-name)装配
如果没有指定name
当@Resource注解在成员变量上时,默认取name=成员变量名称装配,这里是字段名称不是字段类型,如下就是用studentService去和容器中bean的name匹配
当@Resource注解在setter方法上时,默认取name=属性名称装配
@Resource
private StudentService studentService;
@Resource
public void setXXX() {...}
Resource总结
如果同时指定name和type属性,则找到唯一匹配的bean装配,未找到则抛异常
如果指定name属性,则按照名称(by-name)装配,未找到则抛异常
如果指定type属性,则按照类型(by-type)装配,未找到或者找到多个则抛异常(一个type可能有多个实现类,所以可能找到多个结果)
既未指定name属性,又未指定type属性,则按照名称(by-name)装配,如果未找到,则按照类型(by-type)装配
@Autowire和@Resource区别
1.来源不同:@Autowire是spring提供的注解,@Resource是jdk提供的注解
2.默认的依赖查找顺序不同:
@Autowire默认根据类型查找,如果找到多个,再根据名称查找
@Resource默认根据名称查找,找不到再根据类型查找
3.支持的参数不同
@Autowire仅支持require参数
@Resource支持多个参数,比如name,type
4.支持的注入方式不同
@Autowire支持属性注入,setter注入,构造注入
@Resource不支持构造注入,即这个标签不能打在构造方法上实现注入
方法注解
@Bean
方法级别注解,通常使用在@Configuration的配置类中使用
向容器注入一个Bean,id为方法名,类型为方法返回值
@Configuration
public class AppConfig {
@Bean
public TransferService transferService() {
return new TransferServiceImpl();
}
}
相当于xml
<beans>
<bean id="transferService" class="com.acme.TransferServiceImpl"/>
</beans>
@Scope
@Scope作用在方法上,表示当前Bean作用域
@Lazy
@Lazy作用在方法上,表示当前Bean在注入容器时是懒加载模式
@Primary
当某个类型的Bean有多个候选者可以注入时,应当优先加载@Primary标记的Bean
@Profile
当spring.profile.active为对应的值时,才向容器注入当前Bean
AOP面向切面
切面编程并不是spring开创的,来源是java拦截器思想,但spring给切面编程提供了更好的解决方式
抽取程序执行过程中多个部分功能一致的代码,比如:权限校验,日志记录,性能监控,事务控制
底层实现
通过代理机制,springAOP会用到两种代理机制,只能选一种不能混用
代理
什么是代理:将原本的调用,比如person.run();封装一层代理,在代理对象中调用person.run();
静态代理
代码运行前,我们就编写好代理类,编译后生成class文件
缺点十分明显:每一个被代理对象都需要建一个代理类去代理,代码冗杂
动态代理
代理类是在运行过程中产生的,不需要给每一个被代理类编写单独的代理类
JDK 动态代理
针对接口的实现类产生代理
生成接口的实现类,在实现类中通过method.invoke(...)调用被代理对象的方法
Cglib 动态代理
针对没有实现接口的类产生代理
对目标进行继承代理,应用的是字节码增强生成当前类的子类对象
Enhancer类的create()生成代理对象
实现MethodInterceptor接口,重写intercept(...)
methodProxy.invoke(target,objects)调用代理对象方法
如果没有Spring,那么需要我们手动的Proxy.newProxyInstance(xx.xx.xx);
简单来说,就是在目标方法执行时,会通过代理的方式生成一个子类,重写目标方法,在方法体前后执行AOP代码
动态代理注意点
如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP
如果目标对象实现了接口,可以强制使用CGLIB实现AOP
如果目标对象没有实现了接口,必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换
Spring AOP的原理是 JDK 动态代理和CGLIB字节码增强技术,前者需要被代理类实现相应接口,也只有接口中的方法可以被JDK动态代理技术所处理;后者实际上是生成一个子类,来覆盖被代理类,那么父类的final方法就不能代理,因为父类的final方法不能被子类所覆盖。一般而言Spring默认优先使用JDK动态代理技术,只有在被代理类没有实现接口时,才会选择使用CGLIB技术来实现AOP,这样就可能造成代理混用的问题,spring提供了一个配置强制使用cglib代理来解决混用问题:<aop:config proxy-target-class="true" />
AOP名词
Joinpoint(连接点)
目标对象中所有可以增强的方法都是连接点
所谓连接点是指那些可以被拦截到的点,因为spring只支持方法类型的连接点
Pointcut(切入点)
目标对象中已经被增强的方法
所谓切入点是指我们要对哪些连接点Joinpoint进行拦截的定义
Advice(通知/增强)
负责增强的代码
所谓通知是指拦截到Joinpoint之后所要做的事情
通知分为前置通知,后置返回通知,后置通知(最终通知),环绕通知(切面要完成的功能),异常通知
后置返回通知:目标方法出现异常不会执行
后置通知(最终通知):目标出现异常也会执行
Introduction(引介)
引介是一种特殊的通知,在不修改类代码的前提下,Introduction可以在运行期为类动态地添加一些方法或 Field
Aspect(切面)
是切入点和通知(引介)的结合
Target(目标对象)
被代理的目标对象
Weaving(织入)
将通知应用到切点的过程就是织入
是指把增强应用到目标对象来创建新的代理对象的过程,织入完成生成代理对象
Proxy(代理)
通知织入到目标对象以后,形成代理对象
一个类被AOP织入增强后,就产生一个结果代理类
简单的springAOP实现
@Aspect
定义切面,表示该类是切面类
这个注解只是声明这是个切面类,并没有放进Spring容器,通常使用@Component将该类交给spring容器
切面类中可以定义切点,通知
@Pointcut("xxx")
定义切点,两种方式
execution 指定方法,@Pointcut("execution(* com.coolway.*(..))")
@annotation 指定注解,@Pointcut("@annotation(com.coolway.annotation.RequiresLogin)")
@Before等
定义通知,即指定切点处要执行的AOP代码
要给通知注解传入切点
要么使用上面@Pointcut("xxx")定义好的切点
要么重新传入切点,比如@Around("@annotation(com.coolway.annotation.RequiresLogin)")
其他通知注解
@AfterReturning
@Around
@AfterThrowing
@After
切点表达式
完整的表达式:execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)
modifiers-pattern:方法的可见性修饰符,必须是public,通常省略
void com.coolway.service.impl.StudentServiceImpl.getStudent()
ret-type-pattern:方法的返回值类型,如 int,void 等;通常不对返回值做要求,用*表示任意类型返回值
* com.coolway.service.impl.StudentServiceImpl.getStudent()
declaring-type-pattern:方法所在类的全路径名,类名可以定义为部分匹配,比如以ServiceImpl结尾
* com.coolway.service.impl.*ServiceImpl.*(..)
可以配置成包括子包下符合的,使用..符号,如service下任意子包下的impl包的ServiceImpl类的任意方法(任意参数)
* com.coolway.service..impl.*ServiceImpl.*(..)
name-pattern:方法名,如 getOrderDetail();可以用*表示该类的任意方法
* com.coolway.service.impl.StudentServiceImpl.*()
param-pattern:方法的参数类型,如 java.lang.String;..表示任意参数
* com.coolway.service.impl.StudentServiceImpl.*(..)
throws-pattern:方法抛出的异常类型,如 java.lang.Exception;
示例:execution(public void com.coolway.service.impl.StudentServiceImpl.getStudent())
依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
实现
@Aspect
public class AspectDemo {
//定义切点,指定方法
@Pointcut("execution(* com.coolway.*(..))")
public void loginAspectPointcut() {}
//使用定义好的切点
@Before("loginAspectPointcut()")
public void beforeLogin() {
System.out.println("开始访问,查看是否登录");
}
//指定切点为注解
@After("@annotation(com.coolway.annotation.RequiresLogin)")
public void afterLogin() {
System.out.println("结束访问");
}
}
spring事务管理
概述
什么是事务
逻辑上的一组操作,一起成功,一起失败
显然,通常执行一系列的增删改操作,需要开启事务
spring事务管理依托于数据库事务,是通过数据库连接Connection来实现的,并不是spring自己提供的事务,spring提供了更好的方式处理事务
事务特性ACID
原子性:事务不可分割
一致性:事务执行前后数据完整性保持一致
隔离性:事务执行过程中不受其他事务的干扰
持久性:事务结束,数据持久化到数据库
不考虑事务隔离引发的安全性问题
脏读
读到了其他事务正在操作但未提交的数据
A事务访问了一条数据X,并对X做了修改,B事务也访问了X,读取到了A事务修改后的X值,而A事务执行了回滚,X值恢复到了修改前,此时B事务之前读的X就成了脏数据
不可重复读
重复查询时,读到了其他事务更新的数据,导致重复查询结果不一致
A事务在执行过程中多此访问数据X,因为其他事务在多次读取的间隙时间内修改了X值,导致A事务多次读取到的X值不一致
幻读
读到了其他事务插入的数据,导致重复查询结果不一致
在一个事务中,先后两次进行读取相同的数据(一般是范围查询),但由于另一个事务新增或者删除了数据,导致前后两次结果不一致,幻读不可以理解成不可重复读的一种,应当单独理解,因为幻读会严重影响后续操作
事务隔离级别
未提交读 :脏读,不可重复读,虚读都有可能发生 1
已提交读 :避免脏读。但是不可重复读和虚读有可能发生 2
可重复读 :避免脏读和不可重复读.但是虚读有可能发生 4
串行化的 :避免以上所有读问题 8
Mysql默认是repeatable read,级别4
oracle默认是read committed,级别2
事务回滚
代码出现异常会执行事务回滚,事务整体失败
这里的异常指的是没有被处理的异常,一旦使用了try catch finally或者是throw&throws,事务就不会执行回滚
spring事务核心
PlatformTransactionManager平台事务管理器接口
spring本身不直接管理事务,而是提供多种事务管理器,针对不同持久化平台提供不同的实现类,如JDBCTransactionManager,HibernateTransactionManager,JpaTransactionManager等
TransactionDefinition事务管理的属性
transactionManager
通常一个项目只用一个事务管理器,但有些项目会包含多个互不相关的数据源,这时候就需要使用多个事务管理器,通过这个属性指定事务管理器
@Transactional(transactionManager = "txManager#singleton")
isolation,隔离级别
ISOLATION_DEFAULT:这是个 PlatfromTransactionManager 默认的隔离级别,使用数据库默认的事务隔离级别
ISOLATION_READ_UNCOMMITTED:读未提交,允许事务在执行过程中,读取其他事务未提交的数据
ISOLATION_READ_COMMITTED:读已提交,允许事务在执行过程中,读取其他事务已经提交的数据
ISOLATION_REPEATABLE_READ:可重复读,在同一个事务内,任意时刻的查询结果都是一致的
ISOLATION_SERIALIZABLE:所有事务逐个依次执行
未提交读:脏读,不可重复读,虚读都有可能发生 1
已提交读:避免脏读。但是不可重复读和虚读有可能发生 2
可重复读:避免脏读和不可重复读.但是虚读有可能发生 4
串行化的:避免以上所有读问题 8
propagation,传播行为
决定业务方法平行调用时,事务如何处理
PROPAGATION_REQUIRED required,支持当前事务,如果不存在 就新建一个(默认),最常用
A()调用B(),如果A没有开事务,那么会自动开启一个事务,如果A已经开了事务,就在这个事务中执行B,实际上99%的情况都是这种情况,相当于整体操作作为一个事务
PROPAGATION_SUPPORTS support,支持当前事务,如果不存在,就不使用事务
PROPAGATION_MANDATORY mandatory,支持当前事务,如果不存在,抛出异常
下列配置可以保证平行方法没有在同一个事务中执行
PROPAGATION_REQUIRES_NEW requires_new,如果有事务存在,挂起当前事务,创建一个新的事务,这是为了在事务嵌套的情况下,内部事务不影响外部事务,其他事务
PROPAGATION_NOT_SUPPORTED not_supported,以非事务方式运行,如果有事务存在,挂起当前事务
PROPAGATION_NEVER never,以非事务方式运行,如果有事务存在,抛出异常
PROPAGATION_NESTED nested,如果当前事务存在,则嵌套事务执行0
readOnly,是否只读事务
如果一个事务中所有的数据库操作都是只读的,应该给该事务设置只读属性,帮助数据库引擎优化事务,提升数据库读写效率
true,false
timeout,超时设置
设置事务持续时间,超时强制回滚,避免一个事务对连接占用过长的时间
rollbaclFor,何种异常执行回滚
默认情况下Spring会为所有的运行时异常进行回滚
怎么使用spring事务管理
此处不讨论xml配置的方式,只关注注解式
依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
</dependency>
简单demo
@Transactional(isolation = Isolation.REPEATABLE_READ, propagation = Propagation.REQUIRED, rollbaclFor = NullPointException)
public ...(){...}
//实际上也可以都使用默认配置,即
@Transactional
public ...(){...}
spring事务失效
数据库方面
1.数据库本身不支持事务,比如,mysql的myisam不支持事务,当然新版本的mysql已经移除了myisam引擎
spring方面
2.事务没有被spring管理,比如,在某个方法上使用了注解@Transactional,但是这个类没有被注入到spring容器中,那么这个事务其实是无效的
配置方面
3.数据源未配置事务管理器,未开启事务管理,配置transactionManager是开启spring事务管理的第一步,springboot无需手动开启,但xml配置的方式需要手动配置开启
4.事务传播性设置导致,PROPAGATION_NOT_SUPPORTED,PROPAGATION_NEVER,PROPAGATION_SUPPORTS有可能会导致事务失效
代码编写方面
5.方法不是public,@Transactional只有修饰public方法时才会生效,这是spring的明确要求
6.方法是final修饰,final方法无法被代理类重写,也就无法添加事务管理功能
7.同一个类中自身方法调用
比如,在类A的方法a上使用了注解@Transactional,在这个类A中有其他的方法b调用了这个方法a,那么a上标记的这个事务是无效的,因为spring事务管理是通过AOP实现的,而AOP是通过动态代理实现的,而同一个类中自身方法的调用是通过this,没有通过代理类,所以事务是无效的
解决方法:解决办法有多种
1.声明一个新的service,在service中调用这个@Transactional的方法
2.本类中注入自身
@Autowire
XxxService xxxService;
xxxService.do();
3.使用application.getBean()获取当前类对象,并调用目标方法
XxxService xxxService = applicationContext.getBean(XxxService.class);
xxxService.do();
4.使用AopContext,获取当前的代理对象并调用
((XxxService)AopContext.currentProxy()).do();
8.使用cglib代理,cglib代理对接口层的@Transactional是无效的
9.多线程调用,多个线程获取到的数据库连接不一样,则分别属于两个不同的事务(因为spring事务是基于数据库连接实现),显然无法一起提交和回滚
异常处理
10.rollbackFor异常指定错误,如果发生了rollbaclFor指定以外的异常,事务就不会rollback了
11.异常被catch了,如果必须要catch,那么手动做throw,抛出异常
通过springAOP实现自动事务,默认情况下不再需要对每个方法手动打注解
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.springframework.aop.Advisor;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.interceptor.NameMatchTransactionAttributeSource;
import org.springframework.transaction.interceptor.RollbackRuleAttribute;
import org.springframework.transaction.interceptor.RuleBasedTransactionAttribute;
import org.springframework.transaction.interceptor.TransactionAttribute;
import org.springframework.transaction.interceptor.TransactionInterceptor;
/**
* @description 通过AOP切面设置全局事务,拦截service包下面所有方法
* AOP术语:通知(Advice)、连接点(Joinpoint)、切入点(Pointcut)、切面(Aspect)、目标(Target)、代理(Proxy)、织入(Weaving)
*/
@Configuration
public class TransactionManagerConfig {
private static final int TX_METHOD_TIMEOUT = 5000;
/*定义切点变量*/
private static final String AOP_POINTCUT_EXPRESSION = "execution (* com.web.service.impl.*.*(..))";
@Autowired
private PlatformTransactionManager transactionManager;
/**
* @description springBoot事务配置
*/
@Bean
public TransactionInterceptor TxAdvice() {
/*事务管理规则,声明具备事务管理的方法名*/
NameMatchTransactionAttributeSource source = new NameMatchTransactionAttributeSource();
/*只读事物、不做更新删除等*/
/*当前存在事务就用当前的事务,当前不存在事务就创建一个新的事务*/
RuleBasedTransactionAttribute readOnlyRule = new RuleBasedTransactionAttribute();
/*设置当前事务是否为只读事务,true为只读*/
readOnlyRule.setReadOnly(true);
/* transactiondefinition 定义事务的隔离级别;
* PROPAGATION_NOT_SUPPORTED事务传播级别5,以非事务运行,如果当前存在事务,则把当前事务挂起*/
readOnlyRule.setPropagationBehavior(TransactionDefinition.PROPAGATION_NOT_SUPPORTED);
RuleBasedTransactionAttribute requireRule = new RuleBasedTransactionAttribute();
/*抛出异常后执行切点回滚*/
requireRule.setRollbackRules(Collections.singletonList(new RollbackRuleAttribute(Exception.class)));
/*PROPAGATION_REQUIRED:事务隔离性为1,若当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。这是默认值。 */
requireRule.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
/*设置事务失效时间,如果超过5秒,则回滚事务*/
requireRule.setTimeout(TX_METHOD_TIMEOUT);
Map<String, TransactionAttribute> txMap = new HashMap<>();
txMap.put("add*", requireRule);
txMap.put("save*", requireRule);
txMap.put("insert*", requireRule);
txMap.put("update*", requireRule);
txMap.put("delete*", requireRule);
txMap.put("remove*", requireRule);
txMap.put("import*", requireRule);
txMap.put("get*", readOnlyRule);
txMap.put("query*", readOnlyRule);
txMap.put("find*", readOnlyRule);
txMap.put("select*", readOnlyRule);
source.setNameMap(txMap);
TransactionInterceptor txAdvice = new TransactionInterceptor(transactionManager, source);
return txAdvice;
}
/**
* @description 利用AspectJExpressionPointcut设置切面=切点+通知(写成内部bean的方式)
*/
@Bean
public Advisor txAdviceAdvisor() {
/* 声明切点的面
* 切面(Aspect):切面就是通知和切入点的结合。通知和切入点共同定义了关于切面的全部内容——它的功能、在何时和何地完成其功能。
* */
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
/*声明和设置需要拦截的方法,用切点语言描写*/
pointcut.setExpression(AOP_POINTCUT_EXPRESSION);
/*设置切面=切点pointcut+通知TxAdvice*/
return new DefaultPointcutAdvisor(pointcut, TxAdvice());
}
}
spring中运用的设计模式
工厂设计模式:Spring使用工厂模式通过 BeanFactory、ApplicationContext 创建 bean 对象
代理设计模式:Spring AOP 功能的实现
单例设计模式:Spring 中的 Bean 默认都是单例的
模板方法模式:Spring 中 jdbcTemplate、hibernateTemplate 等以 Template 结尾的对数据库操作的类,它们就使用到了模板模式
包装器设计模式:我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源
观察者模式:Spring 事件驱动模型就是观察者模式很经典的一个应用
适配器模式:Spring AOP 的增强或通知(Advice)使用到了适配器模式、spring MVC 中也是用到了适配器模式适配Controller