Spring4和Spring5下aop的区别(Springboot1+升级到2+),可查阅 此博客
一、介绍
Spring 框架的一个关键组件是面向方面的编程(AOP)框架。AOP 是一种编程思想,是面向对象编程(OOP)的一种补充。面向对象编程将程序抽象成各个层次的对象,而面向切面编程是将程序抽象成各个切面。
二、为什么使用aop
aop可以将核心业务代码和其他日志类代码解耦,使得核心代码保持纯净,可以将通用方法抽离,方便维护。
开发中在多个模块间有重复的代码,我们通常是怎么处理的?
- 复制粘贴(代码冗余,维护困难)
- 抽象成一个方法,然后在需要的地方分别调用这个方法(需求多变时,维护困难)
- jdk动态代理(可以实现,但是写起来难)
- AOP(实现简单,方便维护)
2.1 jdk动态代理
// 核心代码(无入侵)
@Component
public class MyMathCalculator implements Calculator {
@Override
public int add(int i,int j){
int result = i + j;
return result;
}
}
// 核心方法的代理类
public class CalcultorProxy {
/**
* 为传入的对象创建一个代理对象
* @param calculator 被代理的对象 要创建代理对象的值
* @return 代理对象
*/
public static Calculator getProxy(Calculator calculator){
// ClassLoader 类加载器
ClassLoader loader = calculator.getClass().getClassLoader();
// 实现的所有接口
Class<?>[] interfaces = calculator.getClass().getInterfaces();
// 方法执行器
InvocationHandler h = new InvocationHandler() {
/**
*
* @param proxy 代理对象,给jdk使用的
* @param method 目标对象的方法
* @param args 执行目标方法所需参数
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 利用反射执行目标方法 invoke即为目标方法执行后返回的值
System.out.println("jdk动态代理~~~");
Object invoke = null;
try {
System.out.println("LogUtil-->start-->方法执行前");
invoke = method.invoke(calculator, args);
System.out.println("LogUtil-->return-->方法执行完成");
}catch (Exception e){
System.out.println("LogUtil-->error-->方法执行失败");
}finally {
System.out.println("LogUtil-->end-->方法执行结束");
}
return invoke;
}
};
Object o = Proxy.newProxyInstance(loader, interfaces, h);
return (Calculator) o;
}
}
// 测试类
@SpringBootTest
public class test {
/**
* 使用jdk动态代理可以和业务代码解耦
* jdk默认的动态代理,如果对象没有实现任何接口,jdk则不能实现代理
*/
@Test
public void test(){
Calculator calculator = new MyMathCalculator();
System.out.println(calculator.add(1, 2));
System.out.println("~~~~~~~~jdk动态代理~~~~~~~~~");
Calculator proxy = CalcultorProxy.getProxy(calculator);
int add1 = proxy.add(1,2);
System.out.println(add1);
}
}
// 3
// ~~~~~~~~jdk动态代理~~~~~~~~~
// jdk动态代理~~~
// LogUtil-->start-->方法执行前
// LogUtil-->return-->方法执行完成
// LogUtil-->end-->方法执行结束
// 3
三、AOP 核心概念
概念 | 描述 |
---|---|
切面(aspect) | 类是对物体特征的抽象,切面就是对横切关注点的抽象 |
连接点(joinpoint) | 被拦截到的点,因为 Spring 只支持方法类型的连接点,所以在 Spring中连接点指的就是被拦截到的方法,实际上连接点还可以是字段或者构造器 |
切入点(pointcut) | 这是一组一个或多个连接点,通知应该被执行 |
通知(advice) | 所谓通知指的就是指拦截到连接点之后要执行的代码,通知分为前置、后置、异常、最终、环绕通知五类 |
横切关注点 | 对哪些方法进行拦截,拦截后怎么处理,这些关注点称之为横切关注点 |
四、AOP通知的类型
@Before 方法执行前运行 前置通知
@After 方法执行结束运行 后置通知
@AfterReturning 方法正常返回时运行 异常通知
@AfterThrowing 目标方法抛出异常后运行 返回通知
@Around 环绕 环绕通知
spring最强大的通知,合并了以上四种通知。利用反射执行方法,类似于method.invoke(obj,args);
环绕通知执行顺序要优先其他通知
try {
@Before
invoke = method.invoke(calculator, args);
@AfterReturning
}catch (Exception e){
@AfterThrowing
}finally {
@After
}
五、AOP 运用场景
- Authentication 权限
- Caching 缓存
- Context passing 内容传递
- Error handling 错误处理
- Lazy loading 懒加载
- Debugging 调试
- logging, tracing, profiling and monitoring 记录 跟踪 优化 校准
- Performance optimization 性能优化
- Persistence 持久化
- Resource pooling 资源池
- Synchronization 同步
- Transactions 事务
六、AOP实现
5.1 xml方式
- 将目标类和切面类通过bean注入到ioc容器中
- 使用aop:config配置aop信息
- 使用<aop:aspect ref=“logUtil03”>标示那个类为切面类
- aop:beforeaop:afteraop:after-throwingaop:after-returningaop:around标示什么时候执行
<?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 https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 目标类 -->
<bean id="myMathCalculator04" class="com.guocoffee.spring.aop.aop03.impl.MyMathCalculator04"></bean>
<!-- 切入类 -->
<bean id="logUtil03" class="com.guocoffee.spring.aop.aop03.util.LogUtil03"></bean>
<aop:config>
<aop:pointcut id="myPointcut" expression="execution(* com.guocoffee.spring.aop.aop03..*(..))"/>
<!-- 相当于@Aspect -->
<aop:aspect ref="logUtil03" order="1">
<aop:before method="logStart" pointcut="execution(* com.guocoffee.spring.aop.aop03..*(..))"></aop:before>
<aop:after method="logEnd" pointcut-ref="myPointcut"></aop:after>
<aop:after-throwing method="logError" pointcut-ref="myPointcut" throwing="exception"></aop:after-throwing>
<aop:after-returning method="logReturn" pointcut-ref="myPointcut" returning="result"></aop:after-returning>
<aop:around method="myAround" pointcut-ref="myPointcut"></aop:around>
</aop:aspect>
</aop:config>
</beans>
public class LogUtil03 {
public void logStart(JoinPoint joinPoint){
// 获取目标发放运行是传输的参数
Object[] args = joinPoint.getArgs();
// 获取方法签名
// 方法签名:int com.guocoffee.spring.aop.aop02.impl.MyMathCalculator03.add(int,int)
Signature signature = joinPoint.getSignature();
System.out.println("方法签名:"+signature);
// 获取方法名
String name = signature.getName();
System.out.println(name + "方法执行开始-->LogUtil-->start-->方法执行前,参数列表:" + Arrays.toString(args));
}
public void logReturn(JoinPoint joinPoint,Object result){
String name = joinPoint.getSignature().getName();
System.out.println(name + "方法执行完成返回-->LogUtil-->return-->方法执行完成返回,返回结果:" + result);
}
public void logError(JoinPoint joinPoint,Exception exception){
String name = joinPoint.getSignature().getName();
exception.printStackTrace();
System.out.println(name + "方法执行异常-->LogUtil-->error-->方法执行失败,异常信息:" + exception);
}
public static void logEnd(JoinPoint joinPoint){
String name = joinPoint.getSignature().getName();
System.out.println(name + "方法执行结束LogUtil-->end-->方法执行结束");
}
public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
// 执行参数
Object[] args = pjp.getArgs();
// 执行方法名
String name = pjp.getSignature().getName();
Object proceed = null;
try {
// @Before
System.out.println(name + "方法执行开始-->around环绕-->方法执行前,参数列表:" + Arrays.toString(args));
// 利用反射执行方法,类似于method.invoke(obj,args);
proceed = pjp.proceed(args);
// @AfterReturning
System.out.println(name + "方法执行完成返回-->around环绕-->方法执行完成返回,返回结果:" + proceed);
} catch (Exception e){
// @AfterThrowing
System.out.println(name + "方法执行异常-->around环绕-->方法执行失败,异常信息:" + e);
} finally {
// @After
System.out.println(name + "方法执行结束-->around环绕-->方法执行结束");
}
// 返回值,此处返回值是什么,对应方法获取的返回值则是啥
// 所以此处一定要返回执行反射后返回的值
return proceed;
}
}
5.2 注解方式
- 将目标类和切面类注入到ioc容器中 @Component
- 标示那个是切面类 @Aspect
- 在切面类中标示在何时调用切面类 @Before @After @AfterReturning @AfterThrowing @Around
- 开启基于注解的aop功能
@Aspect
@Order(1) // 多个切面时运行顺序按照order小的先执行
@Component
public class LogUtil02 {
/**
*
* try {
* @Before
* invoke = method.invoke(calculator, args);
* @AfterReturning
* }catch (Exception e){
* @AfterThrowing
* }finally {
* @After
* }
*
* @Before 方法执行前运行 前置通知
* @After 方法执行结束运行 后置通知
* @AfterReturning 方法正常返回时运行 异常通知
* @AfterThrowing 目标方法抛出异常后运行 返回通知
* @Around 环绕 环绕通知
*
*
* Spring4和Spring5执行顺序存在差异
* 执行顺序(Spring4):
* 正常执行:@Before-->@After-->@AfterReturning
* 异常执行:@Before-->@After-->@AfterThrowing
* 执行顺序(Spring5):
* 正常执行:@Before-->@AfterReturning-->@After
* 异常执行:@Before-->@AfterThrowing-->@After
*
* 切入点表达式
* 固定写法:execution(访问权限修饰符 返回值类型 全类名.方法名(参数)
* 通配符:
* *
* 1、匹配一个或多个字符
* execution(public int com.guocoffee.spring.aop.aop02.impl.*.*(int ,int)
* execution(public int com.guocoffee.spring.aop.aop02.impl.MyMath*.*(int ,int)
* execution(public int com.guocoffee.spring.aop.aop02.impl.MyMath*03.*(int ,int)
* 2、任意参数(第一个参数为int 第二个参数为任意参数)
* execution(public int com.guocoffee.spring.aop.aop02.impl.MyMath*03.*(int ,*)
* 3、只能匹配一层路径
* execution(public int com.guocoffee.spring.aop.*.impl.MyMath*03.*(..)
* 4、任意返回值
* execution(public * com.guocoffee.spring.aop.*.impl.MyMath*03.*(..)
* 5、访问权限修饰符不能写* 可以不写,默认任意修饰符
* execution(* com.guocoffee.spring.aop.*.impl.MyMath*03.*(..)
* ..
* 1、匹配任意参数
* execution(public int com.guocoffee.spring.aop.aop02.impl.MyMath*03.*(..)
* 2、匹配任意层级
* execution(public int com.guocoffee.spring..impl.MyMath*03.*(..)
*
* 最模糊: execution(* *(..)) 或者 execution(* *.*(..)) 任意包下的任意类,尽量不要使用
*
* && || !
*
* && 两个表达式均需要满足
* || 两个表达式只要满足一个
* ! 除表达式以外的
* execution(public int com.guocoffee.spring..impl.MyMath*03.*(..) && execution(public int com.guocoffee.spring..impl.MyMath*02.*(..)
*/
/**
* 表达式抽取
* 1、新建一个无放回值的空方法
* 2、使用@Pointcut注解,将表达是写入其中
* 3、@Before("myAspect()") @After("myAspect()") 在注解里直接使用此方法
*/
@Pointcut("execution(public int com.guocoffee.spring.aop.aop02.impl.*.*(int ,int ))")
public void myAspect(){};
@Before("myAspect()")
public void logStart(JoinPoint joinPoint){
// 获取目标发放运行是传输的参数
Object[] args = joinPoint.getArgs();
// 获取方法签名
// 方法签名:int com.guocoffee.spring.aop.aop02.impl.MyMathCalculator03.add(int,int)
Signature signature = joinPoint.getSignature();
System.out.println("方法签名:"+signature);
// 获取方法名
String name = signature.getName();
System.out.println(name + "方法执行开始-->LogUtil-->start-->方法执行前,参数列表:" + Arrays.toString(args));
}
/**
* returning = "result" 告诉spring,用result来接受执行方法返回的值
* double result:只接受double类型的返回值
* Object result:任意类型的返回值
* @param joinPoint 封装了链接点的所有信息
* @param result 返回结果
*/
@AfterReturning(value = "execution(public int com.guocoffee.spring.aop.aop02.impl.*.*(int ,int ))",returning = "result")
public void logReturn(JoinPoint joinPoint,Object result){
String name = joinPoint.getSignature().getName();
System.out.println(name + "方法执行完成返回-->LogUtil-->return-->方法执行完成返回,返回结果:" + result);
}
/**
* throwing = "exception" 告诉spring,用exception来接受运行方法时出现的异常
*
* NullPointerException exception:只接受空指针异常
* Exception exception:接受所有异常
*
* @param joinPoint 封装了链接点的所有信息
* @param exception 接受错误信息
*/
@AfterThrowing(value = "execution(public int com.guocoffee.spring.aop.aop02.impl.*.*(int ,int ))",throwing = "exception")
public void logError(JoinPoint joinPoint,Exception exception){
String name = joinPoint.getSignature().getName();
exception.printStackTrace();
System.out.println(name + "方法执行异常-->LogUtil-->error-->方法执行失败,异常信息:" + exception);
}
@After("execution(public int com.guocoffee.spring.aop.aop02.impl.*.*(int ,int ))")
public static void logEnd(JoinPoint joinPoint){
String name = joinPoint.getSignature().getName();
System.out.println(name + "方法执行结束LogUtil-->end-->方法执行结束");
}
/**
* 环绕通知是Spring中最强大的,集合了其他四种
*/
@Around("myAspect()")
public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
// 执行参数
Object[] args = pjp.getArgs();
// 执行方法名
String name = pjp.getSignature().getName();
Object proceed = null;
try {
// @Before
System.out.println(name + "方法执行开始-->around环绕-->方法执行前,参数列表:" + Arrays.toString(args));
// 利用反射执行方法,类似于method.invoke(obj,args);
proceed = pjp.proceed(args);
// @AfterReturning
System.out.println(name + "方法执行完成返回-->around环绕-->方法执行完成返回,返回结果:" + proceed);
} catch (Exception e){
// @AfterThrowing
System.out.println(name + "方法执行异常-->around环绕-->方法执行失败,异常信息:" + e);
} finally {
// @After
System.out.println(name + "方法执行结束-->around环绕-->方法执行结束");
}
// 返回值,此处返回值是什么,对应方法获取的返回值则是啥
// 所以此处一定要返回执行反射后返回的值
return proceed;
}
}
<?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 http://www.springframework.org/schema/context/spring-context-4.0.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="com.guocoffee.spring.aop.aop02"></context:component-scan>
<!--
使用注解开启aop
1、将目标类和切面类注入到ioc容器中 @Component
2、标示那个是切面类 @Aspect
3、在切面类中标示在何时调用切面类 @Before @After @AfterReturning @AfterThrowing @Around
4、开启基于注解的aop功能
-->
<!-- 开启基于注解的aop功能,需要aop命名空间 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>