标签:增强 类型 aci try 框架 framework create name 设置
本文启发自关于 Spring AOP (AspectJ) 你该知晓的一切_zejian的博客-CSDN博客_springaop
如果使用传统OOP的方式来对大量重复的代码进行管理,通常采用的是继承或抽取静态工厂方法的方式,然而这两种方式仍然会使项目中出现过多重复的代码。为了能够对业务代码更好的解耦,让每个业务模块更加独立,需要存在一种方式能够解决这个问题,AoP应运而生
字面意思为面向切面编程,其逻辑分为如下几步
抽取重复逻辑
通过将不同模块会使用到的重复的逻辑(如日志、监控等)进行抽取,将其定义在专门的模块中,并将功能划分为多个方法,如方法a,b,c;
分析切入点
对需要用到前面抽取逻辑的模块(如查询订单),首先需要分析哪些方法上及这些方法的哪些位置上应用前面抽取出的功能
织入功能
在分析好切入点后,只需要将需要的模块插入至各个对应的切入点即可
AspectJ是一种AoP的解决方案,其采用编译器或链接期织入的方式,对原有类实现增强,逻辑如下
JointPoint
只要增强的类中哪些方法可以被当作Pointcut点
Pointcut
指定了真正要被增强的方法
Advice
即对Pointcut指定的方法要做的增强
Aspect
Advice+Pointcut构成一个切面
Weaving
将Advice织入到Pointcut的过程
其提供了如下注解
Advice相关
@Before
在目标方法(pointcut)之前执行
@AfterRuturning
在目标方法返回后执行,其中可以设置returning
属性用于接收方法返回值,并将创建属性值同名的方法参数
@AfterThrowing
在目标方法抛出异常后执行,可以设置throwing
属性用于接收方法抛出的异常,并将创建属性值同名的方法参数
@After
在方法结束后return前执行
@Around
类似于定义InvocationHandler中的invoke方法,是前面几种的组合
try{
//前置通知 Before
}catch(Exception e){
//异常通知 AfterThrowing
}finally{
//最终通知 After
}
Pointcut相关
@Pointcut
用于定义切点,Advice注解可以直接使用
Aspect相关
@Aspect
用于修饰切面类
Spring中没有采用类似于AspectJ的方法在编译器生成代理类对象,而是采用动态代理的方式,在运行期生成代理类对象,这样可以配合IoC来使用,更适合Spring框架。运行期生成代理类对象有两种方式,分别为使用JDK 提供的动态代理工具类Proxy,和CGLIB的方式
接口
)这种方式衍生于静态代理,将代理类的生成放在运行期通过反射来创建。但是,都需要被代理类将需要增强的方法放入接口并实现,这样代理类就可以通过反射方式调用被代理类的实现的接口中的方法,完成动态代理
创建代理类对象
使用Proxy.newProxyInstance
来创建,其中需要传入如下三个参数
classLoader
将被代理类的类加载器传入即可
class<?>[] interfaces
传入被代理类实现的所有接口
InvocationHandler
代理类的增强功能
上面最关键的是创建InvocationHandler
的实现类,并重写其中的invoke方法,一个常见的实现模板如下
public class Handler implements InvocationHandler{
//被代理对象
private Object obj;
public Handler() {
}
//将被代理对象传入
public WorkHandler(Object obj) {
this.obj = obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try{
//前置增强部分
//传入被代理对象,反射调用原始方法
Object invoke = method.invoke(obj, args);
//后置增强部分
return invoke;
}catch(Exception e){
//异常增强部分
}finally{
//最终增强部分
}
}
}
使用代理类对象
使用工厂方法newProxyInstance返回的是Object类型,需要向下转型至想要的接口类型并调用接口方法
子类
)通过创建被代理类的子类,采用继承的方式对被代理类中的非final方法进行增强,这种方式适用于没有实现接口的情况,但是要求被代理类及其想要被增强的方法必须不是final的
下面例子摘自关于 Spring AOP (AspectJ) 你该知晓的一切_zejian的博客-CSDN博客_springaop
//被代理的类即目标对象
public class A {
public void execute(){
System.out.println("执行A的execute方法...");
}
}
//代理类
public class CGLibProxy implements MethodInterceptor {
/**
* 被代理的目标类
*/
private A target;
public CGLibProxy(A target) {
super();
this.target = target;
}
/**
* 创建代理对象
* @return
*/
public A createProxy(){
// 使用CGLIB生成代理:
// 1.声明增强类实例,用于生产代理类
Enhancer enhancer = new Enhancer();
// 2.设置被代理类字节码,CGLIB根据字节码生成被代理类的子类
enhancer.setSuperclass(target.getClass());
// 3.//设置回调函数,即一个方法拦截
enhancer.setCallback(this);
// 4.创建代理:
return (A) enhancer.create();
}
/**
* 回调函数
* @param proxy 代理对象
* @param method 委托类方法
* @param args 方法参数
* @param methodProxy 每个被代理的方法都对应一个MethodProxy对象,
* methodProxy.invokeSuper方法最终调用委托类(目标类)的原始方法
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
//过滤不需要该业务的方法
if("execute".equals(method.getName())) {
//调用前验证权限(动态添加其他要执行业务)
AuthCheck.authCheck();
//调用目标对象的方法(执行A对象即被代理对象的execute方法)
Object result = methodProxy.invokeSuper(proxy, args);
//记录日志数据(动态添加其他要执行业务)
Report.recordLog();
return result;
}else if("delete".equals(method.getName())){
//.....
return methodProxy.invokeSuper(proxy, args);
}
//如果不需要增强直接执行原方法
return methodProxy.invokeSuper(proxy, args);
}
}
可见,和JDK动态代理相比,有如下类似点
methodProxy.invokeSuper(proxy, args);
类比于method.invoke(obj, args);
,注意其中传入的对象不同为了使用AspecJ提供的注解的规范,需要开启对AspecJ注解的支持,有如下两种方式
@EnableAspectJAutoProxy
在配置类上修饰
<aop:aspectj-autoproxy/>
在xml中添加
这里包括定义切入点、通知、切面等,可以采用基于注解或基于xml的方式
@Component("logger")
@Aspect
public class Logger {
@Pointcut("execution(* aoptest1.impl.AccountService.*(..))")
private void pt1() {
}
//采用注解的方式配置就需要避免这四种通知和环绕通知同时出现,否则会出现冗余
// @Before("pt1()")
// public void beforePrintLog(){
// System.out.println("前置:打印日志");
// }
/*
后置通知在return之后执行
*/
// @AfterReturning(value = "pt1()", returning = "returnVal")
// public void afterReturningPrintLog(Object returnVal){
// System.out.println("后置:打印日志" + returnVal);
// }
// @AfterThrowing(value = "pt1()", throwing = "e")
// public void afterThrowingPrintLog(Throwable e){
// System.out.println("异常:打印日志" + e.getMessage);
// }
// @After("pt1()")
// public void afterPrintLog(){
// System.out.println("最终:打印日志");
// }
/**
* 环绕通知的方式类似于Proxy创建动态代理对象时新建InvocationHandler对象时重写invoke方法的过程
*
* @param pjp 它可以作为环绕通知的方法参数。在环绕通知执行时,spring框架会为我们提供该接口的实现类对象,直接使用即可。
*/
@Around("pt1()")
public Object aroundPrintLog(ProceedingJoinPoint pjp) {
Object[] args = pjp.getArgs();//获取方法执行参数
try {
System.out.println("前置:打印日志");
Object result = pjp.proceed(args);//执行被拦截的方法
//int i = 1/0;
return result;
} catch (Throwable e) {
System.out.println("异常:打印日志");
throw new RuntimeException(e);
} finally {
System.out.println("最终:打印日志");
}
}
}
不要忘记
@Component
注解
<?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="accSer" class="aoptest1.impl.AccountService">
</bean>
<bean id="logger" class="aoptest1.log.Logger">
</bean>
<aop:config>
<aop:pointcut id="pt1" expression="execution(* aoptest1.impl.AccountService.*(..))"/>
<aop:aspect id="asp1" ref="logger">
<!-- <aop:before method="beforePrintLog" pointcut-ref="pt1"/>-->
<!-- <aop:after-returning method="afterReturningPrintLog" pointcut-ref="pt1"/>-->
<!-- <aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pt1"/>-->
<!-- <aop:after method="afterPrintLog" pointcut-ref="pt1"/>-->
<aop:around method="aroundPrintLog" pointcut-ref="pt1"/>
</aop:aspect>
</aop:config>
</beans>
由于Spring中的事务依赖于AoP,因而在此记录,以下内容整理自JavaGuide (gitee.io)及关于PROPAGATION_NESTED的理解_yanxin1213的博客-CSDN博客
声明式事务又分为两种:
TransactionDefinition 接口中定义了五个表示隔离级别的常量:
支持当前事务的情况:
不支持当前事务的情况:
其他情况:
TransactionDefinition.PROPAGATION_NESTED: 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。
举例如下,定义serviceA.methodA()以PROPAGATION_REQUIRED修饰; 定义serviceB.methodB()以表格中三种方式修饰; methodA中调用methodB
异常状态 | PROPAGATION_REQUIRES_NEW (两个独立事务) | PROPAGATION_NESTED (B的事务嵌套在A的事务中) | PROPAGATION_REQUIRED (同一个事务) |
---|---|---|---|
methodA抛异常 methodB正常 | A回滚,B正常提交 | A与B一起回滚 | A与B一起回滚 |
methodA正常 methodB抛异常 | 1.如果A中捕获B的异常,并没有继续向上抛异常,则B先回滚,A再正常提交; 2.如果A未捕获B的异常,默认则会将B的异常向上抛,则B先回滚,A再回滚 | B先回滚,A再正常提交 | A与B一起回滚 |
methodA抛异常 methodB抛异常 | B先回滚,A再回滚 | A与B一起回滚 | A与B一起回滚 |
methodA正常 methodB正常 | B先提交,A再提交 | A与B一起提交 | A与B一起提交 |
@EnableTransactionManagement
修饰于配置类上
<tx:annotation-driven></tx:annotation-driven>
添加在xml中
可以使用Druid作为数据源
一般为创建DataSourceTransactionManager
对象,传入数据源Bean,并返回为Bean
将@Transactional
注解修饰于类(对public方法有效)或者方法上即可,此注解有如下关键属性
transactionManager
指定事务管理器
propagation
指定事务传播规则
isolation
指定事务隔离级别
readOnly
如果是只读操作,可以设置为true,这样会在运行期被优化执行
rollbackFor
如果不配置,则只在抛出RuntimeException
时回滚,如果指定为Exception.class,则受查异常也会回滚
标签:增强 类型 aci try 框架 framework create name 设置
原文地址:https://www.cnblogs.com/lins1/p/14379835.html