这是我参与「第三届青训营 -后端场」笔记创作活动的第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原生代理)
还是以上面的例子为例说明:
- 首先创建接口
public interface Person {
//声明交作业功能
void giveTask();
}
- 创建被代理的类
public class Student implements Person {
String stuName;
public Student(String stuName) {
this.stuName = stuName;
}
@Override
void giveTask() {
System.out.println(stuName + "交了作业");
}
}
-
创建一个实现 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;
}
}