文章目录
AOP前奏
- 代码混乱:越来越多的非业务需求(日志和验证等)加入后,原有的业务方法急剧膨胀,每个方法在处理核心逻辑的同时还必须兼顾其他多个关注点。
- 代码分散:以日志需求为例,只是为了满足这一单一需求,就不得不在多个模块(方法)里多次重复相同的日志代码。如果日志需求发生变化,必须修改所有模块。
- 可以采用动态代理,但相对来说难度比较高。
AOP简介
- AOP(面向切面编程,Aspect-Oriented Programming):是一种新的方法论,是对传统OOP(面向对象编程,Object-Oriented Programming)的补充。
- AOP的主要编程对象是切面(aspect),而切面模块化横切关注点。
- 在应用AOP编程时,仍然需要定义公共功能,但可以明确的定义这个功能在哪里,以什么方式应用,并且不必修改受影响的类。这样一来横切关注点就被模块化到特殊的对象(切面)里。
- AOP的好处:
- 每个事务逻辑位于一个位置,代码不分散,便于维护和升级。
- 业务模块更简洁,只包含核心业务代码
AOP术语
- 切面(Aspect):横切关注点(跨越应用程序多个模块的功能)被模块化的特殊对象
- 通知(Advice):切面必须要完成的工作
- 目标(Target):被通知的对象
- 代理(Proxy):向目标对象应用通知之后创建的对象
- 连接点(Joinpoint):程序执行的某个特定位置。
如类某个方法调用前、调用后、方法抛出异常后等。连接点由两个信息确定:方法表示的程序执行点,相对点表示的方位。
例如ArithmeticCalculator#add()方法执行前的连接点,执行点为ArithmeticCalculator#add();方位为该方法执行前的位置。 - 切点(Pointcut):每个类都拥有多个连接点:例如rithmeticCalculator中所有方法实际上都是连接点,即连接点是程序类中客观存在的事务。AOP通过切点定位到特定的连接点。类比:连接点相当于数据库中的记录,切点相当于查询条件。切点和连接点不是一对一的关系,一个切点匹配多个连接点,切点通过
org.springframework.aop.Pointcut
接口进行描述,它使用类和方法作为连接点的查询条件。
Spring AOP
- AspectJ:Java社区里最完整最流行的AOP框架
- 在Spring2.0以上版本中,可以使用AspectJ注解或基于XML配置的AOP
在Spring中启用AspectJ注解支持
- 要在Spring应用中使用AspectJ注解,必须在classpath下包含AspectJ类库:aopalliance.jar、aspectj.jar和spring-aspects.jar
- 将aop Schema添加到
<beans>
根元素中。 - 要在Spring IOC容器中启用AspectJ注解支持,只要在bean配置文件中定义一个空的XML元素
<aop:aspectj-autoproxy>
- 当SpringIOC容器侦测到Bean配置文件 中的
<aop:aspectj-autoproxy>
元素时,会自动为与AspectJ切面匹配的Bean创建代理。
用AspectJ注解声明切面
- 要在Spring中声明AspectJ切面,只需要在IOC容器中将切面声明为Bean实例。当在SpringIOC容器中初始化AspectJ切面之后,SpringIOC容器就会为那些与AspectJ切面相匹配的Bean创建代理。
- 在AspectJ注解中,切面只是一个带有@Aspect注解的java类
- 通知是标注有某种注解的简单java方法
- AspectJ支持5种类型的通知注解
- @Before
- @After
- @AfterRunning
- @AfterThrowing
- @Around
测试Demo——前置通知
- 该类声明为切面:
@Component
@Aspect
public class LoggingAspect {
@Before("execution(* aopimpl.ArithmeticCalculator.*(int,int))")
public void beforeMethod(){
System.out.println("The method begins");
}
}
<aop:aspectj-autoproxy />
bug记录
中间报NoSuchBeanDefinitionException
和NoClassDefFoundError
怀疑是Aspectj导包的问题。
AspectJ导入jar包
- aopalliance
- aspectj.weaver
- spring-aop
- spring-aspects
参考链接——AOP开发——AspectJ的使用
Aspectj下载地址——https://www.eclipse.org/aspectj/downloads.php
Spring框架的AOP — AspectJ支持包下载的与安装——https://blog.csdn.net/Angelia620/article/details/85006254
测试Demo——后置通知
- 后置通知是在连接点完成之后执行的,即连接点返回结果或抛出异常的时候,下方的后置通知记录了方法的终止。
- 一个切面可以包含一个或多个通知。
- 后置通知中还不能访问目标方法执行的结果。
//后置通知
@After("execution (* aopimpl.ArithmeticCalculator.*(int,int))")
public void afterMethod(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
List<Object> args = Arrays.asList(joinPoint.getArgs());
System.out.println("The method "+methodName+" ends ");
}
返回通知与异常通知
/**
* 返回通知
* @description:可以获取方法返回的参数
* @param joinPoint
* @param result
* @return: void
* @author: 大颗
* @time: 2020/7/4 17:20
*/
@AfterReturning(value = "execution (* aopimpl.ArithmeticCalculator.*(int,int))",returning = "result")
public void afterReturning(JoinPoint joinPoint,Object result){
String methodName = joinPoint.getSignature().getName();
System.out.println("The method "+methodName+" ends with "+result);
}
异常通知
- 只有连接点抛出异常时才执行异常通知
- 将throwing属性添加到@AfterThrowing注解中,也可以访问连接点抛出的异常。Throwable是所有错误和异常类的超类。所以在异常通知方法可以捕获到任何错误和异常。
- 如果只对某种特殊的异常类型感兴趣,可以将参数声明为其他异常的参数类型,然后通知就只在抛出这个类型及其子类的异常时才被执行。
/**
*
*
* @description:异常通知,可以访问到方法抛出的异常
* @param joinPoint
* @param ex
* @return: void
* @author: km
* @time: 2020/7/4 17:16
*/
@AfterThrowing(value = "execution (* aopimpl.ArithmeticCalculator.*(int,int))", throwing= "ex")
public void afterThrowing(JoinPoint joinPoint,Throwable ex){
String methodName = joinPoint.getSignature().getName();
System.out.println("The method "+methodName+" occurs exception: "+ex);
}
环绕通知
/**
* 环绕通知
* @description: 需要携带ProceedingJoinPoint类型的参数。
* 类似于动态代理的全过程:ProceedingJoinPoint类型的参数可以决定是否
* 执行目标方法。
* 且环绕通知必须要有返回值,返回值即为目标方法的返回值
* @param pjd
* @return: Object
* @author: 大颗
* @time: 2020/7/4 17:28
*/
@Around("execution (* aopimpl.ArithmeticCalculator.*(int,int))")
public Object aroundMethod(ProceedingJoinPoint pjd){
String methodName = pjd.getSignature().getName();
Object result = null;
try{
//前置通知
System.out.println("The method "+methodName+" begins with "+Arrays.asList(pjd.getArgs()));
result = pjd.proceed();
//返回通知
System.out.println("The method "+methodName+" ends with "+result);
} catch (Throwable throwable) {
//异常通知
throwable.printStackTrace();
}
//后置通知
System.out.println("The method "+methodName+" ends ");
return result;
}
切面优先级
可以用@Order(1)指定执行的顺序
@Order(1)
@Aspect
@Component
切入点
/**
* 定义一个方法,用于声明切入点表达式,一般地,该方法中再不需要添入其他的代码
* 使用@Pointcut来声明切入点表达式
* 后面的其它通知直接使用方法名来引入当前的切入点
*/
@Pointcut("execution(int aopimpl.ArithmeticCalculator.*(int,int))")
public void declareJointPointExpression(){}
/**
* 使用方法名来引入切入点
* @param joinPoint
*/
@Before("declareJointPointExpression()")
public void validateArgs(JoinPoint joinPoint){
System.out.println("-->validate: " + Arrays.asList(joinPoint.getArgs()));
}
基于配置文件的方式配置AOP
<!-- 配置bean-->
<bean id="arithmeticCalculator" class="aopimpl.ArithmeticCalculatorImpl">
</bean>
<!-- 配置切面bean-->
<bean id="loggingAspect" class="aopimpl.LoggingAspect"></bean>
<bean id="validationAspect" class="aopimpl.ValidationAspect"></bean>
<!-- 配置aop-->
<aop:config>
<!--配置切点-->
<aop:pointcut id="pointcut" expression="execution(int aopimpl.ArithmeticCalculator.*(int,int))"/>
<aop:aspect ref="loggingAspect" order="1">
<aop:before method="beforeMethod" pointcut-ref="pointcut"/>
<aop:after method="afterMethod" pointcut-ref="pointcut"/>
<aop:after-returning method="afterReturning" pointcut-ref="pointcut" returning="result"/>
<aop:after-throwing method="afterThrowing" pointcut-ref="pointcut" throwing="ex" />
<aop:around method="aroundMethod" pointcut-ref="pointcut"/>
</aop:aspect>
<aop:aspect ref="validationAspect" order="2">
<aop:after method="validateArgs" pointcut-ref="pointcut"></aop:after>
</aop:aspect>
</aop:config>
Spring对JDBC的支持
配置JdbcTemplate
<!-- 导入资源文件-->
<context:property-placeholder location="classpath:db.properties"/>
<bean id="dataSourse" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="user" value="${jdbc.user}"></property>
<property name="password" value="${jdbc.password"/>
<property name="jdbcUrl" value="${jdbc.jdbcUrl"/>
<property name="driverClass" value="${jdbc.driverClass}"/>
<property name="initialPoolSize" value="${jdbc.initPoolSize}"/>
<property name="maxPoolSize" value="${jdbc.maxPoolSize}"/>
<property name="maxStatements" value="0"/>
<property name="checkoutTimeout" value="100"/>
</bean>
<!-- 配置Spring的JdbcTemplate-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSourse"></property>
</bean>
Spring事务
示例:
事务注解
@Transactional(propagation = Propagation.REQUIRES_NEW,
isolation = Isolation.READ_COMMITTED,
timeout = 50,
readOnly = true,
rollbackFor = {IllegalAccessError.class})
事务传播行为(略)
使用propagation 指定事务的传播行为
事务传播行为 | 描述 |
---|---|
PROPAGATION_ REQUIRED | 需要事务处理。有则使用,无则新建。这是 Spring 默认的事务传播行为。该级别的特性是,如果 Context 中已经存在事务,那么就将当前需要使用事务的代码加入到 Context 的事务中执行,如果当前 Context 中不存在事务,则新建一个事务执行代码。这个级别通常能满足大多数的业务场景。 |
PROPAGATION_ SUPPORTS | 支持事务处理。该级别的特性是,如果 Context 存在事务,则将代码加入到 Context 的事务中执行,如果 Context 中没有事务,则使用 非事务 的方式执行。 |
PROPAGATION_ MANDATORY | 强制性要求事务。该级别的特性是,当要以事务的方式执行代码时,要求 Context 中必须已经存在事务,否则就会抛出异常!使用 MANDATORY 强制事务,可以有效地控制 “必须以事务执行的代码,却忘记给它加上事务控制” 这种情况的发生。举个简单的例子:有一个方法,对这个方法的要求是一旦被调用,该方法就必须包含在事务中才能正常执行,那么这个方法就适合设置为 PROPAGATION_MANDATORY 强制事务传播行为,从而在代码层面加以控制。 |
PROPAGATION_ REQUIRES_NEW | 每次都新建一个事务。该级别的特点是,当执行到一段需要事务的代码时,先判断 Context 中是否已经有事务存在,如果不存在,就新建一个事务;如果已经存在,就 suspend 挂起当前事务,然后创建一个新事务去执行,直到新事务执行完毕,才会恢复先前挂起的 Context 事务。 |
PROPAGATION_ NOT_SUPPORTED | 不支持事务。该级别的特点是,如果发现当前 Context 中有事务存在,则挂起该事务,然后执行逻辑代码,执行完毕后,恢复先前挂起的 Context 事务。这个传播行为的事务,可以缩小事务处理过程的范围。举个简单例子,在一个事务中,需要调用一段非核心业务的逻辑操作 1000 次,如果将这段逻辑放在事务中,会导致该事务的范围变大、生命周期变长,为了避免因事务范围扩大、周期变长而引发一些的事先没有考虑到的异常情况发生,可以将这段逻辑设置为 NOT_SUPPORTED 不支持事务传播行为。 |
PROPAGATION_ NEVER | 对事务要求更严格,不能出现事务!该级别的特点是,设置了该级别的代码,在执行前一旦发现 Context 中有事务存在,就会抛出 Runtime 异常,强制停止执行,有我无他! |
PROPAGATION_ NESTED | 嵌套事务。该级别的特点是,如果 Context 中存在事务 A,就将当前代码对应的事务 B 加入到 事务 A 内部,嵌套执行;如果 Context 中不存在事务,则新建事务执行代码。换句话说,事务 A 与事务 B 之间是父子关系,A 是父,B 是子。理解嵌套事务的关键点是:save point。 |
父、子事务嵌套、save point 的说明:
- 父事务会在子事务进入之前创建一个 save point;
- 子事务 rollback ,父事务只会回滚到 save
point,而不会回滚整个父事务;- 父事务 commit 之前,必须先 commit 子事务。
作者:uzip柚子皮 链接:https://www.jianshu.com/p/760399781b78 来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
首先准备如下两个 Service:
class ServiceA { void methodA() { ServiceB.methodB(); } } class ServiceB { void methodB() { } }
- 若 ServiceB.methodB() 的传播行为定义为 PROPAGATION_REQUIRED , 那么在执行
ServiceA.methodA() 的时候,若 ServiceA.methodA() 已经开启了事务,这时调用
ServiceB.methodB(),ServiceB.methodB() 将会运行在 ServiceA.methodA()
的事务内部,而不再开启新的事务。而假如 ServiceA.methodA()
运行的时候发现自己没有在事务中,就会为它分配一个新事务。这样,在 ServiceA.methodA() 或者在
ServiceB.methodB() 内的任何地方出现异常,事务都会被回滚。即使 ServiceB.methodB() 的事务已经被
提交,但是 ServiceA.methodA() 在接下来的过程中 fail 要回滚,ServiceB.methodB()
也会跟着一起回滚。- 假如 ServiceA.methodA() 的传播行为设置为
PROPAGATION_REQUIRED,ServiceB.methodB() 的传播行为为
PROPAGATION_REQUIRES_NEW,那么当执行到 ServiceB.methodB()
的时候,ServiceA.methodA() 所在的事务就会挂起,而 ServiceB.methodB() 会起一个新的事务,等待
ServiceB.methodB() 的事务完成以后,A的事务才会继续执行。PROPAGATION_REQUIRED与
PROPAGATION_REQUIRES_NEW 的事务区别在于事务的回滚程度。因为 ServiceB.methodB
是新起一个事务,那么就是存在两个不同的事务。如果 ServiceB.methodB 已经提交,那么 ServiceA.methodA
失败回滚,ServiceB.methodB 是不会回滚的。如果 ServiceB.methodB 失败回滚,如果它抛出的异常被
ServiceA.methodA 捕获,ServiceA.methodA 事务仍然可能会提交。- 假如 ServiceA.methodA
的事务传播行为是 PROPAGATION_REQUIRED,而 ServiceB.methodB 的事务传播行为是
PROPAGATION_NOT_SUPPORTED,那么当执行到 ServiceB.methodB 时,ServiceA.methodA
的事务挂起,而ServiceB.methodB 以非事务的状态运行完之后,再继续 ServiceA.methodA 的事务。- 假如
ServiceA.methodA 的事务传播行为是 PROPAGATION_REQUIRED, 而 ServiceB.methodB
的事务级别是 PROPAGATION_NEVER ,那么 ServiceB.methodB 执行时就会抛出异常。作者:uzip柚子皮 链接:https://www.jianshu.com/p/760399781b78 来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
REQUIRED
@Transactional(propagation = Propagation.REQUIRED)
REQUIRES_NEW
@Transactional(propagation = Propagation.REQUIRES_NEW)
事务隔离级别
- 脏读:一个事务读到另一个事务未提交的更新数据。(打算提交但是数据回滚了,读取了提交的数据)
- 幻读:是指当事务不是独立执行时发生的一种现象。(读取了插入前的数据)
- 不可重复读:在一个事务里面的操作中发现了未被操作的数据。(读取了修改前的数据)
隔离界别 | 是否会发生脏读 | 不可重复读 | 幻读 | |
---|---|---|---|---|
ISOLATION_DEFAULT | 使用数据库本身的隔离级别 | – | – | – |
ISOLATION_READ_UNCOMITTED | 读未提交 | 是 | 是 | 是 |
ISOLATION_READ_COMMITED | 读已提交 | 否 | 是 | 是 |
ISOLATION_REPEATABLE_READ | 可重复读 | 否 | 否 | 是 |
ISOLATION_SERLALIZABLE | 串行化 | 否 | 否 | 否 |
xml方式配置
<!--1. 配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSourse"></property>
</bean>
<!--2. 配置事务属性-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes >
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<!--3.配置事务切入点-->
<aop:config>
<aop:pointcut id="" expression="execution(* * )"/>
</aop:config>