Java中的动态代理类 Proxy浅析
1、代理的概念与作用
一、生活中的代理
南京人从南京的代理商手中买联想电脑和直接跑到北京找联想总部买电脑,你觉得最终的主体业务目标有什么区别吗?基本上一样吧,都解决了核心问题,但是,一点区别都没有吗?有区别。从代理商那里买真的一点好处都没有吗?从代理那里买电脑方面,我们不用自己乘车跑到北京去买,节省时间和金钱。
二、程序中的代理
要为已存在的多个具有相同接口的目标类的各个方法增加一些系统功能,例如,异常处理、日志、计算方法的运行时间、事务管理、等等,你准备如何做?
编写一个与目标类具有相同接口的代理类,代理类的每个方法调用目标类的相同方法,并在调用方法时加上系统功能的代码。见图1 若代理类只干目标类的事,则代理类没价值。因此代理类方法干点额外的事。
如果采用工厂模式和配置文件的方式进行管理,则不需要修改客户端程序,在配置文件中配置是使用目标类、还是代理类,这样以后很容易切换,譬如,想要日志功能时就配置代理类,否则配置目标类,这样,增加系统功能很容易,以后运行一段时间后,又想去掉系统功能也很容易。
doSomeThing(){
//前置系统功能代码
目标对象.doSomeThing()
//后置系统功能代码
图1 代理架构图
2、动态代理技术
1、要为系统中的各种接口的类增加代理功能,那将需要太多的代理类,全部采用静态代理方式,将是一件非常麻烦的事情!写成百上千个代理类,是不是太累!
2、JVM可以在运行期动态生成出类的字节码,也即创建类对象,把其方法调一万遍,则生成一万个类对象,这种动态生成的类往往被用作代理类,即动态代理类。
3、JVM生成的动态类必须实现一个或多个接口,也就是说生成的动态类必须知道其接口里面有多少个方法。所以,JVM生成的动态类只能用作具有相同接口的目标类的代理。若目标类本身没有实现其接口,则JVM生成不了动态了。解法办法是用第三方CGLIB库。CGLIB库可以动态生成一个类的子类,一个类的子类也可以用作该类的代理。有兴趣的读者可查阅CGLIB相关文档。
4、代理类的各个方法中通常除了要调用目标的相应方法和对外返回目标返回的结果外,还可以在代理方法中的如下四个位置加上系统功能代码:
1.在调用目标方法之前
2.在调用目标方法之后
3.在调用目标方法前后
4.在处理目标方法异常的catch块中
一个示意代码进行辅助说明,例如
Class proxy{
void sayHello(){
……….
try{
target.sayHello();
}catch(Exception e){
………..
}
………….
}
}
3、分析JVM动态生成的类
下面以java 中的Collcetion接口为例阐述动态代理
1、创建实现了Collection接口的动态类和查看其名称,分析Proxy.getProxyClass方法的各个参数。
2、编码列出动态类中的所有构造方法,方法和参数签名
public class ProxyTest {
public static void main(String[] args) throws Exception{
Class clazzProxy= Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);
System.out.println(clazzProxy.getName());
上一步输出为:$Proxy0,表明JVM确实生成了一个类
System.out.println("--begin constructors list---");
Constructor[] constructors = clazzProxy.getConstructors();
for(Constructor constructor : constructors){
String name = constructor.getName();
StringBuilder sBuilder = new StringBuilder(name);
sBuilder.append('(');
Class[] clazzParams = constructor.getParameterTypes();
for(Class clazzParam : clazzParams){
sBuilder.append(clazzParam.getName()).append(',');
}
if(clazzParams!=null && clazzParams.length != 0)
sBuilder.deleteCharAt(sBuilder.length()-1);
sBuilder.append(')');
System.out.println(sBuilder.toString());
输出为:$Proxy0(java.lang.reflect.InvocationHandler),表明生成的动态代理类只有一个有参的构造方法
}
System.out.println("------begin methods list----");
Method[] methods = clazzProxy.getMethods();
for(Method method : methods){
String name = method.getName();
StringBuilder sBuilder = new StringBuilder(name);
sBuilder.append('(');
Class[] clazzParams = method.getParameterTypes();
for(Class clazzParam : clazzParams){
sBuilder.append(clazzParam.getName()).append(',');
}
if(clazzParams!=null && clazzParams.length != 0)
sBuilder.deleteCharAt(sBuilder.length()-1);
sBuilder.append(')');
System.out.println(sBuilder.toString());
输出为:add(java.lang.Object)
hashCode()
equals(java.lang.Object)
clear()
toString()
contains(java.lang.Object)
isEmpty()
addAll(java.util.Collection)
iterator()
size()
toArray([Ljava.lang.Object;)
toArray()
remove(java.lang.Object)
containsAll(java.util.Collection)
removeAll(java.util.Collection)
retainAll(java.util.Collection)
isProxyClass(java.lang.Class)
getProxyClass(java.lang.ClassLoader,[Ljava.lang.Class;)
getInvocationHandler(java.lang.Object)
newProxyInstance(java.lang.ClassLoader,[Ljava.lang.Class;,java.lang.reflect.InvocationHandler)
wait()
wait(long,int)
wait(long)
getClass()
notify()
notifyAll()
表明动态代理类中有实现自目标类Collection 的方法,和继承自Object对象的方法,还有自身的一些方法。
3、创建动态类的实例对象
1、用反射获得构造方法
注意上一步分析可知动态类的构造函数是有参的,因此用clazz.newInstance() (调用无参构造方法)生成实例对象会为报异常。
Class clazzProxy= Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);
Constructor constructor= clazzProxy.getConstructor(InvocationHandler.class);
2、编写一个最简单的InvocationHandler类
class MyInvocationHander implements InvocationHandler{
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
// TODO Auto-generated method stub
return null;
}
}
3、调用构造方法创建动态类的实例对象,并将编写的InvocationHandler类的实例对象传进去
Collection proxy= (Collection)constructor.newInstance(new MyInvocationHander());
4、打印创建的对象和调用对象的没有返回值的方法和getClass方法,演示调用其他有返回值的方法报告了异常。
System.out.println(proxy);
proxy.clear();
proxy.size();
proxy.add(“sinovatio”);
总结思考:让jvm创建动态类及其实例对象,需要给它提供哪些信息?
三个方面:
1、生成的类中有哪些方法,通过让其实现接口的方式进行告知;
2、产生的类字节码必须有个一个关联的类加载器对象;
3、生成的类中的方法的代码是怎样的,也得由我们提供。把我们的代码写在一个约定好了接口对象的方法中,把对象传给它,它调用我的方法,即相当于插入了我的代码。提供执行代码的对象就是那个InvocationHandler对象,它是在创建动态类的实例对象的构造方法时传递进去的。在上面的InvocationHandler对象的invoke方法中加一点代码,就可以看到这些代码被调用运行了。
上面为先搞到类,再用类搞到实例对象。
4、分析动态生成的类的内部代码
1、动态生成的类实现了Collection接口(可以实现若干接口),生成的类有Collection接口中的所有方法和一个接受InvocationHandler参数的构造方法。
2、构造方法接受一个InvocationHandler对象,接受对象了要干什么用呢?该方法内部的代码会是怎样的呢?为了记住这个对象,以后用他。
$Proxy0 implements Collection
{
InvocationHandler handler;
public $Proxy0(InvocationHandler handler)
{
this.handler = handler;
}
}
3、实现Collection接口的动态类中的各个方法的代码又是怎样的呢?InvocationHandler接口中定义的invoke方法接受的三个参数又是什么意思?说明如下:
Client程序调用objProxy.add(“abc”)方法时,涉及三要素:objProxy对象、add方法、“abc”参数
Class Proxy$ {
add(Object object) {
return handler.invoke(Object proxy, Method method, Object[] args);
}
}
$Proxy0 implements Collection
{
InvocationHandler handler;
public $Proxy0(InvocationHandler handler)
{
this.handler = handler;
}
//生成的Collection接口中的方法的运行原理
int size()
{
return handler.invoke(this,this.getClass().getMethod("size"),null);
}
void clear(){
handler.invoke(this,this.getClass().getMethod("clear"),null);
}
boolean add(Object obj){
handler.invoke(this,this.getClass().getMethod("add"),obj);
}
}
我们调用代理方法,如调用proxy.add(“sinovatio”);add()内部调用hanlder对象上的invoke()方法。
加上目标对象:
Collection proxy = (Collection)constructor.newInstance(
new InvocationHandler(){
ArrayList target = new ArrayList();
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Object retVal = method.invoke(target, args);
return retVal;
}
});
proxy.add("aaa");
proxy.add("bbb");
System.out.println(proxy.size());
4、分析先前打印动态类的实例对象时,结果为什么会是null呢?调用有基本类型返回值的方法时为什么会出现NullPointerException异常?
先前打印动态类的实例对象时,System.out.println(proxy);为null由于调用proxy.toString(),调用proxy.size()出现NullPointerException异常的原因是size()返回整数,而我们实现的invoke()方法中返回的为null,类型转换不了。
5、分析为什么动态类的实例对象的getClass()方法返回了正确结果呢?
调用调用代理对象的从Object类继承的hashCode, equals, 或toString这几个方法时,代理对象将调用请求转发给InvocationHandler对象,对于其他方法,则不转发调用请求。
6、分析动态代理的工作原理图