Welcome Huihui's Code World ! !
接下来看看由辉辉所写的关于Spring的相关操作吧
目录
Welcome Huihui's Code World ! !
回顾
一.AOP是什么
场景模拟:
面向切面:
1.专业术语
①目标对象:
②连接点:
③通知:
④代理:
⑤切入点:
⑥适配器:
2.代码演示
2.1将记录日志的代码进行封装
2.2业务逻辑层代码的修改
2.3spring-context.xml的配置
2.4测试
2.5结果
二.AOP中的各种通知
1.前置通知
2.后置通知
3.环绕通知
4.异常通知
5.过滤通知
三.AOP的优点
四.使用AOP的步骤
五.经典面试题:谈谈你对AOP的理解
在开始今天的讲解之前,我们还是来回顾一下Spring的概念吧!
▲Spring是一个开源框架,它由Rod Johnson创建。它是为了解决企业应用开发的复杂性而创建的
▲ Spring使用基本的JavaBean来完成以前只可能由EJB完成的事情
简单来说,Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器框架
在这里我就只是简单的回顾一下Spring,如果想要了解Spring的详情,可以点击这里哦
既然说到了Spring是一个轻量级的控制反转(IOC)和面向切面(AOP)的容器框架,那我们就来看看什么是面向切面!!(想要了解IOC的可以查阅上篇博客)
- 面向切面编程(Aspect-Oriented Programming)是一种编程范式,它的主要目的是通过预编译和运行期动态代理实现程序功能的横切(cross-cutting)特性,如日志记录、性能统计、事务监控等。它可以帮助开发者将这些原本分散在各个方法或类中的业务逻辑抽象出来,提高代码复用性,降低耦合度
- 面向切面编程的核心思想是将程序分为两个部分:切入点和切面。切入点(entry point)是程序执行过程中需要被拦截的代码段,而切面(weaving)则是实现横切功能的代码段。在运行时,通过动态代理技术,将切入点的代码交由切面织入,实现横切的效果
- 面向切面编程是希望能够将通用需求功能从不相关的类当中分离出来,能够使得很多类共享一个行为,一旦发生变化,不必修改很多类,而只是修改这个行为即可
- AOP通过提供另一种思考程序结构的方式来补充了面向对象编程(OOP)。OOP中模块化的基本单元是类(class),而AOP中模块化的基本单元是切面(aspect)。可以这么理解,OOP是解决了纵向的代码复用问题,AOP是解决了横向的代码复用问题
在没有使用AOP的时候,我们是怎么编写代码的呢?在这里,我给大家进行一个模拟
场景模拟:
这里我模拟的场景为线上书城系统,那我需要完成书籍的管理,我应该需要这样编写代码
//首先声明这是模拟代码!!! 实体类:Book:其中是书籍的属性,行为等 Dao类:BookDao:其中是有关于书籍操作的代码 业务逻辑层:BookBiz:...,BookBizImpl:... Web层: BookBiz b=new BookBizImpl(); //增加 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层: BookBiz b=new BookBizImpl(); //增加 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() }
我现在是在上架和下架中添加了日志记录,那如果我要在其他的方法操作中,也添加日志记录的话,我就需要将这一段代码再重复几次,而且还改变了原有代码的结构,这样听起来也是十分麻烦,一模一样的代码在多个地方出现,那如果需求发生改变,需要对打印的日志内容作出修改,那么我们就必须修改用到了日志记录方法中的所有相关代码,如果是100个方法呢?每次就需要手动去修改100个方法中的代码,对项目的维护成本就相当高这也不利于我们的系统维护。在前面说到AOP可以帮助开发者将这些原本分散在各个方法或类中的业务逻辑抽象出来,提高代码复用性,降低耦合度,那么我们便来看看它是怎么提高代码复用性的吧!!
在使用AOP之前,我们还得看看其中的专业术语
面向切面:
1.专业术语
①目标对象:
专业解释:被通知(被代理)的对象
通俗理解:在书店中,商品就是目标。每个商品都有自己的属性(比如价格、名称、库存等)和行为(比如计算促销价格、更新库存等)。收银员通过扫描商品的条形码来与商品进行交互,调用商品的方法来获取商品信息以及执行一些操作。商品本身即代表了目标
②连接点:
专业解释:程序执行过程中明确的点,如方法的调用,或者异常的抛出
通俗理解:在书店中,我们可以将顾客结账的行为看作一个连接点
③通知:
专业解释:在某个特定的连接点上执行的动作,同时Advice也是程序代码的具体实现,例如一个实现日志记录的代码(通知有些书上也称为处理)
通俗理解:
- 前置通知(Before Advice):在切入点前执行的代码,在读者购买图书之前,我们可以记录读者购买的图书信息
- 后置通知(After Advice):在切入点后执行的代码,在读者购买图书之后,我们可以更新图书库存
- 环绕通知(Around Advice):在切入点前后都执行的代码,我们可以对读者进行额外的安全检查和记录日志
- 异常通知(After-Throwing Advice):异常通知是在切入点发生异常时执行的额外功能代码。假设当顾客购买商品的数量大于库存数量时,就会发生异常。我们希望在顾客购买商品时检查库存,并在发生异常时执行异常通知,向顾客显示错误信息并处理异常情况
- 过滤通知(After-Returning Advice):过滤通知是在切入点成功执行后执行的额外功能代码。假设我们有一个特殊会员组,他们在购买商品时可以获得额外的积分。我们可以使用过滤通知来筛选出这些特殊会员,并在成功购买后给他们添加积分
④代理:
专业解释:将通知应用到目标对象后创建的对象(代理=目标+通知)
通俗理解:在书店中,收银员是一个代理角色。他们既代表顾客与商品交互,又代表书店执行一些额外的任务。当顾客带着商品到收银台时,收银员会扫描每个商品的条形码,获取商品信息并计算总价。这里,收银员即充当了顾客与商品之间的代理角色,也充当了超市执行计算总价等额外任务的代理角色
⑤切入点:
专业解释:
多个连接点的集合,定义了通知应该应用到那些连接点 (也将Pointcut理解成一个条件 ,此条件决定了容器在什么情况下将通知和目标组合成代理返回给外部程序)
通俗理解:在书店场景中,我们可能希望在计算折扣方法之前或之后记录日志和进行库存管理。这些切入点决定了我们在代码中操作的位置
⑥适配器:
专业解释:适配器是一个中间组件,用于将面向切面编程框架与原始的业务逻辑代码连接起来(适配器=通知(Advice)+切入点(Pointcut))
通俗理解:在书店场景中,适配器可以将代理对象与书店的购买图书业务逻辑连接起来,使得代理对象能够在购买图书的过程中添加额外的功能
2.代码演示
在上面场景模拟的代码中,我们能够发现记录日志的代码基本相同,那么有没有可能将这部分的代码抽取出来进行封装,统一进行维护呢?同时也可以将日志代码和业务代码完全分离,解耦合
那么我们便可以将业务方法中的非业务核心代码(日志记录)抽离出来形成一个横切面,并且将这个横切面封装成一个对象,将所有的记录日志的代码写到这个对象中,以实现与业务代码的分离,这便是面向切面编程的思想
2.1将记录日志的代码进行封装
在读者购买图书之前,记录读者购买的图书信息
package com.wh.aop; import org.springframework.aop.MethodBeforeAdvice; import java.lang.reflect.Method; import java.util.Arrays; /** * 买书、评论前加系统日志 * @author 王辉 * */ 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+")被调用了"); } }
2.2业务逻辑层代码的修改
package com.wh.aop; public interface IBookBiz { // 购书 public boolean buy(String userName, String bookName, Double price); // 发表书评 public void comment(String userName, String comments); }
package com.wh.aop; import com.wh.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); } }
2.3spring-context.xml的配置
com.wh.aop.IBookBiz
myMethodBeforeAdvice 2.4测试
package com.wh.aop.test; import com.wh.aop.IBookBiz; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class AopTest { @SuppressWarnings("resource") public static void main(String[] args) { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("/spring-context.xml"); // IBookBiz bean = (IBookBiz) applicationContext.getBean("bookBiz"); IBookBiz bean = (IBookBiz) applicationContext.getBean("bookProxy"); bean.buy("张三", "Java编程思想", 9.9); bean.comment("张三", "太深奥了,看不懂"); } }
2.5结果
将需要在其他操作中调用的方法封装起来,这样需要用到的时候便可以直接配置调用了,修改的话也就只需要修改一处地方的代码了
在面向切面的代码演示中,我用到了前置通知来做演示,我们知道,AOP中除了前置通知,还包含其他的通知,那我们也一起来看看吧!!
1.前置通知
这个在上面已经有过演示了,这里就不过多演示了
2.后置通知
买书返利(也可以做更新图书库存的操作)
将买书返利的代码进行封装
package com.wh.aop.advice; import org.springframework.aop.AfterReturningAdvice; import java.lang.reflect.Method; import java.util.Arrays; /** * 买书返利 * @author 王辉 * */ 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); } }
业务逻辑层的代码用的都是上面代码演示中的代码(前置通知的代码)
spring-context.xml的配置
com.wh.aop.biz.IBookBiz
myBefore myAfter 测试的代码也在前置通知中
结果
3.环绕通知
package com.wh.aop.advice; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; import java.util.Arrays; /** * 环绕通知 * 包含了前置和后置通知 * * @author 王辉 * */ 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; } }
业务逻辑层的代码用的都是上面代码演示中的代码(前置通知的代码)
spring-context.xml的配置
com.wh.aop.biz.IBookBiz
myBefore myAfter myInterceptor 测试的代码也在前置通知中
结果
4.异常通知
价格出现异常进行提示
package com.wh.aop.advice; import com.wh.aop.exception.PriceException; import org.springframework.aop.ThrowsAdvice; /** * 出现异常执行系统提示,然后进行处理。价格异常为例 * @author 王辉 * */ public class MyThrowsAdvice implements ThrowsAdvice { public void afterThrowing(PriceException ex) { System.out.println("【异常通知】:当价格发生异常,那么执行此处代码块!!!"); } }
业务逻辑层的代码用的都是上面代码演示中的代码(前置通知的代码)
spring-context.xml的配置
com.wh.aop.biz.IBookBiz
myBefore myAfter myInterceptor myThrows 测试的代码也在前置通知中
结果
5.过滤通知
处理买书返利的bug
不是调用所有方法都能够进行返利的,只有买了书(调用buy)才能够返利
业务逻辑层的代码用的都是上面代码演示中的代码(前置通知的代码)
spring-context.xml的配置
com.wh.aop.biz.IBookBiz
myBefore myAfter myInterceptor myThrows myafterPlus 测试的代码也在前置通知中
结果
为了方便查看,我们可以先将其他的通知注释
对了,记得将价格的负数改为正数
说了这么多,相信你也已经感受到了AOP的作用吧,那我们一起来总结一下吧!!
- 降低模块之间的耦合度;
- 提高了代码的可维护性;
- 提高了代码的复用性;
- 集中分开管理非业务代码和业务代码,逻辑更加清晰;
- 业务代码更加简洁纯粹,没有其他代码的影响
可能内容比较繁多,那我们再来总结一下使用AOP的步骤吧!
0.引入AOP框架:首先,需要在项目中引入AOP框架,例如Spring框架的AOP模块
放到pom.xml中
org.springframework spring-aspects ${spring.version}
定义切面:定义一个切面类,该类包含了一系列与业务逻辑无关的横切关注点。这些关注点可以是日志记录、事务管理、安全性检查等。可以使用注解或XML配置方式定义切面
配置切面:在应用程序的配置文件中,将切面配置为一个切面bean,指定它们与哪些目标对象织入,以及何时进行织入操作。可以使用注解或XML配置方式进行切面的配置
定义连接点和切入点表达式:连接点是在应用程序执行过程中可以插入切面的特定点,例如方法调用、属性访问等。切入点表达式用于匹配连接点,从而确定在何处织入切面。切入点表达式可以根据需求进行灵活的定义
编写通知:通知是切面执行的实际逻辑,它决定在连接点处织入切面的时机和行为。常见的通知类型有前置通知(在连接点之前执行)、后置通知(在连接点之后执行)、环绕通知(在连接点前后执行,可以控制连接点是否执行)等
测试和调试:对应用程序进行测试和调试,确保AOP的功能正确实现
- AOP是一种编程范式,它的目标是通过将横向逻辑(通常称为关注点)从纵向逻辑(核心业务逻辑)中分离出来,实现对系统的模块化和可重用性的提高。在Java开发中,AOP可以使用代理机制或字节码操作技术来实现
- 在AOP中,关注点可以被看做是与核心业务逻辑无关的功能,比如日志记录、安全性检查、事务管理等。通过使用AOP,我们可以将这些关注点从主要的业务逻辑中解耦出来,而不需要改动大量的代码
- 在实际应用中,我们可以使用AOP来解决一些常见的横切关注点问题,比如记录日志、性能监测、权限验证等。通过将这些功能提取为切面,并通过切入点选择需要拦截的连接点,在运行时动态地将切面织入到目标对象中,从而实现了关注点的重用和模块化
- 总之,AOP是一种非常有用的编程范式,可以提高代码的可维护性和重用性。在Java开发中,我们可以使用AOP来解耦关注点与核心业务逻辑,让系统更灵活、可扩展和易于维护
好啦,今天的分享就到这了,希望能够帮到你呢!