首页 > 代码库 > Java动态代理模式
Java动态代理模式
1、如何实现静态代理模式?
能够想到的方式起码有两种继承和聚合。
创建一个接口
package com.jyd.proxy; /** * 定义一个可以工作的接口,定义一系列操作方法 * @author hadoop * */ public interface Workable { void renting(); }
创建一个类继承这个接口
package com.jyd.proxy; import java.util.Random; /** * 定义一个中介人来实现可以工作的接口 * @author hadoop * */ public class Middleman implements Workable { private Random random = new Random(); @Override public void renting() { //中介操作 try { Thread.sleep(random.nextInt(10000)); System.out.println("中介开始租房..."); } catch (InterruptedException e) { e.printStackTrace(); } } }
创建客户端类来调用此方法
package com.jyd.proxy; public class Client { public static void main(String[] args) { Workable workable = new Middleman(); workable.renting(); } }
1.1、使用继承来实现静态代理:
如果我想知道一个类里的某一个方法一共执行多长时间,如何实现了?前提是我们并没有这个类的源码
创建一个类来继承中间类
package com.jyd.proxy; public class MiddlemanTimeProxy extends Middleman { @Override public void renting() { long startTime = System.currentTimeMillis(); System.out.println("开始计时: " + startTime); super.renting();//一个类继承了父类,super会调用自己父类 long endTime = System.currentTimeMillis(); System.out.println("这个方法一共使用多长时间:" + (endTime - startTime)); } }
创建一个客户端类来调用此代理方法
package com.jyd.proxy; public class TimeProxyClient { public static void main(String[] args) { Workable workable = new MiddlemanTimeProxy(); workable.renting(); } }
依次来类推:如果现在我想实现一个记录日志的功能,那么的话重新创建一个类来继承中间人的方法在super. renting()方法的前后来处理日志的操作;现在我又像实现一个权限验证的
功能,那么需要再次创建一个类里继承中间人的方法在super.renting()方法的前后来处理权限验证操作;
重点:
再想如果我想实现一个先记录日志再实现权限验证的功能,那么是不是再创建一个
类来继承中间人的方法在super. renting()方法的前后来处理日志的操作在验证权限,如果这么做的话,就会出现无穷无尽的类继承到最后会出现一个类爆炸的情况。这里我们得出一个结论:如果通过类继承来实现静态代理会出现类爆炸。怎么办???
1.2、使用聚合来实现静态代理:
如果我想知道一个类里的某一个方法一共执行多长时间,如何实现了?前提是我们并没有这个类的源码
创建一个记录日志聚合的代理类
package com.jyd.proxy; public class MiddlemanTimeProxy implements Workable{ //聚合就是一个类包含另一类的引用 private Workable workable; public MiddlemanTimeProxy(Workable workable) { super(); this.workable = workable; } @Override public void renting() { long startTime = System.currentTimeMillis(); System.out.println("开始计时: " + startTime); workable.renting(); long endTime = System.currentTimeMillis(); System.out.println("这个方法一共使用多长时间:" + (endTime - startTime)); } }
创建一个客户端类来调用此代理方法
package com.jyd.proxy; public class TimeProxyClient { public static void main(String[] args) { Middleman m = new Middleman(); MiddlemanTimeProxy mt = new MiddlemanTimeProxy(m); mt.renting(); } }
创建一个权限检查聚合的代理类
package com.jyd.proxy; public class MiddlemanPermissionProxy implements Workable{ private Workable workable; public MiddlemanPermissionProxy(Workable workable) { super(); this.workable = workable; } @Override public void renting(){ System.out.println("开始检查权限..."); workable.renting(); System.out.println("结束检查权限..."); } }
比如说:现在需要实现一个计算时间的功能在检查权限的代理功能,那么的话现在我就不需要重新一个代理类,直接使用之前实现的代理类组合一下即可,这样可以有效的减少代理类的实现
package com.jyd.proxy; public class ProxyClient { public static void main(String[] args) { Workable workable = new Middleman(); MiddlemanTimeProxy mt = new MiddlemanTimeProxy(workable); MiddlemanPermissionProxy mp = new MiddlemanPermissionProxy(mt); mp.renting(); } }
结论:聚合实现静态代理类可以有效减少代理类的创建,所以集合实现静态代理要比继承实现要好。
2、动态代理
那么问题来了,如果一个项目中有很多类要实现各个方法都实现计算方法的执行时间怎么处理???
为每个类都创建一个代理对象吗?显然这样做的方式是非常不可取的,那么我们来分析之前实现的静态代理类,我们发现一个问题一个代理类中包含被代理的对象,被代理对象去调用需要执行的方法,--这样的话基本上这个代理类是一个模板式的代码—>发挥我们想象,这个代理类可不可以动态的去创建???
2.1、将代理对象删除写成固定字符串动态编译运行
可以把代理对象模板类写成一个字符串,通过java的JavaCompile编译器动态来编译此文件,然后通过URLClassLoader加载器来加载对象,然后通过反射来动态调用需要被代理的方法
package com.jyd.proxy; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.lang.reflect.Constructor; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.util.ArrayList; import java.util.List; import javax.tools.JavaCompiler; import javax.tools.JavaCompiler.CompilationTask; import javax.tools.StandardJavaFileManager; import javax.tools.ToolProvider; public class Proxy { private static final String BASE_TARGET_PATH = "d:/src/"; private static final String TARGET_PATH = BASE_TARGET_PATH + "com/jyd/proxy"; private static final String RT = "\r\n"; private static final String PROXY_CLASS_NAME = "$Proxy"; private static URLClassLoader classLoader; /** * 1、分析所有静态代理类,模式都是固定的如 * 开始之前处理逻辑 * target.method()目标对象调用方法 * 目标对象调用之后处理逻辑 * * 处于这种情况的话,那么我通过代码自动生成模式固定的代理类 * @return */ public static Object newInstance(){ // 1、创建一个段代码的字符通过代码编译,生成对象 String proxySource = "package com.jyd.proxy;"+RT+ "public class "+PROXY_CLASS_NAME+" implements Workable{"+RT+ " private Workable workable;"+RT+ " public "+PROXY_CLASS_NAME+"(Workable workable) {"+RT+ " super();"+RT+ " this.workable = workable;"+RT+ " }"+RT+ " @Override"+RT+ " public void renting() {"+RT+ " long startTime = System.currentTimeMillis();"+RT+ " System.out.println(\"开始计时: \" + startTime);"+RT+ " workable.renting();"+RT+ " long endTime = System.currentTimeMillis();"+RT+ " System.out.println(\"这个方法一共使用多长时间:\" + (endTime - startTime));"+RT+ " }"+RT+ "}"; //判断目标路径是否存在 File targetDir = new File(TARGET_PATH); if(!targetDir.exists())targetDir.mkdirs(); //将源码文件生成到磁盘上 File file = new File(TARGET_PATH, PROXY_CLASS_NAME+".java"); try { FileWriter fw = new FileWriter(file); fw.write(new String(proxySource.getBytes(), "UTF-8")); fw.close(); } catch (Exception e) { e.printStackTrace(); } //使用代码动态编译此java文件 JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); StandardJavaFileManager standardJavaFileManager = compiler.getStandardFileManager(null, null, null); List<File> files = new ArrayList<File>(); files.add(file); Iterable compilationUnits = standardJavaFileManager.getJavaFileObjectsFromFiles(files); CompilationTask task = compiler.getTask(null, standardJavaFileManager, null, null, null, compilationUnits); task.call(); //通过URLClassLoader来加载编译好的字节码文件 URL[] urls = null;; try { urls = new URL[]{new URL("file:/" + BASE_TARGET_PATH)}; } catch (MalformedURLException e) { e.printStackTrace(); } URLClassLoader classLoader = new URLClassLoader(urls); //此处加载class类名需要类的全路径 Class<?> clazz = null; try { clazz = classLoader.loadClass("com.jyd.proxy."+PROXY_CLASS_NAME); } catch (ClassNotFoundException e) { e.printStackTrace(); } Object proxyObject = null; try { Constructor<?> constructor = clazz.getConstructor(new Class[]{Workable.class}); proxyObject = constructor.newInstance(new Middleman()); } catch (Exception e) { e.printStackTrace(); } return proxyObject; } public static void main(String[] args) { Workable workable = (Workable)Proxy.newInstance(); workable.renting(); } }
2.2、动态生成代理对象可代理任何接口
2.1中的动态编译的java对象目前所实现的接口还是固定这样不符合我的需求,思考:如何实现可以代理任何接口的代理类???
package com.jyd.proxy; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.util.ArrayList; import java.util.List; import javax.tools.JavaCompiler; import javax.tools.JavaCompiler.CompilationTask; import javax.tools.StandardJavaFileManager; import javax.tools.ToolProvider; public class Proxy { private static final String BASE_TARGET_PATH = "d:/src/"; private static final String TARGET_PATH = BASE_TARGET_PATH + "com/jyd/proxy"; private static final String RT = "\r\n"; private static final String PROXY_CLASS_NAME = "$Proxy"; private static URLClassLoader classLoader; /** * 1、分析所有静态代理类,模式都是固定的如 * 开始之前处理逻辑 * target.method()目标对象调用方法 * 目标对象调用之后处理逻辑 * * 处于这种情况的话,那么我通过代码自动生成模式固定的代理类 * @return */ public static Object newInstance(Class<?> interf){ // 1、创建一个段代码的字符通过代码编译,生成对象 // 1.2、如何将代理对象实现为动态的? // 可以在创建Proxy的newInstance方法的时候将接口对象传入再通过反射来获取此接口中所有的方法 // " @Override"+RT+ // " public void renting() {"+RT+ // " long startTime = System.currentTimeMillis();"+RT+ // " System.out.println(\"开始计时: \" + startTime);"+RT+ // " workable.renting();"+RT+ // " long endTime = System.currentTimeMillis();"+RT+ // " System.out.println(\"这个方法一共使用多长时间:\" + (endTime - startTime));"+RT+ // " }"+RT+ // 1.3、一个接口中有N个方法需要代理实现那么只能循环获取该类中所有的方法 String methodSources = ""; Method[] methods = interf.getMethods(); if(null != methods && methods.length > 0){ for(Method m : methods){ methodSources += " @Override"+RT+ " public void "+m.getName()+"() {"+RT+ " long startTime = System.currentTimeMillis();"+RT+ " System.out.println(\"开始计时: \" + startTime);"+RT+ " target."+m.getName()+"();"+RT+ " long endTime = System.currentTimeMillis();"+RT+ " System.out.println(\"这个方法一共使用多长时间:\" + (endTime - startTime));"+RT+ " }"+RT; } } String proxySource = "package com.jyd.proxy;"+RT+ "public class "+PROXY_CLASS_NAME+" implements "+interf.getName()+"{"+RT+ " private "+interf.getName()+" target;"+RT+ " public "+PROXY_CLASS_NAME+"("+interf.getName()+" target) {"+RT+ " super();"+RT+ " this.target = target;"+RT+ " }"+RT+ methodSources +RT+ "}"; //判断目标路径是否存在 File targetDir = new File(TARGET_PATH); if(!targetDir.exists())targetDir.mkdirs(); //将源码文件生成到磁盘上 File file = new File(TARGET_PATH, PROXY_CLASS_NAME+".java"); try { FileWriter fw = new FileWriter(file); fw.write(new String(proxySource.getBytes(), "UTF-8")); fw.close(); } catch (Exception e) { e.printStackTrace(); } //使用代码动态编译此java文件 JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); StandardJavaFileManager standardJavaFileManager = compiler.getStandardFileManager(null, null, null); List<File> files = new ArrayList<File>(); files.add(file); Iterable compilationUnits = standardJavaFileManager.getJavaFileObjectsFromFiles(files); CompilationTask task = compiler.getTask(null, standardJavaFileManager, null, null, null, compilationUnits); task.call(); //通过URLClassLoader来加载编译好的字节码文件 URL[] urls = null;; try { urls = new URL[]{new URL("file:/" + BASE_TARGET_PATH)}; } catch (MalformedURLException e) { e.printStackTrace(); } URLClassLoader classLoader = new URLClassLoader(urls); //此处加载class类名需要类的全路径 Class<?> clazz = null; try { clazz = classLoader.loadClass("com.jyd.proxy."+PROXY_CLASS_NAME); } catch (ClassNotFoundException e) { e.printStackTrace(); } Object proxyObject = null; try { Constructor<?> constructor = clazz.getConstructor(new Class[]{Workable.class}); proxyObject = constructor.newInstance(new Middleman()); } catch (Exception e) { e.printStackTrace(); } return proxyObject; } public static void main(String[] args) { Workable workable = (Workable)Proxy.newInstance(Workable.class); workable.renting(); } }
2.3、规则由我定,实现靠子类自己
通过2.2的处理,proxy类可以动态生成代理任何接口代理类,但是美中不足的是代理类中所有做的事情是固定,这样则没有灵活性,思考???
此处能不能由子类自己实现需要处理的逻辑了?可以定义一个规范,由子类自己去实现,由于多态方式,当程序运行的时候那个子类来实现的则调用子类的方法
那么此处需要定义一个接口:
package com.jyd.proxy; import java.lang.reflect.Method; /** * 定义一个出来规范的逻辑,让子类去实现自己的业务逻辑 * @author hadoop * */ public interface InvocationHandler { /** * * @param o 当前实例的对象 * @param method 须要被代理的方法 */ public void invok(Object o, Method method); }
创建一个子类处理类来实现记录方法时间的类
package com.jyd.proxy; import java.lang.reflect.Method; public class TimeInvocationHandler implements InvocationHandler { private Object target; public TimeInvocationHandler(Object target) { super(); this.target = target; } @Override public void invok(Object o, Method method) { //开始处理的业务逻辑 long startTime = System.currentTimeMillis(); System.out.println("开始计时: " + startTime); try { //被代理的对象的方法需要调用,针对自己的目标对象,此处没有目标对象,需要将目标对象通过构成函数传入即可 method.invoke(target, new Object[]{}); } catch (Exception e) { e.printStackTrace(); } long endTime = System.currentTimeMillis(); System.out.println("这个方法一共使用多长时间:" + (endTime - startTime)); } }
通过代理类动态创建代理对象
package com.jyd.proxy; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.util.ArrayList; import java.util.List; import javax.tools.JavaCompiler; import javax.tools.JavaCompiler.CompilationTask; import javax.tools.StandardJavaFileManager; import javax.tools.ToolProvider; public class Proxy { private static final String BASE_TARGET_PATH = "d:/src/"; private static final String TARGET_PATH = BASE_TARGET_PATH + "com/jyd/proxy"; private static final String RT = "\r\n"; private static final String PROXY_CLASS_NAME = "$Proxy"; /** * 1、分析所有静态代理类,模式都是固定的如 * 开始之前处理逻辑 * target.method()目标对象调用方法 * 目标对象调用之后处理逻辑 * * 处于这种情况的话,那么我通过代码自动生成模式固定的代理类 * @return */ public static Object newInstance(Class<?> interf, InvocationHandler h){ // 1、创建一个段代码的字符通过代码编译,生成对象 // 1.2、如何将代理对象实现为动态的? // 可以在创建Proxy的newInstance方法的时候将接口对象传入再通过反射来获取此接口中所有的方法 // " @Override"+RT+ // " public void renting() {"+RT+ // " long startTime = System.currentTimeMillis();"+RT+ // " System.out.println(\"开始计时: \" + startTime);"+RT+ // " workable.renting();"+RT+ // " long endTime = System.currentTimeMillis();"+RT+ // " System.out.println(\"这个方法一共使用多长时间:\" + (endTime - startTime));"+RT+ // " }"+RT+ // 1.3、一个接口中有N个方法需要代理实现那么只能循环获取该类中所有的方法 // String methodSources = ""; // Method[] methods = interf.getMethods(); // if(null != methods && methods.length > 0){ // for(Method m : methods){ // methodSources += " @Override"+RT+ // " public void "+m.getName()+"() {"+RT+ // " long startTime = System.currentTimeMillis();"+RT+ // " System.out.println(\"开始计时: \" + startTime);"+RT+ // " target."+m.getName()+"();"+RT+ // " long endTime = System.currentTimeMillis();"+RT+ // " System.out.println(\"这个方法一共使用多长时间:\" + (endTime - startTime));"+RT+ // " }"+RT; // } // } // // 现在需要使用一个接口来规范此业务操作逻辑,需要改写现在处理业务,那么需要处理逻辑的类传入到代理对象中 // String methodSources = ""; Method[] methods = interf.getMethods(); if(null != methods && methods.length > 0){ for(Method m : methods){ methodSources += " @Override"+RT+ " public void "+m.getName()+"() {"+RT+ " try {"+RT+ " java.lang.reflect.Method md = " + interf.getName() +".class.getMethod(\""+m.getName()+"\", new Class[]{});"+RT+ " h.invok(this, md);"+RT+ " }catch(Exception e){e.printStackTrace();}"+RT+ " }"+RT; } } // 此处需要把InvocationHandler对象传入到代理类中,通过InvocationHandler中通过反射来调用被代理的对象的方法 // String proxySource = "package com.jyd.proxy;"+RT+ // "public class "+PROXY_CLASS_NAME+" implements "+interf.getName()+"{"+RT+ // " private "+interf.getName()+" target;"+RT+ // " public "+PROXY_CLASS_NAME+"("+interf.getName()+" target) {"+RT+ // " super();"+RT+ // " this.target = target;"+RT+ // " }"+RT+ // methodSources +RT+ // "}"; String proxySource = "package com.jyd.proxy;"+RT+ "public class "+PROXY_CLASS_NAME+" implements "+interf.getName()+"{"+RT+ " private com.jyd.proxy.InvocationHandler h;"+RT+ " public "+PROXY_CLASS_NAME+"(com.jyd.proxy.InvocationHandler h) {"+RT+ " super();"+RT+ " this.h = h;"+RT+ " }"+RT+ methodSources +RT+ "}"; //判断目标路径是否存在 File targetDir = new File(TARGET_PATH); if(!targetDir.exists())targetDir.mkdirs(); //将源码文件生成到磁盘上 File file = new File(TARGET_PATH, PROXY_CLASS_NAME+".java"); try { FileWriter fw = new FileWriter(file); fw.write(new String(proxySource.getBytes(), "UTF-8")); fw.close(); } catch (Exception e) { e.printStackTrace(); } //使用代码动态编译此java文件 // 获取java中JDK中java编译器,此编译器只在jdk1.6之后提供,如果要做这个实验jdk版本大于1.6 JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); StandardJavaFileManager standardJavaFileManager = compiler.getStandardFileManager(null, null, null); List<File> files = new ArrayList<File>(); files.add(file); Iterable compilationUnits = standardJavaFileManager.getJavaFileObjectsFromFiles(files); CompilationTask task = compiler.getTask(null, standardJavaFileManager, null, null, null, compilationUnits); task.call(); //通过URLClassLoader来加载编译好的字节码文件 URL[] urls = null;; try { urls = new URL[]{new URL("file:/" + BASE_TARGET_PATH)}; } catch (MalformedURLException e) { e.printStackTrace(); } URLClassLoader classLoader = new URLClassLoader(urls); //此处加载class类名需要类的全路径 Class<?> clazz = null; try { clazz = classLoader.loadClass("com.jyd.proxy."+PROXY_CLASS_NAME); } catch (ClassNotFoundException e) { e.printStackTrace(); } Object proxyObject = null; try { // Constructor<?> constructor = clazz.getConstructor(new Class[]{Workable.class}); // proxyObject = constructor.newInstance(new Middleman()); // 此时proxy类中就不能是接口对象而InvocationHandler Constructor<?> constructor = clazz.getConstructor(InvocationHandler.class); proxyObject = constructor.newInstance(h); } catch (Exception e) { e.printStackTrace(); } try { classLoader.close(); } catch (IOException e) { e.printStackTrace(); } return proxyObject; } public static void main(String[] args) { Workable workable = (Workable)Proxy.newInstance(Workable.class, new TimeInvocationHandler(new Middleman())); workable.renting(); } }
3、jdk自带的动态代理实现
3.1、创建一个接口
package com.jyd.proxy; /** * 定义一个可以工作的接口,定义一系列操作方法 * @author hadoop * */ public interface Workable { void renting(); }
3.2、定义一个真实对象来实现Workable接口
package com.jyd.proxy; import java.util.Random; /** * 定义一个中介人来实现可以工作的接口 * @author hadoop * */ public class Middleman implements Workable { private Random random = new Random(); @Override public void renting() { //中介操作 try { Thread.sleep(random.nextInt(1000)); System.out.println("中介开始租房..."); } catch (InterruptedException e) { e.printStackTrace(); } } }
3.3、定义业务处理逻辑的子类实现InvocationHandler接口
package com.jyd.proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; public class TimeInvocationHandler implements InvocationHandler { private Object target; public TimeInvocationHandler(Object target) { super(); this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //开始处理的业务逻辑 long startTime = System.currentTimeMillis(); System.out.println("开始计时: " + startTime); Object result = null; try { //被代理的对象的方法需要调用,针对自己的目标对象,此处没有目标对象,需要将目标对象通过构成函数传入即可 result = method.invoke(target, args); } catch (Exception e) { e.printStackTrace(); } long endTime = System.currentTimeMillis(); System.out.println("这个方法一共使用多长时间:" + (endTime - startTime)); return result; } }
3.4、定义一个客户端来使用代理对象
package com.jyd.proxy; import java.lang.reflect.Proxy; public class Client { public static void main(String[] args) { Workable workable = (Workable) Proxy.newProxyInstance(Workable.class.getClassLoader(), new Class[]{Workable.class}, new TimeInvocationHandler(new Middleman())); workable.renting(); } }
4、Spring AOP
4.1、Spring AOP的实现原理
AOP(Aspect-OrientedProgramming,面向方面编程),可以说是OOP(Object-OrientedPrograming,面向对象编程)的补充和完善。OOP引入封装、继承和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合。当我们需要为分散的对象引入公共行为的时候,OOP则显得无能为力。也就是说,OOP允许你定义从上到下的关系,但并不适合定义从左到右的关系。例如日志功能。日志代码往往水平地散布在所有对象层次中,而与它所散布到的对象的核心功能毫无关系。对于其他类型的代码,如安全性、异常处理和透明的持续性也是如此。这种散布在各处的无关的代码被称为横切(cross-cutting)代码,在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。
4.2、Spring AOP的术语
Aspect(方面):一个关注点的模块化,这个关注点实现可能另外横切多个对象。事务管理是J2EE应用中一个很好的横切关注点例子。方面用spring的Advisor或拦截器实现。
Joinpoint(连接点):程序执行过程中明确的点,如方法的调用或特定的异常被抛出。
Advice(通知):在特定的连接点,AOP框架执行的动作。各种类型的通知包括“around”、“before”、“throws”通知。
Pointcut(切入点):指定一个通知将被引发的一系列连接点的集合。
weaving(织入):组装方面来创建一个被通知对象。
Java动态代理模式