对于熟悉设计模式的宝宝们来说,代理模式的应用场景在生活中随处可见,JDK中的用例也比比皆是(Thread类&Runnable接口的设计)。这种模式的本意在于控制对被代理对象(目标对象)的访问,但在实际运用中可以实现各种复杂的业务逻辑(如记录log,性能分析。。。)。
根据代理类的不同分为静态和动态代理,静态代理需要在程序中手动产生代理类并加入业务逻辑。这种方式虽然客户端操作复杂,但实现起来相对容易。但是对于大多数场景来说,客户端可能并不关心代理类也无需知道它的存在,它关注的是业务逻辑。这种场景必须使用动态代理,客户端只需要指定好自己的业务逻辑,程序运行时会自动产生代理类。动态代理的客户端的操作简便,但实现起来要复杂一些。
如果单纯的从代理模式的定义出发:代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用(摘自《java与模式》)。代理的实现方式可以有两种 : 继承和组合。继承作为一种一般化关系通过覆写父类的方法很容易实现代理。示例代码如下:
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
回到设计模式的层面,代理模式涉及到以下角色:
Subject(抽象主题角色)提供Proxy(代理主题)和RealSubject(真实主题角色)的共同接口,Proxy内部含有RealSubject的引用,从而可以对真实主题对象进行操作。
模拟JDK中的Thread类和Runnable接口
Runnable.java -- Subject
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 、和被代理对象实现相同的接口 。2、对于接口中方法需要包含新的业务逻辑 。3、实现接口方法的时候需要调用被代理对象同名的方法。
如何满足这些条件呢?对于条件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()看起来应该是这个样子的:1、先加入前置逻辑。2、对目标对象进行方法调用,执行方法自身的逻辑。3、加入后置逻辑。
//注意!!!下面的代码只是为了说明,并不能运行。
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