相关概念
aspect:切面,横切多层面的关注点的模块化
Joinpoint:连接点.程序执行期间的一个点,连接点总是代表一个方法的执行.
advice:通知, 在特定连接点处采取的行动.包括before,after,around等.
Pointcut:切入点.定义一个类似正则的表达式,与通知相关联.
简单例子
项目为springboot项目,JDK1.8,工具idea
代码上的注释,是在测试包含所有通知下测的结果,实际开发中是不会都用到的,最多的是around,功能比较强大.
首先添加依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>1.5.9.RELEASE</version>
</dependency>
定义一个切面:
@Aspect //声明切面
@Component //交给spring管理
public class AspectHandler{
/**
* 用作切入点的方法,必须具有void返回类型
* 凡是符合这个表达式的,都将会被执行通知
* 也可以写在每一个通知里;如下:
* @Before("execution(* com.surd.aopdemo.service..*.*(..))")
*/
@Pointcut("execution(* com.surd.aopdemo.service..*.*(..))")
private void pointcut() {}
/**
* 前置增强
* 当没有around时,先执行before, 走after,遇到异常,然后afterthrowing,
* 如果没有异常走afterreturning,在这里可以看到返回值
*/
@Before("pointcut()") //通知类型的一种,所关联的切入点表达式是pointcut方法
public void doBeforeTest(JoinPoint point) {
//想要执行的代码
System.out.println("execute before method===" + point);
}
/**
* 后置增强,方法退出时执行
*/
@AfterReturning(returning = "ret",pointcut = "pointcut()")
public void doAfterReturning(JoinPoint point,Object ret) {
System.out.println("execute AfterReturning" + point + ":return value:" + ret);
ret = "return value";
}
/**
* 最终(final)增强,不管方法退出还是抛出异常都会执行
*/
@After("pointcut()")
public void doAfter(JoinPoint point) {
// 想要执行的代码
System.out.println("execute after method" + point);
}
/**
* 异常增强
*/
@AfterThrowing(throwing = "ex",pointcut = "pointcut()")
public void doAfterThrowing(JoinPoint point,Exception ex) {
System.out.println("execute afterThrowing" + point +"---exception:" + ex);
}
/**
* 环绕增强
* 当around存在的时候,首先执行.再before,如果遇到异常,执行after方法,走afterthrowing结束.
* 当around存在的时候,首先执行.再before,再执行after方法,没有异常然后AfterReturning方法
*/
@Around("pointcut()")
public Object doAround(ProceedingJoinPoint point) throws Throwable {
System.out.println("around before start");
TODO:执行目标方法前执行的代码
Object[] args = point.getArgs();
args[0] = "gulang";
Object ret = point.proceed(args); // 回调目标方法
TODO:执行目标方法后执行的代码
System.out.println("around end");
System.out.println("execute around method" + point);
return ret;
}
}
Service层代码: 当某一个类的方法符合pointcut表达式,就是在idea左边显示相对应的小图标
controller层就不贴出来了,直接调用就好;
在没个通知打上断点,可以观察执行的顺序以及参数值,也就是连接点的内容
其中包含了代理对象,目标对象,方法,参数,动态匹配到的方法等等都可以查看到.
一个简单的例子就完成了. 这样我们就可以使用它去记录日志,缓存等等.
浅析1:
(一)AOP代理实现有两种方法:
1:默认使用标准JDK动态代理,这使得任何接口都可以被代理.
2:也可以使用CGLIB代理,如果业务对象没有实现接口,就默认CGLIB,也可以强制使用.但是有一些问题需要考虑(下图给出官方描述):
1️⃣就是说不能用final修饰类,否则无法覆盖;
2️⃣在spring3.2中,不再需要将cglib添加到项目类路径,spring核心jar已经包含,这就意味着,基于CGLIB代理支持,工作方式相同,JDK动态代理总是有的.
3️⃣代理构造器会被调用两次.
(二)@Aspect注解对于类路径中的自动检测是不够的,所以还需要添加单独的@Component
(三)切入点表达式和切入点签名:
@Pointcut("execution(* com.surd.aopdemo.service..*.*(..))") //切入点表达式
private void pointcut() {} //切入点签名
(四)支持的切入点指示符:
execute: 常用, 方法匹配
@annotation: 注解匹配;(限制匹配连接点的地方有给定的注解)
within:限制匹配某些类型内的连接点(只需执行在匹配类型中声明的方法)
@within:在具有给定注解的类型内部限制匹配连接点(执行使用给定注解的类型中声明的方法)
args: 限制匹配连接点,其中参数是给定类型的实例
@args:限制匹配连接点,实际参数的运行时类型有给定类型的注解
更详细的描述,还有很多指示符,请参考官方文档:https://docs.spring.io/spring/docs/4.0.x/spring-framework-reference/html/aop.html#aop-introduction
切入点表达式:
切入点表达式可以使用&&, ||,!进行组合.也可以通过名称来引用切入点表达式.以下代码摘自官网:
//匹配任何公共方法
@Pointcut(“execution(public * *(..))”)
private void anyPublicOperation(){}
//匹配交易模块中的方法
@Pointcut( “within(com.xyz.someapp.trading。*)”)
private void inTrading(){}
//匹配交易模块中额任何公共方法
@Pointcut(“anyPublicOperation()&& inTrading()”)
private void tradingOperation(){}
表达式格式:execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern)
throws-pattern?)
() : 表示匹配一个不带参数的方法;
(..): 表示匹配任意数量的参数(0个或者多个);
(*): 表示任意类型的一个参数的方法;
(*,String):表示匹配一个方法采用两个参数,第一个是任意类型,第二个必须是String类型;
* : 匹配任意字符,但只能匹配一个元素
.. : 匹配任意字符,可以匹配任意多个元素,表示类时,与*联合使用
+ : 必须跟在类名后面,表示类本身和集成或者扩展指定类的所有类
JoinPoint:
该接口提供了许多有用的方法如:
getArgs(): 返回方法参数
getThis(): 返回代理对象
getTarget(): 返回目标对象
getSignature(): 返回正在被通知的方法的描述
……
暂时写到这里;
小结:
1️⃣: spring aop是基于代理的(JDK动态代理和CGLIB);
2️⃣: 开发中只需要专注于业务功能,像日志这一类功能可以提取出来使用此类方法处理;
3️⃣: 根据应用中实际情况来定义粗粒度,细粒度的切入点,处理不同的需求;
更多详情参考spring aop: https://docs.spring.io/spring/docs/4.0.x/spring-framework-reference/html/aop.html#aop-introduction