JAVAEE框架之Spring
六.AOP
AOP (Aspect Orient Programming),直译过来就是 面向切面编程。AOP 是一种编程思想,是面向对象编程(OOP)的一种补充。面向对象编程将程序抽象成各个层次的对象,而面向切面编程是将程序抽象成各个切面。
当我们系统已经设计好了之后,要去增加功能的时候,尽量少的去之前的代码。
JAVA:面向接口编程,可以定义若干接口,使用实现类实现后续需要增加的功能。
从该图可以很形象地看出,所谓切面,相当于应用对象间的横切点,我们可以将其单独抽象为单独的模块。
再举一个案例:
现实生活的案例,之前的老的居民楼,家里都有水表、电表,每到月末的时候,都有水利公司、电力公司的员工去抄表,电力公司的员工,挨家挨户的去抄表,有问题:太累、可能用户不在家,需要再次上门。这时候电力公司,就想到了AOP,面向切面编程,把各个各户的电表统一放到一个电表箱,由电力公司员工(宋光明)来抄表,大家看下,这时候,抄表员的效率是不是就大幅度提高了。
6.1 为什么需要 AOP
想象下面的场景,开发中在多个模块间有某段重复的代码,我们通常是怎么处理的?显然,没有人会靠“复制粘贴”吧。在传统的面向过程编程中,我们也会将这段代码,抽象成一个方法,然后在需要的地方分别调用这个方法,这样当这段代码需要修改时,我们只需要改变这个方法就可以了。然而需求总是变化的,有一天,新增了一个需求,需要再多出做修改,我们需要再抽象出一个方法–》接口,然后再在需要的地方分别调用这个方法–>接口的实现类,又或者我们不需要这个方法了,我们还是得删除掉每一处调用该方法的地方。实际上涉及到多个地方具有相同的修改的问题我们都可以通过 AOP 来解决。
使用场景:
Authentication 权限 Caching 缓存 Context passing 内容传递 Error handling 错误处理 Lazy loading 懒加载 Debugging 调试 logging, tracing, profiling and monitoring 记录跟踪 优化 校准 Performance optimization 性能优化 Persistence 持久化 Resource pooling 资源池 Synchronization 同步 Transactions 事务
6.2 术语
- 通知(Advice): AOP 框架中的增强处理。通知描述了切面何时执行以及如何执行增强处理。
- 连接点(join point): 连接点表示应用执行过程中能够插入切面的一个点,这个点可以是方法的调用、异常的抛出。在 Spring AOP 中,连接点总是方法的调用。
- 切点(PointCut): 可以插入增强处理的连接点。
- 切面(Aspect): 切面是通知和切点的结合,相同功能模块联系起来构成切面。
- 引入(Introduction):引入允许我们向现有的类添加新的方法或者属性。
- 织入(Weaving): 将增强处理添加到目标对象中,并创建一个被增强的对象,这个过程就是织入。建立业务类和功能的关联,就是织入,类似于织布。
6.3 jar包依赖
pom.xml文件
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>5.0.8.RELEASE</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>1.8.9</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.9</version> </dependency>
6.4 使用AOP的几种方式
- XML方式
- 注解方式
AOP通知类型
前置通知Before advice:在连接点(方法)前面执行,前置通知不会影响连接点的执行,除非此处抛出异常。
正常返回通知After returning advice:在连接点正常执行完成后执行,如果连接点抛出异常,则不会执行。
异常返回通知After throwing advice:在连接点抛出异常后执行。
后置通知After (finally) advice:在连接点执行完成后执行,不管是正常执行完成,还是抛出异常,都会执行返回通知中的内容。
环绕通知Around advice:环绕通知围绕在连接点前后,比如一个方法调用的前后。这是最强大的通知类型,能在方法调用前后自定义一些操作。环绕通知还需要负责决定是继续处理join point(调用ProceedingJoinPoint的proceed方法)还是中断执行。
- 围绕之前给大家布置的关羽骑马的案例来讲:
骑马:赤兔马;接口–》坐骑
关羽和魏国交战,魏国要针对蜀国关二爷进行侦查,侦查的时候,放一个侦察兵过来;
侦察兵只要发现赤兔马跑起来,就给个通知。有几个通知,有提前通知,有滞后通知有其他通知等。
侦察兵做的是提前通知,预警工作;
6.4.1 接口类
public interface Zuoqi { public void eat(int weight); public void run(int juli) throws Exception;//跑 }
6.4.2 目标对象
public class Horse implements Zuoqi { private String city="郑州"; public void eat(int weight) { System.out.println("赤兔马在吃草..."+weight+"斤"); } //方法的重载 public void run(int juli) throws Exception { if(city.equals("麦城")) throw new Exception("赤兔马被绊倒了..."); System.out.println("赤兔马跑起来了...."); } }
6.4.3 切面类
package com.aaa.pojo; import org.aspectj.lang.JoinPoint; /** * Created by 张晨光 on 2020/6/22 15:18 * 侦察兵类:可以针对坐骑进行侦查操作; */ public class ZcbAspect { //前置通知,提前通知魏国人跑;只要马一跑就通知,太累人了。设置一个距离; //又讲了一个JoinPoint:连接点类;它有目标对象相关方法调用的参数; public void qian(JoinPoint jp){ //这时候就要获取刚才的方法参数; Object[] args=jp.getArgs(); //获取连接点类的参数,即目标对象切入点方法的参数,有几个; //测试一下:输出方法名; String name = jp.getSignature().getName(); System.out.println("测试目标对象方法名:"+name); Integer juli= (Integer) args[0]; //将Object类型转换为Integer类型; if(name.equals("run")) { if(juli<=50) System.out.println("快跑,赤兔马快来了..."); } if(name.equals("eat")){ System.out.println("不用管它.."); } } //正常返回通知 public void hou(){ System.out.println("赤兔马拉了一堆马粪走了..."); } //异常通知; public void excep(JoinPoint jp,Exception e){ System.out.println("方法执行异常:"+e.getMessage()); } //5.环绕通知:返回类型是Object不同;参数不同; 测试环绕通知的时间; //可以方法前后 public Object around(ProceedingJoinPoint pjp)throws Throwable{ String method=pjp.getSignature().getName();//获取方法签名; long begin=System.currentTimeMillis();//获取开始时间的毫秒数; System.out.println("开始计时:"); try { return pjp.proceed(); } finally { long end=System.currentTimeMillis();//获取的结束的时间; System.out.println("侦察兵侦查时间:"+(end-begin)); } } }
6.4.4 xml配置
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.1.xsd"> <!--使用xml的方式,来实现aop的功能理解--> <!--1.目标对象:赤兔马--> <bean id="chitu" class="com.aaa.pojo.Horse"/> <!--2.切面类:此处是侦察兵类;--> <bean id="zcb" class="com.aaa.pojo.ZcbAspect"/> <!--3.Aop配置,工作就是织布,就是织入;将切面类和目标对象建立关联 这是一个重点,理解即可,不用背,不用记代码--> <aop:config> <!--3.1 切入点:id名;expression:execution(方法名)表达式;切入点,切的赤兔马(目标对象),run()--> <aop:pointcut id="p1" expression="execution(void *(int))"/> <!--3.2 切面类:ref 引入之前定义好的侦察兵--> <aop:aspect ref="zcb"> <!--3.2.1侦察兵,开始前置通知,给魏王报信--> <aop:before method="qian" pointcut-ref="p1"/> <aop:after-returning method="hou" pointcut-ref="p1"/> <!--注意异常通知的参数有三个;--> <aop:after-throwing method="excep" pointcut-ref="p1" throwing="e"/> </aop:aspect> <!--最终通知--> <aop:after method="last" pointcut-ref="p1"/> <!--环绕通知--> <aop:around method="around" pointcut-ref="p1"/> </aop:config> </beans>
6.4.5 测试类
public class TestAop01 { @Test public void test() throws Exception { ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("beans.xml"); Zuoqi zuoqi= (Zuoqi) ac.getBean("chitu"); //zuoqi.eat(5); //吃了5斤草 zuoqi.run(30); } }
总结:
1.aop的概念;现实案例和开发案例的理解;
2.aop的术语,理解,不用记忆;
ingframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.1.xsd">
```
6.4.5 测试类
public class TestAop01 { @Test public void test() throws Exception { ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("beans.xml"); Zuoqi zuoqi= (Zuoqi) ac.getBean("chitu"); //zuoqi.eat(5); //吃了5斤草 zuoqi.run(30); } }
总结:
1.aop的概念;现实案例和开发案例的理解;
2.aop的术语,理解,不用记忆;
3.aop的案例实践,我们通过赤兔马 、侦察兵来测试;学了两个前置通知和正常返回通知;