JDK 代理:让 Java 对象变得灵活有趣
想象一下,您在一家餐厅里点了一份美味的牛排,而服务员就像是我们编程中的代理。他们负责传达您的需求,并将美食送到您的桌前。在 Java 编程中,JDK 代理就是这样一种角色,它帮助我们在运行时动态地处理方法调用。
什么是 JDK 代理?
JDK 代理是一种设计模式,它允许我们在运行时创建一个代理对象来控制对目标对象的访问。简单来说,它能够在调用真实对象的方法时,添加一些额外的功能,比如日志记录、权限检查等,而无需修改原始对象的代码。代理模式可以分为两种类型:
- 静态代理:代理类在编译时就已经确定,适合功能较为简单的情况。
- 动态代理:代理类在运行时生成,更为灵活,适合需要大量代理对象的场景。
假设存在一个
JDK 代理属于动态代理的范畴,它通过 Java 的反射机制来实现。
静态代理
静态代理的定义
静态代理是一种代理设计模式,通过在代理类中控制对目标类的访问来实现功能的增强。在静态代理中,代理类与目标类需要实现相同的接口,因此在编译阶段就确定了代理的结构。
静态代理的结构
静态代理通常包含以下角色:
- 接口(Subject):定义目标对象和代理对象的公共方法。
- 目标对象(RealSubject):实现接口的具体类,包含核心业务逻辑。
- 代理对象(Proxy):同样实现接口,并持有目标对象的引用,通过调用目标对象的方法来完成具体的功能,通常会在目标方法的前后进行一些操作增强。
静态代理的实现步骤
定义公共接口:声明目标对象和代理对象的公共方法。
实现目标类:目标类实现公共接口,包含实际的业务逻辑。
实现代理类:代理类也实现公共接口,并持有目标对象的引用,通过构造方法将目标对象传入,并在调用方法时进行增强操作。
示例代码
以下是一个静态代理的示例代码,展示如何使用静态代理来对方法进行增强:
// 1. 公共接口
interface Subject {
void doAction();
}
// 2. 目标类
class RealSubject implements Subject {
@Override
public void doAction() {
System.out.println("执行目标类的核心方法");
}
}
// 3. 代理类(必须手动编写代理类,并且代理类与目标类实现相同的接口。)
class ProxySubject implements Subject {
private RealSubject realSubject;
public ProxySubject(RealSubject realSubject) {
this.realSubject = realSubject;
}
@Override
public void doAction() {
System.out.println("在执行目标方法前进行一些操作...");
realSubject.doAction(); // 调用目标对象的方法
System.out.println("在执行目标方法后进行一些操作...");
}
}
// 4. 测试类
public class StaticProxyTest {
public static void main(String[] args) {
RealSubject realSubject = new RealSubject();
ProxySubject proxy = new ProxySubject(realSubject);
proxy.doAction(); // 通过代理类调用目标方法
}
}
JDK的动态代理
JDK 代理的工作原理
JDK 代理的核心在于 Proxy
类和 InvocationHandler
接口。我们可以通过 Proxy
类创建一个代理对象,Proxy 是 Java 自带的一个类,属于 JDK 标准库,位于 java.lang.reflect 包中。因此,你不需要自己实现这个类,只需要使用它来动态生成代理对象。并通过 InvocationHandler
接口来定义当调用代理对象的方法时应该执行的操作。
前提代码:
// 定义一个接口
interface HelloService {
void sayHello(String name);
}
// 实现该接口的真实对象
class HelloServiceImpl implements HelloService {
@Override
public void sayHello(String name) {
System.out.println("Hello, " + name + "!");
}
}
我们在静态代理的时候,除了以上一个接口和一个实现类之外,是不是要写一个代理类呀!在动态代理中代理类是可以动态生成的。这个类不需要写。我们直接写客户端程序即可:
// 使用代理(客户端)
public class Main {
public static void main(String[] args) {
// 创建真实对象
HelloService helloService = new HelloServiceImpl();
// 创建代理对象 进行了强制转换因为需要调用代理对象的方法
HelloService proxy = (HelloService) Proxy.newProxyInstance(
helloService.getClass().getClassLoader(),
helloService.getClass().getInterfaces(),
new DynamicProxy(helloService)
);
// 调用代理对象的方法
proxy.sayHello("Alice");
}
}
HelloService proxy = (HelloService) Proxy.newProxyInstance(
helloService.getClass().getClassLoader(),
helloService.getClass().getInterfaces(),
new DynamicProxy(helloService)
);
这行代码做了两件事:
- 第一件事:在内存中生成了代理类的字节码
- 第二件事:创建代理对象
Proxy
类全名:java.lang.reflect.Proxy
。这是JDK提供的一个类(所以称为JDK动态代理)。主要是通过这个类在内存中生成代理类的字节码。
其中newProxyInstance()
方法有三个参数:
- 第一个参数:类加载器。在内存中生成了字节码,要想执行这个字节码,也是需要先把这个字节码加载到内存当中的。所以要指定使用哪个类加载器加载。
- 第二个参数:接口类型。代理类和目标类实现相同的接口,所以要通过这个参数告诉JDK动态代理生成的类要实现哪些接口。
- 第三个参数:调用处理器。这是一个JDK动态代理规定的接口,接口全名:
java.lang.reflect.InvocationHandler
。显然这是一个回调接口,也就是说调用这个接口中方法的程序已经写好了,就差这个接口的实现类了。
// 代理类 实现了接口
class DynamicProxy implements InvocationHandler {
private Object target; // 真实对象
public DynamicProxy(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Before method call: " + method.getName());
// 调用真实对象的方法
Object result = method.invoke(target, args);
System.out.println("After method call: " + method.getName());
return result;
}
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Before method call: " + method.getName());
// 调用真实对象的方法
Object result = method.invoke(target, args);
System.out.println("After method call: " + method.getName());
return result;
InvocationHandler
接口中有一个方法invoke
,这个invoke方法上有三个参数:
- 第一个参数:
Object proxy
。代理对象。设计这个参数只是为了后期的方便,如果想在invoke方法中使用代理对象的话,尽管通过这个参数来使用。 - 第二个参数:
Method method
。目标方法。 - 第三个参数:
Object[] args
。目标方法调用时要传的参数。
代码示例
下面是一个更详细的 JDK 代理示例:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// 定义一个接口
interface HelloService {
void sayHello(String name);
}
// 实现该接口的真实对象
class HelloServiceImpl implements HelloService {
@Override
public void sayHello(String name) {
System.out.println("Hello, " + name + "!");
}
}
// 代理类
class DynamicProxy implements InvocationHandler {
// 有了目标对象我们就可以在invoke()方法中调用目标方法了
private Object target; // 真实对象
public DynamicProxy(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Before method call: " + method.getName());
// 调用真实对象的方法
Object result = method.invoke(target, args);
// System.out.println("After method call: " + method.getName());
return result; // 将目标对象方法的返回值返回给调用者
}
}
// 使用代理(客户端)
public class Main {
public static void main(String[] args) {
// 创建真实对象
HelloService helloService = new HelloServiceImpl();
// 创建代理对象
HelloService proxy = (HelloService) Proxy.newProxyInstance(
helloService.getClass().getClassLoader(),
helloService.getClass().getInterfaces(),
new DynamicProxy(helloService)
);
// 调用代理对象的方法
String result = proxy.invoke("Proxy!");
System.out.println("Result: " + result);
}
}
代理类在运行时生成,通常不需要手动编写。
Java 动态代理可以通过 JDK 自带的 Proxy 类实现,或者通过第三方库(如 CGLIB)实现。
代理类是在运行时根据目标类自动生成的,因此比静态代理更为灵活。
CGLIB动态代理
CGLIB 动态代理:让你轻松理解的代理魔法
在 Java 编程中,我们经常需要对对象的方法做一些“额外操作”。比如,在调用一个方法前后加上日志、计时,或者对某些操作进行权限控制。要实现这种“增强”功能,我们可以用“代理”来帮忙。今天我带大家一起了解一种非常有趣的代理方式——CGLIB 动态代理。
什么是 CGLIB?
CGLIB 是一个字节码生成库,简单来说,它能在运行时生成类的字节码(就是类的代码),并创建这些类的对象。相比于 JDK 动态代理需要实现接口的限制,CGLIB 可以直接代理一个没有实现接口的类。这就给我们带来了更多灵活性。
CGLIB 动态代理和 JDK 动态代理的区别
在介绍 CGLIB 前,我们先简单对比一下它和 JDK 动态代理的区别:
- JDK 动态代理:只能代理实现了接口的类。使用的是 Java 自带的反射机制。
- CGLIB 动态代理:不需要类实现接口,通过继承的方式来创建代理对象,所以它对那些没有接口的类也能发挥作用。
CGLIB 动态代理的工作原理
CGLIB 动态代理的工作原理是:通过继承目标类生成一个子类,重写目标类的方法来实现增强功能。可以把它想象成你有一个神奇的机器,可以直接复制一个类,然后在这个副本里修改一些方法的实现方式。
由于它是通过继承来实现的,所以目标类不能是 final
的(因为 final
类无法被继承)。另外,目标类里的 final
方法也不能被增强,因为这些方法不能被重写。
CGLIB 动态代理的使用示例
来看一个简单的代码示例,看看如何用 CGLIB 来代理一个类。
首先,我们要引入 CGLIB 库(可以通过 Maven 引入):
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
然后,我们创建一个目标类和代理类。
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
// 客户端
import net.sf.cglib.proxy.Enhancer;
public class CglibProxyExample {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
// 设置要代理的目标类
enhancer.setSuperclass(TargetClass.class);
// 设置回调函数,当目标方法被调用时,执行拦截逻辑
enhancer.setCallback(new CustomMethodInterceptor());
// 创建代理对象
TargetClass proxy = (TargetClass) enhancer.create();
// 调用代理对象的方法
proxy.someMethod();
}
}
// 目标类
class TargetClass {
public void someMethod() {
System.out.println("Executing target method");
}
}
// 单独设置一个类来继承 MethodInterceptor
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
class CustomMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object target, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
// 前增强逻辑
long begin = System.currentTimeMillis();
System.out.println("Before method execution");
// 调用目标方法
Object retValue = methodProxy.invokeSuper(target, args);
// 后增强逻辑
long end = System.currentTimeMillis();
System.out.println("After method execution");
System.out.println("耗时: " + (end - begin) + " 毫秒");
// 返回目标方法的结果
return retValue;
}
}
class TargetClass {
// 目标类的方法
public void someMethod() {
System.out.println("Executing target method");
}
}
代码解析
Enhancer:这是 CGLIB 提供的一个工具类,用来生成代理对象。
enhancer.setSuperclass(TargetClass.class)
:告诉 CGLIB 我们要代理哪个类,这里是TargetClass
。enhancer.setCallback(...)
:设置回调,也就是当代理对象的方法被调用时,我们要做什么。
MethodInterceptor:这个接口可以让我们拦截方法调用,在方法执行前后加上一些自定义逻辑。
intercept
方法是我们增强逻辑的核心,比如这里我们在方法执行前后打印了一些日志。
代理效果:运行这个代码时,我们可以看到:
- 在调用
someMethod()
之前打印了Before method execution
。 - 调用
someMethod()
时执行了目标方法的内容:Executing target method
。 - 最后又打印了
After method execution
。
- 在调用
这就是 CGLIB 动态代理的增强效果——它让我们能够在不修改原有类代码的情况下,灵活地为方法添加“前后钩子”。
CGLIB 动态代理的应用场景
- AOP(面向切面编程):在 Spring 中,CGLIB 经常被用来实现 AOP,当你想要给没有接口的类添加切面(例如日志、事务管理)时,CGLIB 就会派上用场。
- 无需接口的场景:当我们想要代理的类没有实现接口时,可以考虑使用 CGLIB 来创建代理对象。
注意事项
- 由于 CGLIB 是基于继承的,目标类不能是
final
的。 - 如果目标类的方法是
final
,那么这些方法将不能被代理增强。 - 生成代理对象是有一定性能开销的,但在频繁调用时,CGLIB 的性能通常优于 JDK 动态代理。
总结
CGLIB 动态代理是 Java 中实现动态代理的一种强大工具,尤其是在类没有实现接口时,它的作用尤为明显。通过 CGLIB,我们可以在运行时生成类的代理,并在方法前后添加自定义逻辑,极大地提升了代码的灵活性和可扩展性。