1. Spring AOP 概念
1. Aspect(切面)
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、Advice、Pointcut 的载体
在 AspectJ 中,它是一个类;在 Spring XML 中,它是一段配置
2. Join point(连接点)
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.
目标对象中的方法
3. Advice(通知)
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.
要通知的具体内容和通知到方法的哪个位置
| 增强类型 | xml 配置 | 注解 | 应用场景 |
|---|---|---|---|
| 前置增强(Before advice) | aop:before | @Before | 权限控制、记录调用日志 |
| 后置增强(After returning advice) | aop:after-returning | @AfterReturning | 统计分析结果数据 |
| 异常增强(After throwing advice) | aop:throwing | @AfterThrowing | 通过日志记录方法异常信息 |
| 最终增强(After finally advice) | aop:after | @After | 释放资源 |
| 环绕增强(Around advice) | aop:around | @Around | 缓存、性能日志、权限、事务管理 |
4. Pointcut(切点)
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.
它表示连接点的集合,切点只定位到某个方法上
5. Introduction(引介)
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.)
引介是一种特殊的增强,它为类添加一些属性和方法。这样,即使一个业务类原本没有实现某个接口,通过 AOP 的引介功能,我们可以动态地为该业务类添加接口的实现逻辑,让业务类成为这个接口的实现类
6. Target object(目标对象)
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.
目标对象
7. AOP proxy(AOP 代理)
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.
在 Spring Framework 中,AOP 代理是 JDK 动态代理或 CGLIB 代理,它是代理后的对象
8. Weaving(编织)
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.
把代理逻辑加入到目标方法上的过程叫做织入
2. Spring 使用 AspectJ 实现 AOP
2.1 Spring AOP 支持的切入点表达式
2.1.1 execution
代码已经上传至 https://github.com/masteryourself-tutorial/tutorial-spring ,详见
tutorial-spring-framework/tutorial-spring-framework-aop/tutorial-spring-framework-aop-execution工程
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
表达式的最小粒度是方法,在 aop 中主要使用
@Aspect
@Component
@Order(1)
public class LoggingAspect {
/**
* 匹配 pers.masteryourself.tutorial.spring.framework.aop.service 包及其子包中的任意以 save 开头的方法
* execution(* pers.masteryourself.dao.*.*(..)) 表示匹配 pers.masteryourself.dao 包下的任意接口和类的任意方法
* execution(public * pers.masteryourself.dao.*.*(..)) 表示匹配 pers.masteryourself.dao 包下的任意接口和类的 public 方法
* execution(public * pers.masteryourself.dao.*.*()) 表示匹配 pers.masteryourself.dao 包下的任意接口和类的 public 无方法参数的方法
* execution(* pers.masteryourself.dao.*.*(java.lang.String, ..)) 表示匹配 pers.masteryourself.dao 包下的任意接口和类的第一个参数为 String 类型的方法
* execution(* pers.masteryourself.dao.*.*(java.lang.String)) 表示匹配 pers.masteryourself.dao 包下的任意接口和类的只有一个参数,且参数为 String 类型的方法
* execution(public * *(..)) 表示匹配任意的 public 方法
* execution(* save*(..)) 表示匹配任意的以 save 开头的方法
* execution(* pers.masteryourself.dao.IndexDao.*(..)) 表示匹配 pers.masteryourself.dao.IndexDao 接口中任意的方法
* execution(* pers.masteryourself.dao..*.*(..)) 表示匹配 pers.masteryourself.dao 包及其子包中任意的方法
*/
@Pointcut("execution(* pers.masteryourself.tutorial.spring.framework.aop.service..*.save*(..))")
public void declareJointPointExpression() {
}
@Before("declareJointPointExpression()")
public void before() {
System.out.println("aop 生效");
}
}
2.1.2 within
代码已经上传至 https://github.com/masteryourself-tutorial/tutorial-spring ,详见
tutorial-spring-framework/tutorial-spring-framework-aop/tutorial-spring-framework-aop-within工程
表达式的最小粒度是类
within 与 execution 相比,粒度更大,仅能实现到包和接口、类级别。而 execution 可以精确到方法的返回值,参数个数、修饰符、参数类型等
@Aspect
@Component
@Order(1)
public class LoggingAspect {
/**
* 匹配 pers.masteryourself.tutorial.spring.framework.aop.service 包及其子包中的任意方法
* within(pers.masteryourself.tutorial.spring.framework.aop.service.*) 表示匹配 pers.masteryourself.tutorial.spring.framework.aop.service 包中的任意方法
*/
@Pointcut("within(pers.masteryourself.tutorial.spring.framework.aop.service..*)")
public void declareJointPointExpression() {
}
@Before("declareJointPointExpression()")
public void before() {
System.out.println("aop 生效");
}
}
2.1.3 this
代码已经上传至 https://github.com/masteryourself-tutorial/tutorial-spring ,详见
tutorial-spring-framework/tutorial-spring-framework-aop/tutorial-spring-framework-aop-this工程
执行当前对象(代理后的对象)是给定类型的任何方法
JDK 代理时,指向接口和代理类 Proxy,cglib 代理时,指向接口和子类
@Aspect
@Component
@Order(1)
public class LoggingAspect {
/**
* this 表示代理对象的匹配类型是否是 {@link PersonServiceImpl}
* 如果是 JDK 动态代理将不会为方法代理成功,因为在 JDK 中,代理对象会实现 {@link PersonService} 继承 {@link Proxy}接口,所以不是 {@link PersonServiceImpl} 类型
* 有两种解决方案:
* 1. 可以通过 {@link EnableAspectJAutoProxy} 的 proxyTargetClass = true 属性强制使用 cglib 进行代理
* 2. this(pers.masteryourself.tutorial.spring.framework.aop.service.impl.PersonServiceImpl) 换成 this(pers.masteryourself.tutorial.spring.framework.aop.service.PersonService)
*/
@Pointcut("this(pers.masteryourself.tutorial.spring.framework.aop.service.impl.PersonServiceImpl)")
public void declareJointPointExpressionByJDK() {
}
/**
* 如果是 cglib 动态代理会生效,在 cglib 中,代理对象会继承 {@link UserServiceImpl}
*/
@Pointcut("this(pers.masteryourself.tutorial.spring.framework.aop.service.impl.UserServiceImpl)")
public void declareJointPointExpressionByCglib() {
}
@Before("declareJointPointExpressionByJDK() || declareJointPointExpressionByCglib()")
public void before() {
System.out.println("aop 生效");
}
}
2.1.4 target
代码已经上传至 https://github.com/masteryourself-tutorial/tutorial-spring ,详见
tutorial-spring-framework/tutorial-spring-framework-aop/tutorial-spring-framework-aop-target工程
执行目标对象(被代理的对象)是给定类型的任何方法
@Aspect
@Component
@Order(1)
public class LoggingAspect {
/**
* target 表示目标对象的匹配类型是否是 {@link PersonServiceImpl},目标对象类型永远不会发生变化
* 因为要代理的对象就是 {@link PersonServiceImpl},所以肯定是
*/
@Pointcut("target(pers.masteryourself.tutorial.spring.framework.aop.service.PersonService)")
public void declareJointPointExpression() {
}
@Before("declareJointPointExpression()")
public void before() {
System.out.println("aop 生效");
}
}
2.1.5 args
代码已经上传至 https://github.com/masteryourself-tutorial/tutorial-spring ,详见
tutorial-spring-framework/tutorial-spring-framework-aop/tutorial-spring-framework-aop-args工程
执行目标参数具有指定类型实例的任何方法
@Aspect
@Component
@Order(1)
public class LoggingAspect {
/**
* args 表达式的作用是匹配指定参数类型和指定参数数量的方法,与包名和类名无关
* 第一个入参类型必须是 {@link pers.masteryourself.tutorial.spring.framework.aop.bean.User},之后的入参类型无规定
*/
@Pointcut("args(pers.masteryourself.tutorial.spring.framework.aop.bean.User,..)")
public void declareJointPointExpression() {
}
@Before("declareJointPointExpression()")
public void before() {
System.out.println("aop 生效");
}
}
2.1.6 @target
执行目标对象具有指定类型注解的任何方法
2.1.7 @args
执行目标参数具有指定类型注解的任何方法
2.1.8 @within
执行目标对象具有指定类型注解的任何方法
2.1.9 @annotation
代码已经上传至 https://github.com/masteryourself-tutorial/tutorial-spring ,详见
tutorial-spring-framework/tutorial-spring-framework-aop/tutorial-spring-framework-aop-annotation工程
执行目标方法具有指定类型注解的任何方法
@Aspect
@Component
@Order(1)
public class LoggingAspect {
/**
* 作用于标注了 {@link MasterYourself} 注解的方法
*/
@Pointcut("@annotation(pers.masteryourself.tutorial.spring.framework.aop.config.MasterYourself)")
public void declareJointPointExpression() {
}
@Before("declareJointPointExpression()")
public void before() {
System.out.println("aop 生效");
}
}
2.2 Introductions
代码已经上传至 https://github.com/masteryourself-tutorial/tutorial-spring ,详见
tutorial-spring-framework/tutorial-spring-framework-aop/tutorial-spring-framework-aop-introductions工程
@Aspect
@Component
@Order(1)
public class LoggingAspect {
/**
* 让 pers.masteryourself.tutorial.spring.framework.aop.service 下面的类都实现 {@link BaseService}接口,默认使用的 {@link BaseServiceImpl}作为实现
*/
@DeclareParents(value = "pers.masteryourself.tutorial.spring.framework.aop.service.*", defaultImpl = BaseServiceImpl.class)
public static BaseService baseService;
}
3. Spring 使用 XML 实现 AOP
详见文档 https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#aop-schema
代码已经上传至 https://github.com/masteryourself-tutorial/tutorial-spring ,详见
tutorial-spring-framework/tutorial-spring-framework-aop/tutorial-spring-framework-aop-xml工程
<?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 http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="loggingAspect" class="pers.masteryourself.tutorial.spring.framework.aop.config.LoggingAspect"/>
<bean id="personService" class="pers.masteryourself.tutorial.spring.framework.aop.service.PersonService"/>
<aop:config>
<aop:aspect ref="loggingAspect">
<aop:pointcut id="loggingPointcut"
expression="execution(* pers.masteryourself.tutorial.spring.framework.aop.service..*.save*(..))"/>
<aop:before method="beforeMethod" pointcut-ref="loggingPointcut"/>
<aop:after method="afterMethod" pointcut-ref="loggingPointcut"/>
<aop:after-returning method="afterReturning" pointcut-ref="loggingPointcut" returning="result"/>
<aop:after-throwing method="afterThrowing" pointcut-ref="loggingPointcut" throwing="e"/>
<!--<aop:around method="aroundMethod" pointcut-ref="loggingPointcut"/>-->
</aop:aspect>
</aop:config>
</beans>
本文详细介绍了Spring AOP的核心概念,包括切面、连接点、通知、切点、引介、目标对象、AOP代理和编织。同时,深入探讨了Spring如何使用AspectJ和XML实现AOP,以及各种切入点表达式的应用。
2605

被折叠的 条评论
为什么被折叠?



