聊聊JDK中Proxy、InvocationHandler的使用以及动态代理

对于熟悉设计模式的宝宝们来讲,代理模式的应用场景在生活中随处可见,JDK中的用例也比比皆是(Thread类&Runnable接口的设计)。这种模式的本意在于控制对被代理对象(目标对象)的访问,但在实际运用中能够实现各类复杂的业务逻辑(如记录log,性能分析。。。)。java

根据代理类的不一样分为静态和动态代理,静态代理须要在程序中手动产生代理类并加入业务逻辑。这种方式虽然客户端操做复杂,但实现起来相对容易。可是对于大多数场景来讲,客户端可能并不关心代理类也无需知道它的存在,它关注的是业务逻辑。这种场景必须使用动态代理,客户端只须要指定好本身的业务逻辑,程序运行时会自动产生代理类。动态代理的客户端的操做简便,但实现起来要复杂一些。设计模式


若是单纯的从代理模式的定义出发:代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用(摘自《java与模式》)。代理的实现方式能够有两种 : 继承和组合。继承做为一种通常化关系经过覆写父类的方法很容易实现代理。示例代码以下:ide

Target.java性能

package com.exmaple.proxy;
public class Target {
	public void method(){
		System.out.println("Target");
	}
}
Proxy.java

package com.exmaple.proxy;
public class Proxy extends Target {
	@Override
	public void method() {
		System.out.println("business logic");//模拟业务逻辑
		super.method();
	}
}
Client.java

package com.exmaple.proxy;
public class Client {
	public static void main(String[] args) {
		Target t = new Proxy();
		t.method();
	}
}
运行结果:business logic
 Target

回到设计模式的层面,代理模式涉及到如下角色:this

 Subject(抽象主题角色)提供Proxy(代理主题)和RealSubject(真实主题角色)的共同接口,Proxy内部含有RealSubject的引用,从而能够对真实主题对象进行操做。url

模拟JDK中的Thread类和Runnable接口spa

Runnable.java -- Subject.net

package com.exmaple.proxy.aggregate;
public interface Runnable {
	public void run();
}
Thread.java --  Proxy

package com.exmaple.proxy.aggregate;
public class Thread implements Runnable{	
	private Runnable target;	
	public Thread(Runnable target) {
		this.target = target;
	}
	@Override
	public void run() {
		System.out.println("------ do proxy ------");
		target.run();
	}	
}
MyThread.java --  RealSubject
package com.exmaple.proxy.aggregate;
public class MyThread implements Runnable {
	@Override
	public void run() {
		System.out.println("Real subject");
	}
}
Client.java

package com.exmaple.proxy.aggregate;
public class Client {
	public static void main(String[] args) {
		MyThread myThread = new MyThread();
		Thread thread = new Thread(myThread);
		thread.run();
	}
}
运行结果:------ do proxy ------

Real subject翻译

以上两个示例使用的都是静态代理。对于动态代理的实现,常见的有JDK动态代理(Proxy&InvocationHandler)和CGLib动态代理。先来看一下Proxy&InvocationHandler最为典型的使用方式,代码 设计

Moveable.java

package com.exmaple.test;
public interface Moveable {
	public void move();
}
TimeHandler.java
package com.exmaple.test;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class TimeHandler implements InvocationHandler {
	// 被代理对象
	private Object target;
	public TimeHandler(Object target) {
		this.target = target;
	}
	@Override
	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
		Class<?>[] interfaces = proxy.getClass().getInterfaces();
		List<Method> methods = new ArrayList<>();
		if (interfaces != null && interfaces.length > 0) {
			for (Class<?> class1 : interfaces) {
				Method[] methods1 = class1.getMethods();
				List<Method> asList = Arrays.asList(methods1);
				methods.addAll(asList);
			}
		}
		if (methods != null && methods.size() > 0) {
			for (Method m : methods) {
				System.out.println("begin time: " + System.currentTimeMillis());
				// 取得方法参数
				Class<?>[] parameterTypes = m.getParameterTypes();
				Object[] agrs;
				if (parameterTypes != null && parameterTypes.length > 0) {
					agrs = new Object[parameterTypes.length];
					for (int i = 0; i < parameterTypes.length; i++) {
						agrs[i] = parameterTypes[i].newInstance();
					}
					m.invoke(target, agrs);
				} else {
					m.invoke(target);
				}
				System.out.println("end time " + System.currentTimeMillis());
			}
		}
		return null;
	}
}
Train.java
package com.exmaple.test;
public class Train implements Moveable{
	@Override
	public void move() {
		System.out.println("wu wu wu ~ ~ ~");
	}	
}

Client.java

package com.exmaple.test;
import java.lang.reflect.Proxy;
public class Client {
	public static void main(String[] args) {
		Object newProxyInstance = Proxy.newProxyInstance(Client.class.getClassLoader(), new Class[]     {Moveable.class}, new TimeHandler(new Train()));
		Moveable m = (Moveable)newProxyInstance;
		m.move();
	}
}

运行结果:

begin time: 1516190734940
wu wu wu ~ ~ ~
end time 1516190734940

我的感受实际上JDK动态代理并不难理解,只是在类的设计上有些缺陷。InvocationHanddler接口的invoke方法和Method类的invoke方法很容易混淆,特别是在一个类里面须要同时使用这两个方法的时候。另外对于InvocationHanddler接口的invoke方法,Object proxy参数的做用并非很大,只有在极少数状况下才可能会使用到它,能够省略。下面作个简单的分析:

public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h);

Proxy的newProxyInstance方法在程序中生成一个类的源码而后动态编译它这样一个动态代理类就产生了,但它必须知足如下条件 

1 、和被代理对象实现相同的接口 。二、对于接口中方法须要包含新的业务逻辑 。三、实现接口方法的时候须要调用被代理对象同名的方法。

如何知足这些条件呢?对于条件1把接口以参数形式传递进来就能够了。Class<?>[] interfaces参数就是干这个的。对于条件2来讲新的业务逻辑应该是能够灵活替换的,对于可变因素最好的方式就是封装到一个接口中。InvocationHandler h来承担这部分工做:public Object invoke(Object proxy, ) throws Throwable;

先进行简化:public Object invoke(Method method, Object[] args),这样看起来可能更容易体会到这个方法的用意。再简化一些忽略方法的参数最终变成这种形式:public Object invoke(Method method);

InvocationHandler调用处理者,顾名思义:你在方法调用的时候须要作怎样的如何处理?若是须要,请覆写我吧!若是如今有个方法m,想在先后加一些逻辑怎么办?此时的public Object invoke()看起来应该是这个样子的:一、先加入前置逻辑。二、对目标对象进行方法调用,执行方法自身的逻辑。三、加入后置逻辑。

//注意!!!下面的代码只是为了说明,并不能运行。 
public Object invoke(Method method){
		System.out.println("do something before");//前置业务逻辑
		method.invoke(target);//方法调用,为了简化不传递任何参数
		System.out.println("do something after");//后置业务逻辑	
	}

进一步思考,对于invoke方法的参数,一个Method对象,若是它所属的类和它执行调用的目标对象对应的的类并不一致,会出现什么样的场景呢?对于自动生成的代理类来讲,若是咱们实现和被代理对象相同接口时,把方法对象传入invoke(Method method)中加入新的业务逻辑,并对被代理对象(目标对象)进行方法调用,是否是就知足了上述全部要求?因此上面的实例中实现InvocationHandler的时候咱们指定好目标对象。

在来看一下proxy拼凑出的源码,可能会对JDK的动态代理有更加深入的认识

import java.io.File;
import java.io.FileWriter;
import java.lang.reflect.Constructor;
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 {
    public static Object newProxyInstance(Class infce, InvocationHandler h) throws Exception { //JDK6 Complier API, CGLib, ASM
        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(this, md);" + rt +
                         "    }catch(Exception e) {e.printStackTrace();}" + rt +
                         
                         "}";
        }
         
        String src = 
            "package com.bjsxt.proxy;" +  rt +
            "import java.lang.reflect.Method;" + rt +
            "public class $Proxy1 implements " + infce.getName() + "{" + rt +
            "    public $Proxy1(InvocationHandler h) {" + rt +
            "        this.h = h;" + rt +
            "    }" + rt +
             
             
            "    com.bjsxt.proxy.InvocationHandler h;" + rt +
                             
            methodStr +
            "}";
        String fileName = 
            "src/com/bjsxt/proxy/$Proxy1.java";
        File f = new File(fileName);
        FileWriter fw = new FileWriter(f);
        fw.write(src);
        fw.flush();
        fw.close();
         
        //compile
        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();
         
        //load into memory and create an instance
        URL[] urls = new URL[] {new URL("file:/" + "src/")};
        URLClassLoader ul = new URLClassLoader(urls);
        Class c = ul.loadClass("com.bjsxt.proxy.$Proxy1");
        System.out.println(c);
         
        Constructor ctr = c.getConstructor(InvocationHandler.class);
        Object m = ctr.newInstance(h);
        //m.move();
 
        return m;
    }
}
核心在于对这段代码的理解:

for(Method m : methods) {
	methodStr += "@Override" + rt + 
	             "public void " + m.getName() + "() {" + rt +//1
	             "    try {" + rt +
	             "    Method md = " + infce.getName() + ".class.getMethod(\"" + m.getName() + "\");" + rt +//2
	             "    h.invoke(this, md);" + rt +//3
	             "    }catch(Exception e) {e.printStackTrace();}" + rt +	             
	             "}";
        }

上述代码只是模拟实现了一个Proxy,对了InvocationHandler,仍然使用的是jdk自带的,若是咱们对InvocationHandler也进行模拟,彻底能够省略Object proxy参数,运行时并不会有任何异常产生。同时上面代码含义也会更加清晰

for(Method m : methods) {
	methodStr += "@Override" + rt + 
	             "public void " + m.getName() + "() {" + rt +//1
	             "    try {" + rt +
	             "    Method md = " + infce.getName() + ".class.getMethod(\"" + m.getName() + "\");" + rt +//2
	             "    h.invoke(md);" + rt +//3
	             "    }catch(Exception e) {e.printStackTrace();}" + rt +	             
	             "}";
        }
对于会使用到Object proxy参数的特殊场景,给出一个连接参考,原文在 stackoverflow上做者进行了翻译和说明,原文的连接帖子里面也有http://blog.csdn.net/bu2_int/article/details/60150319