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(); } } }
创建client类来调用此方法
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)); } }
创建一个client类来调用此代理方法
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)); } }
创建一个client类来调用此代理方法
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、定义一个client来使用代理对象
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(织入):组装方面来创建一个被通知对象。