Spring AOP全面详解(超级详细)

如果说IOC 是 Spring 的核心,那么面向切面编程AOP就是 Spring 另外一个最为重要的核心@mikechen

AOP的定义

AOP (Aspect Orient Programming),直译过来就是 面向切面编程,AOP 是一种编程思想,是面向对象编程(OOP)的一种补充。

面向切面编程,实现在不修改源代码的情况下给程序动态统一添加额外功能的一种技术,如下图所示:

AOP可以拦截指定的方法并且对方法增强,而且无需侵入到业务代码中,使业务与非业务处理逻辑分离,比如Spring的事务,通过事务的注解配置,Spring会自动在业务方法中开启、提交业务,并且在业务处理失败时,执行相应的回滚策略。

AOP的作用

AOP 采取横向抽取机制(动态代理),取代了传统纵向继承机制的重复性代码,其应用主要体现在事务处理、日志管理、权限控制、异常处理等方面。

主要作用是分离功能性需求和非功能性需求,使开发人员可以集中处理某一个关注点或者横切逻辑,减少对业务代码的侵入,增强代码的可读性和可维护性。

简单的说,AOP 的作用就是保证开发者在不修改源代码的前提下,为系统中的业务组件添加某种通用功能。

AOP的应用场景

比如典型的AOP的应用场景:\

  • 日志记录
  • 事务管理
  • 权限验证
  • 性能监测

AOP可以拦截指定的方法,并且对方法增强,比如:事务、日志、权限、性能监测等增强,而且无需侵入到业务代码中,使业务与非业务处理逻辑分离。

Spring AOP的术语

在深入学习SpringAOP 之前,让我们先对AOP的几个基本术语有个大致的概念。

AOP核心概念

 Spring AOP 通知分类

 Spring AOP 织入时期

Spring AOP三种使用方式

AOP编程其实是很简单的事情,纵观AOP编程,程序员只需要参与三个部分:

1、定义普通业务组件

2、定义切入点,一个切入点可能横切多个业务组件

3、定义增强处理,增强处理就是在AOP框架为普通业务组件织入的处理动作

所以进行AOP编程的关键就是定义切入点和定义增强处理,一旦定义了合适的切入点和增强处理,AOP框架将自动生成AOP代理,即:代理对象的方法=增强处理+被代理对象的方法。

方式1:使用Spring自带的AOP

 
  1. public class LogAdvice implements MethodBeforeAdvice, AfterReturningAdvice,MethodInterceptor {

  2. @Override

  3. public void before(Method method, Object[] objects, Object target) throws Throwable {

  4. //前置通知

  5. }

  6. @Override

  7. public void afterReturning(Object result, Method method, Object[] objects, Object target) throws Throwable {

  8. //后置通知

  9. }

  10. @Override

  11. public Object invoke(MethodInvocation methodInvocation) throws Throwable {

  12. //环绕通知

  13. //目标方法之前执行

  14. methodInvocation.proceed(); //目标方法

  15. //目标方法之后执行

  16. return resultVal;

  17. }

  18. }

配置通知时需实现org.springframework.aop包下的一些接口

  • 前置通知:MethodBeforeAdvice
  • 后置通知:AfterReturningAdvice
  • 环绕通知:MethodInterceptor
  • 异常通知:ThrowsAdvice

创建被代理对象

 
  1. <bean id="orderServiceBean" class="com.apesource.service.impl.OrderServiceImpl"/>

  2. <bean id="userServiceBean" class="com.apesource.service.impl.UserServiceImpl"/>

通知(Advice)

 
  1. <bean id="logAdviceBean" class="com.apesource.log.LogAdvice"/>

  2. <bean id="performanceAdviceBean" class="com.apesource.log.PerformanceAdvice"/>

切入点(Pointcut):通过正则表达式描述指定切入点(某些 指定方法)

 
  1. <bean id="createMethodPointcutBean" class="org.springframework.aop.support.JdkRegexpMethodPointcut">

  2. <!--注入正则表达式:描述那些方法为切入点-->

  3. <property name="pattern" value=".*creat.*"/>

  4. </bean>

Advisor(高级通知) = Advice(通知) + Pointcut(切入点)

 
  1. <bean id="performanceAdvisorBean" class="org.springframework.aop.support.DefaultPointcutAdvisor">

  2. <!--注入切入点-->

  3. <property name="pointcut" ref="createMethodPointcutBean"/>

  4. <!--注入通知-->

  5. <property name="advice" ref="performanceAdviceBean"/>

  6. </bean>

创建自动代理

 
  1. <bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">

  2. <!--Bean名称规则(数组):指定那些bean创建自动代理-->

  3. <property name="beanNames">

  4. <list>

  5. <value>*ServiceBean</value>

  6. <value>*TaskBean</value>

  7. </list>

  8. </property>

  9. <!--通知列表:需要执行那些通知-->

  10. <property name="interceptorNames">

  11. <list>

  12. <value>logAdviceBean</value>

  13. <value>performanceAdvisorBean</value>

  14. </list>

  15. </property>

  16. </bean>

方式2:使用Aspectj实现切面(普通POJO的实现方式)

导入Aspectj相关依赖

 
  1. <!--aop依赖1:aspectjrt -->

  2. <dependency>

  3. <groupId>org.aspectj</groupId>

  4. <artifactId>aspectjrt</artifactId>

  5. <version>1.9.5</version>

  6. </dependency>

  7. <!--aop依赖2: aspectjweaver -->

  8. <dependency>

  9. <groupId>org.aspectj</groupId>

  10. <artifactId>aspectjweaver</artifactId>

  11. <version>1.9.5</version>

  12. </dependency>

通知方法名随便起,没有限制

 
  1. public class LogAspectj {

  2. //前置通知

  3. public void beforeAdvice(JoinPoint joinPoint){

  4. System.out.println("========== 【Aspectj前置通知】 ==========");

  5. }

  6. //后置通知:方法正常执行后,有返回值,执行该后置通知:如果该方法执行出现异常,则不执行该后置通知

  7. public void afterReturningAdvice(JoinPoint joinPoint,Object returnVal){

  8. System.out.println("========== 【Aspectj后置通知】 ==========");

  9. }

  10. public void afterAdvice(JoinPoint joinPoint){

  11. System.out.println("========== 【Aspectj后置通知】 ==========");

  12. }

  13. //环绕通知

  14. public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {

  15. System.out.println("##########【环绕通知中的前置通知】##########");

  16. Object returnVale = joinPoint.proceed();

  17. System.out.println("##########【环绕通知中的后置通知】##########");

  18. return returnVale;

  19. }

  20. /**

  21. * 异常通知:方法出现异常时,执行该通知

  22. */

  23. public void throwAdvice(JoinPoint joinPoint, Exception ex){

  24. System.out.println("出现异常:" + ex.getMessage());

  25. }

  26. }

使用Aspectj实现切面,使用Spring AOP进行配置

 
  1. <!--业务组件bean-->

  2. <bean id="userServiceBean" class="com.apesource.service.impl.UserServiceImpl"/>

  3. <!--日志Aspect切面-->

  4. <bean id="logAspectjBean" class="com.apesource.log.LogAspectj"/>

  5. <!--使用Aspectj实现切面,使用Spring AOP进行配置-->

  6. <aop:config>

  7. <!--配置切面-->

  8. <!--注入切面bean-->

  9. <aop:aspect ref="logAspectjBean">

  10. <!--定义Pointcut:通过expression表达式,来查找 特定的方法(pointcut)-->

  11. <aop:pointcut id="pointcut"

  12. expression="execution(* com.apesource.service.impl.*.create*(..))"/>

  13. <!--配置"前置通知"-->

  14. <!--在pointcut切入点(serviceMethodPointcut)查找到 的方法执行"前",

  15. 来执行当前logAspectBean的doBefore-->

  16. <aop:before method="beforeAdvice" pointcut-ref="pointcut"/>

  17. <!--配置“后置通知”-->

  18. <!--returning属性:配置当前方法中用来接收返回值的参数名-->

  19. <aop:after-returning returning="returnVal"

  20. method="afterReturningAdvice" pointcut-ref="pointcut"/>

  21. <aop:after method="afterAdvice" pointcut-ref="pointcut"/>

  22. <!--配置"环绕通知"-->

  23. <aop:around method="aroundAdvice" pointcut-ref="pointcut"/>

  24. <!--配置“异常通知”-->

  25. <!--throwing属性:配置当前方法中用来接收当前异常的参数名-->

  26. <aop:after-throwing throwing="ex" method="throwAdvice" pointcut-ref="pointcut"/>

  27. </aop:aspect>

  28. </aop:config>

方式3:使用Aspectj实现切面(基于注解的实现方式)

 
  1. //声明当前类为Aspect切面,并交给Spring容器管理

  2. @Component

  3. @Aspect

  4. public class LogAnnotationAspectj {

  5. private final static String EXPRESSION =

  6. "execution(* com.apesource.service.impl.*.create*(..))";

  7. //前置通知

  8. @Before(EXPRESSION)

  9. public void beforeAdvice(JoinPoint joinPoint){

  10. System.out.println("========== 【Aspectj前置通知】 ==========");

  11. }

  12. //后置通知:方法正常执行后,有返回值,执行该后置通知:如果该方法执行出现异常,则不执行该后置通知

  13. @AfterReturning(value = EXPRESSION,returning = "returnVal")

  14. public void afterReturningAdvice(JoinPoint joinPoint,Object returnVal){

  15. System.out.println("========== 【Aspectj后置通知】 ==========");

  16. }

  17. //后置通知

  18. @After(EXPRESSION)

  19. public void afterAdvice(JoinPoint joinPoint){

  20. System.out.println("========== 【Aspectj后置通知】 ==========");

  21. }

  22. //环绕通知

  23. @Around(EXPRESSION)

  24. public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {

  25. System.out.println("##########【环绕通知中的前置通知】##########");

  26. Object returnVale = joinPoint.proceed();

  27. System.out.println("##########【环绕通知中的后置通知】##########");

  28. return returnVale;

  29. }

  30. // 异常通知:方法出现异常时,执行该通知

  31. @AfterThrowing(value = EXPRESSION,throwing = "ex")

  32. public void throwAdvice(JoinPoint joinPoint, Exception ex){

  33. System.out.println("********** 【Aspectj异常通知】执行开始 **********");

  34. System.out.println("出现异常:" + ex.getMessage());

  35. System.out.println("********** 【Aspectj异常通知】执行结束 **********");

  36. }

  37. }

 
  1. <!-- 自动扫描器 -->

  2. <context:component-scan base-package="com.apesource"/>

  3. <!--配置Aspectj的自动代理-->

  4. <aop:aspectj-autoproxy/>

Spring AOP的实现原理

Spring的AOP实现原理其实很简单,就是通过动态代理实现的。

Spring AOP 采用了两种混合的实现方式:JDK 动态代理和 CGLib 动态代理。

  • JDK动态代理:Spring AOP的首选方法。 每当目标对象实现一个接口时,就会使用JDK动态代理。目标对象必须实现接口
  • CGLIB代理:如果目标对象没有实现接口,则可以使用CGLIB代理。

JDK动态代理

Spring默认使用JDK的动态代理实现AOP,类如果实现了接口,Spring就会使用这种方式实现动态代理。

JDK实现动态代理需要两个组件,首先第一个就是InvocationHandler接口。

我们在使用JDK的动态代理时,需要编写一个类,去实现这个接口,然后重写invoke方法,这个方法其实就是我们提供的代理方法。

如下源码所示:

 
  1. /**

  2. * 动态代理

  3. *

  4. * @author mikechen

  5. */

  6. public class JdkProxySubject implements InvocationHandler {

  7. private Subject subject;

  8. public JdkProxySubject(Subject subject) {

  9. this.subject = subject;

  10. }

  11. @Override

  12. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

  13. System.out.println("before 前置通知");

  14. Object result = null;

  15. try {

  16. result = method.invoke(subject, args);

  17. }catch (Exception ex) {

  18. System.out.println("ex: " + ex.getMessage());

  19. throw ex;

  20. }finally {

  21. System.out.println("after 后置通知");

  22. }

  23. return result;

  24. }

  25. }

然后JDK动态代理需要使用的第二个组件就是Proxy这个类,我们可以通过这个类的newProxyInstance方法,返回一个代理对象。

生成的代理类实现了原来那个类的所有接口,并对接口的方法进行了代理,我们通过代理对象调用这些方法时,底层将通过反射,调用我们实现的invoke方法。

 
  1. public class Main { public static void main(String[] args) {

  2. //获取InvocationHandler对象 在构造方法中注入目标对象

  3. InvocationHandler handler = new JdkProxySubject(new RealSubject());

  4. //获取代理类对象

  5. Subject proxySubject = (Subject)Proxy.newProxyInstance(Main.class.getClassLoader(), new Class[]{Subject.class}, handler);

  6. //调用目标方法

  7. proxySubject.request(); proxySubject.response();

  8. }

运行结果:

 
  1. before 前置通知

  2. 执行目标对象的request方法......

  3. after 后置通知

  4. before 前置通知

  5. 执行目标对象的response方法......

  6. after 后置通知

JDK动态代理优缺

优点

JDK动态代理是JDK原生的,不需要任何依赖即可使用;

通过反射机制生成代理类的速度要比CGLib操作字节码生成代理类的速度更快;

缺点

如果要使用JDK动态代理,被代理的类必须实现了接口,否则无法代理;

JDK动态代理无法为没有在接口中定义的方法实现代理,假设我们有一个实现了接口的类,我们为它的一个不属于接口中的方法配置了切面,Spring仍然会使用JDK的动态代理,但是由于配置了切面的方法不属于接口,为这个方法配置的切面将不会被织入。

JDK动态代理执行代理方法时,需要通过反射机制进行回调,此时方法执行的效率比较低;

CGLib代理

CGLIB组成结构

Cglib是一个强大的、高性能的代码生成包,它广泛被许多AOP框架使用,为他们提供方法的拦截,如下图所示Cglib与Spring等应用的关系:

  • 最底层的是字节码 Bytecode ,字节码是Java为了保证“一次编译、到处运行”而产生的一种虚拟指令格式,例如iload_0、iconst_1、if_icmpne、dup等
  • 位于字节码之上的是 ASM ,这是一种直接操作字节码的框架,应用ASM需要对Java字节码、Class结构比较熟悉
  • 位于 ASM 之上的是 CGLIB 、 Groovy 、 BeanShell ,后两种并不是Java体系中的内容而是脚本语言,它们通过ASM框架生成字节码变相执行Java代码,这说明 在JVM中执行程序并不一定非要写Java代码----只要你能生成Java字节码,JVM并不关心字节码的来源 ,当然通过Java代码生成的JVM字节码是通过编译器直接生成的,算是最“正统”的JVM字节码
  • 位于 CGLIB 、 Groovy 、 BeanShell 之上的就是 Hibernate 、 Spring AOP 这些框架了,这一层大家都比较熟悉
  • 最上层的是Applications,即具体应用,一般都是一个Web项目或者本地跑一个程序

所以,Cglib的实现是在字节码的基础上的,并且使用了开源的ASM读取字节码,对类实现增强功能的。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Spring AOPSpring框架中的一个重要模块,它通过动态代理实现了面向切面编程的思想。下面是Spring AOP的XML配置详解: 1. 配置命名空间和约束 在使用Spring AOP之前,需要在XML配置文件中声明Spring AOP的命名空间和约束,如下所示: ```xml <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"> ``` 2. 配置切面 在Spring AOP中,切面由切点和通知组成。切点定义了在哪些方法上进行拦截,通知定义了在拦截时要执行的逻辑。下面是一个切面的配置示例: ```xml <bean id="myAspect" class="com.example.MyAspect"> <property name="transactionManager" ref="txManager"/> </bean> <aop:config> <aop:aspect ref="myAspect"> <aop:pointcut id="serviceMethods" expression="execution(* com.example.Service.*(..))"/> <aop:before pointcut-ref="serviceMethods" method="beginTransaction"/> <aop:after-returning pointcut-ref="serviceMethods" method="commitTransaction"/> <aop:after-throwing pointcut-ref="serviceMethods" method="rollbackTransaction"/> </aop:aspect> </aop:config> ``` 上面的例子中,声明了一个名为“myAspect”的切面,并定义了一个名为“serviceMethods”的切点,拦截com.example.Service包中所有方法的执行。在拦截时,分别执行了beginTransaction()、commitTransaction()和rollbackTransaction()方法。 3. 配置通知 通知是切面中的一个组成部分,它定义了在什么时候执行切面的逻辑。Spring AOP支持五种类型的通知:前置通知、后置通知、返回通知、异常通知和环绕通知。下面是通知的配置示例: ```xml <aop:before pointcut="execution(* com.example.Service.*(..))" method="beforeAdvice"/> <aop:after-returning pointcut="execution(* com.example.Service.*(..))" method="afterReturningAdvice"/> <aop:after-throwing pointcut="execution(* com.example.Service.*(..))" method="afterThrowingAdvice"/> <aop:around pointcut="execution(* com.example.Service.*(..))" method="aroundAdvice"/> ``` 上面的例子中,分别配置了前置通知、返回通知、异常通知和环绕通知,它们都拦截com.example.Service包中的所有方法。在拦截时,分别执行了beforeAdvice()、afterReturningAdvice()、afterThrowingAdvice()和aroundAdvice()方法。 4. 配置引入 引入是Spring AOP中的一个特殊功能,它允许将额外的方法和属性添加到现有的类中,而不需要修改原始类的代码。下面是引入的配置示例: ```xml <aop:config> <aop:aspect ref="myAspect"> <aop:declare-parents types-matching="com.example.Service+" implement-interface="com.example.Transactional" default-impl="com.example.TransactionalImpl"/> </aop:aspect> </aop:config> ``` 上面的例子中,声明了一个名为“myAspect”的切面,并引入了接口com.example.Transactional,将其实现类指定为com.example.TransactionalImpl。这样,在运行时,com.example.Service类就自动实现了com.example.Transactional接口。 以上就是Spring AOP的XML配置详解。在实际开发中,可以根据业务需求和实际情况选择合适的配置方式来实现切面编程。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wy971744839

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值