AOP实现原理 | 青训营笔记

186 阅读8分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的第4篇笔记 AOP 的实现原理是动态代理模式

代理模式:

给某一个对象提供一个代理,并且由代理对象控制对原对象的引用;

即代理对象实现与原对象相同的接口,以便代替真实对象。同时,代理对象可以在执行真实对象的操作时,附加其它操作;

也就是说,代理对象不仅会调用原对象中的方法,还会实现新的方法,相当于对真实对象进行封装;

而主程序只会调用代理对象,原对象则由代理对象进行调用

简单来讲就是套娃

1) 静态代理

首先以一个例子来阐述静态代理

一个班里有学生 Joe 和学生 Tom,Joe 要交作业,但因为想去拉粑粑,所以就委托 Tom 帮他交作业,并给他的作业写上名字

那此时, Tom 就是一个代理,Joe 就成了被代理人,Tom 除了实现交作业的功能,还要实现写名字的功能

代码实现:

首先,Joe 和 Tom 都是 Person

public interface Person {
    //声明交作业功能
    void giveTask();
}

而 Joe 是普通学生,是不会替别人交作业,但是会自己交作业的学生

public class Student implements Person {
    
    String stuName;
    
    public Student(String stuName) {
        this.stuName = stuName;
    }
    
    @Override
    void giveTask() {
        System.out.println(stuName + "交了作业");
    }
}

Tom 就有点牛了,他会替别人交作业,还会替别人写名字

public class StudentsProxy implements Person {
    //被代理的学生
    Student stu;
    
    public StudentProxy(Student stu) {
        this.stu = stu;
    }
    
    //实现新的功能
    @Override
    public void giveTask() {
        //新功能
        System.out.println("给" + stu.stuName + "写上名字");
        System.out.println("给" + stu.stuName + "交上作业");
        //原来的功能
        stu.giveTask();
    }
}

主函数中:

public class ProxyTest {
    public static void main(String[] args) {
        
        //被代理的学生Joe,他的作业上交由代理对象Tom完成
        Person Joe = new Student("Joe");
 
        //生成代理对象,并将Joe传给代理对象
        Person Tom = new StudentsProxy(Joe);
 
        //Tom代理交作业
        Tom.giveTask();
    }
}

输出结果:

给Joe写上名字
给Joe交上作业
Joe交了作业

2) 动态代理

动态代理和静态代理的区别是,静态代理的代理类是我们自己定义好的,而动态代理的代理类是在程序运行时创建的。当有多个方法需要加相同的功能时,静态代理需要依次给每个方法写相同的功能,而动态代理只需要写一次即可

1. 基于接口的动态代理(JDK原生代理)

还是以上面的例子为例说明:

  1. 首先创建接口
public interface Person {
    //声明交作业功能
    void giveTask();
}
  1. 创建被代理的类
public class Student implements Person {
    
    String stuName;
    
    public Student(String stuName) {
        this.stuName = stuName;
    }
    
    @Override
    void giveTask() {
        System.out.println(stuName + "交了作业");
    }
}
  1. 创建一个实现 InvocationHandler 接口的类,类名随便啦,我取的类名是 StuInvocationHandler ,这个类就是个动态代理类,类中有两个需要注意的点:

    • 类中有一个属性,这个属性是被代理的类的对象,在这个例子中被代理的是 Student 类,即是 Student 类的对象
    • 类中有一个方法 invoke( ) 继承自接口 InvocationHandler ,这个方法不仅会调用被代理类的方法,还能增添新的方法,也就是说,通过重写这个方法,新的功能就被添加

代码:

public class StuInvocationHandler implements InvocationHandler {
    
    //被代理类
    Student student;
    
    //构造函数
    public StuInvocationHandler(Student student) {
        this.student = student;
    }
    
    //重写invoke方法
    //在这个方法中,除了新功能以外,其余的代码都原封不动,就这么写就完事了
    //当然,method.invoke()方法中的第一个参数也得自己写,就写被代理的类,此例中就是student
    //method.invoke()方法中第二个参数是原来方法中的参数数组(不重要)
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        
        //新功能
        System.out.println("给" + stu.stuName + "写上名字");
        System.out.println("给" + stu.stuName + "交上作业");
        
        //原来的功能(第一个参数是student)
        Object res = method.invoke(student, args);
        
        //不要忘记return!!!
        return res;
    }
}

上述代码中,我们实现了代理 Student 类的对象

但动态代理有一个好处就是可以代理任何类的对象

被代理类构造函数 就要写成更加牛逼的写法(反射有关知识)

同时需要注意 method.invoke ( ) 方法中第一个参数的改变

如下:

public class StuInvocationHandler implements InvocationHandler {
    
    //被代理类,此写法就能代理任何类,不仅仅能代理Student类
    Object obj;
    
    //构造函数
    public StuInvocationHandler(Object obj) {
        this.obj = obj;
    }
    
    //重写invoke方法
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        
        //新功能
        //利用反射来获取类中的属性
        Field field = obj.getClass().getDeclaredField("stuName");
        field.setAccessible(true);
        //写名字交作业
        System.out.println("给" + field.get(obj) + "写上名字");
        System.out.println("给" + field.get(obj) + "交上作业");
        
        //原来的功能(注意参数!!!!)
        Object res = method.invoke(obj, args);
​
        return res;
    }
}

现在,只剩下在Main函数中实现上述代理功能啦

public class ProxyTest {
    public static void main(String[] args) {
        
        //创建一个被代理对象
        Person Joe = new Student("Joe");
        
        //创建一个与代理对象相关联的InvocationHandler
        //可以形象的理解成Joe的工作(虽然不准确)
        InvocationHandler stuHandler = new StuInvocationHandler(Joe);
        
        //创建一个代理对象,这个函数就这么写就完事了,主要是需要注意第三个参数
        //把Joe的工作给了Tom
        Person Tom = 
            (Person) Proxy.newProxyInstance(Person.class.getclassLoader(),
                                            new Class<?>{Person.class},
                                            stuHandler);
        
        //代理执行交作业的方法
        Tom.giveTask();
    }
}

2. 不基于接口的动态代理(CGlib代理)

现在没有接口进行代理

还是以上面的例子做说明:

现有一个需要被代理的类 Student

注意这个类中有两个构造函数

public class Student {
​
    String stuName;
​
    //这里有一个无参构造函数,作用一会后面会说到
    public Student() {}
​
    public Student(String stuName) {
        this.stuName = stuName;
    }
​
    public void giveTask() {
        System.out.println(stuName + "交了作业");
    }
}

与上面不同的是,这个 Student 类没有继承任何接口,其中的方法也不需要重写

但需要注意的是这里写了一个无参构造函数和一个有参构造函数,作用在下文会讲到

创建一个继承 MethodInterceptor 接口的类,这个类也是一个动态代理类,我取得类名是StuMethodInterceptor

这个代理类与上面基于接口的代理类有相似的地方

特点如下:

  • 需要一个被代理类的属性
  • 需要重写一个 intercept ( ) 方法来增强功能(类似于 JDK 原生代理中的 invoke ( ) 方法)
  • 需要一个方法(在下面的代码中是 getNewProxy ( ) 方法)来创建代理对象:在 JDK 原生代理中,创建代理对象时使用的是 JDK 封装好的方法 Proxy.newProxyInstance( ); 而在 CGlib 代理中,这个方法需要自己写

代码:

public class StuMethodInterceptor implements MethodInterceptor {
​
    //被代理的类
    Student student;
​
    //构造函数
    public StuMethodInterceptor (Student student) {
        this.student = student;
    }
​
    //将代理对象创建的方法
    public Object getNewProxy() {
        
        //这三句话固定写法(因为底层原理我不清楚。。)
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Student.class);//注意这个方法中的参数是被代理的类
        enhancer.setCallback(StuMethodInterceptor.this);
​
        //调用Student类中的无参构造函数,并生成新的对象,返回这个对象(后面会细讲)
        return enhancer.create();
    }
​
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
​
        //新功能
        System.out.println("给" + student.stuName + "写了名字");
        System.out.println("给" + student.stuName + "交了作业");
​
        //原来的功能,注意第一个参数是被代理的对象
        Object res = method.invoke(student, objects);
​
        return res;
    }
}

容易出现报错的地方来了:

在上面的 Student 类中,写了两个构造函数,一个有参数另一个没有参数

而在 StuMethodInterceptor 类中的 getNewProxy ( ) 方法是这样写的

public Object getNewProxy() {
    
    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(Student.class);
    enhancer.setCallback(StuMethodInterceptor.this)    
        
    //注意下面这条语句
    return enhancer.create();
    /*
      这句话的作用是调用Student的构造函数
      如果enhancer.create()方法中没有任何参数,那么就等于调用的是Student类中的无参构造函数
      如果Student类中没有无参构造函数,那这么写就会报错
      想要调用Student类中的有参构造函数需要这样写:
    */
    
    return enhancer.create(new Class[]{String.class}, new Object[]{"Joe"});
    /*
      方法中的第一个参数是个数组
      代表的是Student类中的 构造函数的 所有参数的 类型
      第二个参数也是个数组
      代表的是Student类中的 构造函数的 所有参数的 值
      
      调用这个方法相当于调用Student类中的那个有参构造函数,并将构造方法中String的参数值设为"Joe"
      也就是说创建了一个stuName的值为"Joe"的Student对象
      
      但是这里创建的对象与被代理对象之间没有什么关系
      这个对象的创建只是为了能够实现代理
      里面的属性值无论设置成什么都可以
    */
}

Main函数中:

public class Main {
​
    public static void main(String[] args) {
​
        //被代理的对象
        Person Joe = new Person("Joe");
​
        //创建代理对象
        Person Tom = (Person) new StuMethodInterceptor(Joe).getNewProxy();
​
        //代理执行交作业的方法
        Tom.giveTask();
    }
}

输出结果:

给Joe写了名字
给Joe交了作业
Joe交了作业

与 JDK 原生代理一样,被代理的类可以是任何类,不仅仅可以代理 Student 类

那 StuMethodInterceptor 类写成更牛的样子就是这样:

public class StuMethodInterceptor implements MethodInterceptor {
​
    //被代理的类
    Object obj;
​
    //构造函数
    public StuMethodInterceptor (Object obj) {
        this.obj = obj;
    }
​
    //将代理对象创建的方法
    public Object getNewProxy() {
        
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(obj.class);//注意这个方法中的参数现在是obj
        enhancer.setCallback(StuMethodInterceptor.this);
​
        return enhancer.create();
    }
​
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
​
        //新功能
        //利用发射获取类中的属性
        Field field = obj.getClass().getDeclaredField("stuName");
        field.setAccessible(true);
        //写名字交作业
        System.out.println("给" + field.get(obj) + "写了名字");
        System.out.println("给" + field.get(obj) + "交了作业");
​
        //原来的功能,注意第一个参数现在是obj
        Object res = method.invoke(obj, objects);
​
        return res;
    }
}