JAVA动态代理模拟

时间:2021-01-17 17:02:37

JAVA动态代理模拟

       所谓代理,就是有一个代理类和一个被代理类,代理类帮被代理类执行相关的动作,如房产中介帮房东代理出租房屋,当有客户租房子时,客户只要找房产中介就可以了,中介会帮房东去执行租出去这个动作。当然,中介可以在中间做一些事情,如带客户看房子,最主要的是万恶的中介会收中介费,扯远了。。。。。言归正传

      代理分类

       代理分为静态代理和动态代理两种,不管静态代理还是动态代理,代理类和被代理累一般都是实现同一个接口。

      静态代理

      所谓静态代理,就是如果被代理类的方法在执行之前或之后需要多种操作,那代理类就要写多种,如定义一个接口Flyable(飞行物),有一个fly()方法。有一个被代理类Bird实现了这个接口,现在要计算这只鸟飞行了多久,那我们就要在这只鸟飞行之前记录下时间,飞行之后记录下时间。代码如下:

package com.sf.simba.dynamicproxy.fly;
/**
*
* @author -Simba-
*
*/
public interface Flyable {
void fly();
}


package com.sf.simba.dynamicproxy.fly;

import java.util.Random;
/**
* 被代理类
* @author -Simba-
*
*/
public class Bird implements Flyable {

@Override
public void fly() {
System.out.println("The bird is flying.......");
try {
Thread.sleep(new Random().nextInt(2000));
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

}


package com.sf.simba.dynamicproxy.fly;
/**
* 时间代理类
* 代理类和被代理类实现同一个接口
* @author -Simba-
*
*/
public class TimeBirdProxy implements Flyable {
private Flyable flyable;

public TimeBirdProxy(Flyable flyable){
this.flyable = flyable;
}
@Override
public void fly() {
long start = System.currentTimeMillis();
flyable.fly();
long end = System.currentTimeMillis();
System.out.println("The Flyable has flown "+(end-start)+" millisecond");


}


}



package com.sf.simba.dynamicproxy.fly;
/**
* 测试类
* @author -Simba-
*
*/
public class ProxyMain {

public static void main(String[] args) {
Flyable bird = new Bird();
Flyable timeproxy = new TimeBirdProxy(bird);
timeproxy.fly();

}
}

测试结果如下:

The bird is flying.......
The Flyable has flown 1774 millisecond

结果测试可以记录时间了。


    如果现在我要在被代理类Bird调用fly()方法后要记录下日志,那是不是要在写一个记录日志的代理类,那如果我要在调用之前做一下权限验证呢?那我是不是要在写一个权限验证的代理类,如果我还有更多的需求呢,那我这个代理类是不是要一直写下去?在比如,我现在要代理一个不是Flyable接口的接口但也是记录时间、记录日志、权限校验。。。。。。。。那这样就会导致类爆炸,对以后的维护带来非常的不便,那我们有没有办法让这个代理类动态生成呢?就是这个代理类不管代理什么我们都可以生成,那这样就好维护多了。

     动态代理

          从上面的例子中我们可以发现,静态代理会导致类爆炸,难以维护,那么如果这个代理类如果能够动态生成就好了,那么怎么样才能让我们这个代理类动态生成呢,那就是让这个代理类变成一个字符串,动态的改变这个字符串,然后动态编译成字节码,通过ClassLoad装载字节码生成二进制文件到内存,然后生成实例对象(代理对象),然后执行这个代理对象。代码如下

         

package com.sf.simba.dynamicproxy.fly;

import java.io.File;
import java.io.FileWriter;
import java.lang.reflect.Constructor;
import java.net.URL;
import java.net.URLClassLoader;

import javax.tools.JavaCompiler;
import javax.tools.JavaCompiler.CompilationTask;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;

/**
 * 动态代理生成类
 * @author -Simba-
 *
 */
public class Proxy {
public static Object newProxyInstance() throws Exception{
String rn = "\r\n";
String classStr = "package com.sf.simba.dynamicproxy.fly; " +rn+
"import com.sf.simba.dynamicproxy.fly.Flyable;" +rn+
" public class TimeBirdProxy implements Flyable { " + rn+
" private Flyable flyable; " + rn+
" public TimeBirdProxy(Flyable flyable){ " + rn+
" this.flyable = flyable; " + rn+
" } " + rn+
" @Override " + rn+
" public void fly() { " + rn+
" long startTime = System.currentTimeMillis(); " + rn+
" flyable.fly(); " + rn+
" long endTime = System.currentTimeMillis(); " + rn+
" System.out.println(\" TimeBirdProxy: The Flyable has flown \" + (endTime - startTime)+\" millisecond\"); " + rn+
" } " + rn+
"\n" + rn+
"}";

//定义动态文件生成到哪里叫什么名字
String fileName = System.getProperty("user.dir")+"/src/com/sf/simba/dynamicproxy/fly/TimeBirdProxy.java";
File file = new File(fileName);
FileWriter fw = new FileWriter(file);
//把字符串写入文件
fw.write(classStr);
fw.flush();
fw.close();

//获得编译器
JavaCompiler complier = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fileManager = complier.getStandardFileManager(null, null, null);
Iterable<? extends JavaFileObject> units = fileManager.getJavaFileObjects(fileName);
CompilationTask task = complier.getTask(null, fileManager, null, null, null, units);
//编译,生成字节码
task.call();
fileManager.close();

URL[] url = new URL[]{new URL("file:/"+System.getProperty("user.dir")+"/src/")};
//加载字节码
URLClassLoader classloder = new URLClassLoader(url);
Class<?> loadClass = classloder.loadClass("com.sf.simba.dynamicproxy.fly.TimeBirdProxy");
//获得代理对象构造方法
Constructor<?> constructor = loadClass.getConstructor(Flyable.class);
//生成代理对象实例

Object newInstance = constructor.newInstance(new Bird());//new Bird()为被代理对象
System.out.println(newInstance);
return newInstance;


}

}
测试类如下:

package com.sf.simba.dynamicproxy.fly;
/**
* 测试类
* @author -Simba-
*
*/
public class ProxyMain {

public static void main(String[] args) throws Exception {
//Flyable bird = new Bird();
Flyable timeproxy = (Flyable)Proxy.newProxyInstance();
timeproxy.fly();

}
}

测试结果:

com.sf.simba.dynamicproxy.fly.TimeBirdProxy@100a15d
The bird is flying.......
 TimeBirdProxy: The Flyable has flown 767 millisecond

从上面这个例子可以看出,我不需要这个TimeBirdProxy类了,这个类被Proxy类自动生成了,但这样只能生成时间的代理类,如果要生成日志的代理类我们还是要改字符串,这明显不是我们想要的,所以我们要动态生成fly这个方法,然后在执行。我们需要这样一个接口,接口里的方法能够动态执行被代理类的方法,而代理类持有这个接口的引用,在调用fly()这个方法时调用这个接口里的方法,我们定义这个接口为InvocationHandler,方法为Invoke,这里有点绕,容易晕,先上一个类图。



JAVA动态代理模拟


代码如下:

 package com.sf.simba.dynamicproxy.fly;


import java.lang.reflect.Method;
/**
* 动态代理类持有此接口的引用
* @author -Simba-
*
*/
public interface InvocationHandler {
public void invoke(Object o,Method m);
}

时间处理类如下:

package com.sf.simba.dynamicproxy.fly;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* 时间记录处理类
* @author -Simba-
*
*/
public class TimeHandler implements InvocationHandler {
/**
* 这个target为被代理类
*/
private Object target;

private long starttime;
private long endtime;


public TimeHandler(Object target){
this.target = target;
}

public void pre(){
starttime = System.currentTimeMillis();
}
/**
* 为简单起见,假设被代理对象的方法没有参数,在JDK中的动态代理中是有参数的
*/
@Override
public void invoke(Object o, Method m) {

pre();//方法调用前的操作
try {
m.invoke(target, null);
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
end();//方法调用后的操作
System.out.println(" TimeBirdProxy: The Flyable has flown " + (endtime - starttime)+" millisecond");
}

public void end(){
endtime = System.currentTimeMillis();
}

}


Proxy类变为(注意持有的是InvocationHandler的引用了,而不是Flyable的引用,在生成构造方法的时候传入的是InvocationHandler类):

package com.sf.simba.dynamicproxy.fly;

import java.io.File;
import java.io.FileWriter;
import java.lang.reflect.Constructor;
import java.net.URL;
import java.net.URLClassLoader;

import javax.tools.JavaCompiler;
import javax.tools.JavaCompiler.CompilationTask;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;

/**
* 动态代理生成类
* @author -Simba-
*
*/
public class Proxy {


public static Object newProxyInstance(InvocationHandler handler) throws Exception{
String rn = "\r\n";
String classStr = "package com.sf.simba.dynamicproxy.fly; " +rn+
"import com.sf.simba.dynamicproxy.fly.Flyable;" +rn+
"import com.sf.simba.dynamicproxy.fly.InvocationHandler;" +rn+
" public class TimeBirdProxy implements Flyable { " + rn+
" private InvocationHandler handler; " + rn+
" public TimeBirdProxy(InvocationHandler handler){ " + rn+
" super(); " + rn+
" this.handler = handler; " + rn+
" } " + rn+
" @Override " + rn+
" public void fly() { " + rn+
"try { "+rn+
" java.lang.reflect.Method md = Flyable.class.getMethod(\"fly\");"+rn+
" handler.invoke(this,md);"+rn+
"}catch(Exception e){" +rn+
" e.printStackTrace();" +rn+
"}"+rn+
" } " + rn+
"\n" + rn+
"}";
System.out.println("动态生成的代理类为:\n"+classStr);
//定义动态文件生成到哪里叫什么名字
String fileName = System.getProperty("user.dir")+"/src/com/sf/simba/dynamicproxy/fly/TimeBirdProxy.java";
File file = new File(fileName);
FileWriter fw = new FileWriter(file);
//把字符串写入文件
fw.write(classStr);
fw.flush();
fw.close();

//获得编译器
JavaCompiler complier = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fileManager = complier.getStandardFileManager(null, null, null);
Iterable<? extends JavaFileObject> units = fileManager.getJavaFileObjects(fileName);
CompilationTask task = complier.getTask(null, fileManager, null, null, null, units);
//编译,生成字节码
task.call();
fileManager.close();

URL[] url = new URL[]{new URL("file:/"+System.getProperty("user.dir")+"/src/")};
//加载字节码
URLClassLoader classloder = new URLClassLoader(url);
Class<?> loadClass = classloder.loadClass("com.sf.simba.dynamicproxy.fly.TimeBirdProxy");
//获得代理对象构造方法
Constructor<?> constructor = loadClass.getConstructor(InvocationHandler.class);
//生成代理对象实例

Object newInstance = constructor.newInstance(handler);//new Bird()为被代理对象
System.out.println("-------------------生成的对象类为:"+newInstance.getClass().getName());
return newInstance;


}

}

测试类如下:

package com.sf.simba.dynamicproxy.fly;
/**
* 测试类
* @author -Simba-
*
*/
public class ProxyMain {


public static void main(String[] args) throws Exception {
Flyable bird = new Bird();
InvocationHandler handler = new TimeHandler(bird);
Flyable timeproxy = (Flyable)Proxy.newProxyInstance(handler);
timeproxy.fly();


}
}
生成的结果如下:

动态生成的代理类为:
package com.sf.simba.dynamicproxy.fly; 
import com.sf.simba.dynamicproxy.fly.Flyable;
import com.sf.simba.dynamicproxy.fly.InvocationHandler;
 public class TimeBirdProxy implements Flyable { 
    private InvocationHandler handler; 
    public TimeBirdProxy(InvocationHandler handler){ 
    super(); 
      this.handler = handler; 
    } 
  @Override 
  public void fly() { 
try { 
 java.lang.reflect.Method md = Flyable.class.getMethod("fly");
 handler.invoke(this,md);
}catch(Exception e){
 e.printStackTrace();
}
  } 


}
-------------------生成的对象类为:com.sf.simba.dynamicproxy.fly.TimeBirdProxy
The bird is flying.......
 TimeBirdProxy: The Flyable has flown 1824 millisecond


             通过动态生成的代理类我们可以看出,在代理类的fly()方法中,并没有去执行Bird的fly()方法(持有的引用有Flyable改为了InvocationHandler),而是通过反射机制获取Flyable的fly对应的Method,把Method作为参数传给InvocationHandler的invoke方法,然后执行InvocationHandler的invoke方法,而在这个invoke方法又去掉Flyable的fly对应的反射的invoke方法,在调用这个invoke方法前已经先把Bird这个对象传入,并在调用之前或之后加入时间的计算。如果要生成日志代理对象,只有实现InvocationHandler的invoke方法,并在方法前或者后加上需要的操作。

            但是这个还是不够完美,还是没有达到我们可以代理任何接口的动态代理,如果我们要实现代理任何接口的代理,那我们就要把Flyable的接口换成动态的就好。代码如下:

package com.sf.simba.dynamicproxy.fly;

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.JavaCompiler.CompilationTask;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;

/**
* 动态代理生成类
* @author -Simba-
*
*/
public class Proxy {


public static Object newProxyInstance(Class<?> infc,InvocationHandler handler) throws Exception{
String rn = "\r\n";
String methodStr = "";
//获得此接口的所有方法,即所有方法都要加代理
Method[] methods = infc.getMethods();
for(Method m : methods){
methodStr +="@Override"+rn+
"public "+m.getReturnType().getName()+" "+m.getName()+"(){"+rn+
"try { "+rn+
" java.lang.reflect.Method md = "+infc.getName()+".class.getMethod(\""+m.getName()+"\");"+rn+
" handler.invoke(this,md);"+rn+
"}catch(Exception e){" +rn+
" e.printStackTrace();" +rn+
"}"+rn+
"}"+rn;
}

String classStr = "package com.sf.simba.dynamicproxy.fly; " +rn+
"import com.sf.simba.dynamicproxy.fly.Flyable;" +rn+
"import com.sf.simba.dynamicproxy.fly.InvocationHandler;" +rn+
" public class TimeBirdProxy implements "+ infc.getName()+" { "+ rn +

" private InvocationHandler handler;"+rn+

" public TimeBirdProxy(InvocationHandler handler){"+rn +
"super();" +rn +
" this.handler = handler;"+rn +
" }"+rn +

methodStr
+ rn +
"}";
System.out.println("动态生成的代理类为:\n"+classStr);
//定义动态文件生成到哪里叫什么名字
String fileName = System.getProperty("user.dir")+"/src/com/sf/simba/dynamicproxy/fly/TimeBirdProxy.java";
File file = new File(fileName);
FileWriter fw = new FileWriter(file);
//把字符串写入文件
fw.write(classStr);
fw.flush();
fw.close();

//获得编译器
JavaCompiler complier = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fileManager = complier.getStandardFileManager(null, null, null);
Iterable<? extends JavaFileObject> units = fileManager.getJavaFileObjects(fileName);
CompilationTask task = complier.getTask(null, fileManager, null, null, null, units);
//编译,生成字节码
task.call();
fileManager.close();

URL[] url = new URL[]{new URL("file:/"+System.getProperty("user.dir")+"/src/")};
//加载字节码
URLClassLoader classloder = new URLClassLoader(url);
Class<?> loadClass = classloder.loadClass("com.sf.simba.dynamicproxy.fly.TimeBirdProxy");
//获得代理对象构造方法
Constructor<?> constructor = loadClass.getConstructor(InvocationHandler.class);
//生成代理对象实例

Object newInstance = constructor.newInstance(handler);//new Bird()为被代理对象
System.out.println("-------------------生成的对象类为:"+newInstance.getClass().getName());
return newInstance;


}

}

测试类:

package com.sf.simba.dynamicproxy.fly;
/**
* 测试类
* @author -Simba-
*
*/
public class ProxyMain {

public static void main(String[] args) throws Exception {
Flyable bird = new Bird();
InvocationHandler handler = new TimeHandler(bird);
Flyable timeproxy = (Flyable)Proxy.newProxyInstance(Flyable.class,handler);
timeproxy.fly();

}
}

生成结果如下:


动态生成的代理类为:
package com.sf.simba.dynamicproxy.fly; 
import com.sf.simba.dynamicproxy.fly.Flyable;
import com.sf.simba.dynamicproxy.fly.InvocationHandler;
 public class TimeBirdProxy implements  com.sf.simba.dynamicproxy.fly.Flyable { 
 private InvocationHandler handler;
 public TimeBirdProxy(InvocationHandler handler){
super();
 this.handler = handler;
 }
@Override
public  void  fly(){
try { 
 java.lang.reflect.Method md = com.sf.simba.dynamicproxy.fly.Flyable.class.getMethod("fly");
 handler.invoke(this,md);
}catch(Exception e){
 e.printStackTrace();
}
}


}
-------------------生成的对象类为:com.sf.simba.dynamicproxy.fly.TimeBirdProxy
The bird is flying.......
 TimeBirdProxy: The Flyable has flown 1960 millisecond


         至此,动态代理的模拟已经完成,而JDK下的动态代理比这个更加复杂,加上了参数和一个类实现多个接口的情况,并且JDK动态代理中是直接把java类生成二进制文件,而不是像本文中通过JavaComplier生成字节码然后在生成二进制加入到内存。