一.aop简介
AOP(Aspect-Oriented Programming)是Spring框架的一个重要特性,它通过将横切关注点(cross-cutting concerns)从核心业务逻辑中分离出来,以模块化的方式在整个应用程序中重复使用。以下是关于AOP的简介及其特点:
简介:
- AOP是一种编程范式,它通过将横切关注点切割出来,将其模块化,并将其应用于多个类和模块,以提高代码的重用性和可维护性。
- 横切关注点是指与核心业务逻辑无关但存在于多个类或模块中的非功能性需求,例如日志记录、性能监控、事务管理等。
特点:
- 模块化:AOP允许将横切关注点从核心业务逻辑中提取出来,形成独立的切面(Aspect),使得关注点的逻辑可以独立于各个模块。
- 解耦:AOP通过解耦横切关注点与核心业务逻辑,使得它们可以独立演化和变化,提高了模块之间的松耦合程度。
- 重用性:AOP允许将切面应用于多个类和模块,从而实现了关注点的重用,避免了代码的重复编写。
- 可维护性:将横切关注点抽象为切面后,使得代码结构更清晰,易于理解和维护。
- 动态性:AOP可以在运行时动态地将切面应用到目标对象上,而不需要修改目标对象的源代码,增强了系统的灵活性和可扩展性。
- 多样性:Spring框架支持不同类型的切面编程,包括基于代理的AOP和基于字节码增强的AOP。这样可以选择最适合应用程序需求的AOP实现方式。
在Spring框架中,AOP的实现采用了代理模式和动态代理技术。Spring提供了多种AOP的实现方式,包括基于XML配置的AOP、基于注解的AOP和基于纯Java配置的AOP(JavaConfig)等,开发者可以根据具体需求选择适合的方式来配置和使用AOP。
二.aop中的专业术语
切面(Aspect):切面是一个模块化单元,用于解耦和封装横切关注点的逻辑。切面由切点和通知组成,它定义了何时在何处应用通知。
切点(Pointcut):切点定义了在应用程序中哪些位置应该应用通知。它通过表达式或模式匹配规则来指定需要被拦截的连接点(Join Point)。
连接点(Join Point):连接点是在应用程序执行过程中可以插入切面的特定点。例如方法执行、异常抛出、字段访问等都可以是连接点。
通知(Advice):通知是在切面的特定连接点上执行的动作。在Spring AOP中,有以下几种通知类型:
- 前置通知(Before Advice):在连接点之前执行的通知。
- 后置通知(After Advice):在连接点之后执行的通知。
- 返回通知(After Returning Advice):在连接点成功完成后执行的通知。
- 异常通知(After Throwing Advice):在连接点发生异常时执行的通知。
- 环绕通知(Around Advice):在连接点前后都执行的通知,可以控制方法的执行。
引入(Introduction):引入是一种给现有类添加新方法或属性的方式。它允许在不修改原始类代码的情况下,向类添加新功能。
织入(Weaving):织入是将切面应用到目标对象上以创建新的代理对象的过程。在Spring AOP中,织入可以在编译时、类加载时或运行时进行。
目标对象(Target Object):目标对象是被切面通知的真实对象。它是应用程序中具体业务逻辑的实现。
异常处理(Exception Handling):异常处理是在连接点发生异常时,切面执行的特定动作。它可以捕获和处理异常,提供错误处理或回滚事务等功能。
三.案例演示
1.前置通知
1.1 先准备接口
package com.YU.aop.biz; public interface IBookBiz { // 购书 public boolean buy(String userName, String bookName, Double price); // 发表书评 public void comment(String userName, String comments); }
1.2然后再准备好实现类
package com.YU.aop.biz.impl; import com.YU.aop.biz.IBookBiz; import com.YU.aop.exception.PriceException; public class BookBizImpl implements IBookBiz { public BookBizImpl() { super(); } public boolean buy(String userName, String bookName, Double price) { // 通过控制台的输出方式模拟购书 if (null == price || price <= 0) { throw new PriceException("book price exception"); } System.out.println(userName + " buy " + bookName + ", spend " + price); return true; } public void comment(String userName, String comments) { // 通过控制台的输出方式模拟发表书评 System.out.println(userName + " say:" + comments); } }
1.3对我们的目标对象进行JavaBean配置
<!--目标对象--> <bean class="com.YU.aop.biz.impl.BookBizImpl" id="bookBiz"></bean>
1.4 编写前置系统日志通知
package com.YU.aop.advice; import java.lang.reflect.Method; import java.util.Arrays; import org.springframework.aop.MethodBeforeAdvice; /** * 买书、评论前加系统日志 * @author YU * */ public class MyMethodBeforeAdvice implements MethodBeforeAdvice { @Override public void before(Method arg0, Object[] arg1, Object arg2) throws Throwable { // 在这里,可以获取到目标类的全路径及方法及方法参数,然后就可以将他们写到日志表里去 String target = arg2.getClass().getName(); String methodName = arg0.getName(); String args = Arrays.toString(arg1); System.out.println("【前置通知:系统日志】:"+target+"."+methodName+"("+args+")被调用了"); } }
1.5配置系统通知XML中的JavaBean
<!--通知--> <bean class="com.YU.aop.advice.MyMethodBeforeAdvice" id="myMethodBeforeAdvice"></bean>
1.6 配置代理XML中的JavaBean
<!-- 代理--> <bean class="org.springframework.aop.framework.ProxyFactoryBean" id="bookProxy"> <!-- 配置目标对象 --> <property name="target" ref="bookBiz"></property> <!-- 配置代理接口,目标对象的接口 --> <property name="proxyInterfaces"> <value>com.YU.aop.biz.IBookBiz</value> </property> <property name="interceptorNames"> <list> <value>myMethodBeforeAdvice</value> </list> </property> </bean>
1.7 测试代码开始测试
package com.YU.aop.test; import com.YU.aop.biz.IBookBiz; import org.springframework.context.support.ClassPathXmlApplicationContext; /** * @author YU * @create 2023-08-17 15:14 */ public class Test1 { public static void main(String[] args) { //建模 ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring-context.xml"); // IBookBiz bookBiz = (IBookBiz) context.getBean("bookBiz"); IBookBiz bookBiz = (IBookBiz) context.getBean("bookProxy"); bookBiz.buy("死仔","我的26岁女房客",18.8d); bookBiz.comment("死仔","真好看"); } }
测试结果:
由测试结果可得知,不仅获取到了我们的参数,同时根据方法获取到了我们的系统日志,也就是前置通知
2. 后置通知
2.1 先准备好后置通知的系统日志
package com.zking.aop.advice; import java.lang.reflect.Method; import java.util.Arrays; import org.springframework.aop.AfterReturningAdvice; /** * 买书返利 * @author Administrator * */ public class MyAfterReturningAdvice implements AfterReturningAdvice { @Override public void afterReturning(Object arg0, Method arg1, Object[] arg2, Object arg3) throws Throwable { String target = arg3.getClass().getName(); String methodName = arg1.getName(); String args = Arrays.toString(arg2); System.out.println("【后置通知:买书返利】:"+target+"."+methodName+"("+args+")被调用了,"+"该方法被调用后的返回值为:"+arg0); } }
2.2 配置后置系统通知的XML的JavaBean
<!--后置通知--> <bean class="com.YU.aop.advice.MyAfterReturningAdvice" id="myAfterReturningAdvice"></bean>
并在前面已经配置好的代理接口中添加一个value值
2.3 测试结果
由测试结果我们可以得知,后置通知永远都在方法执行后才会显示通知,与前置通知不同的是每次前面的方法调用后都会返回一个参数
3.环绕通知
3.1 环绕通知就是前置通知和后置通知的结合,在实际应用开发中,我们一般不会单独编写前置通知和后置通知,单独使用前置通知或者后置通知时,我们会使用环绕通知,将里面前置(后置)通知的功能注释,以达到单独使用的目的
3.2 环绕通知的系统日志
package com.YU.aop.advice; import java.util.Arrays; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; /** * 环绕通知 * 包含了前置和后置通知 * * @author Administrator * */ public class MyMethodInterceptor implements MethodInterceptor { @Override public Object invoke(MethodInvocation arg0) throws Throwable { String target = arg0.getThis().getClass().getName(); String methodName = arg0.getMethod().getName(); String args = Arrays.toString(arg0.getArguments()); System.out.println("【环绕通知调用前:】:"+target+"."+methodName+"("+args+")被调用了"); // arg0.proceed()就是目标对象的方法 Object proceed = arg0.proceed(); System.out.println("【环绕通知调用后:】:该方法被调用后的返回值为:"+proceed); return proceed; } }
3.3 配置环绕通知的XML的JavaBean与前置通知和后置通知一致
3.4 测试结果
由测试结果得知,环绕通知就是前置通知和后置通知的结合,优点就是不需要再多次去进行配置及编码,所以就像我们前面所说在实际开发应用中我们一般都会选择使用环绕通知
4.异常通知
4.1 异常通知的系统日志和其他系统日志不同的是,方法名为固定的afterThrowing,不能修改
package com.YU.aop.advice; import org.springframework.aop.ThrowsAdvice; import com.YU.aop.exception.PriceException; /** * 出现异常执行系统提示,然后进行处理。价格异常为例 * @author Administrator * */ public class MyThrowsAdvice implements ThrowsAdvice { public void afterThrowing(PriceException ex) { System.out.println("【异常通知】:当价格发生异常,那么执行此处代码块!!!"); } }
4.2 在我们正常程序出问题没有去配置异常通知时会出现报错,并且不会执行后面的后置通知,如以下情况
4.3 异常处理配置和前面的配置相同
4.4 当我们配置好异常通知模块时,程序出现异常时会上报日志进行提示
5.过滤通知
5.1 直接在XML中配置JavaBean
<!--过滤通知--> <bean class="org.springframework.aop.support.RegexpMethodPointcutAdvisor" id="regexpMethodPointcutAdvisor"> <property name="advice" ref="myAfterReturningAdvice"></property> <property name="pattern" value=".*buy"></property> </bean>
将图中指出部分替换成过滤通知
测试结果:
对比框中内容,在调用过buy方法后进行过滤,第二次调用时不再buy方法而是comment方法
四.总结
aop是面向切面编程,普通程序由上而下正常执行,aop的程序执行是先执行到目标对象的目标方法中,如果连接点上由前置通知,则先执行前置通知再执行目标方法,最后如果目标方法有后置通知则最后执行后置通知代码,不管是前置通知,后置通知,环绕通知,异常通知,过滤通知,代码都是非业务核心代码,如日志、事务的管理(开启、提交、回滚)