前言: 最近在学习spring,mybatis的时候,了解到它们两个底层都用到了动态代理模式来实现功能,所以,有了学习与了解代理模式的想法.
1. 应该如何定义代理模式?
在英文术语上,代理用Proxy表示,由Proxy可以延伸想到代理人,受托人这些名词,顾名思义,这些名词最终含义都可以用一句话来形容:
++即代理对象可以执行被代理对象的大部分功能,对去其他对象进行访问和操作++
如果用上述话语形容,会觉得抽象,晦涩,难于理解,因此我觉得可以用生活中的示例来形容会更加具体,形象化
示例:
当你银行里面有一大笔固定资产,你现在并没有急需要使用这笔钱的地方,但让其趟在银行生灰又太亏了,这时就可以找一个职业投资人,让他拿着你的钱去进行投资等操作
在上述示例中:
你=被代理对象,投资人=代理对象,钱=需要控制的对象,投资市场=需要访问的对象
1.1 代理模式有什么作用? 可以解决哪些问题?
1.1.1 作用
中介隔离作用:
继续以上面的投资为例子,如果要你去投资,你怎么知道应该投什么基金赚钱?应该怎么投?这都是你这个"有钱人"没法做到的事儿或做了没好处的事儿,而投资人在这之中则起到了中介的作用,它一个很大的特征就是,被代理对象想要委托对象实现的方法或接口,代理对象都有.
因此代理对象就像被代理对象和委托对象的中间件,隔离了被代理对象和委托对象之间的交互,全部交互都需要经过代理对象来完成
便于扩展:
在一个已经开发好的程序中,不应该随便去修改别已经写好的代码或者方法,如果想要修改,可以通过代理的方式去扩展该方法
开闭原则,增加功能:
代理类除了是客户类和委托类的中介外,还可以通过代理类增加额外功能扩展委托类的功能.这样只需要修改代理类而不需要修改委托类,符合代码设计的开闭原则.
代理类主要负责为委托类预处理信息,过滤信息,把消息转发给委托类,以及事后对返回结果的处理等.代理类本身并不真正实现服务,而是通过调研委托类相关方法,提供特定服务.真正的业务功能仍然由委托类实现,但可以在业务功能执行前后加入一些公共服务.比如想给项目加入缓存,日志等功能,就可以使用代理类完成,没必要打开已经封装好的委托类.
1.1.2 可以解决什么问题?
- 远程代理: 为位于两个不同地址空间的对象提供了一种另类的访问方式,可以将一些消耗资源较多的对象和操作移至性能更好的计算机中,以提高系统整体运行效率
- 虚拟代理: 通过一个消耗资源较少的对象代表一个资源消耗较多的对象,可以在一定程度上节省系统的运行开销
- 缓冲代理: 为某一个操作的结果提高临时缓存存储空间,以便后续使用中能共享结果,优化系统性能,缩短执行时间
- 保护代理: 可以控制对一个对象的访问权限,为不同用户提供不同级别的使用权限
- 智能引用: 为一个对象的访问(引用)提供一些额外的操作时可以使用
2 代理模式分类之静态代理&动态代理
如果按照代理创建的时期进行分类,可以分为静态代理,动态代理.
静态代理由开发人员创建或特定工具自动生成源代码,再对其编译.即在程序运行之前,代理类就已经被创建了
动态代理是在程序运行时通过反射机制动态创建的
2.1 什么是静态代理?怎么用?
静态代理指已经预先确定了代理和被代理对象之间的关系,例如你和你选择的投资者在进行投资时就已经确定好了雇佣关系. 也就是说在编译前就已经指定好了代理和被代理之间的依赖关系.
用代码来实现这两者之间的关系如下:
首先定义一个代表投资领域的接口
public interface Invest{
//投资股票
void stock(String stockName,int money);
//投资基金
void fund(String fundName,int money);
}
创建老板类型,实现invest接口
public class Boss implements Invest{
@Override
public void stock(String stockName,int money){
System.out.println("准备投资股票"+stockName+money+"人民币");
}
@Override
public void fund(String fundName,int money){
System.out.println("准备投资基金"+fundName+money+"人民币");
}
}
代理投资人投资类,实现Invest接口
public class ProxyInvestor implements Invest{
Invest invest;//持有要代理的那个对象
public ProxyInvestor(Invest invest){
this.invest=invest;
}
@Override
public void stock(String stockName,int money){
invest.stock(stockName,money);
}
@Override
public void fund(String fundName,int money){
invest.fund(fundName,money);
}
}
代理对象的静态代理工厂
public class ProxyFactory{
public static Invest getProxy(){
return new ProxyInvestor(new Boss());
}
}
测试
public static void main(String[] args){
ProxyFactory.getProxy().stock("白酒",1344);
ProxyFactory.getProxy().fund("白酒",134);
}
解析:
可以看到,投资人全权代理了老板的投资行为,使用这种代理模式,可以让老板不需要去了解投资知识就可以进行投资.
同时投资人(代理类)可以对老板(委托类)进行一些方法扩展,可以做到代码设计中开闭原则的情况下对委托类进行功能扩展,但同时得为每一个服务都创建代理类,导致工作量很大,不易于管理;而且接口一旦改变,代理类也需要改变.
2.2 动态代理模式
相较于静态代理模式的预先设计好代理类,动态代理的意义在于生产一个代理对象,来代理真实对象,从而控制真实对象的访问.
动态代理本质而言仍然是代理模式,但他与静态代理不同的是静态代理在执行前就已经确定好代理类和委托类之间的代理关系,而动态代理则是在运行的时候才确定其代理关系的.
代理对象和委托对象之前建立代理关系只需两个步骤
-
代理对象和真实对象建立代理关系
-
实现代理对象的代理逻辑方法.
在java开发中,有许多动态代理技术,比如JDK,CGLIB,Javassist,ASM
其中JDK,CGLIB是最常用的动态代理技术.并且JDK动态代理是JDK自带的功能,CGLIB则是第三方提供的一种技术.
目前,spring常用JDK和CGLIB实现动态代理,Mybatis还是用了javassist
2.2.1 JDK动态代理
JDK动态代理是java.lang.reflect.*包提供的方式,它必须借助接口才能产生代理对象,所以想要实现JDK动态代理,接口必不可少.
JDK实现动态代理实际上最需要使用的是newProxyInstance方法,但是该方法需要接收3个参数
static Object newProxyInstance(ClassLoader loader,Class<T>[] interfaces,InvocationHandler h);
/*
其中
Object: 指的就是代理对象,即通过JDK动态代理返回一个代理对象
ClassLoader loader: 代表与目标对象相同的类加载器,即目标对象.getClass().getClassLoader()
Class<T>[] interfaces: 代表与目标对象实现的所有接口字节码对象数组,使用数组是因为目标类可以有多个接口
InvocationHandler h: 事件处理,即执行目标对象的方法时,会触发事件处理器的方法,然后会把当前执行目标对象的方法作为参数传入
*/
定义接口
public interface Invest{
void stock(String stockName,int money);
void fund(String fundName,int money);
}
目标对象实现接口
public class Boss implements Invest{
@Override
public void stock(String stockName,int money){
System.out.println("准备投资股票"+stockName+money+"人民币");
}
@Override
public void fund(String fundName,int money){
System.out.println("准备投资基金"+fundName+money+"人民币");
}
}
接下来就是建立代理对象和真实服务对象的关系,在JDK动态代理中,要实现代理逻辑就必须去实现java.lang.reflect.InvocationHandler接口,它里面定义了invoke方法,并提供接口数组用于下挂代理对象
构建动态代理直接的代理关系
public class ProxyFactory{
/**
建立代理对象和真实对象的代理关系,并返回代理对象
@param target: 真实对象
@return : 返回代理对象
*/
public static Object getProxy(Object target){
InvaocationHandler handler=new Investor(target);
return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),handler);
}
}
构建出一个动态代理类
public class Investor implements InvocationHandler{
//被代理的对象(真实对象)
private Object target;
public Investor(Object target){
this.target=target;
}
/**
代理方法逻辑
@param proxy:代理对象
@param method: 当前调度方法
@param args: 当前方法参数
@return : 返回代理结果
@throws Throwable: 异常
*/
@Override
public Object invoke(Object proxy,Method method,Object[] args)throw Throwable{
System.out.println("进入代理逻辑方法");
System.out.println("这是在调度真实对象之前所进行的服务");
//调用target真实对象的方法
Object obj=method.invoke(target,args);
System.out.println("调度完真实对象后所进行的语句");
return obj;
}
}
上面就是建立代理关系和实现代理逻辑的具体代码,接下来就是测试
public static void main(String[] args){
Invest proxy=(Invest)ProxyFactory.getProxy(new Boss());
proxy.stock("白酒股票",142323);
}
JDK动态代理实现解析
JDK动态代理实现依赖接口,因此先要用接口定义好操作的规范,然后通过Proxy类产生的代理镀锡去调用真实对象的操作,而这个操作则是被分发给InvocationHandler接口的invoke方法具体执行.
上面代码中通过Proxy.newProxyInstance()方法生成的代理对象proxy去调用了invoke方法,invoke方法又调用了Boss的方法,其中proxy对象是类的系统动态生产的.
2.2.2 CGLIB动态代理
JDK动态代理只能针对实现了接口的类去实现动态代理,无法对没有实现接口的类做动态代理,所以CGLIB应运而生.
CGLIB(Code Generation Library: 代码生成库)是一个强大,高性能的Code生成类库,可以在程序运行期间动态扩展类或接口,其底层使用java字节码操作框架ASM实现.
引入cglib库是比较关键的一环,因为cglib不属于JDK的库文件之一,需要自己去引入库文件.
首先,定义真实对象服务,真实对象不需要实现任何接口
public class Boss{
public void stock(String stockName,int money){
System.out.println("准备投资股票"+stockName+money+"人民币");
}
public void fund(String fundName,int money){
System.out.println("准备投资基金"+fundName+money+"人民币");
}
}
定义拦截器,因为在调用目标方法时,CGLIB会回调MethodInterceptor接口方法拦截,然后实现代理逻辑,类似于InvocationHandler接口
public class Investor implements MethodInterceptor{
@Override
public Object interceptor(Object proxy,Method method,Object[] args,MethodProxy methodProxy)throws Throwable{
System.out.println("CGLIB调用真实对象方法前");
//CGLIB反射调用真实对象方法
Object result=methodProxy.invokeSuper(proxy,args);
System,out.println("调用真实对象方法后");
return result;
}
}
定义代理工厂,生成动态代理
public class ProxyFactory{
public static Object getProxy(Object target){
Enhancer enhancer=new Enhancer();
enhancer.setSuperclass(target.getClass());
enhancer.setCallback(new Investor());
Object targetProxy=enhancer.create();
return targetProxy;
}
}
调用
public static void main(String[] args){
Boss boss=(Boss)ProxyFactory.getProxy(new Boss());
boss.stock("白酒股票",1342);
}
Enhancer代表增强类对象,通过设置超类的方法(setSuperclass),然后通过setCallback方法设置哪个类作为它的代理类.代理类必须实现MethodInterceptor接口