代理模式如何使用
代理模式概述
- 定义:
- 代理模式是一种结构型设计模式,通过为目标对象提供一个代理对象(Proxy),控制对目标对象的访问,增加额外功能(如权限检查、日志、延迟加载)。
- 核心思想:
- 代理对象与目标对象实现相同接口,客户端通过代理访问目标,代理在调用前后插入逻辑。
- 类型:
- 静态代理:编译时定义代理类。
- 动态代理:运行时生成代理(JDK 动态代理、CGLIB)。
如何使用
- 定义接口:目标对象和代理实现同一接口。
- 创建目标类:实现核心业务逻辑。
- 创建代理类:
- 静态代理:手动编写代理类。
- 动态代理:使用
Proxy
或 CGLIB 动态生成。 - 调用代理:客户端通过代理访问目标。
核心点
- 代理模式通过隔离客户端和目标,增强功能,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 通知。
- Spring 容器为
- 配置:
@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,展示理解深度。