设计模式-代理模式

代理模式

类型 优点 缺点
静态代理 简单直观,类型安全 代码重复,每个接口都要写代理类
JDK动态代理 无需每个接口写代理类 只能代理接口
CGLIB动态代理 可以代理类 需要第三方库,性能略低
SpringAop 声名式,零入侵 只能代理public方法

代码

第一部分:普通例子(静态代理,编译期绑定)

核心痛点:为了给每个方法加日志,你必须手动创建一个实现相同接口的代理类,代码冗余且难以维护。

java 复制代码
// 1. 定义业务接口
public interface UserService {
    void saveUser(String name);
    String findUser(Long id);
}

// 2. 真实业务实现(只关心核心逻辑)
public class UserServiceImpl implements UserService {
    @Override
    public void saveUser(String name) {
        System.out.println("核心业务:保存用户 " + name);
    }
    @Override
    public String findUser(Long id) {
        System.out.println("核心业务:查询用户 " + id);
        return "User_" + id;
    }
}

// 3. 静态代理类(必须实现同一接口,并持有真实对象引用)
public class UserServiceStaticProxy implements UserService {
    private final UserService target; // 目标对象

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

    @Override
    public void saveUser(String name) {
        System.out.println("[静态代理] 开始保存...");
        long start = System.currentTimeMillis();
        target.saveUser(name); // 调用真实逻辑
        System.out.println("[静态代理] 保存结束,耗时: " + (System.currentTimeMillis() - start) + "ms");
    }

    @Override
    public String findUser(Long id) {
        System.out.println("[静态代理] 开始查询...");
        long start = System.currentTimeMillis();
        String result = target.findUser(id);
        System.out.println("[静态代理] 查询结束,耗时: " + (System.currentTimeMillis() - start) + "ms");
        return result;
    }
}

// 4. 客户端调用(必须显式创建代理对象)
public class Client {
    public static void main(String[] args) {
        // 真实对象
        UserService realService = new UserServiceImpl();
        // 手动包装成代理
        UserService proxy = new UserServiceStaticProxy(realService);
        
        proxy.saveUser("张三");
        // 输出:开始保存... -> 核心业务... -> 结束,耗时...
        proxy.findUser(1L);
    }
}

静态代理的致命伤:如果我有 OrderService、ProductService 等几十个接口,就得写几十个代理类,且每个方法都要重复写日志代码,完全违背 DRY 原则。


第二部分:Spring中的例子(动态代理 + AOP,运行时绑定)

在Spring中,你完全不需要手动创建 UserServiceStaticProxy 类。Spring 会在容器启动时,利用 JDK动态代理 或 CGLIB 自动生成代理对象,并将横切逻辑织入。

java 复制代码
// 1. 业务代码保持纯净(无任何侵入)
@Service // 交给Spring管理
public class UserServiceImpl implements UserService {
    @Override
    public void saveUser(String name) {
        System.out.println("核心业务:保存用户 " + name);
    }
    @Override
    public String findUser(Long id) {
        System.out.println("核心业务:查询用户 " + id);
        return "User_" + id;
    }
}

// 2. 定义一个切面类(拦截所有Service层的public方法)
@Aspect
@Component
public class LoggingAspect {

    // 切点表达式:拦截 service 包下所有类的所有方法
    @Around("execution(* com.example.service.*.*(..))")
    public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
        String methodName = joinPoint.getSignature().getName();
        
        System.out.println("[Spring AOP] 开始执行: " + methodName);
        long start = System.currentTimeMillis();
        
        try {
            // 重点:这里执行目标对象的真实方法(相当于静态代理里的 target.saveUser())
            Object result = joinPoint.proceed();
            return result;
        } finally {
            long duration = System.currentTimeMillis() - start;
            System.out.println("[Spring AOP] 执行结束,耗时: " + duration + "ms");
        }
    }
}

// 3. 业务调用(完全无感知,直接从容器拿Bean)
@RestController
public class UserController {
    @Autowired
    private UserService userService; // 注意:Spring注入的其实是代理对象!

    @GetMapping("/save")
    public void test() {
        userService.saveUser("李四"); 
        // 实际输出:Spring AOP 开始 -> 核心业务 -> Spring AOP 结束
    }
}