动态代理技术其实并不复杂,它是一个面向方面的程序设计,简单点说,就是不是针对一种特定的方法而设计。比如很多系统中都要记录安全信息、事物信息、生成日志,我们不可能一个方法一个方法的往上加代码吧,那样累赘而麻烦。这就需要用到一个类代理需要的类,然后再代理类中使用我们需要的方法,只要加上了代理类,再调用其中的所有方法都是调用我们代理类上自己定义的方法,这样就实现了面向方面程序设计。
AOP(Object Oriented Programming):
在实际的网络中,我们也很少直接使用需要客服端的信息,中间都需要加载一个代理,这样方便与操作。
代理类的建立:
既然是代理一个类,那么我们肯定无法确定需要代理的是什么类,而又要用到那个类中的方法,这肯定是使用了反射。
代理类的建立,必须得到与需要使用类有关联的代理类的Class文件对象。
通过Proxy.getProxyClass(ClassLoader loader, Class<?>... interfaces);
它必须要接收一个类加载器,和要实现代理接口的Class对象。
既然要使用Proxy这个类,就必须查看其中的构造方法、函数方法。
既然能得到Proxy的Class对象,不妨用反射列出其中的方法,可用如下方法做:
System.out.println("-------------------Proxy Method-----------------------------");
//通过反射返回类中方法数组
Method[] methods = clazzProxy.getMethods();
for(Method method: methods){
String name = method.getName();
StringBuilder sb = new StringBuilder(name);
sb.append("(");
//得到每个方法的参数,通过 getParameterTypes() 方法
Class[] clazzParams = method.getParameterTypes();
//将参数加入一个StringBuilder,并按照格式打印
for(Class clazzParam: clazzParams){
sb.append(clazzParam.getName()).append(",");
}
if(clazzParams.length!=0)
sb.deleteCharAt(sb.lastIndexOf(","));
sb.append(")");
System.out.println(sb.toString());
}
同理得到构造方法,打印结构发现Proxy只有一个构造方法
com.sun.proxy.$Proxy0(java.lang.reflect.InvocationHandler)
其中的方法有:
-------------------Proxy Method-----------------------------
hashCode()
equals(java.lang.Object)
toString()
add(java.lang.Object)
contains(java.lang.Object)
isEmpty()
size()
toArray()
toArray([Ljava.lang.Object;)
addAll(java.util.Collection)
iterator()
remove(java.lang.Object)
clear()
containsAll(java.util.Collection)
removeAll(java.util.Collection)
retainAll(java.util.Collection)
isProxyClass(java.lang.Class)
getProxyClass(java.lang.ClassLoader,[Ljava.lang.Class;)
newProxyInstance(java.lang.ClassLoader,[Ljava.lang.Class;,java.lang.reflect.InvocationHandler)
getInvocationHandler(java.lang.Object)
getClass()
notify()
notifyAll()
wait(long)
wait(long,int)
wait()
大多都是Object类中方法,其中的newProxyInstance建立实例的方法也与InvocationHandler有关,可以推测动态代理主要是通过此方法来实现的。
查看API文档可知,newProxyInstance()返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序。就是说创建出了这个代理,就能完全实现原有接口中的所有功能。那么既然这个是个代理类,当然也可以自己添加新的功能进去,这便是动态代理了,只要我们需要,完全可以加载自己想要方法进去。
下面是一个用动态代理植入广告的程序,我们也可以随意更换其中的广告内容:
这是一个sayHello的接口:
package cn.itcast.day3;
public interface SayHello {
void sayHello();
}
此类实现了sayHello接口,也是我们需要代理的类,在其收尾植入广告:
package cn.itcast.day3;
public class TestSayHello implements SayHello {
public void sayHello() {
System.out.println("Hello sky!");
}
}
创建一个代理对象,并加入植入语句:
package cn.itcast.day3;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ProxyTest {
public static void main(String[] args) throws Exception{
Class clazzProxy = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);
//将接口定义成目标类
final TestSayHello target = new TestSayHello();
// 抽取出获取代理的方法,并调用之
SayHello proxy2 = (SayHello)getProxy(target, new Test());
proxy2.sayHello();
}
private static Object getProxy(final Object target, final Test test) {
//newProxyInstance接收3个参数 ClassLoader, Interfaces, InvocationHandler
Object proxy2 = Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
//实现InvocationHandler中的inovke方法
new InvocationHandler() {
//invoke接收的3个参数实际上就是需要代理类中对象、方法、参数
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
test.beforeMethod(); //调用方法前加入的植入
Object obj = method.invoke(target,args);
test.behindMethod(method); //调用方法前加入的植入
return obj;
}
});
return proxy2;
}
}
最后写需要植入的内容:
package cn.itcast.day3;
import java.lang.reflect.Method;
public class Test {
long startTime = 0;
public void beforeMethod(){
System.out.println("我是插入的广告");
startTime = System.currentTimeMillis();
}
//返回调用此方法使用的时间
public void behindMethod(Method method){
long endTime = System.currentTimeMillis();
System.out.println(method.getName()+":runtime--"+(endTime-startTime));
}
}
打印结果:
我是插入的广告
Hello sky!
sayHello:runtime--0
成功植入广告,并打印出时间,知道这个动态代理的原理后,以后再使用相同的方法时候就简单的多,并且之后的框架如ssh都是基于动态代理产生的,有助于我们理解框架的构建。