前言
上一篇讲到了什么是spring框架,spring框架是一种轻量级的控制反转(ioc)和面向切面(AOP)的容器框架,什么是ioc呢?spring中的ioc是通过依赖的注入来实现对象之间的解耦,将对象的创建和管理,交给我们的容器负责,今天我们就来讲解一下spring中的面向切面编程(AOP)
AOP(面向切面)的概念
AOP的关键词概念
在讲解AOP的概念之前,我们首先来了解一下一些关于AOP的关键词概念
- 目标对象(Target Object):目标对象是应用程序中的实际对象,它包含了核心的业务逻辑,在AOP中切面会织入到目标对象中,以提供额外的功能或者处理横切关注点
- 连接点(Join Point):连接点是指在应用执行的过程中能够插入切面的点。它可以是方法的调用、异常的抛出,字段的访问等等等等,在AOP中,连接点表示在目标对象中可以被拦截的方法
- 通知(Advice):通知是在特定的连接点执行的动作,它定义了连接点何时执行以及执行什么样的操作
- 代理对象(Proxy Object):对象代理是aop在运行的时候为了目标对象创建的对象,它包装了目标对象并且提供了额外的功能,简单来说,就是我们的目标对象+通知
- 切点(Pointcut):切点就是一组连接点的集合,它定义了在哪些连接点上的应用切面的通知
- 适配器(Advisor):适配器(Advisor)在AOP中起到了连接通知和切入点的作用,它使得通知可以在特定的连接点上被触发和执行。通过适配器,我们可以将不同的通知和切入点组合在一起,实现对目标方法的拦截和增强。
AOP中常见的通知类型
- 前置通知(Before Advice):
- 前置通知在目标方法执行之前执行。
- 它可以用于执行一些准备操作或验证逻辑,例如参数检查、权限验证等。
- 如果前置通知抛出异常,将阻止目标方法的执行。
- 后置通知(After Advice):
- 后置通知在目标方法执行之后执行(无论目标方法是否抛出异常)。
- 它可以用于执行一些清理操作、记录日志或处理返回值等。
- 后置通知不能修改目标方法的执行结果。
- 异常通知(After Throwing Advice):
- 异常通知在目标方法抛出异常后执行。
- 它可以用于处理目标方法抛出的异常,例如进行异常处理、日志记录等。
- 异常通知可以选择捕获异常并处理,也可以选择将异常继续向上抛出。
- 环绕通知(Around Advice):
- 环绕通知是最强大和最灵活的通知类型。
- 它可以在目标方法的前后自定义处理逻辑,并完全控制目标方法的执行。
- 环绕通知可以决定是否调用目标方法,可以修改目标方法的参数和返回值,还可以处理异常。
- 过滤通知(Around Advice):
- 过滤通知的概念是在目标方法执行前后插入自定义的逻辑,类似于一个过滤器,可以对目标方法进行拦截和处理。过滤通知可以完全控制目标方法的执行过程,包括修改参数、修改返回值、处理异常等。它提供了最大的灵活性和自由度,可以根据具体的需求来决定是否执行目标方法以及如何处理目标方法的执行。
- 过滤通知的使用场景包括但不限于:
- 条件性拦截:通过过滤通知可以根据特定的条件来决定是否执行目标方法。例如,可以基于某个标志位或配置项的条件判断,只有满足条件时才执行目标方法,否则可以选择跳过目标方法的执行。
- 参数校验和转换:过滤通知可以在目标方法执行前对参数进行校验和转换。例如,可以验证参数的合法性,检查参数是否符合预期的规则,或者进行类型转换,将参数从一种类型转换为另一种类型。
- 缓存和缓存失效:过滤通知可以在目标方法执行前后进行缓存操作。例如,可以在执行目标方法之前从缓存中获取结果,如果缓存中存在结果,则可以直接返回缓存的结果,避免重复执行目标方法。或者在目标方法执行后,将结果存入缓存,以便后续的请求可以直接从缓存中获取结果。
- 事务管理:过滤通知可以在目标方法执行前后进行事务管理。例如,在目标方法执行前开启事务,在目标方法执行后根据执行结果决定是提交事务还是回滚事务。通过过滤通知可以实现对事务的精确控制,确保目标方法的执行符合事务的要求。
Spring AOP的(面向切面编程)
- Spring AOP(面向切面编程)是Spring框架提供的一个重要特性。它允许开发人员通过在应用程序中定义切面(Aspect)来实现横切关注点的模块化。切面可以横跨多个对象和模块,将通用的横切逻辑与业务逻辑分离开来,从而提高代码的可维护性和可重用性。
- 在Spring AOP中,切面通过使用特定的注解或配置来定义,例如使用@Aspect注解。切面可以定义一系列的通知(Advice),包括前置通知(Before)、后置通知(After)、环绕通知(Around)、异常通知(AfterThrowing)和返回通知(AfterReturning)。这些通知可以在目标方法执行的不同阶段被触发,从而插入额外的逻辑。
- Spring AOP的工作原理是通过动态代理实现的。当应用程序中的目标对象被代理时,切面会根据定义的切点(Pointcut)来决定在何处插入通知。在运行时,Spring框架会自动创建代理对象,并将通知织入到目标方法的执行流程中。
- 通过使用Spring AOP,开发人员可以实现诸如日志记录、事务管理、性能监控等横切关注点的功能,而无需修改业务逻辑的代码。这种解耦的设计使得应用程序更加灵活、可扩展,并提高了代码的可维护性。
概念总结:Spring AOP是一种通过切面和通知来实现横切关注点的技术,它能够提高代码的模块化和可重用性,使得开发人员能够更好地关注业务逻辑的实现。
代码示例(搭配使用场景):
上面还算是比较详细的描述了关于AOP以及一些AOP关键词的相关概念,下面用代码给大家看一下AOP到底有什么作用
前置通知
biz层接口代码
package com.wenhao.aop.biz; public interface IBookBiz { // 购书 public boolean buy(String userName, String bookName, Double price); // 发表书评 public void comment(String userName, String comments); }
biz层接口实现代码
package com.wenhao.aop.biz.impl; import com.wenhao.aop.biz.IBookBiz; import com.wenhao.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("书籍价格异常"); } System.out.println(userName + " buy " + bookName + ", spend " + price); return true; } public void comment(String userName, String comments) { // 通过控制台的输出方式模拟发表书评 System.out.println(userName + " say:" + comments); } }
创建前置通知类实现 MethodBeforeAdvice
接口 重写before方法
MethodBeforeAdvice
是 Spring 框架中的一个接口,用于实现在目标方法执行之前执行的通知(Advice)逻辑。- MethodBeforeAdvice 接口定义了一个方法 before(Method method, Object[] args, Object target),当目标方法被调用之前,该方法会被执行。其中,method 参数表示目标方法的反射对象,args 参数表示目标方法的参数,target 参数表示目标方法所属的对象。
- 通过实现 MethodBeforeAdvice 接口并重写 before 方法,您可以自定义在目标方法执行之前要执行的逻辑。例如,您可以在该方法中进行日志记录、权限验证等操作。
- 在 Spring AOP 中,可以将实现了 MethodBeforeAdvice 接口的类配置为切面(Aspect),并通过切入点(Pointcut)来确定在哪些连接点上应用该通知。
package com.wenhao.aop.advice; import java.lang.reflect.Method; import java.util.Arrays; import org.springframework.aop.MethodBeforeAdvice; /** * 买书、评论前加系统日志 * @author Administrator *前置通知 */ 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 + ")被调用了"); } }
配置目标对象,代理对象,代理工厂,以及通知类型
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!--aop讲解--> <!--目标对象--> <bean class="com.wenhao.aop.biz.impl.BookBizImpl" id="bookBiz"></bean> <!--通知--> <bean class="com.wenhao.aop.advice.MyMethodBeforeAdvice" id="myMethodBeforeAdvice"></bean> <!-- 代理工厂 专门用来生产代理的--> <bean class="org.springframework.aop.framework.ProxyFactoryBean" id="bookProxy"> <!-- 配置目标对象--> <property name="target" ref="bookBiz"></property> <!-- 配置代理 目标对象实现的接口--> <property name="proxyInterfaces"> <list> <value>com.wenhao.aop.biz.IBookBiz</value> </list> </property> <!-- 配置通知--> <property name="interceptorNames"> <list> <value>myMethodBeforeAdvice</value> </list> </property> </bean> </beans>
测试
package com.wenhao.aop.demo; import com.wenhao.aop.biz.IBookBiz; import com.wenhao.aop.biz.impl.BookBizImpl; import org.springframework.context.support.ClassPathXmlApplicationContext; /** * @ 用户 liwen * @当前日期 2023/8/17 * @当前项目名称 ssm */ public class demo1 { public static void main(String[] args) { // 获取spring上下文信息 ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring-context.xml"); //调用getbean方法,通过定义的id属性拿到标签对象 IBookBiz bookBiz = (IBookBiz) context.getBean("bookProxy"); //通过标签对象调用买书方法 bookBiz.buy("文昊买书","西游记",9.9d); //调用评论的方法 bookBiz.comment("文昊","好好看"); } }
接下来让我们看看结果
我们可以看到,当文昊在做出买书的行为之前,我们的系统就将此行为的参数记录了下来,在我们做项目的时候,我们就可以以此,来做出我们的权限验证功能等等
后置通知
顾名思义,和前置通知刚好相反,biz层和测试类代码跟上面一样,这里就不做展示了
创建后置通知类实现 AfterReturningAdvice 接口 重写afterReturning方法
- 在
afterReturning
方法中,通过参数获取了以下信息:
arg0
:目标方法返回的结果。arg1
:目标方法的Method
对象,可以获取方法名、参数类型等信息。arg2
:目标方法的参数数组。arg3
:目标对象。
- 在
afterReturning
方法中,通过参数获取了目标对象的类名、目标方法的方法名以及目标方法的参数
package com.wenhao.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); } }
配置通知类型
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!--aop讲解--> <!--目标对象--> <bean class="com.wenhao.aop.biz.impl.BookBizImpl" id="bookBiz"></bean> <!--通知--> <bean class="com.wenhao.aop.advice.MyMethodBeforeAdvice" id="myMethodBeforeAdvice"></bean> <bean class="com.wenhao.aop.advice.MyAfterReturningAdvice" id="myAfterReturningAdvice" ></bean> <!-- 代理工厂 专门用来生产代理的--> <bean class="org.springframework.aop.framework.ProxyFactoryBean" id="bookProxy"> <!-- 配置目标对象--> <property name="target" ref="bookBiz"></property> <!-- 配置代理 目标对象实现的接口--> <property name="proxyInterfaces"> <list> <value>com.wenhao.aop.biz.IBookBiz</value> </list> </property> <!-- 配置通知--> <property name="interceptorNames"> <list> <!-- <value>myMethodBeforeAdvice</value>--> <value>myAfterReturningAdvice</value> </list> </property> </bean> </beans>
运行结果
大家可以看到,在执行了方法之后,系统返给了我们后置通知,并且在后置通知中,我们还可以获取该方法的返回值,由此,我们可以利用后置通知来做出商品返利,日志功能等等
环绕通知
兄弟们再次顾名思义,环绕也就是前置通知和后置通知的结合体
类 MyMethodInterceptor
,实现了 MethodInterceptor
接口。
- 在 invoke 方法中,通过 MethodInvocation 对象获取了以下信息:
- getThis():获取目标对象。
- getMethod():获取目标方法的 Method 对象,可以获取方法名、参数类型等信息。
- getArguments():获取目标方法的参数数组。
- 在 invoke 方法中,通过上述信息获取了目标对象的类名、目标方法的方法名以及目标方法的参数,并将它们打印出来。
- 在目标方法执行前,打印了一条消息表示环绕通知调用前的操作。
- 使用 arg0.proceed() 执行目标方法,即调用目标方法。
package com.wenhao.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; } }
配置通知类型
可以看到环绕通知不仅做到了前置通知的效果,也做到了后置功能的效果,如果其中有一个不想要,不写就好了
异常通知
创建ThrowsAdvice
接口的异常通知类,用于在目标方法抛出指定异常时执行特定的逻辑。
- 在类中定义了一个名为
afterThrowing
的方法,该方法接收一个PriceException
类型的参数。- 在
afterThrowing
方法中,当目标方法抛出PriceException
异常时,会执行该方法内的代码块。
package com.wenhao.aop.advice; import org.springframework.aop.ThrowsAdvice; import com.wenhao.aop.exception.PriceException; /** * 出现异常执行系统提示,然后进行处理。价格异常为例 * @author Administrator *异常通知 */ public class MyThrowsAdvice implements ThrowsAdvice { public void afterThrowing(PriceException ex) { System.out.println("【异常通知】:当价格发生异常,那么执行此处代码块!!!"); } }
配置通知类型
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!--aop讲解--> <!--目标对象--> <bean class="com.wenhao.aop.biz.impl.BookBizImpl" id="bookBiz"></bean> <!--通知--> <bean class="com.wenhao.aop.advice.MyMethodBeforeAdvice" id="myMethodBeforeAdvice"></bean> <bean class="com.wenhao.aop.advice.MyAfterReturningAdvice" id="myAfterReturningAdvice" ></bean> <bean class="com.wenhao.aop.advice.MyMethodInterceptor" id="myMethodInterceptor" ></bean> <bean class="com.wenhao.aop.advice.MyThrowsAdvice" id="myThrowsAdvice" ></bean> <!-- 代理工厂 专门用来生产代理的--> <bean class="org.springframework.aop.framework.ProxyFactoryBean" id="bookProxy"> <!-- 配置目标对象--> <property name="target" ref="bookBiz"></property> <!-- 配置代理 目标对象实现的接口--> <property name="proxyInterfaces"> <list> <value>com.wenhao.aop.biz.IBookBiz</value> </list> </property> <!-- 配置通知--> <property name="interceptorNames"> <list> <!-- <value>myMethodBeforeAdvice</value>--> <!-- <value>myAfterReturningAdvice</value>--> <!-- <value>myMethodInterceptor</value>--> <value>myThrowsAdvice</value> </list> </property> </bean> </beans>
测试类
这里要修改一个参数,让我们的异常通知捕捉到异常
package com.wenhao.aop.demo; import com.wenhao.aop.biz.IBookBiz; import com.wenhao.aop.biz.impl.BookBizImpl; import org.springframework.context.support.ClassPathXmlApplicationContext; /** * @ 用户 liwen * @当前日期 2023/8/17 * @当前项目名称 ssm */ public class demo1 { public static void main(String[] args) { // 获取spring上下文信息 ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring-context.xml"); //调用getbean方法,通过定义的id属性拿到标签对象 IBookBiz bookBiz = (IBookBiz) context.getBean("bookProxy"); //通过标签对象调用买书方法 bookBiz.buy("文昊买书","西游记",-9.9d); //调用评论的方法 bookBiz.comment("文昊","好好看"); } }
运行结果
这里我们的异常通知就会捕捉到异常,同样,我们可以通过此功能来做出异常处理,日志记录的功能
过滤通知
再讲解过滤通知的时候我们来看一个场景
我们可以看到,当文昊买书的时候返利了,但是当文昊评论的时候还是返利了,也就是说,我们在目标对象中有了两个连接点,但是显然,其中有一个我们是不需要的,否则商家必亏本,所以我们需要用到过滤通知
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!--aop讲解--> <!--目标对象--> <bean class="com.wenhao.aop.biz.impl.BookBizImpl" id="bookBiz"></bean> <!--通知--> <bean class="com.wenhao.aop.advice.MyMethodBeforeAdvice" id="myMethodBeforeAdvice"></bean> <bean class="com.wenhao.aop.advice.MyAfterReturningAdvice" id="myAfterReturningAdvice" ></bean> <bean class="com.wenhao.aop.advice.MyMethodInterceptor" id="myMethodInterceptor" ></bean> <bean class="com.wenhao.aop.advice.MyThrowsAdvice" id="myThrowsAdvice" ></bean> <!-- 过滤通知--> <bean class="org.springframework.aop.support.RegexpMethodPointcutAdvisor" id="regexpMethodPointcutAdvisor"> <!-- 过滤哪个通知--> <property name="advice" ref="myAfterReturningAdvice"></property> <!-- 过滤目标对象中的哪个方法--> <property name="pattern" value=".*comment"></property> </bean> <!-- 代理工厂 专门用来生产代理的--> <bean class="org.springframework.aop.framework.ProxyFactoryBean" id="bookProxy"> <!-- 配置目标对象--> <property name="target" ref="bookBiz"></property> <!-- 配置代理 目标对象实现的接口--> <property name="proxyInterfaces"> <list> <value>com.wenhao.aop.biz.IBookBiz</value> </list> </property> <!-- 配置通知--> <property name="interceptorNames"> <list> <!-- <value>myMethodBeforeAdvice</value>--> <!-- <value>myAfterReturningAdvice</value>--> <!-- <value>myMethodInterceptor</value>--> <!-- <value>myThrowsAdvice</value>--> <value>regexpMethodPointcutAdvisor</value> </list> </property> </bean> </beans>
将后置通知配置到过滤通知中,将过滤通知配置到代理工厂中
运行结果
我们可以看到,买书后的返利没有了,只有评论之后才有返利,这就是过滤通知的作用
总结:
Spring AOP(面向切面编程)是Spring框架提供的一个重要特性。它允许开发人员通过在应用程序中定义切面(Aspect)来实现横切关注点的模块化。切面可以横跨多个对象和模块,将通用的横切逻辑与业务逻辑分离开来,从而提高代码的可维护性和可重用性。