对上一文中动态代理的优化:
一、动态生成接口
只需要在使用 Proxy.newProxyInstance() 方法时,将接口作为形参传入,做如下更改即可:
1、改变形参,调用接口的名字。
2、调用时写入要调用的接口名。
扩展接口名很简单,只需要加入要调用的接口名即可。之后的问题是:在这里,只能用 move() 方法,如何对它进行动态生成呢?
二、动态生成方法
将要进行修改的方法进行提取,并对其进行修改。
methodStr 编写方法块,写入 str
在最后
新问题:现在生成的代理,只能生成时间的代理。那么如何改进。
三、产生其他处理方式的代理,调用一个别人指定的处理方式。
(InvocationHandler:方法调用的处理器)
1、该处理器应该有一个 method ,来处理任何方法。而处理的方式,由子类来决定,子类可以灵活的采取处理方法,日志记录,权限设置等。
记录时间的子类
2、产生什么样类型的代理,进行何种的处理 newProxyInstance3(Class infc, InvocationHandler handle)
------------------------------------------------------------------------------------
public static Object newProxyInstance3(Class infc, InvocationHandler handler) throws Exception{
String rt = "\r\n";
String methodStr = "";
Method[] methods = infc.getMethods();
for(Method m : methods){
methodStr += " @Override " + rt +
" public void " + m.getName() + "()" + "{" + rt +
" try {" + rt +
" Method md = " + infc.getName() + ".class.getMethod(\"" + m.getName() + "\"); "+ rt +
" h.invock(this, md);" + rt + ""+
" } catch(Exception e){e.printStackTrace();}" + rt +
" }" + rt +
"}";
System.out.println("methodStr === " + methodStr);
}
String str =
"package com.inspur.simpleProxy;" + rt +
"import java.lang.reflect.Method;" + rt +
"public class TankTimeProxy implements " + infc.getName() + "{" + rt +
" com.inspur.simpleProxy.InvocationHandler h;" + rt +
" public TankTimeProxy(InvocationHandler handler){" + rt +
" this.h = handler;" + rt +
" }" + rt +
methodStr;
/*
* 要生成的目的地
*/
String fileName = System.getProperty("user.dir") + "/src/com/inspur/simpleProxy/TankTimeProxy.java";
File file = new File(fileName);
FileWriter fileWriter = new FileWriter(file);
fileWriter.write(str);
fileWriter.flush();
fileWriter.close();
/*
* 编译,生成 .class 文件
*/
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
System.out.println(compiler.getClass().getName());
StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
Iterable iterable = fileManager.getJavaFileObjects(fileName);
CompilationTask task = compiler.getTask(null, fileManager, null, null, null, iterable);
task.call();
fileManager.close();
/*
* load to memory , create an instance
* 加载到内存,生成新对象
*/
// 将硬盘上的 Java 文件,放到内存中的类中,需要指定路径
// 此方式还可以从网上去 load 类,通过网络传来的类 , 也可以通过此方式来 load 类
// user.dir 当前文件根目录
URL[] urls = new URL[]{new URL("file:/" + System.getProperty("user.dir") + "/src")};
URLClassLoader urlClassLoader = new URLClassLoader(urls);
Class c = urlClassLoader.loadClass("com.inspur.simpleProxy.TankTimeProxy");
System.out.println(c);
// 拿到其中的构造方法,得到方法,通过参数来确定。 找一个构造方法,这个构造方法的参数类型是 Moveable ,便得到了 TankTimeProxy
Constructor constructor = c.getConstructor(InvocationHandler.class);
//Object m = constructor.newInstance(new Tank());
//m.move();
// c.newInstance(); 会产生一个新的构造方法
// Moveable m = (Moveable) constructor.newInstance(new Tank());
// return m;
// 因为我们并不知道 infc 实现的是哪个接口,将新生成的对象作为 Object 类型的返回值 返回回去
Object m = constructor.newInstance(handler);
return m;
}
------------------------------------------------------------------------------------
3、Client 类
一个典型的动态代理创建对象过程可分为以下四个步骤:
1、通过实现 InvocationHandler 接口创建自己的调用处理器,
InvocationHandler handler = new InvocationHandler( ... );
2、通过为 Proxy 类指定 ClassLoader 对象和一组 interface 创建动态代理类
Class clazz = Proxy.getProxyClass(classLoader,new Class[]{...});
3、通过反射机制获取动态代理类的构造函数,其参数类型是调用处理器接口类型
Constructor constructor = clazz.getConstructor(
new Class[]{InvocationHandler.class});
4、通过构造函数创建代理类实例,此时需要将处理器对象作为参数被传入
Interface Proxy = (Interface)constructor.newInstance(new Object[] (handler));
为了简化对象的创建过程,Proxy 类中的 newInstance 方法封装了 2 -- 4,只需要两步即可完成代理对象的创建。
生成的 ProxySubject 继承 Proxy 类实现 Subject 接口,实现的 Subject 的方法实际调用处理器的 invoke 方法,而 invoke 犯法利用反射调用的是被代理对象的方法
Object result=method.invoke( proxied,args );
通过 java.lang.reflect 包下的方法实现动态代理
1、编写接口
2、编写委托类
3、编写处理类,实现 InvocationHandler 接口,实现记录的功能
4、编写 Client 类
上面的 Proxy 类是对类 $Proxy0 类的模拟,此类完成的功能是:
-- 动态生成代理对象
-- 将代理对象进行编译,得到 .class 文件,即 代理对象.class
-- 加载到内存
-- 通过构造方法,得到要调用的对应的方法
(以上是个人理解,自觉无法言明,在网上搜罗信息时发现此博主的文章甚是有理,请大家查看下一篇文章 -- 动态代理深入分析(一)(二))