浅谈Spring AOP的原理和应用

灰太狼 2023-02-25 08:44 148阅读 0赞

1.AOP简介

Aop(Aspect Oriented Programming)大家应该都知道这是面向切面编程思想,主要能够为我们在不影响原来的功能的前提,为软件横向拓展功能。简单来说,把对象中一些公用的行为抽取出来,减少代码冗余性,还可以将业务代码和系统功能代码分离开。其中Aop的实现方向主要分为两类,其中为静态Aop和动态Aop。而以下,我对Spring中的动态AOP进行一系列探讨,(注意:Aop是一种思想,以下主要对Spring Aop技术的探讨的)

2.Aop使用的场景

1)权限验证,可以统一对执行目标方法前进行验证。
2)缓存,可以对目标方法统一进行缓存,建议目标方法名称使用关键的字段识别。
3)异常统一处理,对一系列的方法抛出特定的异常进行特定统一处理。
4)日志系统,可以统一对出参和入参进行日志打印。
5)信息跟踪标记,可以对调用此方法进行统计,或者进行信息标记跟踪。
6)信息校验,统一对一系列方法入参时候进行必要的信息校验。
7)事务,对方法对数据库操作执行时候,异常则回滚事务。
以上只是简单自己的常见场景,AOP应用的方法千差万别,还有很多很多场景,具体还是看业务场景,主要判别的依据是业务代码,还是系统功能代码,而系统功能代码则可以更考虑利用切面编程aop来实现。我简单谈一下自己对Spring AOP理解。

3.AOP相关概念

方面(Aspect):一个关注点的模块化,这个关注点实现可能另外横切多个对象。事务管理是J2EE应用中一个很好的横切关注点例子。方面用Spring的 Advisor或拦截器实现。

连接点(Joinpoint): 程序执行过程中明确的点,如方法的调用或特定的异常被抛出。

通知(Advice): 在特定的连接点,AOP框架执行的动作。各种类型的通知包括“around”、“before”和“throws”通知。通知类型将在下面讨论。许多AOP框架包括Spring都是以拦截器做通知模型,维护一个“围绕”连接点的拦截器链。Spring中定义了四个advice: BeforeAdvice, AfterAdvice, ThrowAdvice,AfterRetruningAdvice和AroundingAdvice。

切入点(Pointcut): 指定一个通知将被引发的一系列连接点的集合。AOP框架必须允许开发者指定切入点:例如,使用正则表达式。 Spring定义了Pointcut接口,用来组合MethodMatcher和ClassFilter,可以通过名字很清楚的理解, MethodMatcher是用来检查目标类的方法是否可以被应用此通知,而ClassFilter是用来检查Pointcut是否应该应用到目标类上。

引入(Introduction): 添加方法或字段到被通知的类。 Spring允许引入新的接口到任何被通知的对象。例如,你可以使用一个引入使任何对象实现 IsModified接口,来简化缓存。Spring中要使用Introduction, 可有通过DelegatingIntroductionInterceptor来实现通知,通过DefaultIntroductionAdvisor来配置Advice和代理类要实现的接口。

目标对象(Target Object): 包含连接点的对象。也被称作被通知或被代理对象。

AOP代理(AOP Proxy): AOP框架创建的对象,包含通知。 在Spring中,AOP代理可以是JDK动态代理或者CGLIB代理。

织入(Weaving): 组装方面来创建一个被通知对象。这可以在编译时完成(例如使用AspectJ编译器),也可以在运行时完成。Spring和其他纯Java AOP框架一样,在运行时完成织入。

在这里插入图片描述
本节主要是对AOP一些基本概念了解和熟悉。而我个人觉得最核心掌握是,首先是思考,利用Spring Aop实现什么功能,达到什么效果。然后思考对什么目标的方法(target)进行增强,这些方法有什么统一的规范,则是我们设计时候需要的统一切入点Pointcut,然后思考对这系列的方法哪个时刻进行增强,而Spring中定义了四个advice: BeforeAdvice, AfterAdvice, ThrowAdvice,AfterRetruningAdvice和AroundingAdvice,最后就是我们要思考如何在这些位置实现我们的系统功能代码了。

4.AOP实现基本原理

Spring提供的实现方式来生成代理对象的方式于两种:JDK proxy和Cglib。具体实现时候使用哪一种,可以配置的。默认的策略是如果目标类是接口,则使用JDK动态代理技术,否则使用Cglib来生成代理。

简单总结两者的区别:

相同点:

  1. 两者都是会为目标对象生成一个代理对象类。

不同点:

  1. 实现原理不同,JDK proxy主要是利用放射机制来实现对的,而Cglib利用ASM开源包,对代理对象class文件加载进来,对其的字节码生成子类进行处理。
  2. 性能上,根据查询多番资料发现Cglib更优化,详细没有自测过,但是随着jdk版本,jdk proxy性能也渐渐优化了。
  3. 各自局限,jdk动态代理只能代理实现了接口的类,cglib不能对final修饰的类进行代理。

所以还是要看情景来选择适合的实现方式,一般情况个人偏向选择cglib。

详细查看,总结来自:https://www.cnblogs.com/tnt-33/p/10149087.html

5.AOP简单实现和应用

Spring Aop实现可以通过xml文件来实现或者利用注解方式,个人更偏向注解方式,编写方式更简单。所以以下列举一下注解实现的方式。

1.maven的依赖

  1. <dependencies>
  2. <dependency>
  3. <groupId>org.springframework</groupId>
  4. <artifactId>spring-aop</artifactId>
  5. <version>4.3.14.RELEASE</version>
  6. </dependency>
  7. <dependency>
  8. <groupId>org.springframework</groupId>
  9. <artifactId>spring-context</artifactId>
  10. <version>4.3.14.RELEASE</version>
  11. </dependency>
  12. <dependency>
  13. <groupId>org.aspectj</groupId>
  14. <artifactId>aspectjweaver</artifactId>
  15. <version>1.8.11</version>
  16. </dependency>
  17. <dependency>
  18. <groupId>commons-logging</groupId>
  19. <artifactId>commons-logging</artifactId>
  20. <version>1.2</version>
  21. </dependency>
  22. <dependency>
  23. <groupId>org.springframework</groupId>
  24. <artifactId>spring-aspects</artifactId>
  25. <version>5.2.1.RELEASE</version>
  26. </dependency>
  27. </dependencies>

2.创建一个简单的方法类

  1. @Service
  2. public class HelloWorld {
  3. public String say(String content){
  4. System.out.println("执行业务代码");
  5. System.out.println("说话:"+content);
  6. return content;
  7. }
  8. }

3.创建一个aop的实现类

  1. @Aspect
  2. @Component
  3. public class LogAspect {
  4. //定义切面的执行规规则
  5. //execution(返回类型 方法名称 传入参数)
  6. @Pointcut("execution(* com.rong.aop.aspect..*.*(..))")
  7. public void pointCut() {
  8. }
  9. @Before("pointCut()")
  10. public void logStart(JoinPoint joinPoint) {
  11. System.out.println("------------------------------------------------");
  12. System.out.println("方法切面开始");
  13. //将JoinPoint相关参数输出
  14. printlnJoinPoint(joinPoint);
  15. System.out.println("------------------------------------------------");
  16. }
  17. //方法结束之后执行
  18. @After("pointCut()")
  19. public void logEnd() {
  20. System.out.println("------------------------------------------------");
  21. System.out.println("方法结束通知");
  22. System.out.println("------------------------------------------------");
  23. }
  24. //抛出异常之后执行
  25. @AfterThrowing("pointCut()")
  26. public void logException() {
  27. System.out.println("------------------------------------------------");
  28. System.out.println("异常通知");
  29. System.out.println("------------------------------------------------");
  30. }
  31. //方法返回之前执行,要比 @After和@AfterThrowing之后,其实 @AfterReturning实现是放在 //finally里面的
  32. @AfterReturning(returning="obj",pointcut = "pointCut()")
  33. public void logReturn(JoinPoint joinPoint,Object obj) {
  34. System.out.println("------------------------------------------------");
  35. System.out.println("返回通知:"+obj);
  36. System.out.println("------------------------------------------------");
  37. }
  38. //该方法主要是对传入参数进行一系列的解释,当内部方法需要进行获取参数,可以参考该方法实现。
  39. private void printlnJoinPoint(JoinPoint joinPoint){
  40. System.out.println("目标方法名为:" + joinPoint.getSignature().getName());
  41. System.out.println("目标方法所属类的简单类名:" + joinPoint.getSignature().getDeclaringType().getSimpleName());
  42. System.out.println("目标方法所属类的类名:" + joinPoint.getSignature().getDeclaringTypeName());
  43. System.out.println("目标方法声明类型:" + Modifier.toString(joinPoint.getSignature().getModifiers()));
  44. //获取传入目标方法的参数
  45. Object[] args = joinPoint.getArgs();
  46. for (int i = 0; i < args.length; i++) {
  47. System.out.println("第" + (i+1) + "个参数为:" + args[i]);
  48. }
  49. System.out.println("被代理的对象:" + joinPoint.getTarget());
  50. System.out.println("代理对象自己:" + joinPoint.getThis());
  51. }
  52. }
@Pointcut的execution的匹配规则

基本公式:execution(返回类型 方法名称 传入参数)

常用的方式:

任意公共方法的执行:

execution(public * *(…))

任何一个以“set”开始的方法的执行:

execution(* set*(…))

HelloWorld接口的任意方法的执行:

execution(* com.rong.aop.aspect.HelloWorld.*(…))

定义在aspect包里的任意方法的执行:

execution(* com.rong.aop.aspect..(…))

定义在aspect包和所有子包里的任意类的任意方法的执行:

execution(* com.rong.aop.aspect….(…))

详细参考此资料:https://www.cnblogs.com/rainy-shurun/p/5195439.html

4.添加一个配置类

  1. package com.rong.aop.aspect;
  2. import org.springframework.context.annotation.ComponentScan;
  3. import org.springframework.context.annotation.EnableAspectJAutoProxy;
  4. //开启扫描
  5. @ComponentScan
  6. //开启切面编程,开启cglib
  7. @EnableAspectJAutoProxy(proxyTargetClass = true)
  8. public class MyConfigAspect {
  9. }

5.添加一个启动类

  1. public class AspectTest {
  2. public static void main(String[] args) {
  3. AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(MyConfigAspect.class);
  4. HelloWorld helloWorld = annotationConfigApplicationContext.getBean(HelloWorld.class);
  5. helloWorld.say("rong is coming");
  6. }
  7. }

6.添加结果

在这里插入图片描述

个人觉得:此框架是比较稳定和完善,而且切入十分方便,更不会对原来的代码有的改动。很顺利的将业务代码和系统功能代码进行解耦。而且利用切入,十分简单。可以对自己系统直接进行横向切入即可。而且以上代码也有一些基本对框架的利用,需要时候可以直接参考,

6.简单介绍@EnableAspectJAutoProxy

@EnableAspectJAutoProxy

该注解有两个方法:

proxyTargetClass默认为false,则是默认使用jdk动态代理,如果配置为ture,则使用cglib。

exposeProxy默认为false,则默认不暴露自己在aopContext,则aopContext无法访问的。

(个人理解是自己调用自己方法,不会执行代理类之后的方法的。)
在这里插入图片描述

而这个注解的核心起作用是AspectJAutoProxyRegistrar\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WIL7y3hx-1594457797738)(D:\\Users\\xuanyue.rong\\AppData\\Roaming\\Typora\\typora-user-images\\image-20200711152853809.png)\]

大概意思是注册一个目标类的代理类,然后去代替ioc容器中的现在的目标类,但是执行流程还是很复杂的。该类重写了注册BeanDefinition的方式。
在这里插入图片描述
在这里插入图片描述

参考来自,很优秀的文章,想详细了解可以查看:https://www.cnblogs.com/foreveravalon/p/8653832.html

​ 简单总结:来说这个注解的作用就是开启aop编程,然后对匹配上的类的方法进行增强。增强的过程大概是:在装载bean的时候,会有个过程判断该bean是否属于需要增强的bean,需要增强则会对继承该bean生成一个代理bean,这个代理bean其实就是重写了需要增强的方法,然后再将该代理bean放进ioc的容器里面,调用该bean时候会代替本来的bean,当然原理的bean还是存在的。但是当有代理bean自己调用自己的方法时候,是不会调用增强的方法,但是具体还是看配置的。

7.总结

​ 本文只是简单介绍了Spring aop和对Spring aop简单的实现,而没有对实现的细节描述,主要是对基本的功能和理解,详细实现可以根据需求再一一查询。所以aop基本功能实现是可以的,但是觉得很遗憾是对spring的源码不了解,还需要很多的探究,因此只对EnableAspectJAutoProxy注解的浅层的了解,深入还需很努力。还望各位可以对本文的错误和不足,希望多多指出,十分希望可以更正和完善。

发表评论

表情:
评论列表 (有 0 条评论,148人围观)

还没有评论,来说两句吧...

相关阅读

    相关 Spring AOP

    > AOP(Aspect Oriented Programming):⾯向切⾯编程,它是⼀种思想,它是对某⼀类事情的集中处理。⽐如⽤户登录权限的效验,没学 AOP 之前,我们所

    相关 jQuery$原理

    1、外层沙箱及命名空间$ 为了避免声明了一些全局变量而污染,把代码放在一个“沙箱执行”,jQuery具体的实现,都被包含在了一个立即执行函数构造的闭包里面,然后在暴露出命名空

    相关 spring

    常用注解: @Component (不推荐使用):注册一个bean到spring,一般使用前三个来指示不同层的bean @Autowired @Resource @R