学习代理前要对反射有一定的了解
代理是一种设计模式,代理模式是一种结构型设计模式,它允许通过创建一个代理对象来控制对另一个对象的访问。代理对象充当了客户端与真实对象之间的中介,它可以在客户端和真实对象之间添加额外的功能或控制访问方式。
代理模式通常通过创建一个共同的接口或抽象类来定义代理对象和真实对象的公共行为。这使得客户端可以在不知道真实对象的情况下直接与代理对象进行交互。
代理模式的优点包括:
然而,代理模式也有一些缺点,包括:
静态代理是代理模式的一种实现方式,它通过创建一个代理类来控制对真实对象的访问。在静态代理中,代理类和真实类都实现相同的接口或继承相同的父类,代理类持有一个对真实类的引用,并在调用真实对象的方法之前或之后执行一些额外的逻辑
先介绍一下代码结构
task接口有一个work方法,以及一个Strign类型的参数
public interface task {
void work(String name);
}
Student继承自work方法同时作为被代理的对象
public class Student implements task{
@Override
public void work(String name) {
System.out.println("学生正在做"+name+"作业");
}
}
代码运行
public static void main(String[] args) {
Student s = new Student();
staticJdk staticJdk = new staticJdk(s);
staticJdk.work("语文");
}
这是静态代理
学生正在做语文作业
这就是静态代理,相当于是多了一层调用
需要注意的是,静态代理的缺点在于每个代理类只能代理一个具体的接口或类,如果需要代理的接口或类很多,就需要编写大量的代理类。此外,静态代理在编译时就确定了代理关系,无法动态地修改代理对象。为了解决这些问题,可以使用动态代理。
首先动态代理是由Proxy类调用newProxyInstance方法生成,需要三个参数分别是
类加载器(在运行时动态地生成代理类的字节码并加载到内存中),被代理对象的接口的类型(能够代理的方法来自于接口),以及InvocationHandler的实现类(代理的真正逻辑)
接口和被代理对象不变
多了一个InvocationHandler实现类
main方法?
?
Student student = new Student();
handler h = new handler(student);
task o = (task) Proxy.newProxyInstance(Student.class.getClassLoader(), Student.class.getInterfaces(), h);
o.work("语文");
这里o就是生成的代理对象,效果还是一样,但是这解决了静态代理中接口如果改变代理类也要改变的缺点
先介绍一个类
ProxyGenerator
?是 Java 中的一个类,它位于?sun.misc
?包下。它是一个用于生成代理类字节码的工具类。该类提供了一些静态方法,可以用来生成代理类的字节码,并将其保存到磁盘上的?.class
?文件中
我把生成的字节码写出到文件中
这个类继承自Proxy实现了task接口,这里的m1 ,m2 ,m3 ...都是反射中Method对象
这个类中有一个静态代码块通过反射获得Method对象
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m4 = Class.forName("task").getMethod("a");
m2 = Class.forName("java.lang.Object").getMethod("toString");
m3 = Class.forName("task").getMethod("work", Class.forName("java.lang.String"));
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
可以看到除了Object类的方法外还多了接口中的方法这就是JDK动态代理只能代理接口中方法的原因
当我们在main方法中通过代理对象调用work方法时
这里的super是Proxy,h是InvocationHandler,再调用invoke的实现方法
思路返回到自己的InvocationHandler中
这里的proxy就是生成的代理对象,Method就是代理类传递的通过反射获得的接口中的方法,args相当于参数。
JDK动态代理是通过Java的反射机制实现的,虽然它提供了一种方便的方式来创建代理对象,但也存在一些缺陷和限制。
以下是JDK动态代理的一些主要缺陷:
只能代理接口:JDK动态代理只能代理接口,无法代理具体的类。这是由于JDK动态代理是基于接口的,它生成的代理类实现了目标接口,并通过实现接口中的方法来实现代理逻辑。如果需要代理的是具体的类而不是接口,就无法使用JDK动态代理,需要考虑其他的代理方式。
无法操作非公共方法:JDK动态代理只能代理目标接口中的公共方法,对于非公共方法无法进行代理。这是因为生成的代理类只能访问目标接口中的公共方法,无法访问目标类中的非公共方法。如果需要代理的方法是非公共方法,就无法使用JDK动态代理。
性能相对较低:相比于静态代理或CGLIB等其他代理方式,JDK动态代理的性能通常较低。这是因为在运行时,每次调用代理对象的方法时都需要通过反射机制来执行相应的逻辑,包括查找方法、参数转换等操作,相比直接调用目标对象的方法会有一定的性能损耗。
无法绕过final方法和类:JDK动态代理无法代理被final
修饰的方法和类。由于final
方法和类无法被继承或覆盖,因此无法在代理类中生成对应的代理逻辑。
无法直接访问目标对象:JDK动态代理通过代理对象间接地调用目标对象的方法,无法直接访问目标对象。这意味着在代理对象中无法使用this
关键字来引用目标对象,也无法在代理对象中直接调用目标对象的方法。
CGLIB(Code Generation Library)是一个开源的第三方库,用于在Java中生成动态代理类。与JDK动态代理不同,CGLIB可以代理具体的类而不仅限于接口。
CGLIB通过操作目标类的字节码来生成代理类的字节码,并在运行时加载和实例化代理类。它使用了字节码操作库ASM来分析目标类的字节码,并生成一个新的字节码类。生成的字节码类继承自目标类,并重写了所需的方法,以实现代理逻辑。
代码介绍?
Student作为被代理对象
public class Student {
public int work(){
System.out.println("正在计算");
System.out.println("计算完成");
return 1;
}
}
tansaction对象提供增强方法
public class tansaction {
public void before(){
System.out.println("完成初始化");
}
public void end(){
System.out.println("完成资源释放");
}
}
需要先导入CGLIB的依赖包
接下来是主代码?
先定义一个方法拦截器继承 MethodInterceptor
public class cglibInterceptor implements MethodInterceptor {
//提供增强方法的对象
tansaction t;
public cglibInterceptor(tansaction t){
this.t =t ;
}
//代码的实际逻辑
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
//MethodProxy是方法的代理类
t.before();
//调用父类的方法
Object o1 = methodProxy.invokeSuper(o, objects);
t.end();
return o1;
}
}
main方法
先看生成的调试信息
这个代理类继承自Student类(main方法的enhancer的setSuperclass 方法)
代理类中有一个work方法
这个方法首先为 MethodInterceptor var10000 赋值
然后调用MethodInterceptor的 intercept 方法的实现?
这里的this是指继承自Student类的当前对象
即我们自定义的方法
通过 invokeSuper 方法调用当前类的父类的方法并传入参数 这里相当于调用父类Student的work方法,从而通过继承实现了动态代理
CGLIB的优点是可以代理具体的类,不受接口的限制,同时还能够绕过final方法和类的限制。然而,由于CGLIB使用了字节码操作和反射,相对于JDK动态代理,它的性能开销可能会稍高一些。
缺点基于继承,使用CGLIB生成的代理类是目标类的子类,因此无法代理被声明为final的类。此外,CGLIB无法代理static方法和final方法,因为它们无法被继承或覆盖。