设计模式之代理模式

概述

代理模式通过引入一个代理对象来控制对原始对象的访问。代理可以在原始对象的方法调用前后添加额外的处理逻辑,例如权限控制、日志记录、事务管理等,而无需修改原始对象的代码。这是一种实现了 AOP (面向切面编程) 的轻量级方式。

代理模式与 Spring 的 AOP (面向切面编程) 思想非常相似,都可以在不修改原有代码和业务流程的情况下,动态地切入新代码,增加新功能。

应用场景

代理模式常用于以下场景:

  • Spring AOP:实现横切关注点的统一处理。
  • 日志打印:在方法调用前后记录日志。
  • 异常处理:统一处理方法调用中的异常。
  • 事务控制:在方法调用前后开启和关闭事务。
  • 权限控制:控制对敏感资源的访问。

代理的分类

代理模式主要分为以下三种类型:

  • 静态代理(Static Proxy): 程序员手动创建或通过工具生成代理类的源代码,并在程序运行前完成编译。代理类和委托类的关系在编译前就已确定。
  • 动态代理(Dynamic Proxy): 在运行时动态生成代理类,通常使用反射机制实现。JDK 动态代理要求委托类必须实现接口。
  • CGLIB 动态代理(CGLIB Dynamic Proxy): 使用 CGLIB 字节码操作库在运行时动态生成代理类,可以代理没有实现接口的类。但 CGLIB 不能代理 final 类。

代码示例

1. 静态代理
概念

静态代理是指在程序运行前,代理类的源代码已经存在,并且代理类和委托类的关系在编译前就已确定。

优点
  • 可以针对具体对象或接口进行代理。
缺点
  • 每个需要代理的对象都需要编写相应的代理类,代码冗余。
示例

需求: 在不修改 UserDao 类的情况下,为其 save 方法添加事务控制。

委托类

1
2
3
4
5
public class UserDao {
public void save() {
System.out.println("保存数据方法");
}
}

代理类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class UserDaoProxy extends UserDao {
private UserDao userDao;

public UserDaoProxy(UserDao userDao) {
this.userDao = userDao;
}

@Override
public void save() {
System.out.println("开启事物...");
userDao.save();
System.out.println("关闭事物...");
}
}

测试类

1
2
3
4
5
6
7
public class Test {
public static void main(String[] args) {
UserDao userDao = new UserDao();
UserDaoProxy userDaoProxy = new UserDaoProxy(userDao);
userDaoProxy.save();
}
}
2. 动态代理(JDK 动态代理)
概念

动态代理是指在程序运行时动态生成代理类,代理类不是预先存在的。JDK 动态代理使用反射机制实现,要求委托类必须实现接口。

优点
  • 无需手动编写代理类,可以在运行时动态指定代理对象,减少代码冗余。
缺点
  • 必须面向接口进行代理,目标业务类必须实现接口。
示例

接口

1
2
3
public interface UserDao {
void save();
}

接口实现类

1
2
3
4
5
6
public class UserDaoImpl implements UserDao {
@Override
public void save() {
System.out.println("保存数据方法");
}
}

InvocationHandler 实现类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

// 每次生成动态代理类对象时,实现了InvocationHandler接口的调用处理器对象
public class InvocationHandlerImpl implements InvocationHandler {
// 这其实业务实现类对象,用来调用具体的业务方法
private Object target;

// 通过构造函数传入目标对象
public InvocationHandlerImpl(Object target) {
this.target = target;
}

// 动态代理实际运行的代理方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("调用开始处理");
// 下面invoke()方法是以反射的方式来创建对象,第一个参数是要创建的对象,第二个是构成方法的参数,由第二个参数来决定创建对象使用哪个构造方法
Object result = method.invoke(target, args);
System.out.println("调用结束处理");
return result;
}
}

测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import java.lang.reflect.Proxy;

public class Test {
public static void main(String[] args) {
// 被代理对象
UserDao userDaoImpl = new UserDaoImpl();
InvocationHandlerImpl invocationHandlerImpl = new InvocationHandlerImpl(userDaoImpl);
// 类加载器
ClassLoader loader = userDaoImpl.getClass().getClassLoader();
Class<?>[] interfaces = userDaoImpl.getClass().getInterfaces();
// 主要装载器、一组接口及调用处理动态代理实例
UserDao newProxyInstance = (UserDao) Proxy.newProxyInstance(loader, interfaces, invocationHandlerImpl);
newProxyInstance.save();
}
}
3. CGLIB 动态代理
概念

CGLIB (Code Generation Library) 动态代理利用 ASM 开源包,对代理对象类的 Class 文件加载进来,通过修改其字节码生成子类来处理。

优点
  • 可以直接代理类,不需要实现接口。
缺点
  • 不能代理 final 类。
  • 需要导入 CGLIB 相关的 jar 包。
示例

接口

1
2
3
public interface UserDao {
void save();
}

接口实现类

1
2
3
4
5
6
public class UserDaoImpl implements UserDao {
@Override
public void save() {
System.out.println("保存数据方法");
}
}

CGLIB 动态代理类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

// 代理主要类
public class CglibProxy implements MethodInterceptor {
private Object targetObject;

// 这里的目标类型为Object,则可以接受任意一种参数作为被代理类,实现了动态代理
public Object getInstance(Object target) {
// 设置需要创建子类的类
this.targetObject = target;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(target.getClass());
enhancer.setCallback(this);
return enhancer.create();
}

// 代理实际方法
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("开启事物");
Object result = proxy.invoke(targetObject, args);
System.out.println("关闭事物");
// 返回代理对象
return result;
}
}

测试类

1
2
3
4
5
6
7
public class Test {
public static void main(String[] args) {
CglibProxy cglibProxy = new CglibProxy();
UserDao userDao = (UserDao) cglibProxy.getInstance(new UserDaoImpl());
userDao.save();
}
}