• Java反射机制(四):动态代理


    一、静态代理

    在开始去学习反射实现的动态代理前,我们先需要了解代理设计模式,那何为代理呢?

    代理模式: 为其他对象提供一种代理,以控制对这个对象的访问。

    先看一张代理模式的结构图:



    简单的理解代理设计: 一个操作接口有两个子类,其中一个真实主题的实现类,另一个是代理类,代理实现类要完成比真实主题实现类更多的内容,而且本身还需要处理一些与具体业务有关的程序代码。

    静态代理示例:

    package org.chen.yuan.prox;
    
    
    interface Subject
    {
        public String say(String name, int age);
    }
    
    
    class RealSubject implements Subject
    {
        @Override
        public String say(String name, int age)
        {
            return "姓名: " + name + ", 年龄: " + age;
        }
    }
    
    
    class ProxSubject implements Subject
    {
        private Subject sub = null;
    
    
        public ProxSubject(Subject sub)
        {
            this.sub = sub;
        }
    
    
        @Override
        public String say(String name, int age)
        {
            this.preRequest(); // 在调用真实角色操作之前所附加的操作
    
    
            String info = sub.say(name, age); // 真实角色所完成的事情
    
    
            this.postRequest(); // 在真实角色操作之后所附加的操作
    
    
            return info;
        }
    
    
        // 自定义的方法,在真实方法执行之前调用的方法
        private void preRequest()
        {
            System.out.println("pre request");
        }
    
    
        // 自定义的方法,在真实方法执行之后调用的方法
        private void postRequest()
        {
            System.out.println("post request");
        }
    
    }
    
    
    public class DynaProxyDemo
    {
        public static void main(String[] args)
        {
            Subject sub = new ProxSubject(new RealSubject());
            String info = sub.say("沉缘", 25);
            System.out.println(info);
        }
    }
    

    输出:

    pre request
    post request
    姓名: 沉缘, 年龄: 25


    说明:

    在实现代理角色对象时,最重要的一点是要有一个真实对象的引用。通过这个引用在代理对象中去调用真实对象的方法,并且可以自定义一些其他的操作,比如本例子中的preRequest()和postRequest(),他们分别在之前和之后被调用。

    大家看代码后可以发现,这里并没有用到反射的知识。而且对于上面的例子来说,真实角色对象是事先必须已经存在的,并且必须是作为代理对象的内部属性。
    如果这样的话,有一个真实角色那就必须在代理角色中有一个他的引用,如果在不知道真实角色的情况下又需要怎么办?这就需要"动态代理"来解决了。


    二、 动态代理(以下内容摘自博文http://blog.csdn.net/a396901990/article/details/26015977)

    静态代理中,一个代理类只能为一个接口服务,那么如果现在有很多个接口的话,则肯定要写很多的代理类了,而且,所有的代理操作除了调用的方法不一样之外,其他的操作都一样,此时,肯定有很多重复的代码。解决这一问题最好的做法是可以通过一个代理类完成全部的代理功能或者说去动态的生成这个代理类,那么此时就必须使用动态代理完成。

    动态代理实现所需要的API:

    Java动态代理类位于java.lang.reflect包下,主要有以下一个接口和一个类:

    1.  InvocationHandler接口:    该接口中仅有一个方法

    public object invoke(Object proxy, Method method, Object[] args)

    在实际使用时,proxy一般是指代理类,method是被代理的方法,args为该方法的参数数组。这个抽象的invoke方法在代理类中动态实现。


    2.  Proxy类:  该类即为动态代理类,这里只介绍一下newProxyInstance()这个方法

    static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)

    ClassLoader loader: 类加载器

    Class<?>[] interfaces: 得到全部的接口

    InvocationHandler h: 得到InvocationHandler接口的子类实例

    这个方法是最主要的方法,它会返回代理类的一个实例,返回后的代理类可以当做被代理类使用。
    如果你要想得到一个加载器的对象,则肯定使用Class类完成。


    实现动态代理需4步骤:

    1)创建一个实现接口InvocationHandler的类,它必须实现invoke方法。

    2)  通过Proxy的静态方法newProxyInstance创建一个代理

    3)  创建被代理的类以及接口

    4)  通过代理调用方法

    下面看这个例子具体说明如何通过上面的4个步骤来建立一个动态代理:


    步骤1和步骤2合并写在一个类中,命名为DynamicProxy

    public class DynamicProxy implements InvocationHandler {
    
    	// 需要被代理类的引用
    	private Object object;
    
    	// 通过构造方法传入引用
    	public DynamicProxy(Object object) {
    		this.object = object;
    	}
    	// 定义一个工厂类,去生成动态代理
    	public Object getProxy() {
    		// 通过Proxy类的newProxyInstance方法动态的生成一个动态代理,并返回它
    		return Proxy.newProxyInstance(object.getClass().getClassLoader(), object
    				.getClass().getInterfaces(), this);
    	}
    	// 重写的invoke方法,这里处理真正的方法调用
    	@Override
    	public Object invoke(Object obj, Method method, Object[] args)
    			throws Throwable {
    		
    		beforeDoing();
    		
    		Object invoke = method.invoke(object, args);
    		
    		afterDoing();
    		
    		return invoke;
    	}
    	
    	public void beforeDoing() {
    		System.out.println("before ............");
    	}
    	
    	public void afterDoing() {
    		System.out.println("after ............."+"
    ");
    	}
    }
    该类实现了InvocationHandler接口,并且自定义了一个getProxy()方法去调用Proxy类的newProxyInstance()去生成一个动态代理。
    步骤3:创建被代理的类以及接口

    //真实角色对象,继承自抽象角色,重写定义的方法。
    public class RealSubject implements Subject1,Subject2{
    
    	//Subject1接口中的方法
    	@Override
    	public void request() {
    		System.out.println("this is real subject");
    	}
    
    	//Subject1接口中的方法
    	@Override
    	public void ask() {
    		System.out.println("this is real ask");
    		
    	}
    	
    	//Subject2接口中的方法
    	@Override
    	public void request2() {
    		System.out.println("this is real subject2");
    		
    	}
    }

    这个类就是我们需要被代理的类,他继承了两个接口分别是Subject1,Subject2

    interface Subject1 {
    	
    	public  void request();
    	
    	public void ask();
    }
    interface Subject2 {
    	
    	public  void request2();
    }

    4.通过代理调用方法

    接下来在main方法中通过动态生成的代理来调用方法

    public static void main(String[] args) {
    		
    		//需要被代理的类
    		RealSubject realSubject = new RealSubject();
    		//用于创建动态代理的类,将被代理类的引用传递进去
    		DynamicProxy dynamicProxy = new DynamicProxy(realSubject);
    		
    		//通过getProxy方法动态的获取代理类,转换成需要调用的接口类型后调用方法
    		Subject1 s1 = (Subject1) dynamicProxy.getProxy();
    		s1.request();
    		s1.ask();
    		
    		//通过getProxy方法动态的获取代理类,转换成需要调用的接口类型后调用方法
    		Subject2 s2 = (Subject2) dynamicProxy.getProxy();
    		s2.request2();
    	}

    输出:

    before ............
    this is real subject
    after .............

    before ............
    this is real ask
    after .............

    before ............
    this is real subject2
    after .............


    简单介绍动态代理内部实现原理:

    例子看完了,肯定有如下疑问:

    动态代理在哪里应用了反射机制?仅仅通过一个InvocationHandler接口和一个Proxy类的newProxyInstance方法是如何动态的生成代理?

    下面就来简单的分析一下InvocationHandler,和Proxy的newProxyInstance方法是如何在运行时动态的生成代理的:

    先看newProxyInstance是如何定义的:

    static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)

    这里需要传入3个参数。先看第二个参数,传入一个接口类型的Class数组。

    上面例子中传入的参数是object.getClass().getInterfaces()

    object是被代理对象,这个参数就是通过反射拿到被代理对象的所有接口

    在上面例子中就是我们定义的Subject1,Subject2接口了

    有了接口数组,就可以通过类似下面的代码使用反射拿到接口中的所有方法:

    for (interface infce : interfaces[]) {
    	Method[] methods = infce.getMethods();
    	for (Method m : method) {
    		m.getName();
    	}
    }
    在正常情况下,知道了被代理的接口和接口里面的方法就可以去生成代理类了。
    大概就是下面这种的一个简单的实现:一个很固定的套路,只要知道实现接口和方法就仿照写出。
    public class ProxySubject implements Subject{
    	
            private RealSubject realSubject;	
        
    	@Override
    	public void request() {
    		
    		realSubject.request();  
    	}
    }
    动态代理还会在代理的方法中做一些其他的操作,如添加日志,时间,权限等操作。这时候就要靠InvocationHandler接口中的invoke方法。看看例子中如何实现的。

    @Override
    public Object invoke(Object obj, Method method, Object[] args) throws Throwable {
    		
    	beforeDoing();
    		
    	Object invoke = method.invoke(object, args);
    		
    	afterDoing();
    		
    	return invoke;
    }

    这些代码是我们自定义的,需要实现什么操作就写在里面。
    这段代码存在于Invocationhandler对象中,这个对象会在调用Proxy的newProxyInstance的方法中传递进去。
    这时候可以通过反射知道被调用方法的名字等信息,之后还是通过字符串的形式拼接处类似下面的动态代理类

    public class ProxySubject implements Subject{
    	
            private RealSubject realSubject;	
    	@Override
    	public void request() {
    		Methond md = Subject.getMethod("methodName");
    		handler.invoke(this, md);  
    	}
    }

    这个大概就是根据传递的接口对象和InvocationHandler结合后应该生成的代理类。但现在的问题是如何去动态的生成上面这样的代理类。
    答案是使用字符串拼接的方式。
    从看上面的代码可以看出,除了接口和调用方法不同其他都相同。而且我们已经通过反射获得了方法和接口名字,这样就可以按着这个“套路”去用字符串拼接成这样的一类。
    大概就是下面这种代码:

      String source = "package com.gxy.proxy;" + rt 
      
    			    + "public class "+ClassName+"implements "+InterfaceName+ rt  
    			    + "{" + rt  
    			    + 		"private "+ ClassName + ClassName.toLowerCase()+" ; " + rt
    			         
    			    +       "@Override"
    			    +		"public Void "+InterfaceName+ "()" + rt  + " {"
    			    + 			"Method md = "+InterfaceName+".getMethod("+ methodName+");" +rt 
    			    +           "hander.invoke(this, md);" + rt
    			    +		"}" + rt 
    			    + "}";
    用反射生成的出来类名,接口名,方法名去动态的创建这样一个类的字符串。
    之后就特定的方法去将这个字符串生成成类。在用反射把这个类取出来。这样就有了这个“动态”生成的代理类了。

  • 相关阅读:
    MySQL5.7初始密码查看及重置
    ps top kill
    Linux基础知识[2]【延迟及定时机制】
    大数加减运算
    字符串分隔
    打印NxN的矩阵
    交叉排序
    去除重复字符并排序
    大数求差——(华为实习招聘机试题)
    图解TCP-IP协议
  • 原文地址:https://www.cnblogs.com/hehe520/p/6330009.html
Copyright © 2020-2023  润新知