【Java知识】java进阶-手撕动态代理
文章目录
- 概述
- 动态代理的两种实现方式
- 基于接口的动态代理(JDK动态代理)
- 实现原理
- 示例代码
- 基于类的动态代理(CGLIB动态代理)
- 实现原理
- 示例代码
- 动态代理的实际应用
- 两种方式的比较
- JDK动态代理
- CGLIB动态代理
- 性能对比
- 总结
概述
Java动态代理是一种设计模式,允许在运行时动态创建代理对象,并在不修改原始类的情况下增强其功能。它主要用于拦截方法调用,进行日志记录、性能监控、事务管理等操作。
动态代理的两种实现方式
- 基于接口的动态代理(JDK动态代理)
- 基于类的动态代理(CGLIB动态代理)
基于接口的动态代理(JDK动态代理)
JDK动态代理主要通过java.lang.reflect.Proxy
类和java.lang.reflect.InvocationHandler
接口实现。它只能代理实现了接口的类。
实现原理
Java动态代理的底层原理主要依赖于Java的反射机制(Reflection)。以下是JDK动态代理的底层实现原理的详细解释:
-
java.lang.reflect.Proxy
类:这是所有动态生成的代理类的父亲类。它提供了一个静态方法newProxyInstance
,用于在运行时创建一个新的代理实例。 -
java.lang.reflect.InvocationHandler
接口:所有代理实例必须关联一个InvocationHandler
实例。当代理实例上的方法被调用时,调用会自动委托给这个InvocationHandler
实例的invoke
方法。 -
动态字节码生成:当
Proxy.newProxyInstance
方法被调用时,JVM会使用ProxyClassFactory
生成一个动态的代理类。这个代理类是被代理接口的一个实现,但它不是通过传统方式实现的,而是在运行时动态生成的。 -
代理类的加载:生成的代理类的字节码会被加载到JVM中,这通常通过调用类加载器的
defineClass
方法完成。 -
代理实例的创建:一旦代理类被加载,JVM会使用它的构造方法创建一个新的实例。这个构造方法接受一个
InvocationHandler
作为参数,该参数在代理实例的方法调用时会被使用。 -
方法调用的分派:当代理实例上的任何方法被调用时,JVM会将调用转发到关联的
InvocationHandler
实例的invoke
方法。invoke
方法的三个参数是:代理实例本身、被调用的方法的Method
对象和方法参数。 -
invoke
方法的逻辑:在invoke
方法内部,你可以定义任何自定义逻辑。通常,你会在这个方法中调用原始对象的方法,并在调用前后添加额外的处理逻辑。 -
返回结果:
invoke
方法的返回值会成为代理方法的返回值。 -
缓存机制:为了性能优化,JVM会缓存生成的代理类。如果同一个组合(接口列表和调用处理器)再次请求一个代理实例,JVM会从缓存中获取代理类,而不是重新生成。
这个过程涉及到的核心技术是Java的反射机制,它允许程序在运行时查询和操作类和对象的属性和方法。动态代理利用了这一点,通过在运行时动态创建类和对象,实现了方法调用的动态分派。
CGLIB动态代理的实现原理与JDK动态代理不同。CGLIB通过使用ASM字节码操作框架在运行时动态生成被代理类的子类来实现代理。这个子类覆盖了父类的方法,并在这些方法中添加了额外的逻辑。当方法被调用时,实际上是调用了子类的覆盖方法,从而实现了方法调用的拦截和增强。CGLIB可以代理没有实现接口的类,但它需要目标类不是final
的,因为final
类不能被继承。
示例代码
首先,我们定义一个接口和其实现类:
public interface Service {void perform();
}public class ServiceImpl implements Service {@Overridepublic void perform() {System.out.println("Service is performing...");}
}
接下来,我们定义一个动态代理类:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;public class ServiceProxy implements InvocationHandler {private final Object target;public ServiceProxy(Object target) {this.target = target;}public Object getProxy() {return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(), this);}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("Before method invoke");Object result = method.invoke(target, args);System.out.println("After method invoke");return result;}
}
最后,使用动态代理:
public class ProxyTest {public static void main(String[] args) {Service service = new ServiceImpl();ServiceProxy serviceProxy = new ServiceProxy(service);Service proxyInstance = (Service) serviceProxy.getProxy();proxyInstance.perform();}
}
运行结果:
Before method invoke
Service is performing...
After method invoke
基于类的动态代理(CGLIB动态代理)
CGLIB(Code Generation Library)是一个强大的字节码生成库,它允许创建基于类的代理,而无需实现接口。CGLIB通过继承目标类并重写其方法来实现代理。
实现原理
CGLIB(Code Generation Library)是一个功能强大的高性能代码生成库,它通过使用字节码处理框架ASM在运行时动态生成类的子类。CGLIB动态代理的实现原理主要包括以下几个方面:
-
字节码生成:CGLIB使用ASM框架来操作字节码,动态生成被代理类的子类。这是CGLIB动态代理的核心,它允许在运行时扩展类的功能而无需修改原类代码。
-
方法拦截:CGLIB通过创建被代理类的子类,并重写其中的方法来实现代理。在子类中,可以拦截对父类方法的调用,并在方法执行前后添加自定义逻辑,如日志记录、权限检查等。
-
FastClass机制:CGLIB提供了FastClass机制,它通过创建索引来快速定位方法,避免了反射调用方法时的性能开销。这种方法调用方式比传统的反射调用要快得多。
-
创建代理对象:使用CGLIB的
Enhancer
类可以创建代理对象。首先设置需要代理的目标类,然后设置一个MethodInterceptor
作为回调,最后调用create
方法生成代理实例。 -
代理类的生成:在调用
Enhancer.create()
方法时,CGLIB会动态生成一个代理类,这个类是被代理类的子类,并且重写了被代理类的所有非final
方法。在代理类的方法中,会调用MethodInterceptor
的intercept
方法,以便在原方法执行前后添加额外的行为。 -
性能考虑:虽然CGLIB在创建代理类时可能会有一些性能开销,但一旦代理类被创建,其方法调用的性能通常优于基于反射的JDK动态代理,因为它避免了反射调用的额外开销。
-
局限性:CGLIB不能代理
final
类或final
方法,因为final
类或方法无法被继承和重写。此外,生成代理类的过程可能比JDK动态代理慢,尤其是在第一次创建代理类时。
CGLIB动态代理的使用步骤通常包括:
- 引入CGLIB依赖。
- 定义一个目标类。
- 创建一个实现了
MethodInterceptor
接口的拦截器类。 - 使用
Enhancer
类创建代理对象。 - 通过代理对象调用方法,方法调用会被拦截器拦截并处理。
CGLIB动态代理广泛应用于AOP框架中,如Spring AOP,以及需要在运行时动态生成代理对象的场景。它提供了一种灵活且高效的方式来增强类的功能。
示例代码
首先,引入CGLIB库:
<dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.3.0</version>
</dependency>
然后,定义一个类,不实现任何接口:
public class CglibService {public void perform() {System.out.println("CglibService is performing...");}
}
接下来,定义一个CGLIB代理类:
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;public class CglibServiceProxy implements MethodInterceptor {private final Object target;public CglibServiceProxy(Object target) {this.target = target;}public Object getProxy() {Enhancer enhancer = new Enhancer();enhancer.setSuperclass(target.getClass());enhancer.setCallback(this);return enhancer.create();}@Overridepublic Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {System.out.println("Before method invoke");Object result = proxy.invokeSuper(obj, args);System.out.println("After method invoke");return result;}
}
最后,使用CGLIB动态代理:
public class CglibProxyTest {public static void main(String[] args) {CglibService service = new CglibService();CglibServiceProxy proxy = new CglibServiceProxy(service);CglibService proxyInstance = (CglibService) proxy.getProxy();proxyInstance.perform();}
}
运行结果:
Before method invoke
CglibService is performing...
After method invoke
动态代理的实际应用
- AOP(面向切面编程):动态代理是AOP的核心技术之一。AOP允许在不修改业务逻辑的情况下添加额外的功能,如日志记录、事务管理和权限控制。
- 远程方法调用(RMI):动态代理可用于创建远程服务的本地代理,使得调用远程方法与调用本地方法一样简便。
- Mock测试:在单元测试中,可以使用动态代理创建Mock对象,以模拟依赖对象的行为。
通过使用动态代理,Java开发者可以在运行时动态地创建代理对象,从而在不修改原始类代码的情况下增强其功能。
两种方式的比较
Java动态代理主要有两种实现方式:JDK动态代理和CGLIB动态代理。它们各有特点和适用场景,也存在一些局限性。
JDK动态代理
实现原理:JDK动态代理基于java.lang.reflect.Proxy
类和java.lang.reflect.InvocationHandler
接口。它在运行时动态生成一个实现了指定接口的代理类,并将方法调用转发到InvocationHandler
的invoke
方法中处理。
优点:
- 实现简单,使用Java内置API。
- 无需依赖第三方库。
- 动态性,可以在运行时根据需要动态地创建代理对象。
- 解耦,将目标对象与横切逻辑分离,降低了代码之间的耦合度。
局限性:
- 只能代理实现了接口的类,不能代理没有实现接口的类。
- 性能开销,使用反射机制来调用目标方法,可能导致一定程度的性能损失。
- 代理类数量,对于每个需要代理的接口,都会生成一个新的代理类,可能导致代理类数量过多。
CGLIB动态代理
实现原理:CGLIB(Code Generation Library)通过字节码生成技术,创建目标类的子类来实现代理。它可以代理没有实现接口的普通类,通过覆盖父类的方法来添加额外的行为。
优点:
- 可以代理没有接口的类。
- 方法调用性能较高,避免了反射调用。
局限性:
- 创建代理类时需要进行字节码操作,性能开销较大。
- 需要依赖cglib和ASM库。
- 被代理类不能被
final
关键字修饰,否则无法生成子类。
性能对比
在性能方面,CGLIB动态代理通常比JDK动态代理更快,因为它不需要通过反射来调用方法。然而,CGLIB在生成和加载代理类的过程中可能会稍微慢一些,因为它涉及到字节码的生成和类加载。
总结
选择JDK动态代理还是CGLIB动态代理,取决于具体的需求:
- 如果目标对象已经实现了接口,且代理逻辑相对简单,JDK动态代理是一个简单直接的选择。
- 如果需要代理没有实现接口的类,或者对性能有较高要求,CGLIB动态代理是更合适的选择。
在实际开发中,Spring框架能够根据情况自动选择使用JDK动态代理或CGLIB来实现代理,提供了更高层次的抽象和便利。