网站Logo Ilren 小记

Java设计模式-代理模式

jack
16
2023-05-13

🎭 代理模式(Proxy Pattern):控制对象访问的智能替身

模式简介
代理模式是一种结构型设计模式,为其他对象提供一种代理以控制对这个对象的访问。就像明星的经纪人——外界不直接接触明星本人,而是通过经纪人来安排各种事务。

📦 应用场景

  • 远程代理(Remote Proxy):为远程对象提供本地代表(如RMI)

  • 虚拟代理(Virtual Proxy):延迟加载大资源对象(如图片懒加载)

  • 保护代理(Protection Proxy):控制对敏感对象的访问权限

  • 智能引用(Smart Reference):在访问对象时执行额外操作(如引用计数)

  • Spring AOP的动态代理

  • MyBatis的Mapper接口代理

🧠 核心实现思路

  1. Subject(抽象主题):定义真实主题和代理的共同接口

  2. RealSubject(真实主题):业务逻辑的实际执行者

  3. Proxy(代理):持有真实主题的引用,控制对它的访问

🧱 代理模式的三种实现方式

1. 静态代理(编译时确定)

// 抽象主题
interface Image {
    void display();
}

// 真实主题
class RealImage implements Image {
    private String filename;
    
    public RealImage(String filename) {
        this.filename = filename;
        loadFromDisk();
    }
    
    private void loadFromDisk() {
        System.out.println("Loading " + filename);
    }
    
    @Override
    public void display() {
        System.out.println("Displaying " + filename);
    }
}

// 代理类
class ProxyImage implements Image {
    private RealImage realImage;
    private String filename;
    
    public ProxyImage(String filename) {
        this.filename = filename;
    }
    
    @Override
    public void display() {
        if (realImage == null) {
            realImage = new RealImage(filename); // 延迟加载
        }
        realImage.display();
    }
}

// 使用
Image image = new ProxyImage("test.jpg");
image.display(); // 第一次访问时加载真实图片

2. JDK动态代理(运行时生成)

// 抽象主题
interface UserService {
    void addUser(String name);
}

// 真实主题
class UserServiceImpl implements UserService {
    public void addUser(String name) {
        System.out.println("添加用户:" + name);
    }
}

// 调用处理器
class LogInvocationHandler implements InvocationHandler {
    private Object target;
    
    public LogInvocationHandler(Object target) {
        this.target = target;
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("【日志】调用方法:" + method.getName());
        return method.invoke(target, args);
    }
}

// 使用
UserService realService = new UserServiceImpl();
UserService proxy = (UserService) Proxy.newProxyInstance(
    realService.getClass().getClassLoader(),
    realService.getClass().getInterfaces(),
    new LogInvocationHandler(realService)
);
proxy.addUser("张三");

3. CGLIB动态代理(无需接口)

// 真实类(无需实现接口)
class ProductService {
    public void saveProduct(String name) {
        System.out.println("保存商品:" + name);
    }
}

// 方法拦截器
class AuthInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("【权限校验】");
        return proxy.invokeSuper(obj, args);
    }
}

// 使用
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(ProductService.class);
enhancer.setCallback(new AuthInterceptor());
ProductService proxy = (ProductService) enhancer.create();
proxy.saveProduct("手机");

💎 最佳实践推荐

Spring AOP 风格代理

@Aspect
@Component
public class LogAspect {
    @Around("execution(* com.example.service.*.*(..))")
    public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("方法调用前:" + joinPoint.getSignature());
        Object result = joinPoint.proceed();
        System.out.println("方法调用后");
        return result;
    }
}

MyBatis Mapper 代理

// 接口定义
public interface UserMapper {
    @Select("SELECT * FROM users WHERE id = #{id}")
    User selectUser(int id);
}

// Spring中自动生成代理
@Autowired
private UserMapper userMapper; // MyBatis动态创建的代理实例

💣 常见问题与解决方案

问题1:JDK代理 vs CGLIB代理?

解决方案

  • JDK代理:基于接口(Spring默认,性能较好)

  • CGLIB代理:基于继承(可代理普通类,创建稍慢)

问题2:如何选择代理方式?

决策树

有接口吗?
├─ 是 → 使用JDK动态代理
└─ 否 → 使用CGLIB代理

问题3:代理对象调试困难?

解决方案

  • 使用System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true")保存代理类

  • 调试时关注InvocationHandler.invoke()方法

📊 模式对比

代理类型

优点

缺点

适用场景

静态代理

结构清晰

类数量爆炸

简单固定场景

JDK动态代理

无需额外依赖

只能代理接口

Spring AOP默认

CGLIB代理

可代理普通类

需额外库

需要代理类的场景


📚 实际应用案例

  1. Spring事务管理

    @Transactional  // 基于代理实现
    public void transferMoney() { ... }
  2. RPC框架客户端

    @Reference  // Dubbo生成的远程服务代理
    private UserService userService;
  3. Hibernate延迟加载

    User user = session.load(User.class, 1L); // 返回代理对象
    user.getName();  // 实际触发数据库查询

🎯 总结建议

  1. 优先选择:Spring AOP(结合注解最简洁)

  2. 性能优化:对高频访问方法考虑静态代理

  3. 设计原则:符合开闭原则(新增功能不修改原类)

  4. 避免滥用:简单场景不必使用代理

  5. 注意陷阱:自调用方法不会被代理拦截

🛠️ 工具推荐

  • Arthas:动态查看运行时的代理类

  • Byte Buddy:新一代字节码生成库

动物装饰