代理模式及其应用
2022-06-07 本文已影响0人
文景大大
一、代理模式简介
代理模式旨在为服务类和客户类之间插入一个代理类,由代理类为客户类进行服务的代理访问。同时也可以对客户的行为进行增强和控制,但对客户类来说是透明的。现实中的例子有(租客、房屋中介、房子)、(结婚新人、婚庆公司、婚礼)、(业主、物业公司、小区管理)等,可以看到,客户都是通过中介代理来完成对目标资源的控制,同时中介又对资源的管理做了一定程度的增强和控制。
要想实现代理模式,必须要满足如下的要求:
- 定义行为接口;
- 服务类和代理类都要实现行为接口;
默认情况下实现的代理模式都是静态代理,参见如下章节。
二、静态代理
public interface ISubject {
void request();
}
@Slf4j
public class RealSubject implements ISubject{
@Override
public void request() {
log.info("RealSubject!");
}
}
@Slf4j
public class Proxy implements ISubject{
private ISubject subject;
public Proxy(ISubject subject){
this.subject = subject;
}
@Override
public void request() {
// 代理类执行服务类职能前后可以进行增强和控制
before();
subject.request();
after();
}
public void before(){
log.info("Proxy before...");
}
public void after(){
log.info("Proxy after...");
}
}
public class Main {
public static void main(String[] args) {
Proxy proxy = new Proxy(new RealSubject());
proxy.request();
}
}
静态代理的特点就是一个服务类对应一个代理类,但是如果服务类比较多的时候,我们就必须创建很多代理类,显得很冗余,因此才有了动态代理。
三、动态代理
相比于静态代理,我们无需为每一个服务类创建一个代理类,可以依靠Java的反射机制,在程序运行期间,动态地为目标服务对象创建代理对象,因此简化了编程工作,提高了系统地可扩展性。
3.1 JKD动态代理
@Slf4j
public class JdkHandler implements InvocationHandler {
/**
* 需要代理的目标服务对象
*/
private Object object;
public JdkHandler(Object object){
this.object = object;
}
/**
* 获取目标服务对象的代理
* @return
*/
public Object getProxy(){
Class<?> clazz = object.getClass();
return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);
}
/**
* 调用目标服务对象的某个方法,同时也可以对服务进行增强和控制
* @param proxy 代理实例
* @param method 目标服务对象的某个方法
* @param args 目标服务对象的方法入参
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 增强行为
before();
// 调用目标服务对象的某个方法
Object result = method.invoke(object, args);
// 增强行为
after();
return result;
}
public void before(){
log.info("代理增强行为 before...");
}
public void after(){
log.info("代理增强行为 after...");
}
}
// 动态代理jdk
public static void main(String[] args) {
JdkHandler jdkHandler = new JdkHandler(new RealSubject());
// 要求目标服务类必须有实现的接口
ISubject subject = (ISubject)jdkHandler.getProxy();
subject.request();
}
可以看到,我们的代理类JdkHandler
必须要实现InvocationHandler
接口,而其内部引用的目标服务对象则是通用的Object类型,而不是如上具体的ISubject
,实现了解耦通用。当执行目标服务对象中的任意方法时,代理类JdkHandler
中的invoke
方法就会得到执行。
3.2 Cglib动态代理
@Slf4j
public class CglibInterceptor implements MethodInterceptor {
/**
* 需要代理的目标服务对象
*/
private Object obj;
public CglibInterceptor(Object obj){
this.obj = obj;
}
/**
* 获取目标服务对象的代理
* @return
*/
public Object getProxy(){
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(obj.getClass());
// 设置拦截器,回调对象设置为自己
enhancer.setCallback(this);
return enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
// 增强行为
before();
// 调用目标服务对象的某个方法
Object result = methodProxy.invoke(obj, objects);
// 增强行为
after();
return result;
}
public void before(){
log.info("代理增强行为 before...");
}
public void after(){
log.info("代理增强行为 after...");
}
}
// 动态代理cglib
public static void main(String[] args) throws Exception {
CglibInterceptor cglibInterceptor = new CglibInterceptor(new RealSubject());
// 不要求目标服务类有实现的接口
RealSubject subject = (RealSubject) cglibInterceptor.getProxy();
subject.request();
}
如上示例使用的cglib在spring中自带的,因此不用单独引入cglib的maven包。总结下来,使用JDK动态代理和Cglib动态代理的区别如下:
- JDK动态代理生成的代理对象是实现目标服务对象的接口,因此要求目标服务对象必须有实现的接口;
- Cglib动态代理生成的代理对象是继承目标服务对象的类,因此不要求目标服务对象必须有实现的接口,但是目标服务对象的类和需要被代理的方法不能是final的,不然没法代理;
- 它们的原理都是动态地生成一个新的代理类,去实现被代理方法的增强逻辑;
- JDK动态代理生成效率快,执行效率慢,因为要使用反射;Cglib动态代理生成逻辑复杂,效率慢,但是执行效率快,不需要反射;
四、应用案例
4.1 通用案例
- 延迟加载,通过代理类来控制,当只有真正需要服务类的时候才进行初始化;
- 访问控制,对客户端调用服务端的请求进行鉴权控制;
- 屏蔽远程服务,代理类可以将调用远程服务的网络细节隐藏起来,使得客户端以为是在调用本地的服务;
- 日志记录,代理类可以在调用服务的前后增加日志的记录;
- 缓存请求结果,代理类可以将调用服务端的结果进行缓存;
4.2 具体案例
- Spring中的ProxyFactoryBean的getObject;
- Spring中实现AOP的AopProxy、CglibAopProxy、JdkDynamicAoproxy;当目标对象有实现接口时Spring会使用后者jdk的动态代理,当目标对象没有实现接口时就会使用Cglib动态代理。当然也可以通过配置来强制每次都使用Cglib动态代理。
- MyBatis中的MapperProxyFactory和MapperProxy,来生成Dao的动态代理,从而和Mapper配置文件映射来实现数据库的操作;
五、使用总结
5.1 优点
- 在客户端毫无察觉的情况下增强和控制服务对象;
- 代理类可以对服务类的生命周期进行管理;
- 即使服务对象还不存在,代理类也可以正常工作;
- 可以基于服务类创建多个代理类,进行不同的增强逻辑,符合开闭原则;
- 将代理对象和目标服务对象分离,实现了解耦,增强了可扩展性;
5.2 缺点
- 会增加代理类,增强和控制逻辑会将代码变得复杂;
- 代理类的增强和控制逻辑会导致服务响应延迟;
5.3 静态代理和动态代理的比较
- 静态代理需要书写源码来实现代理操作,一旦目标服务类增加了新的方法,就必须同步在代理类中增加代理操作,而动态代理是在运行时动态生成目标服务类的代理对象,无需修改或者增加代理类的源码,符合开闭原则;