1 代理模式
代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。通俗的来讲代理模式就是我们生活中常见的中介。
举个例子来说明:张三想买某种用品,虽然他可以自己去找,但是这确实太浪费时间和精力了,或者不好意思去买。于是张三就通过中介Mark来买,Mark来帮张三,张三只是负责选择自己喜欢的的size,然后付钱就可以了。
目的:
(1)通过引入代理对象的方式来间接访问目标对象,防止直接访问目标对象给系统带来的不必要复杂性;
(2)通过代理对象对原有的业务增强;
代理模式一般会有三个角色:
抽象角色:指代理角色和真实角色对外提供的公共方法,一般为一个接口
真实角色:需要实现抽象角色接口,定义了真实角色所要实现的业务逻辑,以便供代理角色调用。也就是真正的业务逻辑在此。
代理角色:需要实现抽象角色接口,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作。将统一的流程控制都放到代理角色中处理!
而访问者不再访问真实角色,而是去访问代理角色。
静态代理在使用时,需要定义接口或者父类,被代理对象与代理对象一起实现相同的接口或者是继承相同父类。一般来说,被代理对象和代理对象是一对一的关系,当然一个代理对象对应多个被代理对象也是可以的。
静态代理,一对一则会出现时静态代理对象量多、代码量大,从而导致代码复杂,可维护性差的问题,一对多则代理对象会出现扩展能力差的问题。
2 静态代理
静态代理是指在编译时由程序员创建或使用工具生成代理类。这个代理类实现了和目标对象相同的接口,并且持有目标对象的引用。静态代理类在调用方法时,可以在调用目标对象的方法之前或之后增加额外的功能。
2.1 工作原理
- 定义接口:定义一个公共接口,该接口由目标对象和代理对象实现。
- 目标对象:实现该接口的具体类,包含了业务逻辑。
- 代理对象:实现该接口的代理类,内部持有目标对象的引用。在代理对象的方法中,可以在调用目标对象的方法前后添加自定义逻辑(如日志、权限检查等)。
2.2 静态代理的示例
java
复制代码
// 定义接口
public interface Service {
void execute();
}
// 目标对象
public class RealService implements Service {
@Override
public void execute() {
System.out.println("Executing service...");
}
}
// 代理对象
public class ServiceProxy implements Service {
private RealService realService;
public ServiceProxy(RealService realService) {
this.realService = realService;
}
@Override
public void execute() {
System.out.println("Before execution...");
realService.execute();
System.out.println("After execution...");
}
}
// 使用
public class Client {
public static void main(String[] args) {
RealService realService = new RealService();
Service serviceProxy = new ServiceProxy(realService);
serviceProxy.execute();
}
}
输出:
mathematica
复制代码
Before execution...
Executing service...
After execution...
2.3 静态代理的优缺点
-
优点:
- 可以在不修改目标对象的情况下为其增加额外功能。
- 代码简单,易于理解。
-
缺点:
- 代码冗余:每个目标类都需要对应的代理类,增加了维护成本。
- 不灵活:如果接口发生改变,代理类也需要修改。
3 动态代理
动态代理是在运行时生成代理类,而不是在编译时。Java 提供了 java.lang.reflect.Proxy
类和 InvocationHandler
接口来实现动态代理。动态代理能够更灵活地处理代理逻辑,因为代理类是在运行时根据需要动态生成的。
3.1 工作原理
- 接口:动态代理通常基于接口来创建代理对象。
- InvocationHandler:定义代理行为的接口,代理对象的所有方法调用都会转发给
InvocationHandler
的invoke
方法。 - Proxy.newProxyInstance() :通过调用
Proxy.newProxyInstance
方法生成代理对象。
3.2 动态代理的示例
java
复制代码
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// 定义接口
public interface Service {
void execute();
}
// 目标对象
public class RealService implements Service {
@Override
public void execute() {
System.out.println("Executing service...");
}
}
// 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("Before execution...");
Object result = method.invoke(target, args);
System.out.println("After execution...");
return result;
}
}
// 使用
public class Client {
public static void main(String[] args) {
RealService realService = new RealService();
Service serviceProxy = (Service) Proxy.newProxyInstance(
realService.getClass().getClassLoader(),
realService.getClass().getInterfaces(),
new ServiceInvocationHandler(realService));
serviceProxy.execute();
}
}
输出:
mathematica
复制代码
Before execution...
Executing service...
After execution...
3.3 动态代理的优缺点
-
优点:
- 灵活性高:动态代理可以在运行时动态生成代理类,不需要为每个目标类手动编写代理类。
- 代码简洁:可以为多个目标类复用相同的代理逻辑。
-
缺点:
- 性能开销:由于动态代理依赖于反射机制,可能会带来一定的性能开销。
- 只能代理接口:Java 原生的动态代理只能代理实现了接口的类,不能代理普通类(但是可以使用第三方库如 CGLIB 进行类的代理)。
4 静态代理与动态代理的对比
特性 | 静态代理 | 动态代理 |
---|---|---|
代理类生成时间 | 编译时 | 运行时 |
实现方式 | 需要手动编写代理类 | 通过 Proxy 类和 InvocationHandler 接口 |
灵活性 | 较低,代理类与目标类紧密耦合 | 较高,可以为任何实现了接口的类生成代理 |
维护成本 | 较高,每个目标类都需要对应的代理类 | 较低,可以复用同一个 InvocationHandler |
性能 | 高(没有反射开销) | 较低(有反射开销) |
5 实际应用场景
- 静态代理:适用于代理类较少且功能明确的场景,如权限控制、日志记录等简单功能。
- 动态代理:常用于框架或库的设计中,如 Spring AOP、MyBatis 中的动态代理机制,能够灵活应对多变的需求。