今天给大家介绍一下通过接口实现动态代理的技术,并且剖析一下JDK中动态代理的实现原理。
在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接口
package com.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class HelloWorldHandler implements InvocationHandler {
// 持有一个Object对象,用来保存被代理对象
Object obj = null;
public HelloWorldHandler(Object obj) {
this.obj = obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
// 在方法执行之前加上自己的逻辑
this.before();
// 执行被代理对象的方法
Object resultObject = method.invoke(obj, args);
// 在方法执行之后加上自己的逻辑
this.after();
//返回方法执行的结果,当然可能为null
return resultObject;
}
public void before() {
System.out.println("Method sayHello start");
}
public void after() {
System.out.println("Method sayHello end");
}
}
然后直接写测试类:
package com.proxy;
import java.lang.reflect.Proxy;
public class HelloWorldTest2 {
public static void main(String[] args) {
HelloWorld helloWorld = new HelloWorldImpl();
HelloWorldHandler helloWorldHandler = new HelloWorldHandler(helloWorld);
// 调用Proxy的静态方法newProxyInstance创建一个代理类
HelloWorld helloWorld2 = (HelloWorld) Proxy.newProxyInstance(helloWorld
.getClass().getClassLoader(), helloWorld.getClass()
.getInterfaces(), helloWorldHandler);
// 调用代理类的同名方法sayHello
helloWorld2.sayHello();
}
}
估计大家已经猜到执行结果了:
Method sayHello start
Hello World!
Method sayHello end
以上代码实现了我们静态代理实现的功能,但是他是可扩展的,实际上他对接口HelloWorld中的所有方法都实现了代理功能,而且代理逻辑由我们自己在HelloWorldHandler中控制。
如果我没猜错的话,读者肯定会首先对上面代码中的这一段感到不耐烦:
HelloWorld helloWorld2 = (HelloWorld) Proxy.newProxyInstance(helloWorld
.getClass().getClassLoader(), helloWorld.getClass()
.getInterfaces(), helloWorldHandler);
大家肯定会说这是啥跟啥啊,只看到给newProxyInstance这个静态方法传进去一坨参数,返回了一个HelloWorld类型的代理对象,但恰恰就是这坨跟***一样的代码是JDK动态代理的核心所在,为了给大家讲清楚其中的原理,接下来我们要进行一项艰巨的工程,我们自己写一个Proxy类,模拟JDK的Proxy类,用我们自己的类产生一个代理类出来,从而搞清楚Proxy类内部是怎么实现的(大家如果感兴趣,也可以打开java源码看一下JDK中真实的Proxy类是啥样的,如果看不懂的话再回来看我接下来模拟的这个类也不晚,毕竟模拟的要比真实的简单些,但是保证原理都是一样的)
此篇博客已太长,而且这个点我好像该洗洗睡了,那我就在下一篇博客中来剖析Proxy类吧
未完待续......