当打算在现有的方法前后加上一些逻辑(比如在前后加上日志),同时又不想去更改现有的代码,就可以使用动态代理。
个人理解动态代理的实现逻辑是: 生成一份源码(FileIO), 并且编译她(JavaCompiler/StandardJavaFileManager/Iterable/CompilationTask), 加载到内存里面(URL[],URLClassLoader.loadClass()), 获取构造函数(class(具体).getConstructor,ctr.newInstance), 调用具体的方法(这个方法使用反射机制来写invoke)
就相当于打算调用class1的method1方法,在自己生成源码的时候,就需要在calss1的method1的方法里面写:
@Override public void method1() { try{ Method md = com.lizhao.class1.class.getMethod("method1"); h.invoke(this,md); // InvocationHandler h; InvocationHandler是自己写的实现方法,可以看下面 }catch(Exception e){ e.printStackTrace(); } } 具体映射的时候是调用这个方法: package com.lizhao; import java.lang.reflect.Method; public class TimeInvocationHandler implements InvocationHandler { private Object object; public TimeInvocationHandler(Object object){ this.object=object; } @Override public void invoke(Object proxy, Method method) throws Exception { System.out.println("start time: "+System.currentTimeMillis());//前逻辑 //proxy.getClass().getMethod(name, null); method.invoke(object);//这里是使用底层的反射,后面加上参数 System.out.println("end time: "+System.currentTimeMillis());//后逻辑 } } 上面是调用的一个过程,因为不全,所以肯定看的不是很明白下面是自己实现的动态代理类:
//代理类 Proxy.java
package com.lizhao; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; 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 Object newProxyInstance(Class infac,InvocationHandler h) throws Exception { String rt = "\r\n"; Method[] methods = infac.getMethods(); String strMethod = ""; //获取传入的类的所有方法 for(Method m:methods){ System.out.println(m.getName()); // strMethod+= // " @Override " + rt // + " public "+m.getReturnType()+" "+m.getName() + "() { " + rt // + " System.out.println(\"start time: \"+System.currentTimeMillis()); " + rt // + " t."+m.getName() + "(); " + rt // + " System.out.println(\"end time: \"+System.currentTimeMillis()); " + rt // + " } " + rt ; strMethod+= " @Override " + rt + " public "+m.getReturnType()+" "+m.getName() + "() { " + rt + " try{"+ rt + " Method md = "+infac.getName()+".class.getMethod(\""+m.getName() + "\");" + rt + " h.invoke(this,md); " + rt + " }catch(Exception e){e.printStackTrace();}"+ rt + " } " + rt ; } String src = "package com.lizhao;" + rt + " import java.lang.reflect.Method; " + rt + "public class TimeProxyN implements " + infac.getName()+"{ " + rt + " private InvocationHandler h ; " + rt + " public TimeProxyN( InvocationHandler h) { " + rt + " this.h = h; " + rt + " } "+ rt + strMethod +rt + "} "+ rt; //String fileName = "file:/D:/studyDir/codeDir/temSrc/com/lizhao/com/lizhao/TimeProxy.java";// java文件存放的路径 String fileName = "D:/studyDir/codeDir/temSrc/com/lizhao/TimeProxyN.java";// java文件存放的路径 System.out.println(fileName); // 写入文件 File f = new File(fileName); //f.createNewFile(); FileWriter fw; fw = new FileWriter(f); fw.write(src);// 将内容写到文件里面 fw.flush(); fw.close(); // 编译文件 JavaCompiler systemJavaCompiler = ToolProvider.getSystemJavaCompiler();// java的编译类 StandardJavaFileManager standardFileManager = systemJavaCompiler.getStandardFileManager(null, null, null);// 管理类 Iterable units = standardFileManager.getJavaFileObjects(fileName);// 将路径传入 CompilationTask task = systemJavaCompiler.getTask(null, standardFileManager, null, null, null, units); task.call(); standardFileManager.close(); // 将类加载到内存里面 URL[] urls = new URL[] { new URL("file:/D:/studyDir/codeDir/temSrc/") }; URLClassLoader urlClassLoader = new URLClassLoader(urls); Class c = urlClassLoader.loadClass("com.lizhao.TimeProxyN"); System.out.println(c.getName()); // 获取构造函数,里面传入的值就是代表 需要传入的类型, // 比如可以传入 (int a,int b),就表示寻找构造构造函数Const(int a,int b) Constructor ctr = c.getConstructor(InvocationHandler.class); Object m = ctr.newInstance(h); return m; } } 通过这个类可以获得一个动态代理对象,而且里面的所有方法都给重写了;InvocationHandler.java接口
package com.lizhao; import java.lang.reflect.Method; public interface InvocationHandler { public void invoke(Object proxy, Method method) throws Exception; } 实现接口的类:TimeInvocationHandler .java
package com.lizhao; import java.lang.reflect.Method; public class TimeInvocationHandler implements InvocationHandler { private Object object; public TimeInvocationHandler(Object object){ this.object=object; } @Override public void invoke(Object proxy, Method method) throws Exception { System.out.println("start time: "+System.currentTimeMillis()); //proxy.getClass().getMethod(name, null); method.invoke(object); System.out.println("end time: "+System.currentTimeMillis()); } } 4.我们想要操作的类的接口: package com.lizhao; public interface Moveable { public void move(); } package com.lizhao; public class Tank implements Moveable{ @Override public void move() { System.out.println("Tank move start"); try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println("Tank move end"); } } 5.在test函数里面测试: package com.lizhao.test; import com.lizhao.Moveable; import com.lizhao.Proxy; import com.lizhao.Tank; import com.lizhao.TimeInvocationHandler; public class Main { public static void main(String[] args) throws Exception { Tank t = new Tank(); Moveable newProxyInstance = (Moveable) new Proxy().newProxyInstance(Moveable.class,new TimeInvocationHandler(t)); newProxyInstance.move(); } } 里面遇到的一些问题记录:String fileName = System.getProperty("user.dir")+"\\src\\com\\lizhao\\TimeProxy.java";//java文件存放的路径、 这个路径是要写文件的全名称,之前就写到了src目录,然后会报找不到文件的错误 JavaCompiler systemJavaCompiler = ToolProvider.getSystemJavaCompiler();//java的编译类 StandardJavaFileManager standardFileManager = systemJavaCompiler.getStandardFileManager(null, null, null );//管理类、 这是获取jdk给我们提供的编译类,如果你是直接使用jre的话就会报空指针错误。因为jre里面是无法获取这个类的。 解决办法:1.window--preference--java--installed jres--》右边编辑框Search,找到自己jdk的目录,也可以是上一层,比如:C:\Program Files\Java\,在这个目录下面查找,然后列表里面就会出现jdk。 2.项目目录上面右键--properties--Java Build Path--remove原来的jre,点击右边的 Add Library,点击第二个Aleernate Jre,这时候就可以选择jdk了 PS:这个地方之前在网上找原因说是jdk版本的问题,然后我去重新下载了个最新的,从1.8.5到1.8.112。中间因为先下载了32位的jdk,然后配置好环境变量后,打开eclipse就报错"exit code=13",是因为jdk版本位数和eclipse版本位数不匹配导致的,又重新下回来了、。。
根据视频 马士兵---【设计模式--动态代理】
虽然原理是这样,但是在我们具体用的时候就不会这个麻烦的,下面就拿SSH框架中的DAO层举个例子:
我们知道在mvc框架里面程序的运行方式是这样的:Client--》Action--》Service--》Dao--model
当我们想要在Dao层加入一些自己的逻辑,同时又不想去改变DAO原来的代码,就像下面这么写:
test.java
package com.bjsxt.service; import java.lang.reflect.Proxy; import java.net.URL; import java.net.URLClassLoader; import org.junit.Test; import org.springframework.beans.factory.BeanFactory; import org.springframework.context.support.ClassPathXmlApplicationContext; import com.bjsxt.dao.UserDAO; import com.bjsxt.invoke.LogInvocationHandler; import com.bjsxt.model.User; public class UserServiceTest { @Test public void test() throws Exception { //使用了spring,和动态代理关系不大,这个就相当于读取配置文件 BeanFactory beanFaction = new ClassPathXmlApplicationContext("beans.xml"); //第一个参数ClassLoader loader URL[] urls = new URL[] { new URL("file:/"+System.getProperty("user.dir")+"/src/") }; URLClassLoader urlClassLoader = new URLClassLoader(urls); //第二个参数 Class<?>[] interfaces, Class classes[] = new Class[] {UserDAO.class}; //第三个参数InvocationHandler h UserService userService = (UserService) beanFaction.getBean("userService"); LogInvocationHandler logInvocationHandler = new LogInvocationHandler(userService.getUserDAO()); //获取代理对象 UserDAO newProxyInstance = (UserDAO) Proxy.newProxyInstance(urlClassLoader ,classes , logInvocationHandler); userService.setUserDAO(newProxyInstance); userService.add(); } }一些我认为比较重要的地方做了注释
相关的类我也贴出来:
UserDAO.class
package com.bjsxt.dao; public abstract interface UserDAO { public void sava(); } UserMySqlDao.java//实现了UserDAO接口 package com.bjsxt.dao.impl; import com.bjsxt.dao.UserDAO; public class UserMySqlDao implements UserDAO{ private String daoName; public String getDaoName() { return daoName; } public void setDaoName(String daoName) { this.daoName = daoName; } public void sava(){ System.out.println("UserMySqlDao:sava()"); } @Override public String toString() { return "daoName:"+daoName; } public void init(){ System.out.println("init UserMySqlDao"); } } LogInvocationHandler.java package com.bjsxt.invoke; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; public class LogInvocationHandler implements InvocationHandler { private Object target; public LogInvocationHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("LogInvocationHandler: log printing"); method.invoke(target); System.out.println("LogInvocationHandler: log end"); return null; } }UserService.java
package com.bjsxt.service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import com.bjsxt.dao.UserDAO; import com.bjsxt.model.User; public class UserService { UserDAO userDAO; public UserService(){ } public UserDAO getUserDAO() { return userDAO; } @Autowired public void setUserDAO(@Qualifier("UserMySql2")UserDAO userDAO) { this.userDAO = userDAO; } public void add(User user){ this.userDAO.sava(); } public void add(){ this.userDAO.sava(); } public void init(){ System.out.println("init UserService"); } public void destroy(){ System.out.println("desrory UserService"); } }运行之后: init UserMySqlDao init UserService LogInvocationHandler: log printing UserMySqlDao:sava() LogInvocationHandler: log end
