java动态代理详解

        很久很久以前,我曾经听过一个网络课,老师手动实现了jdk底层的动态代理,当时不太理解,只手动抄写了一些源码。碰巧今天和别人讨论动态代理的东西,就翻出了以前的老笔记,整理整理写个博客,希望能对初学者有些帮助,也算是加深自己的理解。好了,废话不多说,首先说下最基础的代理模式是啥吧,先上代码:

package com.sunsy.proxy;

public interface Animal {
	void eat();
}
package com.sunsy.proxy;

public class Cat implements Animal {

	@Override
	public void eat() {
		System.out.println("cat eat fish!");
	}

}
package com.sunsy.proxy;

public class CatTimeProxy implements Animal {

	Animal a;
	
	public CatTimeProxy(Animal a) {
		super();
		this.a = a;
	}
	
	@Override
	public void eat() {
		long start = System.currentTimeMillis();
		System.out.println("start time:" + start);
		a.eat();
		long end = System.currentTimeMillis();
		System.out.println("end time:" + end);
	}

}
package com.sunsy.proxy;

public class MainTest {
	
	public static void main(String[] args) {
		Cat t = new Cat();
		CatTimeProxy catTimeProxy = new CatTimeProxy(t);
		catTimeProxy.eat();
	}

}

        以上的Animal接口、Cat类、CatTimeProxy类就构成了一个很简单的代理模式,Cat类是被代理对象,CatTImeProxy是代理类,和被代理对象实现了同样的接口,但是这个类里有个属性就是被代理对象,然后实现接口的方法时要在方法中调用被代理类的接口实现方法。。。说的可能比较绕,但是相信看下代码很容易就理解了。

        好了,最基础的东西说完了,接下来我们就要开始实现自己的动态代理了。首先,我们先创建一个自己的Proxy类,这个类里有个newProxyInstance方法,我们把整个CatTimeProxy类作为一个字符串放到这个方法里,然后我们将这个字符串编译成类放到内存里,这样用户使用的时候只要掉newProxyInstance方法就能拿到代理类了,虽然现在这个字符串现在是写死的,也就是说拿到的代理类是固定的,不过如何去解决这个问题是下一步的工作了,我们还是一步一步来吧。。

package com.sunsy.proxy;

public class Proxy {
	
	public static Object newProxyInstance() {
		
		String src = 
				"package com.sunsy.proxy;" +

				"public class CatTimeProxy implements Animal {"+

					"Animal a;"+
					
					"public CatTimeProxy(Animal a) {"+
						"super();"+
						"this.a = a;"+
					"}"+
					
					"@Override"+
					"public void eat() {"+
						"long start = System.currentTimeMillis();"+
						"System.out.println(\"start time:\" + start);"+
						"a.eat();"+
						"long end = System.currentTimeMillis();"+
						"System.out.println(\"end time:\" + end);"+
					"}"+

				"}";
		
		return null;
	}
	

}

        现在我只在这个类里写了一个字符串,显然,如果我们把这个字符串编译一下,生成class文件,然后把生成的类读到内存里,我们就又向前进了一步,接下来,我们来实现一下:

package com.sunsy.proxy;

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

public class Proxy {
	
	public static Object newProxyInstance() throws Exception {
		
		String rt = "\r\n";
		String src = 
				"package com.sunsy.proxy;" + rt+

				"public class CatTimeProxy1 implements Animal {"+rt+

					"Animal a;"+rt+
					
					"public CatTimeProxy1(Animal a) {"+rt+
						"super();"+rt+
						"this.a = a;"+rt+
					"}"+rt+
					
					"@Override"+rt+
					"public void eat() {"+rt+
						"long start = System.currentTimeMillis();"+rt+
						"System.out.println(\"start time:\" + start);"+rt+
						"a.eat();"+rt+
						"long end = System.currentTimeMillis();"+rt+
						"System.out.println(\"end time:\" + end);"+rt+
					"}"+rt+

				"}";
		System.out.println(System.getProperty("user.dir"));
		String fileName = System.getProperty("user.dir") + "\\src\\com\\sunsy\\proxy\\CatTimeProxy1.java";
		File f = new File(fileName);
		FileWriter fw = new FileWriter(f);
		fw.write(src);
		fw.flush();
		fw.close();
		
		//编译生成class文件
		JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
		StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
		Iterable iterator = fileManager.getJavaFileObjects(fileName);
		CompilationTask t = compiler.getTask(null, fileManager, null, null, null, iterator);
		t.call();
		fileManager.close();
		
		//把class读到内存中
		URL[] urls = new URL[] {new URL("file:/" + System.getProperty("user.dir") + "/src")};
		URLClassLoader uLoader = new URLClassLoader(urls);
		Class c = uLoader.loadClass("com.sunsy.proxy.CatTimeProxy1");
		System.out.println(c);
		
		//创建对象
		Constructor constructor = c.getConstructor(Animal.class);
		Animal animal = (Animal)constructor.newInstance(new Cat());
		return animal;
	}
	

}

        然后我们在main方法里调用Animal animal = (Animal) Proxy.newProxyInstance();animal.eat();可以看到初步效果已经出来了。但是我们现在写的这个代理只能生成一个实现Animal的代理,那么怎么才能和jdk一样可以生成实现任意接口的代理呢?很简单,最容易想到的方法就是把想要实现哪种接口也作为参数传入到newProxyInstance方法中。如下,Proxy里的newProxyInstance变成了下面这样:

public static Object newProxyInstance(Class interf) throws Exception {
		
		String rt = "\r\n";
		String src = 
				"package com.sunsy.proxy;" + rt+

				"public class CatTimeProxy1 implements " + interf.getName() + " {"+rt+

        调用的时候需要传入接口类型,像这样:Animal animal = (Animal) Proxy.newProxyInstance(Animal.class);。

        但是这样还是有问题的,因为我们的字符串里实现的方法(eat方法)是写死的,这显然不合理,别人传了个别的接口,这个接口里没有eat,那不是GG了?所以我们还要获取传入的接口的方法然后做一些处理,newProxyInstance方法就变成了下边这样:

public static Object newProxyInstance(Class interf) throws Exception {
		String rt = "\r\n";
		
		String methodStr = "";
		Method[] methods = interf.getMethods();
		for(Method m : methods) {
			methodStr += "@Override" + rt +
					     "public void " + m.getName() + "() {" + rt + 
					     "long start = System.currentTimeMillis();"+rt+
							"System.out.println(\"start time:\" + start);"+rt+
							"a." + m.getName() + "();" +rt+
							"long end = System.currentTimeMillis();"+rt+
							"System.out.println(\"end time:\" + end);"+rt+
					     
					     "}";
		}
		
		
		String src = 
				"package com.sunsy.proxy;" + rt+

				"public class CatTimeProxy1 implements " + interf.getName() + " {"+rt+

					"Animal a;"+rt+
					
					"public CatTimeProxy1(Animal a) {"+rt+
						"super();"+rt+
						"this.a = a;"+rt+
					"}"+rt+
					
					methodStr +

				"}";
		System.out.println(System.getProperty("user.dir"));
		String fileName = System.getProperty("user.dir") + "\\src\\com\\sunsy\\proxy\\CatTimeProxy1.java";
		File f = new File(fileName);
		FileWriter fw = new FileWriter(f);
		fw.write(src);
		fw.flush();
		fw.close();
		
		//编译生成class文件
		JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
		StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
		Iterable iterator = fileManager.getJavaFileObjects(fileName);
		CompilationTask t = compiler.getTask(null, fileManager, null, null, null, iterator);
		t.call();
		fileManager.close();
		
		//把class读到内存中
		URL[] urls = new URL[] {new URL("file:/" + System.getProperty("user.dir") + "/src")};
		URLClassLoader uLoader = new URLClassLoader(urls);
		Class c = uLoader.loadClass("com.sunsy.proxy.CatTimeProxy1");
		System.out.println(c);
		
		//创建对象
		Constructor constructor = c.getConstructor(interf);
		Object o = constructor.newInstance(new Cat());
		return o;
	}

        好了,这时候我们不管传入啥接口,只要这个接口里有实现方法,那么我们的代理都会给这个方法的前边加上开始时间,后边加上结束时间。但是问题又出现了,我们这个代理加的逻辑是写死的,也就是只能显示开始时间和结束时间,如果我们想变个功能,比如加点日志加点权限控制这种操作,就需要改代码,而且是改我们的那个长长的字符串,这显然顶不住,所以我们要再进行一些操作,我们需要搞个新的类,这个类可以动态的指定代理里要实现的逻辑,这个类就是InvocationHandler接口,如下:

package com.sunsy.proxy;

import java.lang.reflect.Method;

public interface InvocationHandler {

	public void invoke(Object object, Method method);
	
}

        这个接口的invoke有两个参数,method是想要修饰的方法,由于实现类里需要反射去调用方法,所以必须有个对象(只有静态方法不需要先创建对象),所以必须有个Object参数,接下来我们写一个InvocationHandler的实现类——TimeHandler,如下:

package com.sunsy.proxy;

import java.lang.reflect.Method;

public class TimeHandler implements InvocationHandler {

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

	@Override
	public void invoke(Object o, Method m) {   //这里边传入的这个Object是我们自己生成的哪个类的对象
		long start = System.currentTimeMillis();
		System.out.println("start time:" + start);
		
		//要想调用一个方法,必须得有个对象才行(又不是静态方法),所以我们要在传入的参数上加个Object
		try {
			m.invoke(target, new Object[] {});
		} catch (Exception e) {
			e.printStackTrace();
		}
		
		long end = System.currentTimeMillis();
		System.out.println("end time:" + end);
	}

	public Object getTarget() {
		return target;
	}

	public void setTarget(Object o) {
		this.target = o;
	}
}

        然后我们需要修改一下Proxy类,在字符串里动态的生成method并调用,最后创建返回对象的时候改为传入InvocationHandler类型的参数,如下:

package com.sunsy.proxy;

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 interf, InvocationHandler h) throws Exception {
		String rt = "\r\n";
		
		String methodStr = "";
		Method[] methods = interf.getMethods();
		
//		for(Method m : methods) {
//			methodStr += "@Override" + rt +
//					     "public void " + m.getName() + "() {" + rt + 
//					     "long start = System.currentTimeMillis();"+rt+
//							"System.out.println(\"start time:\" + start);"+rt+
//							"a." + m.getName() + "();" +rt+
//							"long end = System.currentTimeMillis();"+rt+
//							"System.out.println(\"end time:\" + end);"+rt+
//					     
//					     "}";
//		}
		
		
		for(Method m : methods) {
			methodStr += "@Override" + rt +
					"public void " + m.getName() + "() {" + rt + 
					"try{" + rt +
					"java.lang.reflect.Method md = " + interf.getName() + ".class.getMethod(\"" + m.getName() + "\");" + rt +
					"h.invoke(this, md);" + rt + 
					"}catch(Exception e){e.printStackTrace();}" + rt + 
					"}";
				     

		}
		String src = 
				"package com.sunsy.proxy;" + rt+

				"public class CatTimeProxy1 implements " + interf.getName() + " {"+rt+

				"com.sunsy.proxy.InvocationHandler h;" + rt +
					"public CatTimeProxy1(com.sunsy.proxy.InvocationHandler h) {"+rt+
						"this.h = h;"+rt+
					"}"+rt+
					
					
					
					methodStr +

				"}";
		System.out.println(System.getProperty("user.dir"));
		String fileName = System.getProperty("user.dir") + "\\src\\com\\sunsy\\proxy\\CatTimeProxy1.java";
		File f = new File(fileName);
		FileWriter fw = new FileWriter(f);
		fw.write(src);
		fw.flush();
		fw.close();
		
		//编译生成class文件
		JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
		StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
		Iterable iterator = fileManager.getJavaFileObjects(fileName);
		CompilationTask t = compiler.getTask(null, fileManager, null, null, null, iterator);
		t.call();
		fileManager.close();
		
		//把class读到内存中
		URL[] urls = new URL[] {new URL("file:/" + System.getProperty("user.dir") + "/src")};
		URLClassLoader uLoader = new URLClassLoader(urls);
		Class c = uLoader.loadClass("com.sunsy.proxy.CatTimeProxy1");
		System.out.println(c);
		
		//创建对象
		Constructor constructor = c.getConstructor(InvocationHandler.class);
		Object o = constructor.newInstance(h);
		return o;
	}
	

}

        然后我们调用一下main方法:

package com.sunsy.proxy;

public class MainTest {
	
	public static void main(String[] args) throws Exception {

		Cat cat = new Cat();
		InvocationHandler h = new TimeHandler(cat);
		Animal animal = (Animal) Proxy.newProxyInstance(Animal.class, h);
		animal.eat();
	}

}

        调用之后会生成一个CatTimeProxy1类,看一看生成的结果是啥,再看看控制台输出的信息,是不是实现了大爷想要添加的逻辑(注意,这时候是不是感觉基本已经大功告成了,如果不是自己写的代码,调用的人完全不知道会有CatTimeProxy1这个类生成,已经很接近jdk底层的动态代理实现了):

package com.sunsy.proxy;
public class CatTimeProxy1 implements com.sunsy.proxy.Animal {
com.sunsy.proxy.InvocationHandler h;
public CatTimeProxy1(com.sunsy.proxy.InvocationHandler h) {
this.h = h;
}
@Override
public void eat() {
try{
java.lang.reflect.Method md = com.sunsy.proxy.Animal.class.getMethod("eat");
h.invoke(this, md);
}catch(Exception e){e.printStackTrace();}
}}

        至此,我们自己手写动态代理就结束了,下面我们来看看正牌的动态代理是咋用的吧,很简单,就几行代码,我就直接贴出来了。以下是华丽的分割线

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------

package com.sunsy.jdkproxy;

public interface Animal {
	void eat();
}
package com.sunsy.jdkproxy;

public class Cat implements Animal {

	@Override
	public void eat() {
		System.out.println("cat eat fish");
	}
	
}
package com.sunsy.jdkproxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class TimeHandler implements InvocationHandler {

	private Object obj;
	public TimeHandler(Object obj) {
		this.obj = obj;
	}
	
	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		System.out.println("start!");
		Object result = method.invoke(obj, args);
		System.out.println("end!");
		return result;
	}

}
package com.sunsy.jdkproxy;

import java.lang.reflect.Proxy;

public class MainTest {

	public static void main(String[] args) {
		Animal animal = new Cat();
		TimeHandler h = new TimeHandler(animal);
		
		Animal proxy = (Animal) Proxy.newProxyInstance(animal.getClass().getClassLoader(), animal.getClass().getInterfaces(), h);
		proxy.eat();
	}
	
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值