在java中,动态代理技术应用非常广泛,我们熟知的Struts2的Interceptor(拦截器)技术,以及spring中的AOP技术的实现,核心技术都是动态代理,在java中实现动态代理的方式有JDK自带的实现方式和CGlib两种方式,我们今天介绍的是JDK自带的动态代理实现方案,也是需要被代理类必须实现接口的动态代理。
说到动态代理,肯定有静态代理,那我们先从静态代理入手,然后再扩展到动态代理,逐步深入地给大家介绍下动态代理的原理,方便大家深入理解。
静态代理
在开始之前,大家肯定知道代理的含义吧,举个例子吧,比如小明家养了个小狗叫小黑,小黑由于天资出众,很聪明,学什么一学就会,时间久了,小黑出名了,有一天一个大导演要拍一个关于狗的电影,想请小黑来当男一号,于是跟小明商量合作事宜。现在问题来了,大导演为啥不直接跟小黑商量呢?大家肯定觉得很好笑,狗又听不懂人话,跟它商量个屁啊,其实在这里小明就是小黑的代理,小明肯定先要审查一下这个导演的资质,确保他是真导演,不是专门潜规则女演员的伪导演,小明也要帮小黑把把关,看这个电影是否适合小黑拍,等等。总之小明这个代理在小黑拍电影之前做了一系列的审查,确保小黑安全的拍电影而不是被拐卖了,这一系列的审查对应到我们的代理程序中就是代理逻辑。希望通过这个小例子大家能明白代理的含义。下面我们就通过一些示例代码给大家看下啥叫静态代理。
先定义一个简单的接口:
package com.proxy;
public interface HelloWorld {
public void sayHello();
}
写一个实现类:
package com.proxy;
public class HelloWorldImpl implements HelloWorld {
@Override
public void sayHello() {
System.out.println("Hello World!");
}
}
测试类:
package com.proxy;
public class HelloWorldTest1 {
public static void main(String[] args) {
HelloWorld helloWorld = new HelloWorldImpl();
helloWorld.sayHello();
}
}
以上代码很简单,就是几个接口一个实现类,实现了sayHello方法,输出举世闻名的Hello World!
现在有个问题,我想在方法sayHello执行前后加点日志逻辑,比如在执行sayHello之前输出"Method sayHello start“,执行之后输出"Method sayHello end“,该怎么实现呢,最简单的方法是修改sayHello方法,在前后加上我们要输出的语句(严格来讲这样不符合要求,因为我的要求是在sayHello执行之前和之后),如果不能改sayHello的源码呢?
有两种方法:继承和聚合
a.继承方式实现静态代理:
再写一个HelloWorldImpl1,继承自HelloWorldImpl,代码如下所示
package com.proxy;
public class HelloWorldImpl1 extends HelloWorldImpl {
// 重写父类的sayHello方法,然后再前后加上自己的逻辑
public void sayHello() {
System.out.println("Method sayHello start");
super.sayHello();
System.out.println("Method sayHello end");
}
}
相当于用HelloWorldImplement1把HelloWorldImpl包装了一下,HelloWorldImpl1可以被看作是HelloWorldImpl的静态代理,客户访问的时候访问这个代理,代理再调用被代理对象的同名方法,在调用之前和之后,代理就可以加上自己的日志逻辑了。
b.聚合方式实现静态代理:
这个地方为什么说是聚合而不是组合,聚合和组合有啥异同,读者感兴趣可自己研究,此处不做为重点
新建一个HelloWorldImpl2,同样实现HelloWorld接口
package com.proxy;
public class HelloWorldImpl2 implements HelloWorld {
// 持有一个HelloWorldImpl的对象
HelloWorldImpl helloWorldImpl = null;
public HelloWorldImpl2(HelloWorldImpl helloWorldImpl) {
this.helloWorldImpl = helloWorldImpl;
}
@Override
public void sayHello() {
System.out.println("Method sayHello start");
helloWorldImpl.sayHello();
System.out.println("Method sayHello end");
}
}
这样也可以,通过持有HelloWorldImpl的对象实现对HelloWorldImpl方法的访问,同时加上自己的日志逻辑,用聚合方式实现静态代理要比用继承方式更加灵活,大家可以想下为什么(因为继承不如聚合灵活,具体读者可研究下二者的异同)。
以上是静态代理,为啥叫它静态代理,因为这个代理是死的,HelloWorldImpl1和HelloWorldImpl2的代码是固定的,这样不够灵活,比如我还有除sayHello之外的方法,是不是要对每个方法都要写同样的代码,再比如我想分别记录下方法执行前后的系统时间,从而计算下方法的执行时间,我们是不是又要搞一个继承或者聚合类,再比如我两者都想要,那是不是还是要在搞一个,再比如我想这两个逻辑执行的顺序换下,先记录系统时间在记日志,是不是还要在搞一个,久而久之就出现了类个数的爆炸性增长,而且会导致类和类之间的关系错综复杂。
大家试想,对于以上问题,我们有什么解决办法?
解决办法很简单,把你想要加的逻辑分离出来,动态的生成代理类,也就是动态的生成诸如HelloWorldImpl1这样的类,怎么动态生成呢,其实java已经为我们提供的相关API,在JDK中有一个接口和一个类,分别是InvocationHandler接口和Proxy类,利用这两者我们就可以完成对任意实现至少一个接口的类的动态代理,这个地方很有意思,为啥是至少实现一个接口呢,想必读者已经意识到了,对,你想的没错,JDK的动态代理的本质是以聚合的方式实现动态代理的。
动态代理
接下来,我们先看一下JDK的动态代理怎么用,然后我再给大家剖析一下内部的实现原理:
我们用JDK的动态代理实现一下上面加日志逻辑的功能
首先先定义一个类HelloWorldHandler实现InvocationHandler接口
然后直接写测试类:
估计大家已经猜到执行结果了:
以上代码实现了我们静态代理实现的功能,但是他是可扩展的,实际上他对接口HelloWorld中的所有方法都实现了代理功能,而且代理逻辑由我们自己在HelloWorldHandler中控制。
如果我没猜错的话,读者肯定会首先对上面代码中的这一段感到不耐烦:
大家肯定会说这是啥跟啥啊,只看到给newProxyInstance这个静态方法传进去一坨参数,返回了一个HelloWorld类型的代理对象,但恰恰就是这坨跟***一样的代码是JDK动态代理的核心所在,为了给大家讲清楚其中的原理,接下来我们要进行一项艰巨的工程,我们自己写一个Proxy类,模拟JDK的Proxy类,用我们自己的类产生一个代理类出来,从而搞清楚Proxy类内部是怎么实现的(大家如果感兴趣,也可以打开java源码看一下JDK中真实的Proxy类是啥样的,毕竟模拟的要比真实的简单些,但是保证原理都是一样的)
我们首先分析下,JDK的Proxy类的newProxyInstance接收三个参数,第一个是ClassLoader,我们为了简单起见,省去这个参数,ClassLoader的具体作用读者感兴趣可上网了解,第二个参数是被代理类实现的接口列表,我们为简单起见,默认被代理类只实现一个接口。
先简单说下JDK的Proxy类干了什么,然后再模拟它,它接收了被代理类实现的接口列表目的是创造一个同样实现这些接口的代理类,这样他就能反射出这些接口中的方法,也就是他可以得到被代理类中的方法列表,然后它还接收一个InvocationHandler的实现类h,在反射出的每一个方法中,会调用h的invoke方法,在h的invoke方法中,先加入自己的逻辑,然后再调用被代理类中的同样方法(注意,h中持有一个被代理对象),将所有的方法反射完成之后,动态编译这个代理类,返回一个代理类的对象。然后我们在外面就可以直接拿接口类型的引用接收这个对象,当调用这个代理类的方法的时候,我们自己加的逻辑自然也就加进去了。
上面只是说的是基本原理,当然JDK的Proxy类在实际执行过程中远比以上描述要复杂,不知各位读者,你们明白了么?如果还不明白的话,我们直接上代码把。
package com.proxy;
import java.io.File;
import java.io.FileWriter;
import java.lang.reflect.Constructor;
import com.proxy.InvocationHandler;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import javax.tools.JavaCompiler;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import javax.tools.JavaCompiler.CompilationTask;
public class Proxy {
//接收一个接口的Class对象,然后接收一个InvocationHandler的对象 h
public static Object newProxyInstance(Class infce, InvocationHandler h) throws Exception {
String methodStr = "";
String rt = "\r\n";
//反射出接口中的所有方法
Method[] methods = infce.getMethods();
for(Method m : methods) {
//对每个方法,我们构造他们的方法字符串,为简单起见,我们默认每个方法都没有返回值
methodStr += "@Override" + rt +
"public void " + m.getName() + "() {" + rt +
" try {" + rt +
//得到该方法的名字
" Method md = " + infce.getName() + ".class.getMethod(\"" + m.getName() + "\");" + rt +
//调用h的invoke方法,至于invoke方法内部执行了什么,可去h中看下
" h.invoke(this, md);" + rt +
" }catch(Exception e) {e.printStackTrace();}" + rt +
"}";
}
String src =
"package com.proxy;" + rt +
"import java.lang.reflect.Method;" + rt +
"import com.proxy.InvocationHandler;" + rt +
//代理类和被代理类实现同样的接口
"public class $Proxy1 implements " + infce.getName() + "{" + rt +
//代理类持有一个InvocationHandler对象,为简单起见,我们自己重新定义一个InvocationHandler接口和实现类
" public $Proxy1(InvocationHandler h) {" + rt +
" this.h = h;" + rt +
" }" + rt +
" InvocationHandler h;" + rt +
methodStr +
"}";
//以下代码为动态编译过程,将以上代码写到一个java文件中,然后编译此文件,在根据编译生成的class文件反射出对象
String fileName =
"d:/src/com/proxy/$Proxy1.java";
File f = new File(fileName);
FileWriter fw = new FileWriter(f);
fw.write(src);
fw.flush();
fw.close();
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null, null, null);
Iterable units = fileMgr.getJavaFileObjects(fileName);
CompilationTask t = compiler.getTask(null, fileMgr, null, null, null, units);
t.call();
fileMgr.close();
URL[] urls = new URL[] {new URL("file:/" + "d:/src/")};
URLClassLoader ul = new URLClassLoader(urls);
Class c = ul.loadClass("com.proxy.$Proxy1");
//反射出那个接受一个InvocationHandler对象为参数的构造方法
Constructor ctr = c.getConstructor(InvocationHandler.class);
//得到一个代理对象,然后返回,至此,大功告成
Object m = ctr.newInstance(h);
//m.move();
return m;
}
}
为简单起见,我们重写了InvocationHandler接口(上面代码中用到的),如下所示:
package com.proxy;
import java.lang.reflect.Method;
public interface InvocationHandler {
public void invoke(Object o, Method m);
}
我们重新定义了InvocationHandler的实现类,如下所示:
package com.proxy;
import com.proxy.InvocationHandler;
import java.lang.reflect.Method;
public class HelloWorldHandler1 implements InvocationHandler {
// 持有一个Object对象,用来保存被代理对象
Object obj = null;
public HelloWorldHandler1(Object obj) {
this.obj = obj;
}
@Override
public void invoke(Object proxy, Method method) {
// 在方法执行之前加上自己的逻辑
this.before();
// 执行被代理对象的方法
try {
Object resultObject = method.invoke(obj, null);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 在方法执行之后加上自己的逻辑
this.after();
// 返回方法执行的结果,当然可能为null
}
public void before() {
System.out.println("Method sayHello start");
}
public void after() {
System.out.println("Method sayHello end");
}
}
测试类如下:
package com.proxy;
import com.proxy.Proxy;
public class HelloWorldTest3 {
public static void main(String[] args) throws Exception {
HelloWorld helloWorld = new HelloWorldImpl();
HelloWorldHandler1 helloWorldHandler = new HelloWorldHandler1(helloWorld);
// 调用Proxy的静态方法newProxyInstance创建一个代理类
HelloWorld helloWorld2 = (HelloWorld) Proxy.newProxyInstance(HelloWorld.class,
helloWorldHandler);
// 调用代理类的同名方法sayHello
helloWorld2.sayHello();
}
}
以上代码也实现了对HelloWorldImpl的动态代理,但是由于简化了结构,所以功能还是很有限,但是基本的原理就是这个样子滴,大家可以自己研究下我们自己模拟的Proxy类,有什么疑问或者问题,欢迎在评论中提出,我将尽可能回复大家。