Skip to content

代理模式如何使用

代理模式概述

  • 定义
  • 代理模式是一种结构型设计模式,通过为目标对象提供一个代理对象(Proxy),控制对目标对象的访问,增加额外功能(如权限检查、日志、延迟加载)。
  • 核心思想
  • 代理对象与目标对象实现相同接口,客户端通过代理访问目标,代理在调用前后插入逻辑。
  • 类型
  • 静态代理:编译时定义代理类。
  • 动态代理:运行时生成代理(JDK 动态代理、CGLIB)。

如何使用

  1. 定义接口:目标对象和代理实现同一接口。
  2. 创建目标类:实现核心业务逻辑。
  3. 创建代理类
  4. 静态代理:手动编写代理类。
  5. 动态代理:使用 Proxy 或 CGLIB 动态生成。
  6. 调用代理:客户端通过代理访问目标。

核心点

  • 代理模式通过隔离客户端和目标,增强功能,Spring AOP 是典型应用。

1. 代理模式核心概念

(1) 角色

  • Subject(抽象主题)
  • 定义目标和代理的公共接口。
  • 例:interface UserService
  • RealSubject(真实主题)
  • 实现业务逻辑的目标对象。
  • 例:UserServiceImpl
  • Proxy(代理)
  • 持有 RealSubject 引用,控制访问并添加逻辑。
  • 例:UserServiceProxy
  • Client(客户端)
  • 通过代理调用服务。

(2) 类图

Client --> [Subject] <|-- [RealSubject]
               ^        <|-- [Proxy]
               |             (holds RealSubject)

(3) 优势

  • 控制访问:如权限验证。
  • 功能增强:如日志、事务。
  • 解耦:客户端不直接依赖目标。

2. 使用方式

(1) 静态代理

  • 场景
  • 简单场景,代理逻辑固定。
  • 步骤
  • 定义接口。
  • 实现目标类。
  • 编写代理类,调用目标并增强。
  • 代码示例
// 1. 接口
public interface UserService {
    void getUser(int id);
}

// 2. 目标类
public class UserServiceImpl implements UserService {
    @Override
    public void getUser(int id) {
        System.out.println("Fetching user: " + id);
    }
}

// 3. 代理类
public class UserServiceProxy implements UserService {
    private UserService target;

    public UserServiceProxy(UserService target) {
        this.target = target;
    }

    @Override
    public void getUser(int id) {
        System.out.println("Logging: Before getUser");
        target.getUser(id);
        System.out.println("Logging: After getUser");
    }
}

// 4. 客户端
public class Client {
    public static void main(String[] args) {
        UserService target = new UserServiceImpl();
        UserService proxy = new UserServiceProxy(target);
        proxy.getUser(1);
    }
}
  • 输出
Logging: Before getUser
Fetching user: 1
Logging: After getUser
  • 局限
  • 每个目标类需单独代理类,代码冗余。
  • 修改逻辑需调整代理代码。

(2) JDK 动态代理

  • 场景
  • 目标对象有接口,需动态生成代理。
  • 步骤
  • 定义接口和目标类。
  • 实现 InvocationHandler,处理代理逻辑。
  • Proxy.newProxyInstance 生成代理。
  • 代码示例
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

// 接口和目标类同上

// 1. InvocationHandler
public class LoggingHandler implements InvocationHandler {
    private Object target;

    public LoggingHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Logging: Before " + method.getName());
        Object result = method.invoke(target, args);
        System.out.println("Logging: After " + method.getName());
        return result;
    }
}

// 2. 客户端
public class Client {
    public static void main(String[] args) {
        UserService target = new UserServiceImpl();
        InvocationHandler handler = new LoggingHandler(target);
        UserService proxy = (UserService) Proxy.newProxyInstance(
            target.getClass().getClassLoader(),
            target.getClass().getInterfaces(),
            handler
        );
        proxy.getUser(1);
    }
}
  • 输出:同静态代理。
  • 优势
  • 动态生成,复用逻辑。
  • 局限
  • 要求目标实现接口。

(3) CGLIB 代理

  • 场景
  • 目标无接口,或需代理非接口方法。
  • 步骤
  • 添加 CGLIB 依赖。
  • 实现 MethodInterceptor
  • Enhancer 生成子类代理。
  • 依赖(Maven):
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>
  • 代码示例
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

// 1. 目标类(无接口)
public class UserService {
    public void getUser(int id) {
        System.out.println("Fetching user: " + id);
    }
}

// 2. MethodInterceptor
public class LoggingInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("Logging: Before " + method.getName());
        Object result = proxy.invokeSuper(obj, args);
        System.out.println("Logging: After " + method.getName());
        return result;
    }
}

// 3. 客户端
public class Client {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(UserService.class);
        enhancer.setCallback(new LoggingInterceptor());
        UserService proxy = (UserService) enhancer.create();
        proxy.getUser(1);
    }
}
  • 输出:同上。
  • 优势
  • 无需接口,代理任意类。
  • 局限
  • 不能代理 final 类或方法。

3. Spring AOP 中的代理模式

  • 结合 Spring AOP(您之前提问):
  • Spring AOP 使用代理模式实现切面。
  • 实现
    • 接口类用 JDK 动态代理。
    • 无接口用 CGLIB。
  • 示例
@Aspect
@Component
public class LogAspect {
    @Around("execution(* com.example.service.*.*(..))")
    public Object log(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("Before: " + joinPoint.getSignature().getName());
        Object result = joinPoint.proceed();
        System.out.println("After: " + result);
        return result;
    }
}

@Service
public class UserService {
    public String getUser(int id) {
        return "User: " + id;
    }
}
  • 原理
    • Spring 容器为 UserService 生成代理。
    • 调用 getUser 时,触发 LogAspect 的 Around 通知。
  • 配置
@SpringBootApplication
@EnableAspectJAutoProxy(proxyTargetClass = true) // 强制 CGLIB
public class Application {}

4. 使用场景

  • 日志记录
  • 在方法前后打印日志。
  • 例:上述代码。
  • 权限验证
  • 检查用户权限(结合 JWT 单点登录)。
  • 例:
public class AuthHandler implements InvocationHandler {
    private Object target;

    public AuthHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (checkToken()) {
            return method.invoke(target, args);
        }
        throw new SecurityException("Unauthorized");
    }

    private boolean checkToken() {
        // 验证 JWT
        return true;
    }
}
  • 事务管理
  • 自动开启/提交事务。
  • 例:Spring @Transactional
  • 缓存
  • 方法结果缓存到 Redis。
  • 延迟加载
  • 延迟初始化对象(如 ORM 实体)。

5. 优缺点

优点

  • 功能扩展:不修改目标代码,添加逻辑。
  • 解耦:代理隔离客户端和目标。
  • 灵活:动态代理支持运行时调整。

缺点

  • 性能开销:代理增加调用层。
  • 复杂性:动态代理调试困难。
  • 限制:JDK 代理需接口,CGLIB 不能代理 final

6. 注意事项

  • 内部调用
  • 代理方法内部直接调用其他方法,代理逻辑不生效。
  • 例:
public class UserService {
    public void methodA() {
        methodB(); // 不触发代理
    }
}
  • 解决:注入代理或用 AopContext.currentProxy()
  • 异常处理
  • 代理需捕获异常,避免中断。
  • 性能
  • 避免复杂代理逻辑,减少反射开销。

7. 延伸与面试角度

  • 与 Spring AOP
  • Spring AOP 基于代理模式,@Aspect 是高级封装。
  • 与单点登录
  • 代理拦截请求,验证 JWT。
  • 与线程安全
  • 代理可加锁(如 ReentrantLock)。
  • 实际应用
  • 电商:权限、日志。
  • 微服务:统一认证。
  • 面试点
  • 问“使用”时,提静态/动态代码。
  • 问“原理”时,提反射和字节码。

8. 总结

代理模式通过代理对象控制目标访问,支持日志、权限、事务等增强。静态代理简单但冗余,JDK 动态代理需接口,CGLIB 更灵活。Spring AOP 是代理模式的典型应用,结合 @Aspect 实现切面。面试时,可提代码示例或场景(如 JWT 验证),结合 Spring AOP,展示理解深度。