静态代理
静态代理是一种在编译时就确定代理类的设计模式。在静态代理中,代理类和被代理类(即目标类)在编译时就已经确定,并且代理类实现了与目标类相同的接口或继承了相同的类,从而能够在代理类中控制对目标类方法的访问。
静态代理的基本结构
静态代理通常涉及三个主要角色:
- 接口(或父类):定义了目标类和代理类的共同方法。
- 目标类:实现接口的具体业务逻辑类,即实际执行操作的类。
- 代理类:实现接口并包含对目标类的引用,用于在执行目标类方法前后添加额外的功能。
静态代理的实现示例
假设我们有一个接口 Service
,目标类 ServiceImpl
实现了该接口,并且代理类 ServiceProxy
也实现了该接口。
// 接口
public interface Service {
void performAction();
}
// 目标类
public class ServiceImpl implements Service {
@Override
public void performAction() {
System.out.println("执行 ServiceImpl 类中的 performAction 方法.");
}
}
// 代理类
public class ServiceProxy implements Service {
private ServiceImpl serviceImpl;
public ServiceProxy(ServiceImpl serviceImpl) {
this.serviceImpl = serviceImpl;
}
@Override
public void performAction() {
System.out.println("在执行 ServiceImpl 类中的 performAction 方法之前.");
serviceImpl.performAction();
System.out.println("在执行 ServiceImpl 类中的 performAction 方法之后.");
}
}
// 使用代理
public class Main {
public static void main(String[] args) {
ServiceImpl serviceImpl = new ServiceImpl();
Service serviceProxy = new ServiceProxy(serviceImpl);
serviceProxy.performAction();
}
}
输出:
在执行 ServiceImpl 类中的 performAction 方法之前
执行 ServiceImpl 类中的 performAction 方法
在执行 ServiceImpl 类中的 performAction 方法之前
静态代理的特点
- 预定义性:代理类在编译时就确定了,且代理类的代码是手动编写的,因此它的功能是预定义的。
- 扩展性:静态代理可以在不修改目标类的情况下,为目标类的方法添加一些额外的功能,如日志记录、权限控制等。
- 代码冗余:如果有多个目标类需要代理,可能会导致大量的重复代码,因为每个目标类都需要对应的代理类。
- 耦合性:代理类需要依赖具体的目标类,这使得静态代理在某种程度上增加了代码的耦合度。
静态代理的优缺点
-
优点:
- 控制性:可以在方法调用的前后进行额外操作,适合对一些特定行为进行控制。
- 简洁性:代理类的行为是明确且可控的,易于理解。
-
缺点:
- 代码冗余:如果有很多接口或类需要代理,会产生大量的代理类,导致代码量增大。
- 不够灵活:由于代理类在编译时就确定,无法根据运行时的需求动态生成代理类。
动态代理
动态代理是一种在运行时生成代理类的设计模式,代理类不需要在编译时就确定。Java 中的动态代理通过反射机制来实现,它允许在运行时动态地为接口生成代理类,从而在方法调用时添加额外的行为,例如日志记录、事务管理、权限控制等。
动态代理的基本概念
在动态代理中,有两个主要角色:
- 代理对象(Proxy Object):这是在运行时生成的代理类实例,负责调用目标对象的方法并可以在调用前后执行额外的逻辑。
- Invocation Handler:这是一个处理器接口,定义了代理对象在方法调用时需要执行的逻辑。
InvocationHandler
接口只有一个invoke
方法,负责处理对代理对象方法的调用。
动态代理的实现
Java 中的动态代理主要通过 java.lang.reflect.Proxy
类和 java.lang.reflect.InvocationHandler
接口来实现。
示例
假设我们有一个接口 Service
,及其实现类 ServiceImpl
:
public interface Service {
void performAction();
}
public class ServiceImpl implements Service {
@Override
public void performAction() {
System.out.println("执行 ServiceImpl 类中的 performAction 方法.");
}
}
现在,我们通过动态代理来为 Service
接口生成一个代理对象,在方法调用前后添加一些额外的逻辑。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// 创建 InvocationHandler 实现类
public class ServiceInvocationHandler implements InvocationHandler {
private Object target;
public ServiceInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("先执行于" + target.getClass().getSimpleName());
// 调用目标对象的方法
Object result = method.invoke(target, args);
System.out.println("后执行于" + target.getClass().getSimpleName());
return result;
}
}
// 使用动态代理生成代理对象
public class Main {
public static void main(String[] args) {
// 创建目标对象
Service service = new ServiceImpl();
// 创建代理对象
Service proxyInstance = (Service) Proxy.newProxyInstance(
service.getClass().getClassLoader(),
service.getClass().getInterfaces(),
new ServiceInvocationHandler(service)
);
// 调用代理对象的方法
proxyInstance.performAction();
}
}
输出:
先执行于 ServiceImpl
执行 ServiceImpl 类中的 performAction 方法.
后执行于 ServiceImpl
动态代理的特点
-
灵活性:
- 代理类在运行时动态生成,意味着你可以在运行时决定代理的行为,而不需要在编译时确定。
-
减少重复代码:
- 动态代理可以复用相同的
InvocationHandler
来代理不同的目标对象,避免了静态代理中为每个目标对象创建一个代理类的重复代码。
- 动态代理可以复用相同的
-
解耦:
- 通过动态代理,可以将横切关注点(如日志记录、事务管理)与业务逻辑解耦,使代码更加模块化、易于维护。
-
适用于接口:
- 在 Java 的标准动态代理机制下,只有实现接口的类才能使用动态代理。如果没有接口,可以使用字节码操作库(如 CGLIB)实现对类的动态代理。
动态代理的应用场景
- AOP(面向切面编程):动态代理是 Spring AOP 的核心技术,通过代理为目标方法添加切面逻辑。
- RPC(远程过程调用):在一些 RPC 框架中,动态代理用于封装远程调用的细节,使得调用远程方法像调用本地方法一样简单。
- 拦截器:动态代理可以用于在方法执行前后执行一些拦截逻辑,比如权限验证、日志记录等。
动态代理与静态代理的对比
特点 | 静态代理 | 动态代理 |
---|---|---|
代理类的生成时间 | 编译时生成 | 运行时动态生成 |
代码实现 | 代理类代码手动编写 | 代理类通过反射机制动态生成 |
代理对象的数量 | 每个目标类都需要一个代理类 | 通过同一个代理类处理多个目标对象 |
扩展性 | 扩展性较差,添加新功能时需要修改代理类代码 | 扩展性好,可以在运行时灵活添加功能 |
适用范围 | 适用于结构简单、功能明确的场景 | 适用于复杂的场景,如AOP、RPC等 |
总结
静态代理适用于简单的场景,但在面对复杂需求时,动态代理更为合适。动态代理通过在运行时生成代理类,提供了极大的灵活性和扩展性,使得代码更具模块化和可维护性。它广泛应用于框架开发、AOP编程、RPC调用等领域,是Java编程中非常重要的技术手段。