2019-08-21-24_Java动态代理的一些理解
前言
Github:https://github.com/HealerJean
主要用来做方法的增强,让你可以在不修改源码的情况下,增强一些方法,在方法执行前后做任何你想做的事情(甚至根本不去执行这个方法) 因为在
InvocationHandler
的invoke
方法中,通过反射,可以直接获取正在调用方法对应的Method
对象,具体应用的话,比如可以添加调用日志,做事务控制等。
一、动态代理实现
1、执行代理:
public interface ProxyInvoker {
/**
* 通过给定的代理对象和方法调用来执行方法。
*/
Object invoke(Object proxy, java.lang.reflect.Method method, Object[] args) throws
InvocationTargetException, IllegalAccessException;
}
@Getter
public abstract class AbstractProxyInvoker implements ProxyInvoker {
/**
* object
*/
private final Object object;
/**
* JdkDynamicProxyHandler
*
* @param object object
*/
public AbstractProxyInvoker(final Object object) {
this.object = object;
}
}
public class DefaultProxyInvoker extends AbstractProxyInvoker implements ProxyInvoker {
/**
* JdkDynamicProxyHandler
*
* @param object object
*/
public DefaultProxyInvoker(Object object) {
super(object);
}
/**
* 可以在这里做切面
*
* @param proxy proxy
* @param method method
* @param args args
* @return {@link Object}
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws InvocationTargetException, IllegalAccessException {
return method.invoke(getObject(), args);
}
}
2、被代理方法
public interface BuyHouse {
void buyHosue();
int countAdd(int i);
}
public class BuyHouseImpl implements BuyHouse {
@Override
public void buyHosue() {
System.out.println("我要买房");
}
@Override
public int countAdd(int i) {
return i++;
}
}
1、JDK
动态代理
⬤ 只能代理实现了接口的类:这是 JDK
动态代理的一个主要限制。它基于 Java
的反射机制,通过实现一个或多个接口来创建代理实例。这意味着,如果你想要对一个类使用 JDK
动态代理,那么这个类必须至少实现了一个接口。
⬤ 使用场景:当你需要为实现了接口的类添加一些额外的行为(如日志记录、安全检查等),而不想修改原有类代码时,JDK
动态代理是一个很好的选择。
⬤ 实现方式:JDK
动态代理通过 java.lang.reflect.Proxy
类和 java.lang.reflect.InvocationHandler
接口来实现。Proxy
类提供了创建代理实例的静态方法,而InvocationHandler
接口需要被实现以定义当代理实例的方法被调用时应该执行的操作。
1)代码实现
a、 JdkDynamicProxy
public class JdkDynamicProxy implements IDynamicProxy {
/**
* acquireProxy
*
* @param interfaceClass interfaceClass
* @param proxyInvoker invoker
* @return {@link T}
*/
@Override
public <T> T acquireProxy(Class<T> interfaceClass, ProxyInvoker proxyInvoker) {
ClassLoader classLoader = interfaceClass.getClassLoader();
InvocationHandler invocationHandler = proxyInvoker::invoke;
@SuppressWarnings("unchecked")
T result = (T) Proxy.newProxyInstance(classLoader, new Class[]{interfaceClass}, invocationHandler);
return result;
}
}
b、JdkAspectProxyInvoker
@Slf4j
public class JdkAspectProxyInvoker extends AbstractProxyInvoker {
/**
* JdkAspectProxyInvoker
*
* @param object object
*/
public JdkAspectProxyInvoker(Object object) {
super(object);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws InvocationTargetException, IllegalAccessException {
log.info("JdkAspectProxyInvoker#invoke before");
Object result = method.invoke(this.getObject(), args);
log.info("JdkAspectProxyInvoker#invoke end");
return result;
}
}
2、目标接口实现类
public class BuyHouseImpl implements BuyHouse {
@Override
public void buyHosue() {
System.out.println("我要买房");
}
}
2)验证
⬤ 第一个参数 指定当前目标对象使用的类加载器,获取加载器的
⬤ 第二个参数 指定目标对象实现的接口的类型,使用泛型方式确认类型,
这里的参数就是接口列表
⬤ 第三个参数 指定动态处理器
@Test
public void jdk() throws Exception {
JdkAspectProxyInvoker dynamicInvoker = new JdkAspectProxyInvoker(new BuyHouseImpl());
JdkDynamicProxy jdkDynamicProxy = new JdkDynamicProxy();
BuyHouse buyHouse = jdkDynamicProxy.acquireProxy(BuyHouse.class, dynamicInvoker);
buyHouse.buyHosue();
// byte[] bytes = ProxyGenerator.generateProxyClass("$Proxy0", buyHouse.getClass().getInterfaces());
// String code = IOUtils.toString(bytes, "utf-8");
// System.out.println(code);
// FileOutputStream fileOutputStream = new FileOutputStream("/Users/healerjean/Desktop/logs/porxy.class");
// IOUtils.write(bytes, fileOutputStream);
}
3)生成的代理类
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
import com.hlj.moudle.design.D09避免浪费.D21Proxy代理模式.BuyHouse;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class $Proxy0 extends Proxy implements BuyHouse {
private static Method m1;
private static Method m2;
private static Method m4;
private static Method m0;
private static Method m3;
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int countAdd(int var1) throws {
try {
return (Integer)super.h.invoke(this, m4, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final void buyHosue() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m4 = Class.forName("com.hlj.moudle.design.D09避免浪费.D21Proxy代理模式.BuyHouse").getMethod("countAdd", Integer.TYPE);
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
m3 = Class.forName("com.hlj.moudle.design.D09避免浪费.D21Proxy代理模式.BuyHouse").getMethod("buyHosue");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
4)总结
问题1:jdk
动态代理 是什么原理?
答案:jdk
动态代理是由 java
内部的反射机制来实现的 ,使用动态代理的对象必须实现一个或多个接口。
问题2:jdk
动态代理只能对接口进行代理,那么为什么呢?
答案:Java
的继承机制注定了这些动态代理类们无法实现对 class
的动态代理。动态代理实际上是程序在运行中,根据被代理的接口来动态生成代理类的 class
文件,并加载 class
文件运行的过程,通过反编译被生成的 $Proxy0
.class
文件发现:
class类定义为:
public final class $Proxy0 extends Proxy implements Interface {
public $Proxy0(InvocationHandler paramInvocationHandler) {
super(paramInvocationHandler);
}
$Proxy0
继承了需要被代理的类,由于 java
的单继承,动态生成的代理类已经继承了 Proxy
类的,就不能再继承其他的类,所以只能靠实现被代理类的接口的形式,故 JDK
的动态代理必须有接口。
2、gclib
动态代理
⬤ 针对类实现代理:与 JDK
动态代理不同,CGLIB
(Code
Generation
Library
)可以代理没有实现接口的类。它通过继承目标类来创建代理对象,并覆盖其中的方法。
⬤ 使用场景:当你需要代理的类没有实现接口,或者你想要在代理中访问目标类的私有成员时,CGLIB
是一个很好的选择。
⬤ 实现方式:CGLIB
在运行时动态生成目标类的子类,并在子类中覆盖目标类的方法。当调用代理对象的方法时,实际上会调用到CGLIB
生成的子类中的方法,这些方法会进一步调用 MethodInterceptor
接口(或类似的回调接口)的实现,从而允许你在方法调用前后添加自定义的行为。
1)代码实现
1、CglibDynamicProxy
public class CglibDynamicProxy implements IDynamicProxy {
@Override
public <T> T acquireProxy(Class<T> interfaceClass, ProxyInvoker proxyInvoker) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(interfaceClass);
enhancer.setCallback((MethodInterceptor) (o, method, objects, methodProxy) ->
proxyInvoker.invoke(o, method, objects));
@SuppressWarnings("unchecked")
T proxy = (T) enhancer.create();
return proxy;
}
}
2)验证
@Test
public void cglib() throws Exception {
DefaultProxyInvoker dynamicInvoker = new DefaultProxyInvoker(new BuyHouseImpl());
CglibDynamicProxy cglibDynamicProxy = new CglibDynamicProxy();
BuyHouse buyHouse = cglibDynamicProxy.acquireProxy(BuyHouse.class, dynamicInvoker);
buyHouse.buyHosue();
}
3)总结
⬤
CGLib
是一个强大、高性能的Code
生产类库,可以实现运行期动态扩展java
类,Spring
在运行期间通过CGlib
继承要被动态代理的类,重写父类(被代理的类)的方法,实现AOP
面向切面编程。⬤
JDK
的动态代理有一个限制,就是使用动态代理的对象必须实现一个或多个接口,如果想代理没有实现接口的类,就可以使用Cglib
实现。Cglib
不能对final
类以及final
方法进行代理。因为他是继承被代理类的子类,如果被代理类使用了final
就不能被继承
3、Javassist
Javassist
在性能上与其他两种技术相比没有绝对的显著优势或劣势。其性能表现主要取决于具体的使用方式和场景。在需要进行复杂字节码操作或动态修改类定义的场景中,Javassist
可能会表现出更好的灵活性和适应性。然而,在选择使用哪种技术时,还需要综合考虑其他因素(如易用性、维护成本等)来做出决策⬤ 适用于需要更复杂的字节码操作或动态修改类定义的情况。
⬤ 当
JDK
动态代理和CGLib
无法满足需求时,可以考虑使用Javassist
。
1)代码实现
public class JavassistDynamicProxy implements IDynamicProxy {
/**
* acquireProxy
*
* @param interfaceClass interfaceClass
* @param proxyInvoker invoker
* @return {@link T}
*/
@Override
public <T> T acquireProxy(Class<T> interfaceClass, ProxyInvoker proxyInvoker) {
ProxyFactory factory = new ProxyFactory();
factory.setInterfaces(new Class[]{interfaceClass});
MethodHandler methodHandler = (self, thisMethod, proceed, args) ->
proxyInvoker.invoke(self, thisMethod, args);
try {
@SuppressWarnings("unchecked")
T proxy = (T) factory.create(new Class[0], new Object[0], methodHandler);
return proxy;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
2)验证
@Test
public void javassist() throws Exception {
DefaultProxyInvoker dynamicInvoker = new DefaultProxyInvoker(new BuyHouseImpl());
JavassistDynamicProxy cglibDynamicProxy = new JavassistDynamicProxy();
BuyHouse buyHouse = cglibDynamicProxy.acquireProxy(BuyHouse.class, dynamicInvoker);
buyHouse.buyHosue();
}
3)总结
1、代理接口实现类
当 Javassist
用于代理实现了接口的类时,其性能可能与 JDK
动态代理相近。JDK
动态代理本身就是为了高效地代理接口而设计的,它通过动态生成代理类并利用Java反射机制来实现对接口方法的拦截。Javassist
虽然提供了更底层的字节码操作能力,但在仅需要代理接口的场景下,其优势并不明显。
2、代理未实现接口的类
对于未实现接口的类,JDK
动态代理无法直接使用,而 CGLib
和 Javassist
都可以通过不同的方式来实现代理。CGLib
通过继承目标类来生成代理类,这种方式在某些情况下可能会引入额外的性能开销,因为代理类需要继承目标类的所有方法和属性。而 Javassist
则可以通过直接修改目标类的字节码来实现代理,这种方式在某些场景下可能会更加高效,但也可能因为字节码操作的复杂性而引入性能瓶颈。
3、复杂字节码操作
Javassist
的强大之处在于它提供了丰富的 API
来直接操作 Java
字节码。这使得 Javassist
能够在运行时动态地修改类的定义、添加或删除方法、修改方法体等。当需要进行复杂的字节码操作时,Javassist
可能会比其他两种技术更加灵活和强大。然而,这种灵活性也可能导致性能上的不确定性,因为字节码操作的复杂性和效率往往难以预测。
4、实际应用场景
在实际应用中,Javassist
的性能表现往往取决于具体的使用场景。例如,在 AOP
(面向切面编程)场景中,Javassist
可以通过在运行时动态地向类中添加或修改方法来实现横切关注点的注入(如日志记录、性能监控等)。这种方式可能会比传统的静态 AOP
实现更加灵活和高效,因为它不需要在编译时生成额外的代理类或字节码。
二、动态代理总结
1、jdk
代理和 CGLIB
代理的区别
1)JDK
和 CGLib动
态代理性能对比-教科书上的描述
1、CGLib
所创建的动态代理对象在实际运行时候的性能要比 ` JDK` 动态代理高不少,有研究表明,大概要高10倍;
2、但是 CGLib
在创建对象的时候所花费的时间却比 JDK
动态代理要多很多,有研究表明,大概有8倍的差距;
3、因此,对于 singleton
的代理对象或者具有实例池(单例)的代理,因为无需频繁的创建代理对象,所以比较适合采用 CGLib
动态代理,反之,则比较适用 JDK
动态代理。
2)适用场景不一样
在 1.6 和 1.7 的时候,
JDK
动态代理的速度要比CGLib
动态代理的速度要慢,但是并没有教科书上的 10 倍差距,在JDK1.8
的时候,JDK
动态代理的速度已经比CGLib
动态代理的速度快很多了,但是JDK
动态代理和CGLib
动态代理的适用场景还是不一样的哈!
1、JDK
动态代理适用于实现了接口的类,通过接口来定义代理的行为。
2、CGLIB
代理适用于没有实现接口的类,通过继承目标类来创建代理对象,并覆盖其中的方法。
2、Spring
在选择用 JDK
还是 CGLiB
的依据
1)目标对象是否实现了接口(@aspect
会自动识别)
JDK
动态代理:当Spring
配置的目标对象实现了至少一个接口时,Spring
默认使用JDK
动态代理来创建代理。这是因为JDK
动态代理是基于接口实现的,它利用了Java
的反射机制在运行时动态地创建代理类。使用JDK
动态代理,Spring
可以方便地为目标对象的方法调用添加前置、后置、环绕等增强处理。CGLIB
代理:当目标对象没有实现任何接口时,Spring
将使用CGLIB
来创建代理。CGLIB
是一个强大的高性能代码生成库,它通过在运行时动态创建目标类的子类来实现代理。与JDK
动态代理相比,CGLIB
可以代理没有实现接口的类,并且可以覆盖父类的方法。
2) 性能和资源消耗
JDK
动态代理:由于JDK
动态代理是基于Java
反射机制实现的,其性能损耗相对较小,生成的代理类也较为轻量级,不需要生成额外的字节码文件,而是在运行时通过动态生成字节码的方式完成代理逻辑。这样可以减少类加载的时间和占用的内存空间。CGLIB
代理:CGLIB
在创建代理对象时需要生成更多的字节码,因此在性能上可能会比JDK
动态代理稍差。但是,CGLIB
提供的代理能力更为强大,可以代理没有实现接口的类,并且可以覆盖父类的方法。
3) 显式配置
- 尽管
Spring
默认根据目标对象是否实现了接口来选择代理方式,但开发者也可以通过显式配置来指定使用哪种代理方式。例如,在Spring
的配置文件中,可在Java
配置中,则可以通过@EnableAspectJAutoProxy(proxyTargetClass = true)
来实现相同的效果。