什么是AOP
传统方式存在的问题:
代码混乱:越来越多的非业务需求(日志和验证等)加入后,原有的业务方法急剧膨胀。每个方法在处理核心逻辑的同时还必须兼顾其他多个关注点。
代码分散:以日志需求为例,只是为了满足这个单一的需求,就不得不在多个模块(方法)里多次重复相同的日志代码,如果日志需求发生变化,必须修改所有模块。
使用代理解决问题:
代理模式的原理:使用一个代理将对象包装起来,然后用该代理对象取代原始对象。任何对原始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转到原始对象上。
什么是 AOP
面向切面编程是一种思想,其编程思想就是把散布于不同业务但功能相同的代码从业务逻辑中抽取出来,封装成独立的模块,这些独立的模块被称为切面,比如权限认证、日志、事务、上下文处理、异常处理、懒加载、缓冲。
面向切面的目的就是解耦合,分离系统的各种关注点,将业务处理主流程(核心关注点)和与主流程关系不大的部分(横切关注点)分离,做到职责单一,专人做专事。
我们的实现目标就是将外围业务看做单独的关注点,在需要他们的时候可以及时的运用而且无需提前整合到核心模块中。每个关注点与核心业务模块分离,作为单独的功能,横切几个核心业务模块。这种抽象级别的技术就叫做AOP面向切面编程。而单独的这些关注点,叫做横切关注点。
与OOP对⽐,⾯向切⾯,传统的OOP开发中的代码逻辑是⾃上⽽下的,⽽这些过程会产⽣⼀些横切性问题,这些横切性的问题和我们的主业务逻辑关系不⼤,这些横切性问题不会影响到主逻辑实现的,但是会散落到代码的各个部分,难以维护。AOP是处理⼀些横切性问题,AOP的编程思想就是把这些问题和主业务逻辑分开,达到与主业务逻辑解耦的⽬的。使代码的重⽤性和开发效率更⾼。
Spring AOP底层技术
- Spring 底层使用了动态代理来实现 AOP,分为 JDK 动态代理和 CGLIB 动态代理,默认使用 JDK 动态代理,当在注解中更改 proxyTargetClass 属性的值为 true,或者在 xml 中配置 proxy-target-class 属性为 true 时,这两种方式才会使用 CGLIB 动态代理。
在xml中需要配置: <aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy> 在注解配置中需要使用一个注解: @EnableAspectJAutoProxy(proxyTargetClass = true)
- 下面这篇文章详细解释了,静态代理跟动态代理的实现方式,这里就不详细解释了。
- https://blog.youkuaiyun.com/Future_LL/article/details/107102941
JDK 动态代理和 CGLIB 动态代理区别:
CGLIB代理继承 (extends) 目标对象,JDK动态代理实现 (implements) 目标对象,默认使用的是 JDK 动态代理。
面试题:JDK 动态代理为什么只能基于接口
解答: JDK 底层源码已经帮代理对象自动继承了 Proxy 这个类,由于 Java 是单继承语法,所以不可能再去继承目标对象,只能去实现目标对象。
AOP和AspectJ的关系
Spring AOP、AspectJ 都是 AOP 的实现,Spring AOP 有⾃⼰的语法,但是语法复杂,所以 Spring AOP 借助了 AspectJ 的注解,但是底层技术还是 Spring ⾃⼰的。
详情看官网:
官网给出了两种实现方式分别为:
Enabling @AspectJ Support with Java Configuration
Enabling @AspectJ Support with XML Configuration
这两种方式都可以实现 AOP,第一种是基于 Java 注解的配置,第二种是基于 xml 的配置,由于第二种实现较为复杂,大部分情况使用第一种方式。
Spring AOP概念
在给出实现前,先来根据官网了解一些概念:
Aspect: A modularization of a concern that cuts across multiple classes. Transaction management is a good example of a crosscutting concern in enterprise Java applications. In Spring AOP, aspects are implemented by using regular classes (the schema-based approach) or regular classes annotated with the
@Aspect
annotation (the @AspectJ style).Join point: A point during the execution of a program, such as the execution of a method or the handling of an exception. In Spring AOP, a join point always represents a method execution.
Advice: Action taken by an aspect at a particular join point. Different types of advice include “around”, “before” and “after” advice. (Advice types are discussed later.) Many AOP frameworks, including Spring, model an advice as an interceptor and maintain a chain of interceptors around the join point.
Pointcut: A predicate that matches join points. Advice is associated with a pointcut expression and runs at any join point matched by the pointcut (for example, the execution of a method with a certain name). The concept of join points as matched by pointcut expressions is central to AOP, and Spring uses the AspectJ pointcut expression language by default.
Introduction: Declaring additional methods or fields on behalf of a type. Spring AOP lets you introduce new interfaces (and a corresponding implementation) to any advised object. For example, you could use an introduction to make a bean implement an
IsModified
interface, to simplify caching. (An introduction is known as an inter-type declaration in the AspectJ community.)Target object: An object being advised by one or more aspects. Also referred to as the “advised object”. Since Spring AOP is implemented by using runtime proxies, this object is always a proxied object.
AOP proxy: An object created by the AOP framework in order to implement the aspect contracts (advise method executions and so on). In the Spring Framework, an AOP proxy is a JDK dynamic proxy or a CGLIB proxy.
Weaving: linking aspects with other application types or objects to create an advised object. This can be done at compile time (using the AspectJ compiler, for example), load time, or at runtime. Spring AOP, like other pure Java AOP frameworks, performs weaving at runtime.
Advice 通知类型:
Before advice: Advice that runs before a join point but that does not have the ability to prevent execution flow proceeding to the join point (unless it throws an exception).
After returning advice: Advice to be run after a join point completes normally (for example, if a method returns without throwing an exception).
After throwing advice: Advice to be executed if a method exits by throwing an exception.
After (finally) advice: Advice to be executed regardless of the means by which a join point exits (normal or exceptional return).
Around advice: Advice that surrounds a join point such as a method invocation. This is the most powerful kind of advice. Around advice can perform custom behavior before and after the method invocation. It is also responsible for choosing whether to proceed to the join point or to shortcut the advised method execution by returning its own return value or throwing an exception.
各种连接点Join point的意义
使用 execution 最为广泛,所以拿它举例,当然还有一些其他的连接点,这里就不说明了,官网都有详细说明。
详情看官网:
// 用于匹配方法执行 join points连接点,最小粒度方法,在aop中主要使用。 execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?) // 这里问号表示当前项可以有也可以没有,其中各项的语义如下: modifiers-pattern:方法的可见性,可省略,如public,protected; ret-type-pattern:方法的返回值类型,如int,void等; declaring-type-pattern:方法所在类的全路径名,如com.spring.Aspect; name-pattern:方法名类型,如buisinessService(); param-pattern:方法的参数类型,如java.lang.String; throws-pattern:方法抛出的异常类型,如java.lang.Exception;
举例:
// 匹配com.chenss.dao包下的任意接口和类的任意方法 @Pointcut("execution(* com.chenss.dao.*.*(..))") // 匹配com.chenss.dao包下的任意接口和类的public方法 @Pointcut("execution(public * com.chenss.dao.*.*(..))") // 匹配com.chenss.dao包下的任意接口和类的public 无方法参数的方法 @Pointcut("execution(public * com.chenss.dao.*.*())") // 匹配com.chenss.dao包下的任意接口和类的第一个参数为String类型的方法 @Pointcut("execution(* com.chenss.dao.*.*(java.lang.String, ..))") // 匹配com.chenss.dao包下的任意接口和类的只有一个参数,且参数为String类型的方法 @Pointcut("execution(* com.chenss.dao.*.*(java.lang.String))") // 匹配com.chenss.dao包下的任意接口和类的只有一个参数,且参数为String类型的方法 @Pointcut("execution(* com.chenss.dao.*.*(java.lang.String))") // 匹配任意的public方法 @Pointcut("execution(public * *(..))") // 匹配任意的以te开头的方法 @Pointcut("execution(* te*(..))") // 匹配com.chenss.dao.IndexDao接口中任意的方法 @Pointcut("execution(* com.chenss.dao.IndexDao.*(..))") // 匹配com.chenss.dao包及其子包中任意的方法 @Pointcut("execution(* com.chenss.dao..*.*(..))")
AOP实现
<!-- 使用注解和xml的方式有所不同,但表达的意思是相同的 --> 在xml中需要配置: <!-- 使用AspectJ注解起作用: 自动为配置的类生成代理对象 --> <aop:aspectj-autoproxy></aop:aspectj-autoproxy> 在注解配置中需要使用一个注解: @EnableAspectJAutoProxy
- 定义一个 MathCalculator 实验类:
public class MathCalculator { public int div(int i, int j) { System.out.println("MathCalculator...div..."); return i / j; } }
- 定义 LogAspects 切面类:
/** * 切面类 * @Aspect: 告诉Spring当前类是一个切面类 * @Order: 使用@Order注解指定切面的优先级,值越小优先级越高 */ @Order(value = 1) @Aspect public class LogAspects { /** * 抽取公共切入点表达式 * 1、本类引用: logStart()、logEnd()、logException * 2、其他的切面引入: logReturn() * * public int com.future.aop.MathCalculator.*(..) * public: 访问修饰符,("里边写切入表达式") * int: 返回值类型 * *: 表示MathCalculator类中的所有方法 * (..): 任意参数 */ @Pointcut("execution(public int com.future.aop.MathCalculator.*(..))") public void pointCut(){ } /** * 这个@Before是org.aspectj.lang.annotation.Before的 * 前置通知(@Before): logStart(),在目标方法(div)运行之前运行 * * 注意: JoinPoint必须出现在参数的第一位,否则Spring是无法识别的 * * 如果pointCut()这个方法与logStart()不在一个包下,那么需要写全路径 * 举例: com.atguigu.aop.LogAspects.pointCut() * 如果是同包不同代码下,那么需要如下方式 * 举例: LogAspects.pointCut() */ @Before(value = "pointCut()") public void logStart(JoinPoint joinPoint){ //获取参数列表 Object[] argsList = joinPoint.getArgs(); //joinPoint.getSignature().getName(): 获得方法名 System.out.println(joinPoint.getSignature().getName() + " 方法运行...@Before: 参数列表是:{ " + Arrays.asList(argsList) + " }"); } // 后置通知(@After): logEnd(),在目标方法(div)运行之后运行【无论方法正常结束还是异常结束】 @After("pointCut()") public void logEnd(JoinPoint joinPoint){ System.out.println(joinPoint.getSignature().getName() + " 方法结束...@After"); } // 返回通知(@AfterReturning): logReturn(),在目标方法(div)正常返回之后运行 @AfterReturning(value = "com.future.aop.LogAspects.pointCut()", returning = "result") public void logReturn(JoinPoint joinPoint,Object result){ System.out.println(joinPoint.getSignature().getName() + " 方法正常返回...@AfterReturning: 计算结果:{ " + result + " }"); } // 异常通知(@AfterThrowing): logException(),在目标方法(div)出现异常之后运行 @AfterThrowing(value = "pointCut()", throwing = "exception") public void logException(JoinPoint joinPoint,Exception exception){ System.out.println(joinPoint.getSignature().getName() + " 方法异常...异常信息:{" + exception + "}"); } /** * 环绕通知,需要携带ProceedingJoinPoint类型的参数 * 环绕通知类似于动态代理的全过程: ProceedingJoinPoint类型的参数可以决定是否执行目标方法 * 且环绕通知必须有返回值,返回值即为目标方法的返回值 */ @Around(value = "pointCut()") public Object logAround(ProceedingJoinPoint proceedingJoinPoint) { try { // 前置通知 System.out.println("The method " + proceedingJoinPoint.getSignature().getName() + " begins with " + Arrays.asList(proceedingJoinPoint.getArgs())); // 后置通知 // proceed(): Proceed with the next advice or target method invocation System.out.println("The method " + proceedingJoinPoint.getSignature().getName() + " ends with " + proceedingJoinPoint.proceed()); } catch (Throwable throwable) { // 异常通知 System.out.println("The method " + proceedingJoinPoint.getSignature().getName() + " occurs exception: " + throwable); } // 后置通知 System.out.println("The method " + proceedingJoinPoint.getSignature().getName() + " ends "); return 520; } }
- 定义 MainConfigOfAOP 配置类:
// 配置类,相当于在xml中配置AOP @EnableAspectJAutoProxy @Configuration public class MainConfigOfAOP { //把业务逻辑类加入到容器中 @Bean public MathCalculator calculator(){ return new MathCalculator(); } //把切面类加入到容器中 @Bean public LogAspects aspects(){ return new LogAspects(); } }
- 定义 IOCTest_Aop 验证类:
// 验证 public class IOCTest_Aop { @Test public void test01(){ //创建Ioc容器 AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfigOfAOP.class); //1、不要自己创建MathCalculator对象,要使用Spring容器中的组件 MathCalculator math = context.getBean(MathCalculator.class); int value = math.div(1, 1); System.out.println("value = " + value); //关闭容器,对多实例不起作用 context.close(); } }
- 结果:
/** * The method div begins with [1, 1] * div 方法运行...@Before: 参数列表是:{ [1, 1] } * MathCalculator...div... * The method div ends with 1 * The method div ends * div 方法结束...@After * div 方法正常返回...@AfterReturning: 计算结果:{ 520 } * value = 520 */
有兴趣的同学可以关注我的个人公众号,期待我们共同进步!!!