1.AOP是什么?
- 面向切面编程(Aspect-Oriented Programming)是一种编程范式,它的主要目的是通过预编译和运行期动态代理实现程序功能的横切(cross-cutting)特性,如日志记录、性能统计、事务监控等。它可以帮助开发者将这些原本分散在各个方法或类中的业务逻辑抽象出来,提高代码复用性,降低耦合度
- 面向切面编程的核心思想是将程序分为两个部分:切入点和切面。切入点(entry point)是程序执行过程中需要被拦截的代码段,而切面(weaving)则是实现横切功能的代码段。在运行时,通过动态代理技术,将切入点的代码交由切面织入,实现横切的效果
- 面向切面编程是希望能够将通用需求功能从不相关的类当中分离出来,能够使得很多类共享一个行为,一旦发生变化,不必修改很多类,而只是修改这个行为即可
- AOP通过提供另一种思考程序结构的方式来补充了面向对象编程(OOP)。OOP中模块化的基本单元是类(class),而AOP中模块化的基本单元是切面(aspect)。可以这么理解,OOP是解决了纵向的代码复用问题,AOP是解决了横向的代码复用问题.
2.案列:
场景模拟:这里我模拟的场景为线上书城系统
首先进行一个没有使用Aop的模拟代码展示:
实体类:Book:其中是书籍的属性,行为等 Dao类:BookDao:其中是有关于书籍操作的代码 业务逻辑层:BookBiz:...,BookBizImpl:... Web层:new 对象 //增加 public int add(Book book) { b.add() } //修改 public int edit(Book book) { b.edit() } //删除 public int del(Book book) { b.del() } //上架 public int up(Book book) { b.up() } //下架 public int down(Book book) { b.down() }
但是很多时候会出现这样的情况:
①用户购买了书籍,却说自己没有收到货物...
②店家发出的货物为空货物,而其却为了牟利,矢口否认...
针对这样的情况,很多时候都没有证据来证明到底是真是假,而在我们成熟的系统中,通常都会添加一个叫做‘日志记录’的东西,它可以用来记录和跟踪系统、应用程序或事件的活动和状态的过程,通俗来说就是使用这个系统的用户的每一步操作都会被记录下来,这样就话就成为一个证据:那成熟的系统应该是怎么样?
实体类:Book:其中是书籍的属性,行为等 Dao类:BookDao:其中是有关于书籍操作的代码 业务逻辑层:BookBiz:...,BookBizImpl:... Web层:new对象 //增加 public int add(Book book) { b.add() } //修改 public int edit(Book book) { b.edit() } //删除 public int del(Book book) { b.del() } //上架 public int up(Book book) { datetime=..//操作时的时间 username=...//操作的用户名 args = ...//参数 logBiz.add(datetime,username,args);//将其都添加到日志中去 b.up() } //下架 public int down(Book book) { datetime=..//操作时的时间 username=...//操作的用户名 args = ...//参数 logBiz.add(datetime,username,args);//将其都添加到日志中去 b.down() }
现在是在上架和下架中添加了日志记录,如果要在其他的方法操作中,也添加日志记录的话,那就需要将这一段代码再重复几次,这样就有点麻烦,而且还改变了原有代码的结构,如果需求发生改变,需要对打印的日志内容作出修改,那就必须修改用到了日志记录方法中的所有相关代码,如果是1000个方法呢?每次就需要手动去修改1000个方法中的代码,对项目的维护成本就会很高这也不利于我们的系统维护。然后我们可以用到AOP可以帮助开发者将将原本分散在各个方法或类中的业务逻辑抽象出来,提高代码复用性,降低耦合度,接下怎么提高代码的复用性:
3.spring的aop的专业术语
项目代码但是从上往下依次执行,而现在加入了面向切面的思想,当我们的代码执行到目标对象是,查看连接点是否有前置通知,先执行前置通知,再执行目标方法,如果没有前置通知,那么就直接执行目标方法,最后看连接点上是否有后置通知,如果有,就再执行后置通知,如果没有就执行完了。
- 连接点(Joinpoint):程序执行过程中明确的点,如方法的调用,或者异常的抛出.
- 目标(Target):被通知(被代理)的对象,就是完成具体的业务逻辑 ,比如书籍的增删改查
- 通知(Advice):在某个特定的连接点上执行的动作,同时Advice也是程序代码的具体实现,例如一个实现日志记录的代码(通知有些书上也称为处理) ,完成切面编程,非业务核心代码
- 代理(Proxy):将通知应用到目标对象后创建的对象(代理=目标+通知), 例子:外科医生+护士只有代理对象才有AOP功能,而AOP的代码是写在通知的方法里面的
- 切入点(Pointcut):多个连接点的集合,定义了通知应该应用到那些连接点 , (也将Pointcut理解成一个条件 ,此条件决定了容器在什么情况下将通知和目标组合成代理返回给外部程序),比如给新增方法添加日志功能
- 适配器(Advisor):适配器=通知(Advice)+切入点(Pointcut)
注:目标对象只负责业务逻辑代码
通知对象负责AOP代码,这二个对象都没有AOP的功能,只有代理对象才有。
4.代码模拟
4.1 前置通知
首先,我们先写service接口和实现类进行模拟,在里面写两个方法
package com.sy.aop.biz; public interface IBookBiz { // 购书 public boolean buy(String userName, String bookName, Double price); // 发表书评 public void comment(String userName, String comments); }
然后,写实现类,重新这两个方法,并且做了一个价格的判断
package com.sy.aop.biz.impl; import com.sy.aop.biz.IBookBiz; import com.sy.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); } }
接下来要写上面价格判断的异常
package com.sy.aop.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.sy.aop.advice; import org.springframework.aop.MethodBeforeAdvice; import java.lang.reflect.Method; import java.util.Arrays; /** * 买书、评论前加系统日志 * @author shenyan * */ 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+")被调用了"); } }
最后,进行一个配置
!--aop--> <!-- 目标对象 --> <bean class="com.sy.aop.biz.impl.BookBizImpl" id="bookBiz"></bean> <!-- 通知 --> <bean class="com.sy.aop.advice.MyMethodBeforeAdvice" id="methodBeforeAdvice"></bean> <!-- 代理=目标+通知 --> <bean class="org.springframework.aop.framework.ProxyFactoryBean" id="bookProxy"> <property name="target" ref="bookBiz"></property> <property name="proxyInterfaces"> <list> <value>com.sy.aop.biz.IBookBiz</value> </list> </property> <property name="interceptorNames"> <list> <value>myMethodBeforeAdvice</value> </list> </property> </bean>
前台的一个验证:
package com.sy.aop.demo; import com.sy.aop.biz.IBookBiz; import org.springframework.context.support.ClassPathXmlApplicationContext; /** * @author 谌艳 * @site www.shenyan.com * @create 2023-08-17 21:13 */ public class demo1 { public static void main (String [] args){ ClassPathXmlApplicationContext context=new ClassPathXmlApplicationContext("spring-context.xml"); IBookBiz bookBiz=(IBookBiz) context.getBean("bookBiz"); bookBiz.buy("花花","天下掉下一个林妹妹",18.88); bookBiz.comment("嘿嘿","嘿嘿嘿真好看"); } }
结果为展示:
3.2.后置通知
有了前面的铺垫,直接再创建一个后置通知的类,比起前置通知,多了一个参数,就是返回参数
package com.sy.aop.advice; import org.springframework.aop.MethodBeforeAdvice; import java.lang.reflect.Method; import java.util.Arrays; /** * 买书、评论前加系统日志 * @author shenyan * */ 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+")被调用了"); } }
接着,配置文件即可
然后前台看结果;
package com.sy.aop.demo; import com.sy.aop.biz.IBookBiz; import org.springframework.context.support.ClassPathXmlApplicationContext; /** * @author 谌艳 * @site www.shenyan.com * @create 2023-08-17 21:13 */ public class demo1 { 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("花花","天下掉下一个林妹妹",18.88); bookBiz.comment("嘿嘿","嘿嘿嘿真好看"); } }
3.3.环绕通知
结合了前置通知和后置通知,它两个都有所以一般常用这个,
它只有一个参数,但是这一个参数相当于上面前置通知和后置通知的3,4个参数
package com.sy.aop.advice; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; import java.util.Arrays; /** * 环绕通知 * 包含了前置和后置通知 * * @author shenyan * */ 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.4.异常通知
先建一个类,但是注意,这个异常通知的类,重写的话,方法名字只能是这个,否则报错
然后配置文件:
然后在前台测试:
结果展示:
3.5.过滤通知
过滤通知就是那个适配器,它不需要再建一个类,直接再配置文件里面配置就可以了,需要正则判断,这里举例过滤的是后置通知
结果展示:
今天小编的分享就结束呐,生活总是需要不断去学习新的知识,多想想然后再去实操,持之以恒,经验和思维都会发生转变,我们要保持谦虚学习和自信的态度,各位加油!