前言
我们都知道Spring中最经典的两个功能就是IOC和AOP
我们之前也谈过SpringIOC的思想 容器编程思想了
今天我们来谈谈SpringAOP的思想
首先AOP被称之为面向切面编程
实际上面向切面编程是面向对象的编程的补充和完善
重点就是对某一类问题的集中处理
前面我们写的统一异常管理和统一结果返回以及拦截器都是基于这个思想来创建的
我们发现这里的共性就是这些操作都有一个特点,他们都是统一操作的接口...
但是拦截器作用的是接口,AOP这类提供的操作的是方法
概念
切点(Printcut):这里的切点可以理解为切入的点,这里指的是一组规则,告诉程序对哪些方法来进行增强
连接点(Join Point):指的是满足上述切点规则的方法
通知(Advice): 对方法在前面/后面/周围加上一些处理(共性功能)
切面(Aspect): 切点 + 通知 (一类问题的解决方案)
SpringAOP
Spring 提供了一个通用的接口,可以帮助我们实现AOP的功能
比如对应用程序的监控,我们可以对每个接口之间加上计算实际运行时间
从计算的瓶颈解决问题
我们可以记录开始时间和节数时间然后作差,但是如果每个接口都一个一个写就没意思了
如果这里调用链非常长,我们还需要一个一个写,就太难受了呀
于是我们来使用AOP来解决问题
这里我们要先导入aop的依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
这里虽然文件是Aspect包中的,但是却是Spring实现的
简单的实现
@Aspect @Component @Slf4j public class TimeRecordAspect { @Around("execution(* com.example.bookmanager.controller.*.*(..))") public Object recordTime(ProceedingJoinPoint joinPoint) throws Throwable { //记录开始时间 long startTime = System.currentTimeMillis(); //目标方法 Object proceed = joinPoint.proceed(); //结束时间 long endTime = System.currentTimeMillis(); //打印日志 log.info("方法执行时间"+(endTime-startTime)+"ms"); return proceed; } }
这样我们就可以在调用接口的时候打印其执行的时间了
以上的@Around注解表示就是环绕
在目标函数执行的前后都会执行
通知类型有如下几种
• @Around: 环绕通知, 此注解标注的通知⽅法在⽬标⽅法前, 后都被执⾏
• @Before: 前置通知, 此注解标注的通知⽅法在⽬标⽅法前被执⾏
• @After: 后置通知, 此注解标注的通知⽅法在⽬标⽅法后被执⾏, ⽆论是否有异常都会执⾏
• @AfterReturning: 返回后通知, 此注解标注的通知⽅法在⽬标⽅法后被执⾏, 有异常不会执⾏
• @AfterThrowing: 异常后通知, 此注解标注的通知⽅法发⽣异常后执⾏
1.切点:这里的规则就是上面的Around里面的切点表达式
2.与切点表达式匹配的都是他描述的方法,也称之为连接点
3.通知 目标方法前后要做的操作
那么多包两层会怎么样呢????
比如先使用@Around再使用@Before和@After.....
这里就像一个栈一样,先进后出
注意:Around一定是要有返回值的,因为其有目标方法的执行
Around的优先级高于其他
接口正常时
接口异常时
我们发现before和after在接口正常和异常的情况下都是会执行的
切点
注意需要一个空参方法
我们可以使用@Pointcut注解来代替大量重复的注解,后面的切点表达式就可以实现复用了
注意,如果切点是private的,在其他的类还是不能使用,声明为public并且使用全限定名即可
注:默认执行顺序为字典序,不可取,我们可以使用一个@Order来改变其优先级
数字越大优先级越低
下面我们来介绍一下切点表达式的内容
execution(* com.example.bookmanager.controller.*.*(..))
execution + 访问修饰符(可省略) + 返回类型 + 全类名 + 参数 + 异常(可省略)
这里的*表示任意单词可以替换返回类型,包名,类名方法等
..表示匹配多个任意符号
假设我们想对不同的类中的不同的方法来执行对应的连接
这个时候使用切点表达式就不能很好的完成问题了 我们就需要使用自定义注解对需要进行通知的方法进行修饰了
自定义注解
首先我们需要创建一个注解
我们需要声明他的作用域和生命周期
然后我们需要去做他的配置类/实现类
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) public @interface MyAspect { }
我们暂时只实现一个Around功能
这里的@annotation就表示对什么注解生效
@Aspect @Component @Slf4j public class MyAspectDemo { @Around("@annotation(com.example.springaop.config.MyAspect)") public Object doAround(ProceedingJoinPoint joinPoint) { log.info("do around before"); Object o = null; try { o = joinPoint.proceed(); } catch (Throwable e) { throw new RuntimeException(e); } log.info("do around after"); return o; } }
后面我们可以用@MyAspect注解来修饰对应的方法
这样在调用的时候就可以实现其Around功能
SpringAOP的实现方式??
1.使用Aspect注解来实现
2.使用自定义注解来实现
3.基于XML文件使用api来实现 config:aop
4.基于代理来实现(比较久远)
代理模式(重点)
定义:
为其他对象提供⼀种代理以控制对这个对象的访问. 它的作⽤就是通过提供⼀个代理类, 让我们在调⽤⽬标⽅法的时候, 不再是直接对⽬标⽅法进⾏调⽤, ⽽是通过代理类间接调⽤.
代理前
代理后
生活中的代理也有很多
比如中介,经纪人,经销商,秘书........
主要角色:
Subject:业务接口类
RealSubject:业务实现类 具体的业务执行,也可以是被代理对象
Proxy:代理类,为RealSubject的代理
适用场景:
1.无法直接调用目标对象
2.目标对象给我们提供的功能不够,我们希望对目标对象已提供的方法进行功能增强
代理模式分为静态代理和动态代理
我们以生活中的中介来举例
静态代理就是我选好想看的房源之后,房源已经绑定了对应的中介了
动态代理就是我们选好想看的房源之后,平台自动分配中介
这种提前分配就称之为静态代理
运行时分配的就是动态代理
静态代理代码实现
接口
public interface Subject { void rentHouse(); }
房东
public class RealHouseSubject implements Subject{ @Override public void rentHouse() { System.out.println("我要卖房子"); } }
中介
public class HouseProxy implements Subject{ private RealHouseSubject target; public HouseProxy(RealHouseSubject target) { this.target = target; } @Override public void rentHouse() { System.out.println("开始代理"); target.rentHouse(); System.out.println("结束代理"); } }
类似于适配器模式,这里也是持有了第三方的房东授权
缺点是代码都写死了
动态代理实现
//JDK代理 public class JDKInvocationHandler implements InvocationHandler { private RealHouseSubject target; public JDKInvocationHandler(RealHouseSubject target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("开始代理"); Object o = method.invoke(target, args); System.out.println("结束代理"); return o; } } //main方法 RealHouseSubject subject = new RealHouseSubject(); Subject proxy= (Subject)Proxy.newProxyInstance(subject.getClass().getClassLoader(), new Class[]{Subject.class},new JDKInvocationHandler(subject)); proxy.rentHouse();
这里有两种 CGLib和 JDK动态代理
这里就不做要求了
注意代理是基于反射实现的
主要记得这里的两个区别
JDK可以代理接口 不能代理类
CGLib可以代理接口也可以代理类
但是性能我不太确认,网上说两个性能谁高的都有,可能有硬件的影响
博主可以后面测测看
注意Spring和SpringBoot在这里AOP的实现也是有差异的
代理工厂中有一个参数 proxyTargetClass
默认Spring是false 默认实现接口使用JDK代理
SpringBoot从2.x之后设置为true 默认全部使用CGLib实现代理
proxyTargetClass | ⽬标对象 | 代理⽅式 |
false | 实现了接⼝ | jdk代理 |
false | 未实现接⼝(只有实现类) | cglib代理 |
true | 实现了接⼝ | cglib代理 |
true | 未实现接⼝(只有实现类) | cglib代理 |
小总结
AOP是⼀种思想,是对某⼀类事情的集中处理.Spring框架实现了AOP,称之为SpringAOP
2. SpringAOP常⻅实现⽅式有两种:
1.基于注解@Aspect来实现
2.基于⾃定义注解来实现,还有⼀些更原始的⽅式,⽐如基于代理,基于xml配置的⽅式,但⽬标⽐较少⻅
3. SpringAOP是基于动态代理实现的,有两种⽅式:
1.基本JDK动态代理实现
2.基于CGLIB动态代理
实现.运⾏时使⽤哪种⽅式与项⽬配置和代理的对象有关