AOP在Spring中原理
通过实现BeanPostProcessor接口的postProcessBeforeInitialization和postProcessAfterInitialization方法,在初始化前后用代理对象加强原来的类
实现方式
- 预编译:AspectJ
- 运行期动态代理(JDK动态代理、CGLib动态代理):SpringAOP、JbossAOP
AOP的相关术语
- jointpoint(连接点):是指那些被拦截到的点。在spring中是指方法,因为spring只支持方法类型的连接点。
- pointcut(切入点):对哪些joinpoint进行拦截
- advice(通知/增强):拦截到的jionpoint所做的事情。方法层面的。
- 前置通知
- 后置通知
- 异常通知(抛出异常)
- 最终通知(无论如何都执行)
- 环绕通知(方法执行的前后都通知,甚至可以阻止目标方法执行)
- introduction(引介):特殊的通知,类层面的。
- target(目标对象):代理的目标对象
- weaving(织入):把增强应用到目标对象来创建新的代理对象的过程
- proxy(代理):被AOP织入增强后产生的结果代理类
- aspect(切面):切入点+通知的结合
- advisor:一般的切面,advice本身就是一个切面,对目标类所有方法进行拦截
- PointcutAdvisor:代表具有切点的切面,可以指定拦截目标类哪些方法
- IntroductionAdvisor:引介切面
底层实现
- JDK动态代理:对接口做动态代理。代理类继承InvocationHandler接口使用Proxy.newProxyInstance(被代理的接口)
- CGLIB的动态代理:对实现类做子类增强。使用Enhancer增强
总结
- spring在运行期间,生成动态代理对象,不需要特殊的编译器
- spring AOP的底层是通过JDK动态代理或CGLib动态代理技术为目标Bean执行横向织入
- 若目标对象实现了若干接口,spring使用JDK的java.lang.reflect.Proxy类代理
- 若目标对象没有实现任何接口,spring使用CGLib库生成目标对象的子类
- 程序中优先对接口创建代理,便于程序解耦维护
- 标记为final的方法,不能被代理,因为无法进行覆盖
- JDK动态代理,是针对接口生成子类,接口中方法不能使用final修饰
- CGLib是针对目标类生产子类,因此类或方法不能使用final
- spring只支持方法的连接点,不提供属性的连接点
AspectJ用法
通知类型
- @Before 前置通知
- @AfterReturning 后置通知
- @Around 环绕通知
- @AfterThrowing 异常抛出通知
- @After 最终final通知
切入点表达式
语法: execution(<访问修饰符>? <返回类型> <方法名>(<参数>) <异常>)
举例:
- 匹配所有类public方法 execution(public * *(..))
- 匹配指定包下所有类方法 execution(* top.linxz.dao.*(..)) 不包含字包
- 包含子包execution(* top.linxz.dao..(..)) ..表示包、子孙包下所有类
- 匹配指定类所有方法 execution(* top.linxz.service.UserService.*(..))
- 匹配实现特定接口所有类方法execution(* top.linxz.dao.GenericDAO+.*(..))
- 匹配所有save开头的方法 execution(* save*(..))
通知的使用
- 配置Aspect
@Aspect
public class MyAspectAnno{
...
}
- 前置通知
@Before(value="execution(* top.linxz.java.UserDao(..))")
public void before(JoinPoint joinPoint){
//joinPoint就是切到的方法
}
- 后置通知
@AfterReturning(value="execution(* top.linxz.java.UserDao(..))", returning="returning")
public void afterReturing(Object returning){
//这个returning就是拿到的返回值
}
- 环绕通知
@Around(value="execution(* top.linxz.java.UserDao(..))", returning="returning")
public Object afterReturing(ProceedingJoinPoint joinPoint){
System.out.println("环绕前通知")
Object obj=joinPoint.proceed();//执行目标方法,如果不调用,目标方法就被拦截了。
System.out.println("环绕后通知")
return obj;
}
- 异常通知
@AfterThrowing(value="execution(* top.linxz.java.UserDao(..))",throwing="e")
public void afterThrowing(Throwable e){
}
- 最终通知,无论如何即使有异常也执行
@After(value="execution(* top.linxz.java.UserDao(..))")
public void afterThrowing(Throwable e){
}
- @Pointcut为切点命名
- 切点方法必须为private void 无参数方法,方法名为切点名
- 当通知多个切点时,可以使用||进行连接
参考
- Spring AOP,https://class.imooc.com/course/587
- 基于AspectJ的AOP开发,https://class.imooc.com/course/588
关于我:
linxinzhe,全栈工程师,目前供职于某500强通信企业。人工智能,区块链爱好者。
GitHub:https://github.com/linxinzhe
欢迎留言讨论,也欢迎关注我~
我也会关注你的哦!
网友评论