代理模式是很常见的一种设计模式,大量使用在AOP,他的主要目的是:
- 控制目标对象的访问
- 修改目标对象的方法(网上常见的说法叫增强,其实也可以削弱,所以我认为"修改"更恰当)
- 遵循开闭原则
本文将通过几个简单Demo层层深入代理模式的设计思想。
代理模式角色划分
目标对象:target object
代理对象:proxy object
静态代理与动态代理的区别
静态代理:代理类由程序员手动创建
动态代理:代理类由程序动态生成
模型类User
public class User {
public User(String name, int age) {
this.name = name;
this.age = age;
}
private String name;
private int age;
}
静态代理
继承方式
业务类UserService
public class UserService {
public int insertUser(User user) {
System.out.println("假装访问数据库,插入一条User数据");
return 1;
}
}
启动类Application
public class Application {
public static void main(String[] args) {
UserService userService = new UserService();
User user = new User("tbryant", 18);
userService.insertUser(user);
}
}
如果需要记录insertUser方法的执行时间,我们可以在insertUser方法内部添加代码,但考虑到以下两点,不建议这么做。
1.在没有业务类源码的情况下,无法对方法进行修改
2.有多处调用insertUser方法,但并非所有地方都要记录执行时间
这个时候就能使用代理,最简单的方式就是新写一个类,继承UserService,记录执行时间。
记录执行时间类UserServiceTime
public class UserServiceTime extends UserService {
@Override
public int insertUser(User user) {
System.out.println("=====记录执行时间=====");
return super.insertUser(user);
}
}
这样可以在调用的地方控制,如果不需要记录执行时间就用UserService类,需要则用UserServiceTime类。
换一个需求,如果需要为insertUser方法添加日志,就要再新写一个类,继承UserService,记录执行日志。
记录执行日志类UserServiceLog
public class UserServiceLog extends UserService {
@Override
public int insertUser(User user) {
System.out.println("=====记录执行日志=====");
return super.insertUser(user);
}
}
如果需求又变了,要先记录执行时间,再记录执行日志,那就再写一个类,继承UserServiceLog类。
先记录执行时间类,再记录执行日志类UserServiceTimeLog
public class UserServiceTimeLog extends UserServiceLog {
@Override
public int insertUser(User user) {
System.out.println("=====记录执行时间=====");
return super.insertUser(user);
}
}
如果需求变成先记录执行日志,再记录执行时间,那么继承方式的静态代理是不能调整顺序的,只能再写一个UserServiceLogTime类,继承UserServiceTime类。。。这样围绕一个业务类就会衍生无数个类,这就是继承方式的缺陷,因此需要使用聚合方式。
聚合(接口)方式
聚合方式有两个特点:目标对象和代理对象实现同一个接口;代理对象包含目标对象。
业务接口IUserService
public interface IUserService {
public int insertUser(User user);
}
业务类UserService
public class UserService implements IUserService {
@Override
public int insertUser(User user) {
System.out.println("假装访问数据库,插入一条User数据");
return 1;
}
}
记录执行时间类UserServiceTime
public class UserServiceTime implements IUserService {
IUserService target;
public UserServiceTime(IUserService target) {
target = target;
}
@Override
public int insertUser(User user) {
System.out.println("=====记录执行时间=====");
return target.insertUser(user);
}
}
记录执行日志类UserServiceLog
public class UserServiceLog implements IUserService {
IUserService target;
public UserServiceLog(IUserService target) {
target = target;
}
@Override
public int insertUser(User user) {
System.out.println("=====记录执行日志=====");
return target.insertUser(user);
}
}
启动类Application
public class Application {
public static void main(String[] args) {
User user = new User("tbryant", 18);
System.out.println("demo1:原逻辑");
IUserService target = new UserService();
target.insertUser(user);
System.out.println("demo2:记录执行时间");
IUserService proxyTime = new UserServiceTime(target);
proxyTime.insertUser(user);
System.out.println("demo3:记录执行日志");
IUserService proxyLog = new UserServiceLog(target);
proxyLog.insertUser(user);
System.out.println("demo4:先记录执行日志,再记录执行时间");
IUserService proxyLogTime = new UserServiceLog(proxyTime);
proxyLogTime.insertUser(user);
System.out.println("demo5:先记录执行时间,再记录执行日志");
IUserService proxyTimeLog = new UserServiceTime(proxyLog);
proxyTimeLog.insertUser(user);
}
}
执行结果
聚合方式相比继承方式,其优点是:在代理每种功能的时候只需要写一个类,然后可以在调用的地方控制顺序,能够灵活应对需求变化。但demo中只代理了一个接口,如果有多个接口,仍然存在类过多的问题,因此需要使用动态代理。
动态代理
在spring中大量运用代理,所用的技术是JDK动态代理和CGLib动态代理。实现动态代理的技术还有很多,本文只讨论JDK动态代理和CGLib动态代理两种。
JDK动态代理
基于聚合方式。
特点:代理类与目标类实现同一个接口。
本文仅展示简单Demo,手动模拟JDK动态代理,请参考此链接Java基础 代理模式 实现JDK动态代理。
业务接口IUserService
public interface IUserService {
public int insertUser(User user) throws Throwable;
}
业务类UserService
public class UserService implements IUserService {
@Override
public int insertUser(User user) {
System.out.println("假装访问数据库,插入一条User数据");
return 1;
}
}
代理逻辑类IUserServiceInvocationHandler
public class IUserServiceInvocationHandler implements InvocationHandler {
private Object target;
public IUserServiceInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("JDK动态代理:调用IUserServiceInvocationHandler.invoke方法");
return method.invoke(target, args);
}
}
启动类Application
public class Application {
public static void main(String[] args) throws Throwable {
User user = new User("tbryant", 18);
// JDK动态代理
IUserService jdkProxy = (IUserService) Proxy.newProxyInstance(Application.class.getClassLoader(),
new Class[]{IUserService.class}, new IUserServiceInvocationHandler(new UserService()));
jdkProxy.insertUser(user);
}
}
CGLib动态代理
基于继承方式。底层使用ASM技术操作.class文件。
特点:代理类继承目标类,代理类实现EnhancedConfiguration接口。
添加依赖build.gradle
// https://mvnrepository.com/artifact/cglib/cglib
compile group: 'cglib', name: 'cglib', version: '3.2.11'
业务类UserService
public class UserService {
public int insertUser(User user) {
System.out.println("假装访问数据库,插入一条User数据");
return 1;
}
}
代理逻辑类UserServiceMethodInterceptor
public class UserServiceMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("CGLib动态代理:调用UserServiceMethodInterceptor.intercept方法");
return proxy.invokeSuper(obj, args);
}
}
启动类Application
public class Application {
public static void main(String[] args) {
User user = new User("tbryant", 18);
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserService.class);
enhancer.setCallback(new UserServiceMethodInterceptor());
enhancer.setCallbackType(UserServiceMethodInterceptor.class);
UserService proxy = (UserService) enhancer.create();
proxy.insertUser(user);
}
}
源码地址:ProxyPatternDemo模块