什么是动态代理
在代理模式中,主要有三个部分,接口:决定提供什么服务
真实对象:继承于接口,实现接口方法,但不能被直接访问、
代理对象:代理真实对象,继承于接口,若要实现服务,通过代理对象来提供服务
通过这样的代理模式,便可以不直接访问真实对象,而通过代理对象来实现功能,同时,在代理对象中,还能对原对象方法的功能进行增强。
而所谓动态代理,实际上是众多设计模式中代理模式的一种实现方式,它不需要创建具体的类文件,代理对象直接创建在内存中。
动态代理代码简单实现
写了个简单的代码来演示动态代理,先贴上代码,然后再进行解释。
/**
* 接口:买票服务
*/
public interface Sales_tickets {
/**
* 卖票
* @param num 票数
* @return 票
*/
public String sale(int num);
/**
* 查询是否有票
* @return
*/
public String query();
}
/**
* 航空公司,Sales_tickets的实现类
*/
public class CD_Air implements Sales_tickets{
public String sale(int num){
String string = Integer.toString(num) +"张票";
return string;
}
@Override
public String query() {
return "有票";
}
}
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class Test_proxy {
public static void main(String[] args) {
Sales_tickets cd_air = new CD_Air();
//与真实对象保持一样的类加载器和接口数组,同时定义处理器
//proxy_air即为cd_air的代理对象
Sales_tickets proxy_air = (Sales_tickets)Proxy.newProxyInstance(cd_air.getClass().getClassLoader(), cd_air.getClass().getInterfaces(), new InvocationHandler() {
//代理逻辑编写的方法,代理对象调用所有的方法都会触发该方法执行
//proxy,代理对象 method,方法对象 args,传递给方法执行的实际参数
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if(method.getName().equals("sale")){
doSomethingBefore();
String string = (String) method.invoke(cd_air,args);
doSomethingAfter();
return string;
}
else{
String string = (String) method.invoke(cd_air,args);
return string;
}
}
});
String string = proxy_air.query();
System.out.println(string);
System.out.println("##############################");
string = proxy_air.sale(5);
System.out.println(string);
}
public static void doSomethingBefore(){
System.out.println("doSomethingBefore");
}
public static void doSomethingAfter(){
System.out.println("doSomethingAfter");
}
}
首先,定义了一个Sales_tickets接口,其中定义了两个抽象方法,sale(卖票)和query(查询是否有票),然后定义了一个CD_Air作为Sales_tickets的实现类,,这是我们的真实对象,这是个航空公司,可以具体的卖票和查询是否有票。
然后定义了一个Test_Proxy来测试我们的代理。在test_proxy中我们定义了一个proxy_air对象,作为代理对象,它就像是携程飞猪这种代理购票公司。
通过Proxy.newProxyInstance()
方法来返回一个代理对象,其中传入参数列表有三,一是要代理的真实对象的类加载器,二是真实对象的接口数组,三是代理对象的执行逻辑。
在代理对象的执行逻辑中有一个必须要实现的也是十分重要的方法invoke方法。它也有三个传入参数,一是代理对象,二是方法对象,三是方法执行的参数。
当我们调用代理对象的任何一个方法时,都会触发执行代理对象的invoke方法。
在这里,我在invoke方法中进行了一次判断,如果是调用的是sale方法,那么便会先调用一个doSomethingBefore()
,这代表着在调用真实对象的方法之前进行的增强操作,这里可以对传入的参数进行一次处理,也就是动态代理的参数增强。
然后是调用真实对象的sale方法。
然后是调用一个doSomethingAfter()
,这代表着在调用真实对象的方法之后的增强操作,这里可以对真实对象方法的返回值进行一次增强(也就是处理)。
然后返回最终的返回值。
而如果调用的是代理对象的query方法,它就只会调用真实对象的query方法,返回返回值。
代码运行的结果如下图所示。
下面我们来用一个图来理解一下:
可以从图中看到,test_proxy并没有与真实对象进行交互,它一直与代理对象进行交互,在test_proxy眼里,它根本可以不知道代理对象的存在,它也不知道在代理对象中对真实对象的方法进行了增强(修改),所以代理对象就像是test_proxy与真实对象之间的一种“透明桥接”(不愧是网工出身的我,也就是一种透明的存在)。test_proxy以为自己是直接和航空公司进行交互的。
同时,代理对象的实现就是调用真实对象的方法并在其调用前后增加代码逻辑。真正完成卖票和查询服务功能的对象还是真实对象,所以代理对象只能说是“代理”而已。
关于动态代理的理解
这里动态代理对象和我们普通生活中的代理很像,作为中间代理商,把用户和厂家分开,不仅实现厂家的功能,还在厂家的功能上进行增强,比如厂家的卖东西只是单纯的卖商品,而代理商还在卖商品的同时送你赠品,还给一个送货上门的服务。
我们要牢记一个事实,就是这里的主要业务功能还是真实对象来实现的,代理对象只是将真实对象的业务功能拿过来,然后对其进行修改,或者这样说,代理对象就是对真实对象的方法进行封装。
调用代理对象的方法,实际只是将真实对象的方法名和参数传入invoke方法,代理对象并不具备执行该方法的能力,如果不调用原对象执行,那么,该名字对应的方法就不能执行,所以,只有method.invoke(真实对象,参数),使用真实对象调用方法。
有这样一个比喻,很形象,invocationHandler就是一个增强器,proxy就是一个调度器。
来个示意图,大概是这样一个意思。
(由于我现在还没有正式接触框架,所以不能就框架在本文中写些什么,等我学了框架如果还记得,就来更新下本文吧。动态代理是所有aop注解的本质)
对于这里如何对方法进行增强的问题,我们可以结合上面一节的代码说一说。上面代码在代理对象的sale方法里,在调用真实对象的sale方法前后,我们有doSomethingBefore()和doSomethingAfter()这两个方法,我们可以分别对sale方法的参数和返回值进行增强(修改),比如,我们可以将ars[0]中的5取出来,给它加个1,或者减个1什么的,这就是修改(增强),又比如我们可以将真实对象sale方法返回字符串进行修改,给它拼接一个字符串或者进行替换什么的,如此便是修改,便是增强。
代理对象对真实对象的方法进行封装,增加了代码逻辑,但是让用户却无法察觉该方法发生了变化。
目前的我看来,其能够做到的功能还有,在不修改原类的情况下,能够实现在原类的功能基础上增强功能的程度,这样其实是很方便的,因为这样的应用场景对我来说其实很多,就比方说,我经常需要对原代码进行一些修改,但是修改了原代码我其实弄的很麻烦,相似的代码,不同的版本,然后每个版本很多方法都是一样的,复制过来过去很麻烦,而如果使用动态代理,我就可以不去修改代码的主体,只是在运行时对原对象的功能进行增强,这样不仅能够提高我写代码的效率,节省时间,而且维护起来也会更加的方便,结构更加的清晰。