Spring深入浅出AOP(下)

简介: 面向切面编程(AOP)对编程结构提供了另一种思考方式,给面向对象编程(OOP)进行了补充。OOP 模块化的关键单元是类(Class),而 AOP 的模块化关键单元是切面(Aspect),切面实现的模块可以横切多个类型和对象,例如事务管理。AOP 框架是 Spring 的关键组件之一,Spring IoC 容器不依赖于 AOP,AOP 给 Spring IoC 提供了一个强大的中间件解决方案。


三、AOP 的通知处理



Spring 提供了五大通知类型,每个通知的执行的位置不一样,我们可以通过在定义通知的方法上添加参数来获取当前 AOP 拦截切点的一些信息,比如拦截目标类,代理类,方法参数等等。


1. JoinPoint


在前置通知、后置通知、最终通知和异常通知中,可以在方法的第一个参数使用JoinPoint接口,他提供了一些方法如下:

  • getArgs():返回方法的参数
  • getThis():返回代理对象
  • getTarget():返回目标对象
  • getSignature():返回被代理对象的方法和类相关的信息


2. ProceedingJoinPoint


在环绕通知中,可以在方法中使用ProceedingJoinPoint接口,它是JoinPoint接口的子接口,增加的proceed()方法用来调用被代理方法,它的返回值也就是调用被代理方法执行完的返回值,如果一个切面配置了多个通知,它会调用下一个通知,我们也可以使用另一个方法proceed(Object[])来给代理的方法传递参数。

注意:虽然在环绕通知中,proceed()方法不调用或调用多次都是合法的,但最好调用且只调用一次。


3. Advice 相关接口


在某些特殊场景,我们需要自定义一个通知,例如使用 Advisor 来实现定义切面,此时需要实现不同的接口来实现不同的通知。


3.1 MethodBeforeAdvice


通过实现MethodBeforeAdvice接口来实现前置通知。

public class AopBeforeAdvice implements MethodBeforeAdvice {
    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        // ...
    }
}


3.2 AfterReturningAdvice


通过实现AfterReturningAdvice接口来实现后置通知。

public class AopAfterReturningAdvice implements AfterReturningAdvice {
    @Override
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        // ...
    }
}


3.3 MethodInterceptor


通过实现MethodInterceptor接口来实现环绕通知,proceed()的用法和前面讲解的ProceedingJoinPoint用法类似。

public class AopAroundAdvice implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        return invocation.proceed();
    }
}


3.4 ThrowsAdvice


通过实现ThrowsAdvice接口来实现异常通知,这个接口没有任何方法,通过特定的方法来实现异常通知功能,支持的方法形式如下:

public void afterThrowing(Exception ex)
public void afterThrowing(Method method, Object[] args, Object target, Exception ex)

上面两种方法中的Exception参数可以换成其他特定的异常类型,表示只抛出该类型异常时才执行通知。比如定义只抛出空指针才执行的异常通知:

public class AopAfterThrowingAdvice implements ThrowsAdvice {
    public void afterThrowing(Method method, Object[] args, Object target, NullPointerException ex) {
        // ...
    }
}


4. 通知的执行顺序


当同一个切点上存在多个通知时,在“进入”连接点情况下,优先级高的通知先执行(给定两个前置通知,优先级高的先执行),在“退出”连接点情况下,优先级高的通知后执行(给定两个后置通知,优先级高的后执行)。

当同一个切点上存在不同的切面的两个通知,除非你指定,否则执行顺序是未知的,你可以在切面类实现Ordered接口来指定优先级,或者使用@Order注解来指定,Ordered.getValue()返回的值(或注解的值)越小的优先级越高。

在基于 XML 方式实现时,可以在标签中配置order属性来配置切面的优先级。

当同一个切点上存在相同切面的两个通知,执行顺序是未知的,因为没有方法通过反射 javac 编译的类来获取声明顺序。

微信图片999.png

四、AOP 的切点定义



切点控制通知执行的范围,Spring AOP 只支持 Spring beans 的方法切入,所以你定义的切点表达式只能拦截 Spring beans 的方法。


1.execution


匹配方法执行切点,它是 Spring AOP 中最原始的切点表达式。它的一般格式如下:

// 中括号里面的可以省略
execution([方法访问权限] 返回类型 [类全路径名.]方法名(参数类型) [异常类型])
// 示例
execution(* cn.codeartist.spring.aop.service..*.*(..))

只有execution表达式匹配到的方法,才能被拦截,切点表达式支持一些简单的通配符,*号表示单个任何类型,..符号表示任意个,例如参数类型的匹配,()匹配没有参数的方法,(..)匹配任意个参数的方法,(*)匹配一个参数的方法,(*,String)匹配两个参数的方法,并且第二个参数必须为String类型。还有其他示例:

// 匹配任何public访问权限的方法
execution(public * *(..))
// 匹配所有以set开头命名的方法
execution(* set*(..))
// 匹配AccountService接口(或类)的所有方法
execution(* com.xyz.service.AccountService.*(..))
// 匹配service包下所有类的方法
execution(* com.xyz.service.*.*(..))
// 匹配service包下及其子包下所有类的方法
execution(* com.xyz.service..*.*(..))


2.within


匹配指定类里面的方法,不匹配接口,within表达式的参数指定的是类全路径名。例如:

// 匹配service包下所有类的方法
within(com.xyz.service.*)
// 匹配service包下及其子包下所有类的方法
within(com.xyz.service..*)


3.target


匹配目标类对象里面的方法,目标类就是我们在代码中定义的类,target表达式的参数指定的是类全路径名。例如:

// 匹配实现AccountService接口的类对象的方法
target(com.xyz.service.AccountService)


4.this


匹配代理类对象里面的方法,我们在后面会讲到 Spring AOP 代理机制,那样会更好理解this表达式,this表达式的参数指定的是类全路径名。

// 匹配实现AccountService接口的代理类对象的方法
this(com.xyz.service.AccountService)


5.args


匹配方法里面的参数类型,args表达式的参数指定的是一个或多个参数的类全路径名。例如:

// 匹配参数只有一个且属于Serializable类型(或实现Serializable接口的类)的所有方法
args(java.io.Serializable)

该表达式与execution(* *(java.io.Serializable))表达式功能不一样,args匹配参数在运行时类型为Serializable类型,也可以是Serializable的子类,而execution匹配方法声明的参数类型为Serializable类型。


6.bean


匹配 Bean 名称,也支持*通配符。例如:

// 匹配在Spring容器中,bean名称为tradeService的类方法
bean(tradeService)
// 匹配在Spring容器中,bean名称以Service结尾所有类的方法
bean(*Service)

在前面讲解的几种表达式,withinthistarget都是对类型的匹配,within只能匹配类而不能匹配接口,target匹配目标类,对接口和类都能够匹配到,this匹配代理类,所以使用 JDK 动态代理的时候,不能匹配到实现接口的类,原因我们会在 AOP 代理机制中讲到。匹配情况如下:

表达式拦截位置 within this target
接口
实现接口的类
不实现接口的类


7.@annotation


匹配带有指定注解的方法,对接口中方法的注解无效。例如:

// 匹配所有含有@Transactional注解的方法
@annotation(org.springframework.transaction.annotation.Transactional)


8.@within


匹配带有指定注解的执行方法所在的类,对接口无效,在父类中使用,如果子类没有重写方法也会生效。例如:

// 匹配所有含有@Transactional注解的类
@within(org.springframework.transaction.annotation.Transactional)


9.@target


匹配带有指定注解的目标类对象,对接口无效,对父类无效,它要求对象的运行时类型必须与被注解的目标类型是同一个类型。例如:

// 匹配所有含有@Transactional注解的目标类
@target(org.springframework.transaction.annotation.Transactional)


10.@args


匹配方法参数带有指定注解的方法。例如:

// 匹配运行时参数含有@Classified注解的方法
@args(com.xyz.security.Classified)

前面讲解的几种注解拦截表达式中,表达式参数都是注解类型,@within@target都是对类型是否含有注解的匹配,只需要记住,@within匹配方法实际执行所在的类,@target只匹配类型。匹配情况如下:

注解位置 @within @target
接口
实现接口的类
不实现接口的类
父类(子类重写方法)
父类(子类未重写方法)
子类(子类重写方法)
子类(子类未重写方法)


11. 切点表达式的组合


你可以使用&&||!来组合多个切点表达式,表示多个表达式“与”、“或”和“非”的逻辑关系。例如:

// 匹配符合anyOldTransfer()切点表达式并且参数第一个为Account类型的方法
@Before("anyOldTransfer() && args(account,..)")
public void validateAccount(Account account) {
    // ...
}
// 参数绑定
@Before("@annotation(demo)")
public void doAtMethod(Demo demo) {
    // ...
}

切点表达式中的参数类型,可以与通知方法的参数通过名称绑定,这样在表达式中就不需要写类或注解的全路径名,而且能直接获取到切点拦截的参数信息。

为了使得切点的匹配性能达到最佳,在编写表达式匹配切点目标时,应该尽可能缩小匹配范围,存在的切点表达式可以分为下面三大类:

  • 类型表达式:匹配某个特定的切入点,例如execution
  • 作用域表达式:匹配某组特定的切入点,例如within
  • 上下文表达式:基于上下文匹配某些特定的切入点,例如thistarget@annotation

一个好的切点表达式应该至少包含前两种(类型和作用域),你可以使用上下文表达式来基于切入点上下文匹配或在通知中绑定上下文。单独使用类型表达式或上下文表达式比较消耗性能(时间或内存)。作用域表达式匹配的性能非常快,所以表达式中尽可能的使用作用域类型。


五、AOP 代理机制



Spring AOP 是基于代理实现的,默认使用标准 JDK 动态代理,只有在接口被代理的时候启用。AOP 代理也使用 CGLib 动态代理,在类被代理的时候启用,也就是类没有实现任何接口的时候。


1. JDK动态代理与CGLib动态代理


JDK 动态代理是通过实现接口来生成代理类,而 CGLib 动态代理是通过继承类来生成代理子类,通过子类对父类方法的重写覆盖来实现代理,所以 CGLib 对final修饰的方法不能代理。

微信图片888.png

在 Spring AOP 中,你可以通过以下配置强制使用 CGLib 代理:

基于 XML 配置

<aop:config proxy-target-class="true">
    <!-- other beans defined here... -->
</aop:config>

基于注解配置

<aop:aspectj-autoproxy proxy-target-class="true"/>
@EnableAspectJAutoProxy(proxyTargetClass = true)

在前面讲解切点表达式时,this用来匹配代理类,使用 JDK 动态代理时,实现接口的类与代理类只是实现了同一个接口,不是同一个类型,所以无法匹配到。而使用 CGLib 动态代理时,代理类是接口实现类的子类,所以使用this能匹配到。


目录
相关文章
|
29天前
|
XML Java 数据安全/隐私保护
Spring Aop该如何使用
本文介绍了AOP(面向切面编程)的基本概念和术语,并通过具体业务场景演示了如何在Spring框架中使用Spring AOP。文章详细解释了切面、连接点、通知、切点等关键术语,并提供了完整的示例代码,帮助读者轻松理解和应用Spring AOP。
Spring Aop该如何使用
|
12天前
|
监控 安全 Java
什么是AOP?如何与Spring Boot一起使用?
什么是AOP?如何与Spring Boot一起使用?
40 5
|
17天前
|
Java 开发者 Spring
深入解析:Spring AOP的底层实现机制
在现代软件开发中,Spring框架的AOP(面向切面编程)功能因其能够有效分离横切关注点(如日志记录、事务管理等)而备受青睐。本文将深入探讨Spring AOP的底层原理,揭示其如何通过动态代理技术实现方法的增强。
47 8
|
17天前
|
Java 开发者 Spring
Spring AOP 底层原理技术分享
Spring AOP(面向切面编程)是Spring框架中一个强大的功能,它允许开发者在不修改业务逻辑代码的情况下,增加额外的功能,如日志记录、事务管理等。本文将深入探讨Spring AOP的底层原理,包括其核心概念、实现方式以及如何与Spring框架协同工作。
|
17天前
|
XML 监控 安全
深入调查研究Spring AOP
【11月更文挑战第15天】
32 5
|
17天前
|
Java 开发者 Spring
Spring AOP深度解析:探秘动态代理与增强逻辑
Spring框架中的AOP(Aspect-Oriented Programming,面向切面编程)功能为开发者提供了一种强大的工具,用以将横切关注点(如日志、事务管理等)与业务逻辑分离。本文将深入探讨Spring AOP的底层原理,包括动态代理机制和增强逻辑的实现。
27 4
|
2月前
|
存储 缓存 Java
Spring高手之路23——AOP触发机制与代理逻辑的执行
本篇文章深入解析了Spring AOP代理的触发机制和执行流程,从源码角度详细讲解了Bean如何被AOP代理,包括代理对象的创建、配置与执行逻辑,帮助读者全面掌握Spring AOP的核心技术。
43 3
Spring高手之路23——AOP触发机制与代理逻辑的执行
|
1月前
|
Java Spring
[Spring]aop的配置与使用
本文介绍了AOP(面向切面编程)的基本概念和核心思想。AOP是Spring框架的核心功能之一,通过动态代理在不修改原代码的情况下注入新功能。文章详细解释了连接点、切入点、通知、切面等关键概念,并列举了前置通知、后置通知、最终通知、异常通知和环绕通知五种通知类型。
30 1
|
1月前
|
安全 Java 测试技术
Java开发必读,谈谈对Spring IOC与AOP的理解
Spring的IOC和AOP机制通过依赖注入和横切关注点的分离,大大提高了代码的模块化和可维护性。IOC使得对象的创建和管理变得灵活可控,降低了对象之间的耦合度;AOP则通过动态代理机制实现了横切关注点的集中管理,减少了重复代码。理解和掌握这两个核心概念,是高效使用Spring框架的关键。希望本文对你深入理解Spring的IOC和AOP有所帮助。
35 0
|
3月前
|
设计模式 Java 测试技术
spring复习04,静态代理动态代理,AOP
这篇文章讲解了Java代理模式的相关知识,包括静态代理和动态代理(JDK动态代理和CGLIB),以及AOP(面向切面编程)的概念和在Spring框架中的应用。文章还提供了详细的示例代码,演示了如何使用Spring AOP进行方法增强和代理对象的创建。
spring复习04,静态代理动态代理,AOP