Aop进阶(一)
1、引言
这篇博客主要记载的是我对Aop的理解,和以前使用Aop的一种模糊理解。Aop底层是动态代理,动态代理的底层又是反射。主要是针对Spring-Aop的理解。需要具备Aop的切入点、切面、链接点、通知等基础知识。
2、反射
反射是Java自身核心的一个知识点,反射具体就是指在程序运行期间,对于任意一个类可以获取这个类的属性和方法,对于任意一个对象,能够调用它的任意一个方法和属性。
简单的代码演示:参考:[简单的反射](https://www.cnblogs.com/haodawang/p/5967185.html),主要是使用Java类库中reflect包中的各种方法。
import java.lang.reflect.*;
public class Main {
public static void main(String[] args) throws Exception{
//返回A的构造方法
Constructor c = A.class.getConstructor();
//返回A类的所有为public 声明的构造方法
Constructor[] cons = A.class.getConstructors();
//返回A类所有的构造方法,包括private
Constructor[] cons2 = A.class.getDeclaredConstructors();
//返回A类的第一个public 方法
Method m = A.class.getMethod("say");
//执行
m.invoke(A.class.newInstance(), null);
//返回A类所有的public 方法
Method[] ms = A.class.getMethods();
//返回A类所有的方法,包括private
Method[] allMs = A.class.getDeclaredMethods();
//返回A类的public字段
Field field = A.class.getField("i");
System.out.println(field.get(A.class.newInstance()));
//返回A类的static 字段
System.out.println(field.get(null));
}
}
class A{
public int i = 1;
public static int b = 2;
public A(){
System.out.println("无参构造");
}
private A(String s){
System.out.println("有参构造"+s);
}
public void say(){
System.out.println("say");
}
}
3、动态代理
在了解动态代理之间,我们需要了解静态代理。静态代理是指实现同一个接口的两个类。一个类是代理类,一个类是被代理类。在代理类可以new一个被代理类,达到代理的效果。就比如房东、中介、和客户的关系。房东和中介都有卖房方法,房东是被代理类,中介是代理类。房东只要提自己的卖房要求,至于签合同、带领客户参加这种活动可以交给中介进行。中介就是代理类。静态代理是属于编译期间,也就是编译期间就有真正的class文件,缺点是它只能针对特定的类。而动态代理却不同,它可以在运行期间,具有普适性。Aop就是用的动态代理。
动态代理是怎么实现的?这就是反射,动态代理依靠的就是反射。在Java种有两种动态代理,1、jdk动态代理(接口)2、cglib动态代理(父类)Spring中默认是jdk动态代理。
jdk 动态代理
在jdk动态代理种,要求代理类和被代理类必须实现共同接口。创建代理对象的步骤:1、实现InvocationHandler接口 2、使用newProxyInstance方法
//创建一个动物接口,接口中就只有一个方法
public interface Animal {
void eat();
}
===================================================================================
//创建一个猫类实现这个接口,这个小猫作为被代理类
public class Cat implements Animal {
@Override
public void eat() {
System.out.println("吃鱼");
}
}
然后书写一个类,这个类主要目的是创建代理类(猫的代理对象),并且执行增强的方法
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ProxyJdk implements InvocationHandler {
private Object object;
public Object createProxyCat(Object o){
this.object=o;
/**
* newProxyInstance()方法中有三个参数
* 第一个参数:loader 是代理类的类加载器
* 第二个参数:interfaces 代理类实现的接口列表
* 第三个参数:h,也就是实现InvocationHandler的类,用于将方法分派到的调用程序
*/
return Proxy.newProxyInstance(
object.getClass().getClassLoader(),
object.getClass().getInterfaces(),
this);
}
/**
*
* @param proxy 调用方法的代理实例
* @param method 代理对象上的接口方法
* @param args 参数
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("闻到香味,跑过来!");
//实现被代理类的方法
Object invoke = method.invoke(object, args);
System.out.println("吃完离开");
return invoke;
}
//进行测试
public static void main(String[] args) {
ProxyJdk jdk=new ProxyJdk();
Animal proxyCat = (Animal)jdk.createProxyCat(new Cat());
proxyCat.eat();
}
}
#运行结果
闻到香味,跑过来!
吃鱼
吃完离开
cglib 动态代理
cglib 动态代理的思想是,一个类继承父类的所有公开方法就可以重写这些方法然后进行增强。创建代理对象,需要实现MethodInterceptor接口,并使用Enhancer类。
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class ProxyCglib implements MethodInterceptor {
private Enhancer enhancer=new Enhancer();
public Object createProxy(Class clazz){
//设置为公共的类
enhancer.setSuperclass(clazz);
//建立关联关系
enhancer.setCallback(this);
return enhancer.create();
}
/**
*
* @param o 代理对象本身
* @param method 被代理的方法
* @param objects 函数调用的参数
* @param methodProxy 方法的代理
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("问起来鱼不行");
//相当与jdk代理中的invoke方法()
Object o1 = methodProxy.invokeSuper(o, objects);
System.out.println("受不了,跑步离开");
return o1;
}
public static void main(String[] args) {
ProxyCglib proxyCglib=new ProxyCglib();
//创建代理对象
//这里传的参数主要是类的,可以是Class.forName("类的全名"),或者是 new Cat().getClass
Animal proxyCat =(Animal) proxyCglib.createProxy(Cat.class);
proxyCat.eat();
}
}
#结果
问起来鱼不行
吃鱼
受不了,跑步离开
4、SpringBoot Aop
在SpringBoot中Aop的使用非常简单,SpringBoot Aop帮我们封装了很多底层,默认使用的是jdk动态代理,可一配置打开cglib动态代理。
1、导入依赖
可以选择 aspectjweaver 也可以选择Aop starter依赖,建议使用starter依赖,因为starter一个包就包含了很多依赖项,并且容易配置。
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.6</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>2.6.4</version>
</dependency>
2、创建切面类
切入点的匹配方式使用的是execution进行匹配。
@Component
@Aspect
public class TestAop {
//pointcut 切入点的集合,即在哪里干的集合
@Pointcut("execution(* com.andrew.study.service.impl.TestServiceImpl.*(..))")
private void pointCutMethod(){
}
//环绕通知
@Around("pointCutMethod()")
public void doAround(ProceedingJoinPoint pip){
System.out.println("----------");
System.out.println("1200一个月,可以不");
System.out.println("不行,起码1800一个月");
try {
Object proceed = pip.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
System.out.println("成交");
}
//前置通知
@Before("pointCutMethod()")
public void doBefore(){
System.out.println("请问是陈先生吗?");
}
@After("pointCutMethod()")
public void doAfter(){
System.out.println("入住之后,不准改变房子的格局");
}
}
在切入点的匹配上选择自定义注解的方式
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Test2 {
}
修改后的切面类为
@Component
@Aspect
public class Test2Aop {
@Pointcut("@annotation(com.andrew.study.aspect.Test2)")
private void test(){
}
@Before("test()")
public void doBefore(){
System.out.println("今天有什么菜呢?");
}
@Around("test()")
public void doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("开饭了");
Object proceed = proceedingJoinPoint.proceed();
System.out.println("小明洗碗!");
}
@After("test()")
public void doAfter(){
System.out.println("谁最后一个吃完谁洗碗");
}
}
在自定义注解方式下,需要在需要增强的方法上加上注解。
@Service
public class Teste2ServiceImpl implements ITestService {
@Test2
@Override
public void test1() {
System.out.println("有番茄炒蛋、水煮肉片");
}
}
# 结果
开饭了
今天有什么菜呢?
有番茄炒蛋、水煮肉片
谁最后一个吃完谁洗碗
小明洗碗!
对于没有自定义注解的方式切面类,直接使用就行
@RequestMapping("/test1")
public void test(){
testService.test1();
}
# 结果
----------
1200一个月,可以不
不行,起码1800一个月
请问是陈先生吗?
这个房子卖给你了
入住之后,不准改变房子的格局
成交
5、拓展
前面几点,是Aop中比较简单的实现原理,下面介绍Aop在SpringBoot中的工作流程。
在了解Aop的底层原理之前,我们需要了解BeanFactoryPostProcessor 和 BeanPostProcess ,这两个接口都是spring 通过配置文件或xml获取Bean声明生成后的BeanDefinition后允许对生成BeanDefinition进行再次封装的入口。在Spring或者Spring Boot进行校验后,应用程序会根据@Aspect注解切面类进行解析,它会根据PointCut匹配代理类,然后创建对象。核心校验方法是wrapIfNecessary,这个方法中有一个getAdvicesAndAdvisorsForBean,主要是返回一个Advisor数据集合,这个主要是将bean和集合中Advisor的Advice的pointCut匹配到。如果匹配到,就为这个bean创建对象。
具体流程
- 从Advisor缓存中获取所有的Advisor
- 遍历Advisor集合,从Advisor集合中获取PointCut,获取方法匹配器MethodMatcher,通过match方法匹配一个当前Advisor是否能作用于目标类,如果能放入集合中
- 如果第二部集合为空,直接返回,否则根据配置选择jdk代理还是cglib代理
- cglib代理需要构建Callback拦截增强器集合,然后创建CallbackFilter过滤器,选择合适的拦截增强器对bean实现增强处理
- 返回创建的代理对象