引言
在生活中,我们经常能见到代理模式的身影,比如商品代购、房屋中介。
在软件开发中,代理模式为其他对象提供一种代理,以控制对这个对象的访问。代理类负责增强或控制对目标对象的访问,而核心业务逻辑仍然由被代理对象完成。这种增强常用于日志记录、权限校验、事务管理或性能监控。
代理模式主要分为三种实现方式:
- 静态代理:在编译阶段就确定了代理关系,需要为每个被代理类手动编写一个代理类。
- JDK动态代理:利用Java反射机制,在运行时动态为接口的创建代理对象
- Cglib动态代理:基于字节码技术,通过创建被代理类的子类来实现代理,不要求目标类实现接口。
用户服务调用
我们以一个简单的用户服务(UserService)为例,来逐步讲解代理模式的应用。其类图如下:
Client: 客户端,需要使用用户服务的调用方。
UserService: 用户服务接口,定义业务主要方法。
UserServiceImpl: 业务实现类。
基础代码示例
首先,我们定义服务接口和实现类,作为后续所有代理模式演示的基础。
用户服务接口
// UserService.java
public interface UserService {
User getUserById(Integer id);
Boolean addUser(User user);
}
用户服务实现类
为简化示例,我们只打印日志模拟业务操作。
// UserServiceImpl.java
@Slf4j
public class UserServiceImpl implements UserService {
@Override
public User getUserById(Integer id) {
System.out.println("UserServiceImpl 查询用户");
return new User();
}
@Override
public Boolean addUser(User user) {
System.out.println("UserServiceImpl 添加用户");
return true;
}
}
客户端使用
非常简单的调用示例
public static void main(String[] args) throws Throwable {
// 正常的客户端调用
System.out.println("===============正常调用 UserServiceImpl 方法===================");
UserService userService = new UserServiceImpl();
userService.getUserById(1);
System.out.println();
}
运行结果
===============正常调用 UserServiceImpl 方法===================
UserServiceImpl 查询用户
例子作为引子,也是我们日常开发中经常用到的核心示例
代理模式类图
除了普通用户服务调用相关组件外,代理模式新增了UserServiceProxy这个代理类。其有两个特征:
- 实现了与目标对象相同的接口
UserService, 保证了对外的行为一致性。 - 持有被代理对象
UserServiceImpl的引用,以便在执行增强逻辑后可调用真正的业务方法。
引入代理类后,Client 不再直接与 UserServiceImpl 交互,而是通过 UserServiceProxy 来间接调用。这使得 UserServiceImpl 可以更关注业务逻辑本身。
静态代理
静态代理是最简单、直观的实现方式,但缺点也很明显:每个被代理类都需要一个对应的代理类,当系统规模变大时,会产生大量的冗余代码。
静态代理类
@Slf4j
public class UserServiceProxy implements UserService {
// 持有被代理对象的引用
private final UserService userService;
public UserServiceProxy(UserService userService) {
this.userService = userService;
}
@Override
public User getUserById(Integer id) {
System.out.println("类 UserServiceProxy, 方法 -> getUserById, 参数 -> " + id);
// 调用被代理对象的真实业务逻辑
User user = userService.getUserById(id);
System.out.println("类 UserServiceProxy, 方法 -> getUserById 执行结束");
return user;
}
@Override
public Boolean addUser(User user) {
System.out.println("类 UserServiceProxy, 方法 -> addUser, 参数 -> " + user);
Boolean result = userService.addUser(user);
System.out.println("类 UserServiceProxy, 方法 -> addUser 执行结束");
return result;
}
}
客户端使用静态代理
// 这里使用静态代理对象
UserService userServiceProxy = new UserServiceProxy(userService);
userServiceProxy.getUserById(2);
运行结果
===============使用静态代理调用===================
类 UserServiceProxy, 方法 -> getUserById, 参数 -> 2
UserServiceImpl 查询用户
类 UserServiceProxy, 方法 -> getUserById 执行结束
JDK动态代理
为了解决静态代理的冗余问题,JDK提供了动态代理机制。它可以在运行时动态创建代理对象,但是要求被代理的类必须实现至少一个接口。
JDK动态代理主要涉及两个核心组件:
Proxy类: 提供newProxyInstance静态方法,用于动态创建代理对象。InvocationHandler接口: 我们需要实现这个接口,编写具体的增强逻辑。
日志处理代理类
下面我们实现一个通用的日志处理类,它可以在任意方法执行前后打印日志
public class LogHandler implements InvocationHandler {
// 持有被代理对象
private final Object target;
// 在构造方法时传入,即创建后表明对象代理了目标类
public LogHandler(Object target) {
this.target = target;
}
// 重点, 其中proxy是代理对象,method为执行的方法, args为调用方法时的参数
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 打印方法名,参数等信息
System.out.println("代理类 -> LogHandler, 方法名 -> " + method.getName());
if (args != null) {
// 打印 args 参数
for (Object arg : args) {
System.out.println("代理类 -> LogHandler, 参数 -> " + arg);
}
}
// 通过 method.invoke 来执行被代理对象的方法。
Object result = method.invoke(target, args);
// 被代理对象方法执行结束后代理类处理的事。
System.out.println("代理类 -> LogHandler, 方法名 -> " + method.getName() + " 执行结束");
return result;
}
}
方法耗时统计代理类
为了展示动态代理的灵活性,我们再实现一个通用的耗时统计类。
public class TimeCostHandler implements InvocationHandler {
private final Object target;
public TimeCostHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long start = System.currentTimeMillis();
long end = System.currentTimeMillis();
Object result = method.invoke(target, args);
System.out.println("代理类 -> TimeCostHandler, 方法 -> " + method.getName() + " 耗时 -> " + (end - start) + " 毫秒");
return result;
}
}
客户端使用JDK动态代理
System.out.println("===============使用JDK动态代理调用===================");
UserService userServiceLogProxy= (UserService)Proxy.newProxyInstance(
userService.getClass().getClassLoader(),
userService.getClass().getInterfaces(),
new LogHandler(userService));
userServiceLogProxy.getUserById(3);
System.out.println();
System.out.println("===============使用JDK动态代理复合调用1===================");
UserService composeProxy1= (UserService)Proxy.newProxyInstance(
userService.getClass().getClassLoader(),
userService.getClass().getInterfaces(),
// 注意这里是先运行UserServiceLogProxy,再运行 TimeCostHandler
new TimeCostHandler(userServiceLogProxy));
// timeCostProxy 代理了 LogProxy
composeProxy1.getUserById(4);
System.out.println();
System.out.println("===============使用JDK动态代理复合调用2===================");
// 先构建TimeCostHandler
UserService userServiceTimeCostProxy = (UserService)Proxy.newProxyInstance(
userService.getClass().getClassLoader(),
userService.getClass().getInterfaces(),
new TimeCostHandler(userService)
);
UserService composeProxy2= (UserService)Proxy.newProxyInstance(
userService.getClass().getClassLoader(),
userService.getClass().getInterfaces(),
// 注意这里是先运行TimeCostHandler,再运行 UserServiceLogProxy
new LogHandler(userServiceTimeCostProxy));
composeProxy2.getUserById(5);
System.out.println();
上面代码实现了JDK动态代理,以及复合动态代理调用。
运行结果
可以重点观察复合调用时顺序以及输出的区别
===============使用JDK动态代理调用===================
代理类 -> LogHandler, 方法名 -> getUserById
代理类 -> LogHandler, 参数 -> 3
UserServiceImpl 查询用户
代理类 -> LogHandler, 方法名 -> getUserById 执行结束
===============使用JDK动态代理复合调用1===================
代理类 -> LogHandler, 方法名 -> getUserById
代理类 -> LogHandler, 参数 -> 4
UserServiceImpl 查询用户
代理类 -> LogHandler, 方法名 -> getUserById 执行结束
代理类 -> TimeCostHandler, 方法 -> getUserById 耗时 -> 0 毫秒
===============使用JDK动态代理复合调用2===================
代理类 -> LogHandler, 方法名 -> getUserById
代理类 -> LogHandler, 参数 -> 5
UserServiceImpl 查询用户
代理类 -> TimeCostHandler, 方法 -> getUserById 耗时 -> 0 毫秒
代理类 -> LogHandler, 方法名 -> getUserById 执行结束
Cglib动态代理
Cglib(Code Generation Library)是一个强大的字节码生成库,它通过继承被代理类来创建代理,因此不要求目标类实现接口。Spring AOP在目标类未实现接口时,会默认使用Cglib。
Cglib动态代理主要涉及两个核心组件:
MethodInterceptor接口: 实现此接口增强业务逻辑。Enchancer类: 用于创建代理对象
这里我们依然以日志为例
日志处理代理类
public class LogInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("Cglib代理类 -> LogInterceptor, 方法 -> " + method.getName());
if (objects != null) {
// 打印 args 参数
for (Object arg : objects) {
System.out.println("Cglib代理类 -> LogInterceptor, 参数 -> " + arg);
}
}
// 通过MethodProxy调用被代理类的方法,性能优于反射
Object result = methodProxy.invokeSuper(o, objects);
System.out.println("Cglib代理类 -> LogInterceptor, 方法执行结束");
return result;
}
}
客户端使用Cglib动态代理
// 创建 Enhancer 对象
Enhancer enhancer = new Enhancer();
// 设置被代理的类, 基于实现子类方式实现
enhancer.setSuperclass(UserServiceImpl.class);
// 设置代理类需要做的事, MethodInterceptor实现类
enhancer.setCallback(new LogInterceptor());
// enhancer.create()方法创建出被代理对象。
UserService proxy = (UserService) enhancer.create();
proxy.getUserById(6);
System.out.println();
运行结果
===============使用CGLIB动态代理调用===================
Cglib代理类 -> LogInterceptor, 方法 -> getUserById
Cglib代理类 -> LogInterceptor, 参数 -> 6
UserServiceImpl 查询用户
Cglib代理类 -> LogInterceptor, 方法执行结束
JDK动态代理 VS Cglib动态代理
| 对比 | JDK动态代理 | Cglib动态代理 |
|---|---|---|
| 原理 | 基于反射机制,动态生成实现了接口的代理类 | 基于字节码技术,动态生成目标类的子类作为代理类 |
| 性能 | 早期版本性能较低,JDK 1.8后性能大幅提升,两者接近 | 方法调用通过 MethodProxy 转发,效率较高 |
| 适用场景 | 被代理类需要实现接口 | 被代理类不能是 final 类,方法**不能是 final 或 static** |
| 实现接口 | 实现InvocationHandler接口 | 实现 MethodInterceptor接口 |
| 代理对象创建 | 由Proxy.newProxyInstance 静态方法创建 | 由 Enhancer对象创建 |
如何选择代理方式
在实际开发中,可以遵循以下原则:
- 优先使用 JDK 动态代理:如果目标类实现了接口,优先选择 JDK 动态代理。它是 Java 原生支持,没有额外的依赖,且性能在 JDK 8+ 后已经非常优秀。
- 必要时使用 Cglib:当目标类没有实现接口,或者需要代理的方法无法通过接口暴露时,使用 Cglib。但需要注意目标类和方法不能是
final的。 - Spring AOP 的默认策略:Spring 框架会自动在两者之间切换。如果目标类实现了至少一个接口,默认使用 JDK 动态代理;否则使用 Cglib。你也可以通过配置强制使用 Cglib:
spring.aop.proxy-target-class=true。
Spring AOP实现动态代理
Spring AOP(面向切面编程)的底层正是基于JDK动态代理和Cglib动态代理实现的。下面通过一个简单的示例,展示如何在Spring Boot中使用AOP。
1、pom.xml引入aop
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2、声明自定义注解
我们定义一个 @MyLog 注解,用于标记需要打印日志的方法。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyLog {
}
3、实现切面
创建切面类, 用 @Around通知来增强所有标注了 @MyLog的方法。
@Aspect
// 将切面注册到Spring容器
@Component
public class MyLogAspect {
// @Around 注解 表示在方法执行时进行拦截,拦截的方法是 @MyLog 注解的方法
// 同样还可以用 @Before, @After, @AfterReturning, @AfterThrowing
@Around("@annotation(org.tech.annotation.MyLog)")
public Object aroundLog(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("类 -> " + joinPoint.getTarget().getClass().getName() + " 方法 -> " + joinPoint.getSignature().getName());
// 执行被代理的方法, 注意参数和返回值。
Object result = joinPoint.proceed(joinPoint.getArgs());
System.out.println("类 -> " + joinPoint.getTarget().getClass().getName() + " 方法 -> " + joinPoint.getSignature().getName() + " 执行结束");
return result;
}
}
4、开启AOP并注册Bean
在启动类上添加 @EnableAspectJAutoProxy 注解开启AOP支持,并注册我们的业务Bean。
@SpringBootApplication
@EnableAspectJAutoProxy
public class TechLabsApplication {
@Bean
public UserService userService() {
return new UserServiceImpl();
}
public static void main(String[] args) {
SpringApplication.run(TechLabsApplication.class, args);
}
}
5、使用注解@MyLog
@Slf4j
public class UserServiceImpl implements UserService {
// 这里使用@MyLog注解,被代理的方法会打印日志
@MyLog
@Override
public User getUserById(Integer id) {
System.out.println("UserServiceImpl 查询用户");
return new User();
}
@Override
public Boolean addUser(User user) {
System.out.println("UserServiceImpl 添加用户");
return true;
}
}
6、示例
通过实现ApplicationRunner接口在启动后执行方法。
@Component
public class TestApplicationRunner implements ApplicationRunner {
private final UserService userService;
public TestApplicationRunner(UserService userService) {
this.userService = userService;
}
@Override
public void run(ApplicationArguments args) throws Exception {
// 启动后运行
userService.getUserById(5);
}
}
7、结果显示
类 -> org.tech.service.impl.UserServiceImpl 方法 -> getUserById
UserServiceImpl 查询用户
类 -> org.tech.service.impl.UserServiceImpl 方法 -> getUserById 执行结束
动态代理常见场景
动态代理因其灵活性和非侵入性,在现代框架和业务系统中被广泛应用。以下是几个典型的应用场景:
- 日志记录与监控:在不修改业务代码的前提下,统一记录方法的调用参数、返回值、执行时间等,用于系统监控和问题排查。
- 事务管理:Spring 通过
@Transactional注解,利用动态代理在方法执行前后开启、提交或回滚事务,使业务代码专注于数据操作本身。 - 权限控制:在方法执行前检查当前用户是否拥有调用权限,实现统一的认证和授权逻辑。
- 缓存管理:在方法执行前先查询缓存,如果命中则直接返回,否则执行目标方法并将结果存入缓存。
总结
本文从生活中的例子切入,详细介绍了代理模式的三种主要实现方式:静态代理、JDK动态代理和Cglib动态代理。并通过一个用户服务的例子,一步步展示了如何从直接调用演进到使用代理增强。最后,通过Spring AOP的示例和常见应用场景,说明了动态代理在实际开发中的重要性。
理解代理模式的原理,不仅能帮助我们更好地使用Spring等主流框架,也能在遇到需要功能增强而又不想侵入业务代码的场景时,提供一种优雅的解决方案。