目录
1.AOP面向切面编程
1.1.AOP介绍
OOP(Object Oriented Programming ) 面向对象编程,万物皆对象!
AOP(Aspect Oriented Programming),即面向切面编程,可以说是OOP(Object Oriented Programming,面向对象编程)的补充和完善。OOP引入封装、继承、多态等概念来建立一种对象层次结构,用于模拟公共行为的一个集合。不过OOP允许开发者定义纵向的关系,但并不适合定义横向的关系,例如日志功能。日志代码往往横向地散布在所有对象层次中,而与它对应的对象的核心功能毫无关系对于其他类型的代码,如安全性、异常处理和透明的持续性也都是如此,这种散布在各处的无关的代码被称为横切(cross cutting),在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。
AOP技术恰恰相反,它利用一种称为"横切"的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"\",即切面。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。
使用"横切"技术,AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处基本相似,比如权限认证、日志打印、事务处理。AOP的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。
1.2AOP图解
AOP编程底层代理设计模式!Spring框架底层使用的代理设计模式来完成AOP!
通过动态代理,可以在指定位置执行对应流程。这样就可以将一些横向的功能抽离出来形成一个独立的模块,然后在指定位置插入这些功能。这样的思想,被称为面向切面编程,亦即AOP。
2.AOP术语
1.target:目标类,需要被代理的类。例如:UserServiceImpl
2.Joinpoint(连接点):所谓连接点是指那些可能被拦截到的方法。例如:所有的方法
3.PointCut 切入点:已经被增强的连接点。例如:addUser()
4.advice 通知/增强,增强代码。例如:after、before
5. Weaving(织入):是指把增强advice应用到目标对象target来创建新的代理对象proxy的过程.
6.proxy 代理类:通知+切入点(由动态代理自动生成的类)
7. Aspect(切面): 是切入点pointcut和通知advice的结合
8.引介(Introduction):引介是一种特殊的增强,它为类添加一些属性和方法。这样,即使一个业务类原本没有实现某个接口,通过AOP的引介功能,我们可以动态地为该业务类添加接口的实现逻辑,让业务类成为这个接口的实现类。
具体可以根据下面这张图来理解:
3.1 Spring AOP基于xml
准备操作对象
/**
* 目标类
*/
public class CarServiceImpl implements CarService {
public Integer add(Car c) {
System.out.println("新增Car到数据库:"+c);
return 1;
}
public Integer update(Car c) {
System.out.println("更新Car到数据库:"+c);
return 1;
}
}
在想要的server前面添加增强类
public class TransactionTx {
/**
* 前置增强(通知)
* JoinPoint joinPoint 连接点(封装了目标方法信息)
*/
public void openTx(JoinPoint joinPoint){
Object target = joinPoint.getTarget(); //目标类对象
Object proxyObject = joinPoint.getThis(); //代理类对象
Signature signature = joinPoint.getSignature(); //目标方法
String name = signature.getName();//目标方法名字
int modifiers = signature.getModifiers();//目标方法修饰符
Object[] args = joinPoint.getArgs(); // 方法的参数
System.out.println("-前置增强(通知)---开启事务---,目标方法的方法名:"+name+",方法参数:"+ Arrays.toString(args));
}
/**
* 返回增强(返回通知) 后置通知(目标方法如果执行有异常,那么异常通知执行,返回通知不执行!)
*/
public void commitTx(JoinPoint joinPoint,Object result){
System.out.println("--返回增强(返回通知)--提交事务---:返回值:"+result);
}
/**
* 异常增强(通知)
* 目标方法如果执行有异常,那么异常通知执行,返回通知不执行!
* 目标方法没有异常,那么正常执行,执行返回通知
*/
public void rollbackTx(JoinPoint joinPoint,Throwable e){
System.out.println("--异常增强(通知)--回滚事务--- 异常啦:"+e.getMessage());
}
/**
* 最终增强(通知):无论目标方法是否有异常,都会执行!
*/
public void finnalMethod(JoinPoint joinPoint){
System.out.println("--最终增强(通知)--释放系统资源--");
}
/**
* 环绕通知
* @param joinPoint
* @return
*/
public Object aroundMethod(ProceedingJoinPoint joinPoint) {
Object target = joinPoint.getTarget(); //目标类对象
Object proxyObject = joinPoint.getThis(); //代理类对象
Signature signature = joinPoint.getSignature(); //目标方法
String name = signature.getName();//目标方法名字
int modifiers = signature.getModifiers();//目标方法修饰符
Object[] args = joinPoint.getArgs(); // 方法的参数
Object result=null;
try {
System.out.println("---环绕(前)---开启事务---,目标方法的方法名:"+name+",方法参数:"+ Arrays.toString(args));
//调用目标类目标方法
result=joinPoint.proceed();
System.out.println("---环绕(返回)---提交事务---:返回值:"+result);
} catch (Throwable e) {
//e.printStackTrace();
System.out.println("--环绕(异常)--回滚事务--- 异常啦:"+e.getMessage());
} finally {
System.out.println("--环绕(最终)--释放系统资源--");
}
return result;
}
织入目标对象(xml)
<!--增强类对象-->
<bean id="tx" class="com.it.tx.TransactionTx"/>
<!--目标类对象-->
<bean id="carService" class="com.it.service.impl.CarServiceImpl"/>
<!--配置切面(告诉Spring框架,哪些类中的哪些方法需要被代理,如何代理)-->
<aop:config>
<!--pointcut:切入点表达式(哪个类中哪个方法需要被代理)-->
<aop:pointcut id="pc" expression="execution(* com.it.service.impl.*.*(..))"/>
<!--如何代理,目标方法之前还是之后?-->
<aop:aspect ref="tx">
<!--前置增强-->
<aop:before method="openTx" pointcut-ref="pc"/>
<!-- 返回增强(返回通知) 后置通知-->
<aop:after-returning method="commitTx" returning="result" pointcut-ref="pc"/>
<!--环绕通知-->
<aop:around method="aroundMethod" pointcut-ref="pc"/>
<!--异常通知-->
<aop:after-throwing method="rollbackTx" throwing="e" pointcut-ref="pc"/>
<!--最终通知-->
<aop:after method="finnalMethod" pointcut-ref="pc"/>
</aop:aspect>
</aop:config>
<!--默认值false 表示使用JDK动态代理
true 使用CGLIB动态代理,但是如果没有接口,不管ture false 都是CGLIB代理!
-->
<aop:aspectj-autoproxy proxy-target-class="false"/>
切入点表达式(可以使用通配符)
测试
@Test
public void test1(){
ClassPathXmlApplicationContext factory = new ClassPathXmlApplicationContext("cars.xml");
CarService carService = factory.getBean(CarService.class);
ComputerService computerService = factory.getBean(ComputerService.class);
Integer count = carService.add(new Car(1, "BMW", "川A3244"));
System.out.println(count>0?"新增成功":"新增失败");
}
3.2Spring中AOP基于注解
添加配置类
@ComponentScan("com.it")
@EnableAspectJAutoProxy(proxyTargetClass = false) //默认使用JDK动态代理
public class SpringConfig {
}
添加操作对象
@Service
public class CarServiceImpl implements CarService {
public Integer add(Car c) {
System.out.println("新增Car到数据库:"+c);
return 1;
}
public Integer update(Car c) {
System.out.println("更新Car到数据库:"+c);
return 1;
}
}
添加增强类
@Aspect //增强类
@Component
@Order(1)
public class TransactionTx {
/**
* 前置增强(通知)
*/
@Before(value = "execution(* com.it.service.impl.*.*(..))")
public void openTxB(JoinPoint joinPoint){
Object target = joinPoint.getTarget(); //目标类对象
Object proxyObject = joinPoint.getThis(); //代理类对象
Signature signature = joinPoint.getSignature(); //目标方法
String name = signature.getName();//目标方法名字
int modifiers = signature.getModifiers();//目标方法修饰符
Object[] args = joinPoint.getArgs(); // 方法的参数
System.out.println("-TransactionTx--B--前置增强(通知)---开启事务---,目标方法的方法名:"+name+",方法参数:"+ Arrays.toString(args));
}
@Before(value = "execution(* com.it.service.impl.*.*(..))")
public void openTxA(JoinPoint joinPoint){
Object target = joinPoint.getTarget(); //目标类对象
Object proxyObject = joinPoint.getThis(); //代理类对象
Signature signature = joinPoint.getSignature(); //目标方法
String name = signature.getName();//目标方法名字
int modifiers = signature.getModifiers();//目标方法修饰符
Object[] args = joinPoint.getArgs(); // 方法的参数
System.out.println("-TransactionTx--A--前置增强(通知)---开启事务---,目标方法的方法名:"+name+",方法参数:"+ Arrays.toString(args));
}
/**
* 返回增强(返回通知) 后置通知(目标方法如果执行有异常,那么异常通知执行,返回通知不执行!)
*/
//@AfterReturning(value = "execution(* com.it.service.impl.*.*(..))",returning = "result")
public void commitTx(JoinPoint joinPoint,Object result){
System.out.println("--返回增强(返回通知)--提交事务---:返回值:"+result);
}
/**
* 异常增强(通知)
* 目标方法如果执行有异常,那么异常通知执行,返回通知不执行!
* 目标方法没有异常,那么正常执行,执行返回通知
*/
//@AfterThrowing(value = "execution(* com.it.service.impl.*.*(..))",throwing = "e")
public void rollbackTx(JoinPoint joinPoint,Throwable e){
System.out.println("--异常增强(通知)--回滚事务--- 异常啦:"+e.getMessage());
}
/**
* 环绕通知
* @param joinPoint
* @return
*/
//@Around(value = "execution(* com.it.service.impl.*.*(..))")
public Object aroundMethod(ProceedingJoinPoint joinPoint) {
Object target = joinPoint.getTarget(); //目标类对象
Object proxyObject = joinPoint.getThis(); //代理类对象
Signature signature = joinPoint.getSignature(); //目标方法
String name = signature.getName();//目标方法名字
int modifiers = signature.getModifiers();//目标方法修饰符
Object[] args = joinPoint.getArgs(); // 方法的参数
Object result=null;
try {
System.out.println("---环绕---开启事务---,目标方法的方法名:"+name+",方法参数:"+ Arrays.toString(args));
//调用目标类目标方法
result=joinPoint.proceed();
System.out.println("---环绕---提交事务---:返回值:"+result);
} catch (Throwable e) {
//e.printStackTrace();
System.out.println("--环绕--回滚事务--- 异常啦:"+e.getMessage());
} finally {
System.out.println("--环绕--释放系统资源--");
}
return result;
}
/**
* 最终增强(通知):无论目标方法是否有异常,都会执行!
*/
//@After(value = "execution(* com.it.service.impl.*.*(..))")
public void finnalMethod(JoinPoint joinPoint){
System.out.println("--最终增强(通知)--释放系统资源--");
}
}
测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {SpringConfig.class})
public class TestSpringAop {
@Autowired
CarService carService;
@Test
public void test1(){
Integer count = carService.add(new Car(1, "BMW", "川A3244"));
System.out.println(count>0?"新增成功":"新增失败");
}
}
4.Spring基于XML的通知执行顺序
4.1.XML文件配置说明
4.2.各种通知说明
配置前置通知:在切入点方法执行之前执行
配置后置通知(返回通知):在切入点方法正常执行之后执行。它和异常通知永远只能执行一个
配置异常通知:在切入点方法执行产生异常之后执行。它和后置通知永远只能执行一个
配置最终通知:无论切入点方法是否正常执行,它都会在其后面执行
配置环绕通知:可以在代码中手动控制增强方法何时执行
注意:后置(返回通知)通知和最终通知的区别:后置通知时在方法成功执行后会执行的,如果出现异常就不执行。而最终通知时无论是否出现异常都会执行的,感觉类似于finally
4.3.在xml配置同一个切入点且不出现异常时的执行顺序
注意,椭圆中顺序不固定,具体顺序与配置文件的申明顺序有关
结论:XML配置AOP,通知的执行顺序和Spring版本无关。和通知的声明顺序有关,只能确定前置通知一定在目标方法之前!
4.3.1.情况一
<!--3.2.配置通知-->
<aop:aspect ref="tx">
<!--前置通知-->
<aop:before method="openTx" pointcut-ref="pointcut"/>
<!--环绕通知-->
<aop:around method="aroundAdvice" pointcut-ref="pointcut"/>
<!--后置通知(返回通知)-->
<aop:after-returning method="CommitTx" pointcut-ref="pointcut" returning="value"/>
<!--最终通知-->
<aop:after method="finnallyMethod" pointcut-ref="pointcut"/>
<!--异常通知-->
<aop:after-throwing method="Rollback" pointcut-ref="pointcut" throwing="ex"/>
</aop:aspect>
顺序:
4.3.2.情况二
<!--3.2.配置通知-->
<aop:aspect ref="tx">
<!--环绕通知-->
<aop:around method="aroundAdvice" pointcut-ref="pointcut"/>
<!--前置通知-->
<aop:before method="openTx" pointcut-ref="pointcut"/>
<!--后置通知(返回通知)-->
<aop:after-returning method="CommitTx" pointcut-ref="pointcut" returning="value"/>
<!--最终通知-->
<aop:after method="finnallyMethod" pointcut-ref="pointcut"/>
<!--异常通知-->
<aop:after-throwing method="Rollback" pointcut-ref="pointcut" throwing="ex"/>
</aop:aspect>
顺序:
结论一:前置通知和环绕通知的顺序和申明顺序有关,申明在前的先执行
4.3.3.情况三
<aop:aspect ref="tx">
<!--环绕通知-->
<aop:around method="aroundAdvice" pointcut-ref="pointcut"/>
<!--前置通知-->
<!--<aop:before method="openTx" pointcut-ref="pointcut"/>-->
<!--后置通知(返回通知)-->
<aop:after-returning method="CommitTx" pointcut-ref="pointcut" returning="value"/>
<!--最终通知-->
<aop:after method="finnallyMethod" pointcut-ref="pointcut"/>
<!--异常通知-->
<aop:after-throwing method="Rollback" pointcut-ref="pointcut" throwing="ex"/>
</aop:aspect>
顺序:
4.3.4.情况四
<aop:aspect ref="tx">
<!--环绕通知-->
<aop:around method="aroundAdvice" pointcut-ref="pointcut"/>
<!--前置通知-->
<!--<aop:before method="openTx" pointcut-ref="pointcut"/>-->
<!--最终通知-->
<aop:after method="finnallyMethod" pointcut-ref="pointcut"/>
<!--后置通知(返回通知)-->
<aop:after-returning method="CommitTx" pointcut-ref="pointcut" returning="value"/>
<!--异常通知-->
<aop:after-throwing method="Rollback" pointcut-ref="pointcut" throwing="ex"/>
</aop:aspect>
顺序:
4.3.5.小结
结论:Spring基于XML的申明式通知的执行顺序与配置文件中的申明顺序有关
4.4.Spring基于注解的通知执行顺序
我们在网上查找关于SpringAop执行顺序的的资料,大多数时候,你会查到如下的答案:
4.4.1.正常情况
4.4.2.异常情况
上述测试结果是在Spring的5.2.6.RELEASE版本下进行测试,换成5.2.7.RELEASE版本测试结果就不同了!
从Spring5.2.7开始,在相同@Aspect类中,通知方法将根据其类型按照从高到低的优先级进行执行:@Around,@Before ,@AfterReturning, @After,@AfterThrowing。
4.4.3结论
经过上面的资料文档查阅,我能给出的结论是:
从Spring5.2.7开始,Spring AOP不再严格按照AspectJ定义的规则来执行advice,而是根据其类型按照从高到低的优先级进行执行:
没有异常:@Around,@Before ,@AfterReturning, @After
有异常:@Around,@Before , @AfterThrowing @After