一.spring的aop的特点
Spring就是一个轻量级的控制反转(IOC)和面向切面(AOP)的容器框架 。 上一篇已经讲解了IoC控制反转的特点,今天我们来了解面向切面AOP的世界吧!!
1.1 aop的简介
Spring的AOP(面向切面编程)可以理解为在编程中,我们常常会有一些通用的功能需求,比如记录日志、事务管理、权限验证等一些非业务核心代码。这些功能可能会散落在很多代码中,如果每次都手动去添加这些功能,不仅麻烦还容易出错。而AOP就是为了解决这个问题而产生的。
1. AOP的第一个特点是它能够将这些通用功能从业务逻辑中分离出来,形成一个独立的模块,我们把这个模块称为切面(Aspect)。切面里面包含了要执行的通用功能代码。
2. 松耦合:AOP通过在运行时动态地将切面织入到目标对象中,实现了对目标对象的横向切面扩展。这种方式不需要修改目标对象的代码,只需要在配置文件中指定切点和切面的关系,从而实现了目标对象和切面之间的松耦合。
3. 代码复用:AOP将通用功能封装在切面中,可以在多个目标对象中复用同一个切面,避免了代码的重复编写,提高了代码的复用性。
4. 面向切面的编程:AOP允许我们通过切面来对多个目标对象的多个方法进行统一的管理。我们可以在切面中定义通用的功能,并通过切点来指定目标对象的哪些方法需要应用这个通用功能。
总的来说,AOP的特点包括将通用功能从业务逻辑中分离出来、松耦合、代码复用和面向切面的编程。这些特点使得AOP成为了一个重要的编程范式,可以提高代码的可维护性、可扩展性和开发效率。
1.2 举例
比如:当我们大家伙合伙做一个线上书城的项目,如果其中有个贪心的人使用系统,上架了一本明令禁止售卖的书,然后被人举报了,但是这个时候贪心的人下架了该书,而那个人又没有截图,证据,那是不是就发现不了,再者如果客户已经在该系统中下单,但是,客户付了钱之后,那个贪心的人想白嫖将该订单删除,那是不是就没有记录,客户也找不到投诉,那么这个时候,我们就要在系统添加日志文件,记录每一次的交易,上架,下架的数据,那么是不是就可以被记录,而spring的aop就是这样的来完成这些,除必要的增删改之外的技术代码,非业务的核心代码
二.spring的aop的专业术语
之前我们的项目代码但是从上往下依次执行,而现在加入了面向切面的思想,所以当我们的代码执行到目标对象是,查看连接点是否有前置通知,先执行前置通知,再执行目标方法,如果没有前置通知,那么就直接执行目标方法,最后看连接点上是否有后置通知,如果有,就再执行后置通知,如果没有就执行完了
- 连接点(Joinpoint):程序执行过程中明确的点,如方法的调用,或者异常的抛出.
- 目标(Target):被通知(被代理)的对象,就是完成具体的业务逻辑 ,比如书籍的增删改查
- 通知(Advice):在某个特定的连接点上执行的动作,同时Advice也是程序代码的具体实现,例如一个实现日志记录的代码(通知有些书上也称为处理) ,完成切面编程,非业务核心代码
- 代理(Proxy):将通知应用到目标对象后创建的对象(代理=目标+通知), 例子:外科医生+护士只有代理对象才有AOP功能,而AOP的代码是写在通知的方法里面的
- 切入点(Pointcut):多个连接点的集合,定义了通知应该应用到那些连接点 , (也将Pointcut理解成一个条件 ,此条件决定了容器在什么情况下将通知和目标组合成代理返回给外部程序),比如给新增方法添加日志功能
- 适配器(Advisor):适配器=通知(Advice)+切入点(Pointcut)
注:目标对象只负责业务逻辑代码
通知对象负责AOP代码,这二个对象都没有AOP的功能,只有代理对象才有
三.论证模拟
3.1.前置通知
首先,我们先写service接口和实现类进行模拟,在里面写两个方法
package com.zking.aop.service; /** * @author yinzi * @create 2023-08-17 14:52 */ public interface IBookService { // 购书 public boolean buy(String userName, String bookName, Double price); // 发表书评 public void comment(String userName, String comments); }
然后,写实现类,重新这两个方法,并且做了一个价格的判断
package com.zking.aop.service; import com.zking.exception.PriceException; /** * @author yinzi * @create 2023-08-17 14:54 */ public class BookServiceImpl implements IBookService{ public BookServiceImpl() { 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 + " 买了 " + bookName + ", 花了 " + price); return true; } public void comment(String userName, String comments) { // 通过控制台的输出方式模拟发表书评 System.out.println(userName + "评论:" + comments); } }
接下来要写上面价格判断的异常
package com.zking.exception; public class PriceException extends RuntimeException { public PriceException() { super(); } public PriceException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { super(message, cause, enableSuppression, writableStackTrace); } public PriceException(String message, Throwable cause) { super(message, cause); } public PriceException(String message) { super(message); } public PriceException(Throwable cause) { super(cause); } }
然后,我们先创建一个类,将类名.方法名,携带的参数,作为日志存储到数据库。
package com.zking.aop.advice; import java.lang.reflect.Method; import java.util.Arrays; import org.springframework.aop.MethodBeforeAdvice; /** * 调用项目中的某一接口实现类的方式时,将类名.方法名,携带的参数,作为日志存储到数据库。 * @author yinzi * */ public class MyMethodBeforeAdvice implements MethodBeforeAdvice { public void before(Method method, Object[] args, Object target) throws Throwable { String targetName = target.getClass().getName();//方法类 String methodName = method.getName();//方法的名字 String params = Arrays.toString(args);//方法参数 String msg = "【系统日志】:前置日志->"+targetName+"."+methodName+",携带的参数:"+params; System.out.println(msg); } }
最后,我们进行一个配置
前台论证:
3.2.后置通知
有了前面的铺垫,现在我们直接再创建一个后置通知的类,比起前置通知,多了一个参数,就是返回参数
package com.zking.aop.advice; import org.springframework.aop.AfterReturningAdvice; import java.lang.reflect.Method; import java.util.Arrays; /** * @author yinzi * @create 2023-08-17 15:51 */ public class MyAfterReturningAdvice implements AfterReturningAdvice { public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable { String targetName = target.getClass().getName(); String methodName = method.getName(); String params = Arrays.toString(args); String msg = "【系统日志】:后置日志->" + targetName + "." + methodName + ",携带的参数:" + params + ";目标对象所调用的方法的返回值:" + returnValue; System.out.println(msg); } }
其次,配置文件即可
最后,前台看结果:
3.3.环绕通知
就是结合了前置通知和后置通知,它两个都有所以一般常用这个,
它只有一个参数,但是这一个参数相当于上面前置通知和后置通知的3,4个参数
package com.zking.aop.advice; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; import java.lang.reflect.Method; import java.util.Arrays; /** * 环绕通知 * @author Administrator * */ public class MyMethodInterceptor implements MethodInterceptor { public Object invoke(MethodInvocation invocation) throws Throwable { Object target = invocation.getThis(); Method method = invocation.getMethod(); Object[] args = invocation.getArguments(); String targetName = target.getClass().getName(); String methodName = method.getName(); String params = Arrays.toString(args); String msg = "【环绕通知】:正在调用->" + targetName + "." + methodName + ",携带的参数:" + params; System.out.println(msg); Object returnValue = invocation.proceed(); String msg2 = "【环绕通知】:目标对象所调用的方法的返回值:" + returnValue; System.out.println(msg2); return returnValue; } }
然后就是配置文件
最后前台测试:
3.4.异常通知
老样子,先建一个类,但是注意,这个异常通知的类,重写的话,方法名字只能是这个,否则报错
然后就是配置文件
最后前台测试;
因为前面那个异常的类,及service类做了价格的判断
3.5.过滤通知
过滤通知就是那个适配器,它不需要再建一个类,直接再配置文件里面配置就可以了,需要正则判断,这里举例过滤的是后置通知
前台结果: