一、概述
-
AOP
,全称Aspect Oriented Programming
,中文名面向切面编程。 -
AOP
是通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术。 -
AOP
是传统OOP
的一种延续和增强,使用AOP
可以降低了业务之间的耦合,提高了代码的灵活性和可拓展性。 -
AOP
在Java
中比较有名的实现有AspectJ
和Spring AOP
-
AspectJ
是eclipse
名下的一个子项目,可以说是AOP
在Java
中最早也最全面的实现。AspectJ
提供了很多强大的功能,详细信息可以见官网:https://www.eclipse.org/aspectj/doc/released/progguide/index.html -
Spring AOP
是后起之秀,它开发的目的不是提供完整的AOP
实现,而是为了和Spring IOC
集成, 帮助解决开发中的常见问题。
-
二、术语
Aspect
(切面): 散布于不同业务但功能相同的代码从业务逻辑中抽取出来,封装成独立的模块,这些独立的模块被称为切面,代表软件开发中需要关注的一个方面。例如事务,日志。Jointcut
(连接点): 程序中执行的某一点。在Spring AOP
特指某个方法执行,在AspectJ
中还可以指定字段赋值取值、构造方法执行等。Advice
(通知增强): 在连接点处采取的操作,通知增强的执行时机包含before
,after
,around
,after return
,after throwing
等。Pointcut
(切点): 匹配连接点的操作(表达式)。这是AOP
的核心,Spring AOP
默认使用AspectJ Pointcut
表达式。Introduction
(引介): 在类级别上添加额外的方法或字段。Spring AOP
可以向任何Advice添加新的接口方法Target object
(目标对象): 被一个或多个切面Advice
的对象。Spring AOP
中指的是被代理的对象Aop proxy
(Aop代理对象): 指的是由Aop框架创建,用来实现切面的对象。Spring AOP
中的代理对象指的是由JDK
动态代理或Cglib
动态代理创建的对象Weaving
(织入): 连接切面和应用程序的对象用来创建Advice。Spring AOP
在运行时织入。使用AspectJ
可以在编译前将增强代码织入.java
文件中,也可以在编译后织入.class
文件中,当然,也可以在运行时织入
三、注解
序号 | 注解名 | 含义 |
---|---|---|
1 | @Aspect | 标记此类包含各种pointcut, advice, and introduction定义 需要加上@Component被Spring检测到 |
2 | @Pointcut | 定义一个切点,标记的方法必须是void返回值 |
3 | @Before | 定义一个Before Advice,标记的方法在所匹配连接点方法执行前执行 |
4 | @After | 定义一个After Advice,标记的方法在所匹配连接点方法执行后执行 |
5 | @AfterReturning | 定义一个After returning Advice,标记的方法在所匹配连接点方法返回值后执行. 属性returning用来设置Adive方法签名中要将返回值绑定到的参数的名称 |
6 | @AfterThrowing | 定义一个After throwing Advice,标记的方法在所匹配连接点方法抛出异常后执行。属性throwing用来设置Adive方法签名中要将异常绑定到的参数的名称 |
7 | @Around | 定义一个Around Advice,标记的方法第一个参数必须是ProceedingJoinPoint ,在Advice方法体内需要调用proceed()方法;方法返回值最好是Object,否则如果切点匹配的方法有返回值会拿不到。 |
Spring AOP
支持以下在@Pointcut
中使用的Aspect切点表达式
-
execution
最主要的表达式,表示方法执行语法规则如下,?代表可选
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)
- modifiers-pattern 修饰符(public等) . 支持通配符
*
- ret-type-pattern 方法返回值类型 支持通配符
*
, 可以匹配任何返回值类型 - declaring-type-pattern 方法所属类类型 ,指定类的全路径,支持通配符
*
- name-pattern 方法名字, 支持通配符
*
,可用来部分或全部匹配方法名字. - param-pattern 参数,支持通配符
*
和..
, 其中*
代表一个任意类型的参数,而..
代表零个或多个任意类型的参数。例如,()
匹配一个不接受任何参数的方法,而(..)
匹配一个接受任意数量参数的方法,(*)
匹配了一个接受一个任何类型的参数的方法,(*,String)
匹配了一个接受两个参数的方法,其中第一个参数是任意类型,第二个参数必须是String类型。 - throws-pattern 异常,支持通配符
*
示例
序号 表达式 说明 1 execution( public * *(..))
任何public方法的执行 2 execution(* set*(..))
任何以set开头的方法 3 execution(* com.xyz.service.AccountService.*(..))
AccountService这个接口的所有方法 4 execution(* com.xyz.service.*.*(..))
com.xyz.service
包下所有方法com.xyz.service.*
表示包的类.*
表示所有方法5 execution(* com.xyz.service..*.*(..))
com.xyz.service
包下及子包所有方法com.xyz.service..*
表示包及子包6 execution(* com.xyz.service.*.*())
com.xyz.service
包下不带参数的方法7 execution(* com.xyz.service.*.*(..) throws Exception)
com.xyz.service
包下声明带throws Exception异常的方法以下并不常见,具体可以查看官方文档
- modifiers-pattern 修饰符(public等) . 支持通配符
-
within
匹配指定包或类的方法执行 -
this
匹配当前AOP代理对象的类型,必须是类型全限定名,不能包含通配符 -
target
匹配当前目标对象类型的执行方法,必须是类型全限定名,不能包含通配符 -
args
匹配当前执行的方法传入的参数为指定类型的执行方法,注意是匹配传入的参数类型,不是匹配方法签名的参数类型;参数类型列表中的参数必须是类型全限定名,通配符不支持;args属于动态切入点,这种切入点开销非常大,非特殊情况最好不要使用 -
@target
、@args
、@within
、@annotation
四、执行顺序
以下顺序指增强一个方法Method的各个Advice顺序
单个切点
单个切点匹配到Method
多个切点
多个切点匹配到Method, 还是以Method执行为核心, 正常执行如下,异常执行类似。多个切点可以加Order注解配置顺序,值越低优先级越高,越先执行。相同优先级就是包加类字符串排序的顺序。
五、流程及源码分析
SpringAOP
和IOC
紧密相连,当我们配置好AOP
后启动,IOC
容器生成Bean
,如果这个Bean
中的方法被AOP
匹配到,那么IOC
实际会生成一个Bean
代理对象。下面以一个例子来简单分析一下流程
@Aspect
@Component
public class OneAop {
private final Logger log = LoggerFactory.getLogger(this.getClass());
@Pointcut("execution(* net.guides.springboot2.springboot2jpacrudexample.controller.UserController.*(..))")
public void pointCut() {
}
/**
* Run before the method execution.
* @param joinPoint
*/
@Before("pointCut()")
public void logBefore(JoinPoint joinPoint) {
log.debug("logBefore running .....");
}
/**
* Run after the method returned a result.
* @param joinPoint
*/
@After("pointCut()")
public void logAfter(JoinPoint joinPoint) {
log.debug("logAfter running .....");
}
/**
* Run after the method returned a result, intercept the returned result as well.
* @param joinPoint
* @param result
*/
@AfterReturning(pointcut = "pointCut()", returning = "result")
public void logAfterReturning(JoinPoint joinPoint, Object result) {
log.debug("logAfterReturning running .....");
}
/**
* Run around the method execution.
* @param joinPoint
* @return
* @throws Throwable
*/
@Around("pointCut()")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
log.debug("logAround before running .....");
Object result = joinPoint.proceed();
log.debug("logAround after running .....");
return result;
}
/**
* Advice that logs methods throwing exceptions.
*
* @param joinPoint join point for advice
* @param error exception
*/
@AfterThrowing(pointcut = "pointCut()", throwing = "error")
public void logAfterThrowing(JoinPoint joinPoint, Throwable error) {
log.debug("logAfterThrowing running .....");
}
}
UserController
@RestController
public class UserController {
private static final Logger logger = LoggerFactory.getLogger(UserController.class);
@GetMapping("/exception")
public Object exception() throws Exception{
logger.info("controller exception invoke");
throw new Exception("异常");
}
@GetMapping("/test")
public void test() {
logger.info("controller invoke");
}
}
-
最重要的一个类
AbstractAutoProxyCreator
, 这是BeanPostProcess
的实现类,实现了postProcessAfterInitialization
方法,会通过wrapIfNecessary
判断IOC
容器中的Bean是不是需要代理 -
wrapIfNecessary
中会为Bean获取Advice, 获取不到就是DO_NOT_PROXY
-
先获取全部Advice,再查找能够匹配Bean的Advice,还会进行排序. 如下图,
Advisor
是Advice基本接口
-
在获取Advice时,会从带有@Aspect注解的Bean中查找
-
从Aspect中拿Advice,会先从缓存拿,拿不到从工厂创建
-
通过工厂创建,会遍历Aspect中方法,查找Advice相关的生成
-
拿到所有Advice,会找出匹配Bean的Advice
-
有Advice的Bean就可以为它生成代理对象了
-
用Cglib代理,还是JDK代理