在 Java 中,代理模式可以分为静态代理和动态代理,它们都是通过代理对象来间接调用目标对象的方法,但它们的实现方式和使用场景有所不同。本文将详细阐述静态代理与动态代理的区别,并解释为什么要称之为“静态”和“动态”。
1. 静态代理和动态代理的区别
1.1 静态代理
静态代理是指在编译期间代理类已经存在,并且代理类与被代理类实现相同的接口。在静态代理中,开发者需要手动编写代理类代码,并显式地调用被代理对象的方法。
- 实现方式:手动创建代理类,并通过组合被代理对象的方式代理其方法。
- 代理类生成时机:编译时,代理类已经存在于代码中。
- 使用场景:适用于较简单且方法数量有限的场景。
示例代码:
public interface Service {
void performTask();
}
public class RealService implements Service {
@Override
public void performTask() {
System.out.println("Performing task in RealService");
}
}
public class ProxyService implements Service {
private RealService realService;
public ProxyService(RealService realService) {
this.realService = realService;
}
@Override
public void performTask() {
System.out.println("Before task");
realService.performTask();
System.out.println("After task");
}
}
1.2 动态代理
动态代理则不同,它是在运行时动态生成代理类,开发者不需要手动编写代理类代码。Java 提供了两种主要的动态代理方式:JDK 动态代理和 CGLIB 动态代理。
-
实现方式:
- JDK 动态代理:通过
Proxy
类和InvocationHandler
接口动态生成代理类,仅支持代理接口。 - CGLIB 动态代理:通过字节码生成技术代理类,支持代理普通类。
- JDK 动态代理:通过
-
代理类生成时机:运行时通过反射或字节码生成技术创建代理类。
-
使用场景:适用于复杂场景,特别是需要代理大量类或接口时,动态代理更为灵活。
JDK 动态代理示例:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// 代理接口
public interface Service {
void performTask();
}
// 被代理类
public class RealService implements Service {
@Override
public void performTask() {
System.out.println("Performing task in RealService");
}
}
// 动态代理处理器
public class ProxyInvocationHandler implements InvocationHandler {
private Object target;
public ProxyInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Before task");
Object result = method.invoke(target, args); // 调用实际方法
System.out.println("After task");
return result;
}
}
// 客户端使用
public class Client {
public static void main(String[] args) {
Service realService = new RealService();
// 创建动态代理实例
Service proxyInstance = (Service) Proxy.newProxyInstance(
realService.getClass().getClassLoader(),
realService.getClass().getInterfaces(),
new ProxyInvocationHandler(realService)
);
proxyInstance.performTask();
}
}
创建动态代理参数解释
realService.getClass().getClassLoader()
- 类加载器
-
这个参数用于指定代理类的类加载器。由于动态代理是运行时生成代理类的字节码,在创建代理类时,Java 需要通过某个类加载器将代理类加载到内存中。在这里,我们选择使用被代理对象
realService
的类加载器,即realService.getClass().getClassLoader()
。为什么要用被代理类的类加载器?
-
代理类是为特定对象(
realService
)动态生成的,代理类与被代理类的运行环境(类加载器)应该一致,以避免命名空间或类加载问题。 -
使用被代理对象的类加载器可以确保代理类和被代理类在同一上下文中运行,避免类加载冲突或访问不到需要的资源。
realService.getClass().getInterfaces()
- 接口列表
-
动态代理要求代理类实现与被代理类相同的接口。在这里,通过
realService.getClass().getInterfaces()
获取的是被代理类RealService
实现的所有接口。 -
JDK 动态代理是基于接口的代理,这意味着只有实现了接口的类才能使用 JDK 动态代理。如果被代理类没有实现任何接口,那么 JDK 动态代理是无效的,这时可以考虑使用 CGLIB 等其他代理机制,它们可以代理没有实现接口的类。这个参数定义了代理类的“行为规范”,即代理类必须能替代被代理类的接口,从而保证在客户端看来,代理对象和被代理对象是可以互换的。
new ProxyInvocationHandler(realService)
- 调用处理器
InvocationHandler
是一个接口,它定义了代理类中方法的调用逻辑。这个参数传入的是一个实现了InvocationHandler
接口的对象——ProxyInvocationHandler
,它接收了realService
作为参数,表示在代理类中,当某个方法被调用时,实际的处理会委托给ProxyInvocationHandler
,而InvocationHandler
会通过反射机制调用被代理对象(realService
)的相应方法。- 在每次调用代理对象的方法时,都会触发
InvocationHandler
的invoke
方法,在这个方法中我们可以做额外的逻辑处理,比如日志记录、权限验证、事务管理等。
1.3 静态代理与动态代理的对比
对比项 | 静态代理 | 动态代理 |
---|---|---|
代理类生成时机 | 编译时,手动编写代理类 | 运行时,动态生成代理类 |
代理方式 | 需要实现相同的接口,手动调用被代理对象的方法 | 通过反射或字节码生成,自动调用被代理对象的方法 |
实现复杂度 | 相对简单,但需要手动创建代理类 | 相对复杂,但代理过程更灵活 |
适用场景 | 方法少、逻辑简单的场景 | 方法多、需要灵活代理的场景 |
性能 | 性能稍高,代理类提前生成 | 性能稍低,代理类在运行时生成 |
扩展性 | 不易扩展,每个接口都需要一个对应的代理类 | 容易扩展,代理多个类或接口更加灵活 |
2. 为什么叫“静态”和“动态”?
2.1 静态代理
静态代理之所以称为“静态”,是因为代理类在编译期间已经固定,即代理类是显式定义的,开发者需要在代码中手动编写并维护这些代理类。每次如果要更改代理的行为,或者代理不同的类,开发者都需要重新编写或修改代理类。因此,静态代理具有静态固定的特征,代理的类和逻辑都在编译阶段确定。
- 静态的含义:编译期确定,代理类是固定的,不会在运行时动态生成。
2.2 动态代理
动态代理则称为“动态”,是因为代理类并不是在编译期间定义的,而是在运行时通过反射或字节码生成技术动态生成。开发者不需要手动编写代理类,JDK 或 CGLIB 在运行时根据需要动态地创建代理类,并将方法调用委托给实际对象。这种方式非常灵活,可以在运行时根据需求动态决定代理的逻辑或代理的对象。
- 动态的含义:运行期生成,代理类是动态生成的,具有高度的灵活性。
3. 总结
静态代理和动态代理是两种常用的代理模式,尽管它们的实现方式不同,但本质都是通过代理类控制对目标对象的访问。静态代理的代理类在编译期生成,比较简单易懂,但缺乏灵活性;而动态代理则是在运行时生成代理类,提供了更强的灵活性,适合于代理多个类或接口的场景。
因此,它们被称为“静态”和“动态”主要取决于代理类的生成时机。静态代理在编译时生成,表现为“静态”;而动态代理在运行时生成,表现为“动态”。