AOP 面向切面编程
将一些共有的操作(日志、统计、安全控制、异常处理等)从业务逻辑代码中分离出来,实现复用,且不影响业务逻辑代码
AOP相关概念
- Aspect 切面:包含多个Advice(通知)和Pointcut(切入点)声明
- Join Point 连接点:Advice(通知,增强)可以运行的时机(函数的执行,异常的捕获)(Spring AOP中表示函数的执行) – 可以被切入的地方
- Pointcut 切入点:用于匹配连接点的谓词
- Spring默认使用AspectJ切入点表达式
- Advice 通知(增强): 要在Join Point(连接点)上执行的操作(函数执行前,函数返回后…)。每个Advice关联一个Pointcut表达式,将在被Pointcut匹配的Join Point上运行
- Spring AOP的Advice类型–在何时执行:
- 前置通知 Before Advice: 函数执行前
- 后置通知 After returning advice: 函数正常返回后执行
- 最终通知 After(Finally) Advice:函数退出后(正常返回或抛出异常)
- 环绕通知 **Around Advice **: 可在函数执行前后执行操作 – 在Advice中手动调用被切入的函数(切入点)
- 异常通知 After throwing Advice:异常抛出后
- Introduction :用于附加 字段或行为到一个类型上。
- Spring AOP可将新的接口和其实现附加到被增强的对象上
- Target Object:被一个或多个Aspect增强的对象
- Weaving:将Aspect与Target Object联系起来增强对象的过
- 可在编译时、加载时、运行时完成
Spring中使用AOP
Spring同时集成Spring AOP 和 AspectJ
- Spring AOP:
- Spring提供的AOP框架实现
- 通过动态代理实现AOP
- 其目标是实现与Spring IoC容器的结合 ,增强对象由容器管理
- 只能增强函数的执行
- 借用了AspectJ的一小部分,可以使用@AspectJ注解来定义aspect等
- AspectJ:独立的AOP框架,完备的AOP框架
- 使用Spring AOP可以通过AspectJ的注解(@AspectJ Support),或配置文件(XML)
Spring AOP
实现机制
-
Spring AOP通过动态代理实现AOP
-
Spring AOP默认使用JDK的动态代理 和 CGLIB的动态代理
- 若被增强的类实现了接口,使用JDK的动态代理。生成一个实现该接口的代理类来实现代理。
- 若被增强的类没有实现接口,则使用CGLIB。生成一个被代理的类的子类来实现代理。
通过@AspectJ支持的方式使用Spring AOP
-
Spring AOP借用AspectJ定义的一系列注解,来完成切入点的解析和匹配 —> @AspectJ支持
-
使用注解启用@AspectJ支持,Spring Boot中默认开启
@Configuration @EnableAspectJAutoProxy public class AppConfig { }
-
声明切面**@Aspect**, 并注册到容器@Component
-
声明切入点**@Pointcut(“切入点表达式”)**
@Aspect @Component public class TestAspect { @Pointcut("execution(public * *(..))") // 切入点表达式:匹配所有共有方法 private void anyPublicOperation() {} // 切入点签名 }
-
切入点表达式为 AspectJ的表达式格式,用于匹配连接点。Spring AOP支持的AspectJ 切入点标识符(pointcut designer, PDC)
- execution(…):匹配函数的执行。格式
- execution(public * *(…)) 所有共有方法
- execution(* set*(…)) 所有set开头的方法
- execution(* com.xyz.service.AccountService.*(…)) 所有被AccountService接口定义的方法
- execution(* com.xyz.service..(…)) service包下所有方法
- execution(* com.xyz.service….(…)) service包及其子包下所有方法
- within(…)
- within(com.xyz.service.*) service包下
- within(com.xyz.service…*) service及其子包下
- this(…) 指定类型的代理(Aspect为被增强的类创建的代理对象,与被代理的类具有相同的类型)
- target(…) 指定类型的Target Object
- args(…) 拥有指定参数类型的方法
- @target(…) 拥有指定注解的对象
- @within(…) 拥有指定注解的类
- @annotation(…) 拥有指定注解的方法
- @args(…) 参数用于指定注解的方法
- Spring AOP拓展的标识符 bean(…) :匹配bean的id或名称
- execution(…):匹配函数的执行。格式
-
切入点表达式可使用&& || !进行连接
-
常用切入点表达式
package com.xyz.myapp; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; @Aspect public class CommonPointcuts { /** * A join point is in the web layer if the method is defined * in a type in the com.xyz.myapp.web package or any sub-package * under that. */ @Pointcut("within(com.xyz.myapp.web..*)") public void inWebLayer() {} /** * A join point is in the service layer if the method is defined * in a type in the com.xyz.myapp.service package or any sub-package * under that. */ @Pointcut("within(com.xyz.myapp.service..*)") public void inServiceLayer() {} /** * A join point is in the data access layer if the method is defined * in a type in the com.xyz.myapp.dao package or any sub-package * under that. */ @Pointcut("within(com.xyz.myapp.dao..*)") public void inDataAccessLayer() {} /** * A business service is the execution of any method defined on a service * interface. This definition assumes that interfaces are placed in the * "service" package, and that implementation types are in sub-packages. * * If you group service interfaces by functional area (for example, * in packages com.xyz.myapp.abc.service and com.xyz.myapp.def.service) then * the pointcut expression "execution(* com.xyz.myapp..service.*.*(..))" * could be used instead. * * Alternatively, you can write the expression using the 'bean' * PCD, like so "bean(*Service)". (This assumes that you have * named your Spring service beans in a consistent fashion.) */ @Pointcut("execution(* com.xyz.myapp..service.*.*(..))") public void businessService() {} /** * A data access operation is the execution of any method defined on a * dao interface. This definition assumes that interfaces are placed in the * "dao" package, and that implementation types are in sub-packages. */ @Pointcut("execution(* com.xyz.myapp.dao.*.*(..))") public void dataAccessOperation() {} }
-
声明增强(Advice)
@Aspect @Component public class TestAspect { //--------------------------------------------------------------------------------- @Pointcut("execution(public * *(..))") private void anyPublicOperation() {} //声明Advice---------------------------------------------------------------------------------------- @Before("com.xyz.myapp.CommonPointcuts.dataAccessOperation()") public void doAccessCheck() { // ... } //使用签名指定切入点------------------------------------------------------------------ @AfterReturning(pointcut = "anyPublicOperation()", returning = "returnVal") public void doAccessCheck(JoinPoint joinPoint, Object returnVal) { //通过连接点的函数签名 Signature signature = joinPoint.getSignature(); MethodSignature methodSignature = (MethodSignature) signature; Class<?> returnType = methodSignature.getReturnType(); //..... } //--------------------------------------------------------------------------------- @AfterThrowing("com.xyz.myapp.CommonPointcuts.dataAccessOperation()") public void doRecoveryActions() { // ... } //--------------------------------------------------------------------------------- @After("com.xyz.myapp.CommonPointcuts.dataAccessOperation()") public void doReleaseLock() { // ... } //--------------------------------------------------------------------------------- @Around("com.xyz.myapp.CommonPointcuts.businessService()") public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable { //开始 Object retVal = pjp.proceed(); //执行函数 //完成 return retVal; } //捕获参数2--------------------------------------------------------------------------- @Before("com.xyz.myapp.CommonPointcuts.dataAccessOperation() && args(account,..)") public void validateAccount(Account account) { // ... } //捕获参数2--------------------------------------------------------------------------- @Pointcut("com.xyz.myapp.CommonPointcuts.dataAccessOperation() && args(account,..)") private void accountDataAccessOperation(Account account) {} @Before("accountDataAccessOperation(account)") public void validateAccount(Account account) { // ... } }
- 第一个参数可是为JoinPoint,以获取其切入的连接点的信息(Around Advice的应为ProceedingJoinPoint (JoinPoint子类))
- 可通过JoinPoint获取连接点的签名。对于函数,签名为MethodSignature类型
-
Advice执行的优先级:
- 进入连接点时执行的Advice(Before Advice),高优先级的先执行
- 离开连接点时执行的Advice(After Advice…),高优先级的后执行
- 使用**@Order或实现org.springframework.core.Ordered**设置Aspect类的优先级
- 同Aspect下Advice的优先级: @Around > @Before > @After > @AfterReturning > @AfterThrowing
Introductions
-
使用Aspect声明一个被增强的对象实现了一个接口,并提为这些对象提供接口的实现
-
@DeclareParents使被匹配的类型,实现新的接口,并提供默认实现
@Aspect public class UsageTracking { //使得service下的类 是接口UsageTracked的实现,并提供了默认实现 //(DefaultUsageTracked implements UsageTracked) @DeclareParents(value="com.xzy.myapp.service.*+", defaultImpl=DefaultUsageTracked.class) public static UsageTracked mixin; @Before("com.xyz.myapp.CommonPointcuts.businessService() && this(usageTracked)") public void recordUsage(UsageTracked usageTracked) { usageTracked.incrementUseCount(); } }