zoukankan      html  css  js  c++  java
  • Java深入浅出系列(四)——深入剖析动态代理--从静态代理到动态代理的演化

    静态代理

     


              如上图,在程序执行之前。程序猿就要编写Proxy。然后进行编译,即在程序执行之前,代理类的字节码文件就已经生成了(Proxy类的class文件已经存在了)。

     

             静态代理尽管在增强现有的接口业务功能方面有非常大长处,可是大量使用这样的静态代理,会使系统内的类的规模大量增大,不易维护。而且Proxy类和RealSubject类功能本质上是一样的。仅仅只是Proxy起到了一个中介的作用,这样的代理在系统中的存在导致了系统结构的臃肿和松散。

        为了解决问题。产生了动态代理。动态代理是在系统执行中,在须要代理的地方依据接口以及RealSubject动态的生成代理Proxy类,用完之后就会销毁,避免了Proxy类在系统中冗余的问题了。

     

    来看以下的样例:

             通过静态代理的实现。手写字节码动态生成代理:

          

    /*
     * 售票服务接口
     */
    public interface TicketService {
    
    	//售票
    	public void sellTicket();
    	
    	//问询
    	public void inquire();
    	
    	//退票
    	public void withdraw();
    }
    /**
     * 售票服务接口实现类,车站
     * @author Emily-T
     *
     */
    public class Station implements TicketService {
    
    	@Override
    	public void sellTicket() {
    		System.out.println("----------售票------------");
    	}
    
    	@Override
    	public void inquire() {
    		System.out.println("--------------问询-------------");
    
    	}
    
    	@Override
    	public void withdraw() {
    		System.out.println("-------------退票--------------");
    
    	}
    
    }
    /**
     * 车票代售点
     * 
     * @author Emily-T
     *
     */
    public class StationProxy implements TicketService {
    
    	private Station station;
    
    	// get,set与这样的构造函数的有什么差别?
    	public StationProxy(Station station) {
    		this.station = station;
    	}
    
    	@Override
    	public void sellTicket() {
    
    		//  1.做真正业务前,提示信息           
    		this.showAlertInfo("××××您正在使用车票代售点进行购票,每张票将会收取5元手续费!××××");
    		//  2.调用真实业务逻辑           
    		station.sellTicket();
    		//  3.后处理           
    		this.takeHandlingFee();
    		this.showAlertInfo("××××欢迎您的光临。再见!××××
    ");
    	}
    
    	@Override
    	public void inquire() {
    		//  1做真正业务前。提示信息          
    		this.showAlertInfo("××××欢迎光临本代售点,问询服务不会收取不论什么费用,本问询信息仅供參考,详细信息以车站真实数据为准。××××");
    		//  2.调用真实逻辑           
    		station.inquire();
    		//  3。后处理           
    		this.showAlertInfo("××××欢迎您的光临。再见!××××
    ");
    
    	}
    
    	@Override
    	public void withdraw() {
    		//  1。真正业务前处理           
    		this.showAlertInfo("××××欢迎光临本代售点,退票除了扣除票额的20%外。本代理处额外加收2元手续费!

    ××××"); //  2.调用真正业务逻辑            station.withdraw(); //  3.后处理            this.takeHandlingFee(); } /* *  展示额外信息        */ private void showAlertInfo(String info) { System.out.println(info); } /* * 收取手续费 */ private void takeHandlingFee() { System.out.println("收取手续费,打印发票。

    。。。 "); } }

    public class Test {
    
    	public static void main(String[] args) throws Exception {
    		createProxy();
    
    	}
    	
    	private static void createProxy() throws Exception{
    		ClassPool pool = ClassPool.getDefault();
    		
    		CtClass cc = pool.makeClass("com.ltt.proxy.StationProxy");
    		
    		//设置接口
    		CtClas interface1 = pool.get("com.ltt.proxy.TicketService");
    		cc.setInterfaces(new CtClass[]{interface1});
    		
    		//设置field
    		CtField field = CtField.make("private com.ltt.proxy.Station station;",cc);
    		
    		cc.addField(field);
    		
    		CtClass stationClass = pool.get("com.ltt.proxy.Station");
    		CtClass[] arrays = new CtClass[]{stationClass};
    		CtConstructor ctc = CtNewConstructor.make(arrays,null,CtNewConstructor.PASS_NONE,null,null,cc);
    		
    		//设置构造函数内部信息
    		ctc.stBody("{this.station=$1;}");
    		cc.addConstructor(ctc);
    		
    		//创建收取手续takeHandlingFee方法
    		CtMethod takeHandlingFee = CtMethod.make("private void takeHandlingFee(){}",cc);
    		takeHandlingFee.setBody("System.out.println("收取手续费,打印发票");");
    		cc.addMethod(takeHandlingFee);
    		
    		//创建showAlertInfo方法
    		CtMethod showInfo = CtMethod.make("private void showAlertInfo(String info){}",cc);
    		showInfo.setBody("System.out.println($1);");
    		cc.addMethod(showInfo);
    		
    		//售票
    		CtMethod sellTicket = CtMethod.make("public void sellTicket(){}",cc);
    		sellTicket.setBody("{this.showAlertInfo("xxx您正在使用车票代售点进行购票。每张票将会收取5元手续费!xxx");"+"station.sellTicket();"
    		+"this.takeHandlingFee();"
    				+"this.showAlertInfo("xxx欢迎您的光临,再见! xxxx");}");
    		
    		cc.addMethod(sellTicket);
    		
    		//加入inquire方法
    		CtMethod inquire = CtMethod.make("public void inquire(){}",cc);
    		inquire.setBody("{this.showAlertInfo("xxxx欢迎光临本代售点,问询服务不会收取不论什么费用。本问询信息仅供參考,"
    				+ "详细信息以车站真实数据为准!xxxx");"
    				+ "station.inquire();"
    				+"this.showAlertInfo("xxxx欢迎您的光临。再见!xxxx");}");
    		cc.addMethod(inquire);
    		
    		//加入widthraw方法
    		CtMethod withdraw = CtMethod.make("public void withdraw(){}",cc);
    		withdraw.setBody("{this.showAlertInfo("xxxx欢迎光临本代售点,退票除了扣除票额的20%外,本代理处额外加收2元手续费!xxxx");"
    				+"station.withdraw();"
    				+"station.takeHandlingFee();}");
    		cc.addMethod(withdraw);
    		
    		//获取动态生成的class
    		Class c = cc.toClass();
    		//获取构造器
    		Constructor constructor = c.getConstructor(Station.class);
    		//通过构造器实例化
    		TicketService o = (TicketService) constructor.newInstance(new Station());
    		o.inquire();
    		cc.writeFile("D://Test");
    		
    	}
    
    }

            通过上面的代码发现。我们手动创建的代理,里面都有非常多的业务逻辑。冗余性代码非常多,可扩展性非常差,并且本来是为了降低冗余代码。解耦的。这样反而添加了冗余代码以及代理生成的难度。假设是业务逻辑非常复杂的业务,这样的做法是不可取的。

             那么以下我们进一步抽象,把手动创建代理抽象封装。就有了如今的JDK或者是CGLIB动态代理。

     


             从图中能够看出,在调用真正的方法前后插入业务代码。也就是在触发(invoke)真实角色方法之前或者之后做一些额外的业务。为了构造出具有通用型和简单性的代理类,能够将全部的触发真实角色动作交给一个触发的管理器,让这个管理器统一地管理触发。

    这样的管理器就是InvocationHandler.

     

            在静态代理中,代理Proxy中的方法都制定了调用特定的RealSubject中相应的方法。动态代理是将自己的方法功能实现交给InvocationHandler角色。外界对Proxy角色中的每个方法的调用,Proxy都会交给InvocationHandler来处理。InvocationHandler调用详细对象的角色方法。

     

    代理类应该和真实对象实现功能同样。所以有两种方式:

    1、代理类和真实对象实现同样的接口

    2、通过继承。代理类继承RealSubject。这样Proxy有了RealSubject的功能,代理类还能够重写RealSubject中的方法实现多态。

    那么这两种方式各自是:第一种是JDK动态代理的实现方式,另外一种是CGLIB的实现方式。


           从原理上我们不难理解JDK动态代理以及CGLIB动态代理。都是通过一步步的抽象封装从而达到解决某类问题。产生详细应用方案的过程。

  • 相关阅读:
    好系统重装助手重装电脑系统步骤
    U盘加载速度慢的解决方法
    Win10应用商店缓存信息多如何去清理?
    怎么消除文件左上角的白色小框?
    U盘被识别但不显示盘符怎么样才能解决?
    【Gamma】Scrum Meeting 2
    【技术博客】 关于laravel5.1中文件上传测试的若干尝试
    【Beta】Phylab 发布说明
    【Beta】Phylab 测试报告
    【Beta】Scrum Meeting 10
  • 原文地址:https://www.cnblogs.com/gccbuaa/p/7141182.html
Copyright © 2011-2022 走看看