标签:动态 print sts finally and exists tcl 代理模式 额外
代理模式
代理模式的定义:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。Spring 中让我们耳熟能详的 AOP 的底层就是用到了动态代理实现的,以处理调用代理对象的处理方法前后的特殊处理。
优点:
1、职责清晰,真实的角色就是实现实际的业务逻辑,不用关心其他非本职责的事务,通过后期的代理完成一件完成事务,附带的结果就是编程简洁清晰。代理对象可以在客户端和目标对象之间起到中介的作用,这样起到了中介的作用和保护了目标对象的作用。 2、高扩展性。 3、智能化。
缺点:
1、由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。 2、实现代理模式需要额外的工作,有些代理模式的实现非常复杂。优点
模式结构:
静态代理是由程序员创建或工具生成代理类的源码,再编译代理类。所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。动态代理是在实现阶段不用关心代理类,而在运行阶段才指定哪一个对象。
应用实例: 1、Windows 里面的快捷方式。 2、猪八戒去找高翠兰结果是孙悟空变的,可以这样理解:把高翠兰的外貌抽象出来,高翠兰本人和孙悟空都实现了这个接口,猪八戒访问高翠兰的时候看不出来这个是孙悟空,所以说孙悟空是高翠兰代理类。 3、买火车票不一定在火车站买,也可以去代售点。 4、一张支票或银行存单是账户中资金的代理。支票在市场交易中用来代替现金,并提供对签发人账号上资金的控制。 5、spring aop。
静态代理:
如上图,就是静态代理的简单实现的示意图,接下去用代码来体现:
先来一个抽象角色(即实体所共同的接口):
public interface Person { //找对象的方法 void findLove(); //找工作 void findJob(); }
定义真是对象,即代理对象:
public class Programmer implements Person { @Override public void findLove() { //程序员工作很忙需要找媒婆帮忙找对象 //找对象也要有一些要求 System.out.println("找对象,肤白貌美大长腿"); } @Override public void findJob() { } }
代理对象媒婆登场:
public class MeiPo { private Person person; //没办法扩展 public MeiPo(Person person){ this.person = person; } //目标对象的引用给拿到 public void findLove(){ System.out.println("根据你的要求物色"); this.person.findLove(); System.out.println("匹配成功准备结婚"); } }
测试:运行以下代码完成代理帮程序员找对象。
public class StaticProxyTest { public static void main(String[] args) { //只能帮程序员找对象,不能帮销售,会计等人 //因为我所代理的对象很明确 //静态代理毫无拓展性可言 MeiPo meipo = new MeiPo(new Programmer()); meipo.findLove(); } }
缺点:
1)代理类和委托类实现了相同的接口,代理类通过委托类实现了相同的方法。这样就出现了大量的代码重复。如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。
2)代理对象只服务于一种类型的对象,如果要服务多类型的对象。势必要为每一种对象都进行代理,静态代理在程序规模稍大时就无法胜任了。
动态代理:
JDK 动态代理:代理类与委托类实现同一接口,主要是通过代理类实现InvocationHandler并重写invoke方法来进行动态代理的,在invoke方法中将对方法进行增强处理,不需要硬编码接口,代码复用率高底层使用反射机制进行方法的调用
通过代码的方式演示 JDK动态代理,这里的抽象角色(接口)就沿用上面的 Person 接口。真实对象(即被代理类,要找对象的程序员)也沿用上面的。修改代理类(媒婆):
public class JDKMeipo implements InvocationHandler { //被代理的对象,把引用给保存下来 private Person target; public Object getInstance(Person target) throws Exception{ this.target = target; Class<?> clazz = target.getClass(); //用来生成一个新的对象(字节码重组来实现) return Proxy.newProxyInstance(clazz.getClassLoader(),clazz.getInterfaces(),this); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("我是媒婆:我要给你找对象,现在已经拿到你的需求"); System.out.println("开始物色"); method.invoke(this.target,args); System.out.println("如果合适的话,就准备办事"); return null; } }
测试:运行以下代码可以看到代理成功帮被代理对象寻到对象。
public class JDKProxyTest { public static void main(String[] args) { try { Person obj = (Person)new JDKMeipo().getInstance(new Programmer()); obj.findLove(); } catch (Exception e) { e.printStackTrace(); } } }
JDK动态代理的原理如下:
以上这个过程就叫字节码重组,稍后我们会通过手写JDK动态代理来深入的看看这个动态代理是怎么运作的。
Cglib 动态代理:可以在运行时对类或者是接口进行增强操作,且委托类无需实现接口,底层将方法全部存入一个数组中,通过数组索引直接进行方法调用。
从代码的角度来看看Cglib代理的实现:
被代理类:
public class ZhangSan { //同样是找对象 public void findLove(){ System.out.println("肤白貌美大象腿"); } }
代理类媒婆上线:
public class CglibMeipo implements MethodInterceptor { public Object getInstance(Class<?> clazz) throws Exception{ //Enhancer是CGLIB的字节码增强器 Enhancer enhancer = new Enhancer(); //要把哪个设置为即将生成的新类的父类 enhancer.setSuperclass(clazz); enhancer.setCallback(this); return enhancer.create(); } @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { //业务的增强 System.out.println("我是媒婆:我要给你找对象,现在已经拿到你的需求"); System.out.println("开始物色"); methodProxy.invokeSuper(o,objects); System.out.println("如果合适的话,就准备办事"); return null; } }
测试类:运行以下代码能看到代理成功。
public class CglibTest { public static void main(String[] args) { try { ZhangSan obj = (ZhangSan)new CglibMeipo().getInstance(ZhangSan.class); obj.findLove(); System.out.println("--------------------------------"); System.out.println(obj.getClass()); } catch (Exception e) { e.printStackTrace(); } } }
JDK代理要求被代理的类必须实现接口,有很强的局限性。而CGLIB动态代理则没有此类强制性要求。简单的说,CGLIB会让生成的代理类继承被代理类,并在代理类中对代理方法进行强化处理(前置处理、后置处理等)。在CGLIB底层,其实是借助了ASM这个非常强大的Java字节码生成框架。
JDK动态代理的底层实现的原理在上文中有提到过,是通过字节码重组来实现的,那么我们要自己实现的话需要以下步骤:
我们都知道在使用JDK动态代理的时候需要调用以下方法:
Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces, InvocationHandler h)
其中三个参数:
loader: 用哪个类加载器去加载代理对象,我们这里需要实现自己的类加载器去加载我们动态生成的类文件。
interfaces:动态代理类需要实现的接口,JDK代理要求被代理的类必须实现接口。这里需要通过这个接口去获取被代理的方法集组装到Proxy类中。
h:动态代理方法在执行时,会调用h里面的invoke方法去执行。
我们需要覆盖 JDK 的Proxy的 newProxyInstance方法,ClassLoader , InvocationHandler,上代码:
Proxy:
public class GPProxy { public static final String ln = "\r\n"; public static Object newProxyInstance(GPClassLoader classLoader,Class<?> [] interfaces,GPInvocationHandler h){ try { //1、动态生成源代码.java文件 String src = generateSrc(interfaces); //2、Java文件输出磁盘 String filePath = GPProxy.class.getResource("").getPath(); System.out.println(filePath); File f = new File(filePath + "$Proxy0.java"); FileWriter fw = new FileWriter(f); fw.write(src); fw.flush(); fw.close(); //3、把生成的.java文件编译成.class文件 // 通过JavaCompiler进行编译 JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); //获取标准文件管理器实现的新实例 StandardJavaFileManager manage = compiler.getStandardFileManager(null,null,null); //获取给定文件的文件对象。 返回文件对象列表 Iterable iterable = manage.getJavaFileObjects(f); // 创建任务 JavaCompiler.CompilationTask task = compiler.getTask(null,manage,null,null,null,iterable); // 执行编译 task.call(); manage.close(); //4、编译生成的.class文件加载到JVM中来 Class proxyClass = classLoader.findClass("$Proxy0"); Constructor c = proxyClass.getConstructor(GPInvocationHandler.class); f.delete(); //5、返回字节码重组以后的新的代理对象 return c.newInstance(h); }catch (Exception e){ e.printStackTrace(); } return null; } private static String generateSrc(Class<?>[] interfaces){ StringBuffer sb = new StringBuffer(); sb.append("package com.wuzz.demo.pattern.proxy.custom;" + ln); sb.append("import com.wuzz.demo.pattern.proxy.staticed.Person;" + ln); sb.append("import com.wuzz.demo.pattern.proxy.custom.GPInvocationHandler;" + ln); sb.append("import java.lang.reflect.Method;" + ln); sb.append("public class $Proxy0 implements " + interfaces[0].getName() + "{" + ln); sb.append("GPInvocationHandler h;" + ln); sb.append("public $Proxy0(GPInvocationHandler h) { " + ln); sb.append("this.h = h;"); sb.append("}" + ln); for (Method m : interfaces[0].getMethods()){ sb.append("public " + m.getReturnType().getName() + " " + m.getName() + "() {" + ln); sb.append("try{" + ln); sb.append("Method m = " + interfaces[0].getName() + ".class.getMethod(\"" + m.getName() + "\",new Class[]{});" + ln); sb.append("this.h.invoke(this,m,null);" + ln); sb.append("}catch(Throwable e){" + ln); sb.append("e.printStackTrace();" + ln); sb.append("}"); sb.append("}"); } sb.append("}" + ln); return sb.toString(); } }
ClassLoader:继承ClassLoader 重写 findClass 方法。根据我们指定的路径去加载重新生成的动态类$Proxy0
public class GPClassLoader extends ClassLoader{ private File classPathFile; public GPClassLoader(){ String classPath = GPClassLoader.class.getResource("").getPath(); this.classPathFile = new File(classPath); } @Override protected Class<?> findClass(String name) { String className = GPClassLoader.class.getPackage().getName() + "." + name; if(classPathFile != null){ File classFile = new File(classPathFile,name.replaceAll("\\.","/") + ".class"); if(classFile.exists()){ FileInputStream in = null; ByteArrayOutputStream out = null; try{ in = new FileInputStream(classFile); out = new ByteArrayOutputStream(); byte [] buff = new byte[1024]; int len; while ((len = in.read(buff)) != -1){ out.write(buff,0,len); } return defineClass(className,out.toByteArray(),0,out.size()); }catch (Exception e){ e.printStackTrace(); }finally { if(null != in){ try { in.close(); } catch (IOException e) { e.printStackTrace(); } } if(out != null){ try { out.close(); } catch (IOException e) { e.printStackTrace(); } } } } } return null; } }
InvocationHandler:
public interface GPInvocationHandler { Object invoke(Object proxy, Method method, Object[] args) throws Throwable; }
然后需要修改媒婆的代码:实现我们自己定义的Handler
public class CustomMeipo implements GPInvocationHandler { //被代理的对象,把引用给保存下来 private Person target; public Object getInstance(Person target) throws Exception{ this.target = target; Class<?> clazz = target.getClass(); //用来生成一个新的对象(字节码重组来实现) return GPProxy.newProxyInstance(new GPClassLoader(),clazz.getInterfaces(),this); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("我是媒婆:我要给你找对象,现在已经拿到你的需求1"); System.out.println("开始物色1"); method.invoke(this.target,args); System.out.println("如果合适的话,就准备办事1"); return null; } }
测试:运行以下代码可以看到结果,跟JDK动态代理的效果一样。
public class CustomPorxyTest { public static void main(String[] args) { try { Person obj = (Person)new CustomMeipo().getInstance(new XieMu()); System.out.println(obj.getClass()); obj.findLove(); } catch (Exception e) { e.printStackTrace(); } } }
标签:动态 print sts finally and exists tcl 代理模式 额外
原文地址:https://www.cnblogs.com/wuzhenzhao/p/10298842.html