JAVA反射机制与动态代理

来源:互联网 发布:windows vnc mac 编辑:程序博客网 时间:2024/05/16 07:43

一、JAVA的反射机制
首先,JAVA程序必须通过类加载机制之后才能运行,正常情况下,我们运行的程序在编译阶段,类就已经被加载了。
Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种在运行时可以获得对象和类的信息、动态加载类、动态访问、调用目标类型的字段、方法以及构造函数 的功能就称为Java语言的反射机制。
如果对C++的RTTI比较熟悉的同学,需要注意的是反射与RTTI(Run-Time Type Identification运行时发现和使用类型信息)的区别,对于RTTI来说,编译器在编译时打开和检查.class文件,而对于反射机制来说,.class文件在编译时是不可获取的,是在运行时打开和检查.class文件。两者只是在不同的体系,描述同一件事情,反射机制更完整一些。
Java反射机制允许程序在运行时判断分析任意一个类的结构,包括成员变量和方法,并调用任意一个对象的方法。Eclipse可以自动弹出对象的方法及属性,就是利用了反射的原理。
java动态代理可以在不改变被调用对象源码的前提下,在被调用方法前后增加自己的操作,极大地降低了模块之间的耦合性,Java的动态代理就是利用了反射的特性来实现的。

二、基本用法
1、获取Class对象

·A.class :不会加载类,也不会执行静态代码段;·Class.forName("cn.test.reflect.A") :要求JVM查找并加载指定的类,也就是说JVM会执行该类的静态代码段;·new A().getClass() :通过对象获取class

2、反射创建对象

·通过默认构造函数创建对象:Class<?> t = Class.forName("cn.test.reflect.A"); t.newInstance();·通过指定构造函数创建对象:Class<?> t = Class.forName("cn.test.reflect.A"); Constructor<?> cons[] = t.getConstructors(); A a = (A) cons[2].newInstance("aa","bb");注:① Class<?>表示任何类型的类;② newInstance()方法只能调用public的无参构造函数,它和new关键字创建实例的区别:创建对象的方式不一样,newInstance是使用类加载机制,new是创建一个新类;

3、Class类常用方法

▶ getName() :获得类的完整名字;▶ getSuperclass() :获得类的父类;▶ newInstance() :通过类的不带参数的构造方法创建这个类的一个对象;▶ getFields() :获得当前类和父类中的public类型的所有属性;▶ getDeclaredFields() :获得当前类(不包含父类)声明的所有属性,包括privatepublic;注:对于某个属性field,设置field.setAccessible(true),即可访问private的属性值,如field.get(obj)▶ getMethods() :获得当前类和父类中public类型的所有方法;▶ getDeclaredMethods() :获得当前类(不包含父类)声明的所有方法,包括privatepublic;▶ getMethod(String name, Class[] parameterTypes) :获得类的指定方法,name参数指定方法的名字,parameterTypes 参数指定方法的参数类型;▶ getConstructors() :获得当前类的public类型的构造方法;▶ getDeclaredConstructors() :获得当前类的publicprivate类型的构造方法;▶ getConstructor(Class[] parameterTypes) :获得类的特定构造方法,parameterTypes 参数指定构造方法的参数类型;▶ getInterfaces() :获得实现的接口;▶ getSuperclass() :获得继承的父类;

有关反射的案例代码有些多,这里不列出来。可以参看:
http://blog.csdn.net/snda452258253/article/details/72775364

三、代理
代理,是一种十分常见的设计模式,即为其他对象提供一个代理从而控制对这个对象的访问。使用代理模式的场景有很多(代理模式作用),比如控制对远程对象的访问;对象实例化代价很高,出于性能考虑,需要延迟进行加载;出于安全性的角度,需要屏蔽特定对象的访问;客户端无法直接操作实际对象,需要借助代理类在客户端和目标对象之间起到中介作用等等。

PS:(前两种使用场景很容易理解,主要说明后两种场景)
1、出于安全性的角度,需要屏蔽特定对象的访问
除了当前类能够提供的功能外,我们还需要补充一些其他功能。
最容易想到的情况就是权限过滤,我有一个类做某项业务,但是由于安全原因只有某些用户才可以调用这个类,此时我们就可以做一个该类的代理类,要求所有请求必须通过该代理类,由该代理类做权限判断,如果安全则调用实际类的业务开始处理。可能有人说为什么我要多加个代理类?我只需要在原来类的方法里面加上权限过滤不就完了吗?在程序设计中有一个类的单一性原则问题,这个原则很简单,就是每个类的功能尽可能单一。为什么要单一,因为只有功能单一这个类被改动的可能性才会最小,就拿刚才的例子来说,如果你将权限判断放在当前类里面,当前这个类就既要负责自己本身业务逻辑、又要负责权限判断,那么就有两个导致该类变化的原因,现在如果权限规则一旦变化,这个类就必需得改,显然这不是一个好的设计。
代理类一般是做些除原始类核心功能以外的其他功能,比如权限、事务等等都要专门的代理来实现。当我们的代码每个类代表一个主要功能,而不是将所有功能混在一个类中,那么代码无疑清晰有条理的,易于维护,比如我要修改权限,就不必打开原始类代码,直接修改权限代理类就可以了。就很少发生修改一个BUG,带来一个新BUG的烦恼问题。

2、为什么无法直接操作实际对象?
一种情况是你需要调用的对象在另外一台机器上,你需要跨越网络才能访问,如果让你直接coding去调用,你需要处理网络连接、处理打包、解包等等非常复杂的步骤,所以为了简化客户端的处理,我们使用代理模式,在客户端建立一个远程对象的代理,客户端就象调用本地对象一样调用该代理,再由代理去跟实际对象联系,对于客户端来说可能根本没有感觉到调用的东西在网络另外一端,这实际上就是Web Service的工作原理。
一种情况虽然你所要调用的对象就在本地,但是由于调用非常耗时,你怕影响你正常的操作,所以特意找个代理来处理这种耗时情况,一个最容易理解的就是Word里面装了很大一张图片,在word被打开的时候我们肯定要加载里面的内容一起打开,但是如果等加载完这个大图片再打开Word用户等得可能早已经跳脚了,所以我们可以为这个图片设置一个代理,让代理慢慢打开这个图片而不影响Word本来的打开的功能。申明一下我只是猜可能Word是这么做的,具体到底怎么做的就未可知了。

代理类负责为委托类预处理消息、过滤消息、转发消息,以及消息被委托类执行后的后续处理。代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。
为了保证行为的一致性,代理类和委托类通常会实现相同的接口,故而在访问者看来并没有区别。然而,通过代理类这一中间层,可以有效地隐藏和保护被代理的真实对象,从而达到了控制访问的目的。同时也为实施不同控制策略预留了空间,从而在设计上获得了更大的灵活性。
生活中的例子:过年加班比较忙,没空去买火车票,这时可以打个电话到附近的票务中心,叫他们帮你买张回家的火车票,当然这会附加额外的劳务费。但要清楚票务中心自己并不卖票,只有火车站才真正卖票,票务中心卖给你的票其实是通过火车站实现的。
上面这个例子,你就是“客户(client)”,票务中心就是“代理角色(ProxySubject)”,火车站是“真实角色(RealSubject)”,卖票称为“抽象角色(Subject)”。
代理模式

静态代理
下面通过懒加载的例子来看一下Java中的静态代理模式是如何实现的。
所谓的懒加载,就是延迟加载,它的核心思路就是,如果当前没有真正地使用某个对象时,就不去创建这个对象,而是使用一个代理去替代它的位置,在真正需要调用的时候才去创建真正的对象。对于很多系统而言,初始化时启动的服务太多,导致用户体验比较糟糕或者系统负载过重,这个时候懒加载就有其用武之地了。这个想法其实和反射的思想是一致的,后者是在运行时获取类的信息,对类进行操作,目的之一同样也是在系统启动服务之后,再启动一些项目的加载,不至于初始化耗时太久,延迟响应度。(PS:单例模式-懒汉也是利用懒加载实现的,不过线程不安全,在多线程下不能正常工作。线程安全的懒汉写法效率又很低,大部分情况下不需要同步。)

interface MyInterface {//通过接口声明真实角色Subject的方法    void doSomething();}class Subject implements MyInterface {//真实角色Subject,实现确实的业务,供代理角色SubjectProxy调用    public Subject() {        try {            Thread.sleep(1000);//初始化,可能耗费很长时间        } catch (InterruptedException e) {            e.printStackTrace();        }    }    public void doSomething() {        System.out.println("Subject do something!");    }}class SubjectProxy implements MyInterface {//SubjectProxy代理类,通过调用真实角色Subject中的具体方法来实现业务,并可以附加自己的操作    private Subject real; //Subject对象创建的时候并不实例化    public SubjectProxy() {}//init    public void doSomething() {        //只有在需要的时候,调用代理类的doSomething方法的时候才会实例化Subject对象,这就是懒加载        if (real == null) {            real = new Subject();        }        real.doSomething();    }}class Test {    public static void main(String[] args) {        SubjectProxy proxy = new SubjectProxy(); //使用代理        proxy.doSomething(); //Subject对象直到需要时才被创建    }}

【运行结果】:
Subject do something!

观察代码可以发现每一个代理类只能为一个接口服务,这样一来程序开发中必然会产生过多的代理,而且,所有的代理操作除了调用的方法不一样之外,其他的操作都一样,则此时肯定是重复代码。
解决这一问题最好的做法是可以通过一个代理类完成全部的代理功能,那么此时就必须使用动态代理完成。

四、动态代理
按照代理的创建时期,代理类可以分为两种:
静态代理:由程序员创建或特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了。
动态代理:在程序运行时,运用反射机制动态创建而成。

假如有这样的需求,要在某些模块方法调用前后加上一些统一的前后处理操作,比如在添加购物车、修改订单等操作前后统一加上登陆验证与日志记录处理,该怎样实现?首先想到最简单的就是直接修改源码,在对应模块的对应方法前后添加操作。如果模块很多,你会发现,修改源码不仅非常麻烦、难以维护,而且会使代码显得十分臃肿。
这时候就轮到动态代理上场了,它可以通过反射在被调用方法前后加上自己的操作,而不需要更改被调用类的源码,大大地降低了模块之间的耦合性,体现了极大的优势。

动态代理:可以提供对另一个对象的访问,同时隐藏实际对象的具体事实。代理一般会实现它所表示的实际对象的接口。代理可以访问实际对象,但是延迟实现实际对象的部分功能,实际对象实现系统的实际功能,代理对象对客户隐藏了实际对象。客户不知道它是与代理打交道还是与实际对象打交道。

使用Java动态代理机制的好处:
1、减少编程的工作量:假如需要实现多种代理处理逻辑,只要写多个代理处理器就可以了,无需每种方式都写一个代理类。
2、系统扩展性和维护性增强,程序修改起来也方便多了(一般只要改代理处理器类就行了)。

使用Java动态代理机制的限制:
目前根据GOF的代理模式,代理类和委托类需要都实现同一个接口。也就是说只有实现了某个接口的类可以使用Java动态代理机制。动态代理在运行期通过接口动态生成代理类。,这为其带来了一定的灵活性,但这个灵活性却带来了两个问题,
1、代理类必须实现一个接口,如果没实现接口会抛出一个异常。但是,事实上使用中并不是遇到的所有类都会给你实现一个接口。因此,对于没有实现接口的类,目前无法使用该机制。有人说这不是废话吗,本来Proxy模式定义的就是委托类要实现接口的啊!但是没有实现接口的类,该如何实现动态代理呢?当然不是没有办法,那就是CGLib.
2、性能影响,因为动态代理使用反射的机制实现的,首先反射肯定比直接调用要慢,其次使用反射大量生成类文件可能引起Full GC造成性能影响,因为字节码文件加载后会存放在JVM运行时区的方法区(或者叫持久代)中,当方法区满的时候,会引起Full GC,所以当你大量使用动态代理时,可以将持久代设置大一些,减少Full GC次数。

动态代理的实现
1、JDK动态代理
JDK是针对接口,利用代理类来实现接口中方法的动态代理。
Java 提供了一个类和一个接口:
java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口来方便实现动态代理。

Proxy(代理) 类:
Proxy类是专门完成代理的操作类,可以通过此类为一个或多个接口动态地生成实现类,此类提供了如下的操作方法:

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,   InvocationHandler h) throws IllegalArgumentException

newProxyInstance参数说明:
ClassLoader loader:类加载器
Class<?>[] interfaces:接口列表,得到全部的接口
InvocationHandler h:调用处理器,得到InvocationHandler接口的子类实例

由于该方法newProxyInstance返回的是Object实例,所以需要解释一下生成的动态代理类的继承关系:动态代理类继承了Proxy类,并实现了接口列表interfaces中的所有接口。 因而可以安全地转型为其代理的所有接口。

PS:类加载器
在Proxy类中的newProxyInstance()方法中需要一个ClassLoader类的实例,ClassLoader实际上对应的是类加载器,在Java中主要有一下三种类加载器:
Booststrap ClassLoader:此加载器采用C++编写,一般开发中是看不到的;
Extendsion ClassLoader:用来进行扩展类的加载,一般对应的是jre\lib\ext目录中的类;
AppClassLoader:(默认)加载classpath指定的类,是最常使用的是一种加载器。

InvocationHandler接口:

public interface InvocationHandler {   public Object invoke(Object proxy,Method method,Object[] args) throws Throwable;   }

invoke参数说明:
Object proxy:指被代理的对象。
Method method:指代的是我们所要调用真实对象的某个方法的Method对象
Object[] args:方法调用时所需要的参数

可以将InvocationHandler接口的子类想象成一个代理的最终操作类,替换掉ProxySubject。

JDK动态加载实例:

import java.lang.reflect.Constructor;import java.lang.reflect.InvocationHandler;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;import java.lang.reflect.Proxy;interface MyInterface1 {    void foo();}interface MyInterface2 {    void bar();}class Subject implements MyInterface1, MyInterface2 {// Subject实现2个接口的2个方法    @Override    // @Override 表示方法声明的目的是覆盖超类型中的方法声明。如果用注释类型的编译器注释了一个方法,    // 那么就需要生成一条错误消息,除非至少有下列条件之一:    // 该方法确实覆盖或实现了在超类型中声明的方法;该方法具有与在对象中声明的任何公共方法相同的签名。    public void foo() {        System.out.println("foo!");    }    @Override    public void bar() {        System.out.println("bar!");    }}class InvocationHandlerImpl implements InvocationHandler {// InvocationHandler的子类实现其接口,动态代理类    Subject subject = null;    public InvocationHandlerImpl() {// 代理类的构造方法中,实例化subject对象        subject = new Subject();    }    // invoke意为调用,在代理实例上处理方法调用,并返回结果。当在与之关联的代理实例上调用方法时,将在调用处理程序上调用该invoke方法。    public Object invoke(Object proxy, Method method, Object[] args)            throws Throwable {        Object result = null;        System.out.println("调用方法前操作...invoke method: " + method.getName());// 输出方法名        // invoke方法执行被调用方法主体        result = method.invoke(subject, args); // 返回的是对象        System.out.println("调用方法后操作...result = " + result + "\n");        return result;    }}public class Test {    public static void main(String[] args) {        ClassLoader loader = Subject.class.getClassLoader();// 获得Subject的类加载器        Class[] interfaces = Subject.class.getInterfaces();// 获得Subject类实现的接口列表        InvocationHandlerImpl h = new InvocationHandlerImpl();// 处理器子类IncovationHandlerImpl的实例化对象h        // 创建一个动态代理的实例        Object proxy = Proxy.newProxyInstance(loader, interfaces, h); // 为一个或多个接口动态地生成实现类;                                                                        // 类加载器,接口列表,处理器IncovationHandler        ((MyInterface1) proxy).foo();// 通过代理类调用Subject方法        ((MyInterface2) proxy).bar();        // 以上几句和下面这段是等价的        // //生成动态代理类的类对象        // Class proxyClass = Proxy.getProxyClass(loader, interfaces);        // //通过反射获取生成的动态代理类的构造方法        // Constructor constructor=null;        // try {        // constructor = proxyClass.getConstructor(new Class[] {        // InvocationHandler.class });        // } catch (NoSuchMethodException e) {        // e.printStackTrace();        // } catch (SecurityException e) {        // e.printStackTrace();        // }        // //通过构造函数对象生成动态代理类的实例        // Object proxy;        // try {        // proxy = constructor.newInstance(h);        // ((MyInterface1) proxy).foo();//通过代理类调用Subject方法        // ((MyInterface2) proxy).bar();        // } catch (InstantiationException e) {        // e.printStackTrace();        // } catch (IllegalAccessException e) {        // e.printStackTrace();        // } catch (IllegalArgumentException e) {        // e.printStackTrace();        // } catch (InvocationTargetException e) {        // e.printStackTrace();        // }    }}

上面的例子中,创建一个动态代理的实例,就是为一个或多个接口动态地生成类:

Object proxy = Proxy.newProxyInstance(loader, interfaces, h); // 为一个或多个接口动态地生成实现类;

上面的实现和下面的代码等价:

//生成动态代理类的类对象Class proxyClass = Proxy.getProxyClass(loader, interfaces);//通过反射获取生成的动态代理类的构造方法Constructor constructor = proxyClass.getConstructor(new Class[] { InvocationHandler.class });//通过构造函数对象生成动态代理类的实例Object proxy = constructor.newInstance(h);

从以上的过程我们可以了解到,动态代理的实现过程为:
1、依赖于Java 的动态类加载机制。在创建动态代理类对象时,首先根据传入的接口列表动态地生成动态代理类的字节码;
2、在将动态生成的字节码加载进JVM后就可以利用反射机制获取构造方法等信息;
3、根据Constructor对象动态地生成动态代理类的实例。

动态代理类实现了接口列表中所有接口,方法内部都是通过调用处理器的invoke(Object proxy,Method method,Object[] args)方法进行实现的的。

2、Cglib动态代理
JDK的动态代理机制只能代理实现了接口的类,只能代理接口中的方法,而不能实现接口的类就不能实现JDK的动态代理。
JDK的自带的动态代理也是动态生成了字节码(当然当中还用了反射),只是他的生成字节码的方式必须和接口绑定。

cglib(Code Generation Library)是一个强大的,高性能,高质量的Code生成类库。它可以在运行期扩展Java类与实现Java接口。
cglib是针对类来实现代理的,他的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理。
cglib并不是针对接口,而是针对普通类来动态的生成字节码。

使用cglib代理需下载cglib.jar文件,
下载地址:http://www.java2s.com/Code/Jar/c/Downloadcloudcglibjar.htm

Cglib动态代理实例:

import java.lang.reflect.Method;import net.sf.cglib.proxy.Enhancer;import net.sf.cglib.proxy.MethodInterceptor;import net.sf.cglib.proxy.MethodProxy;class Snake{ //主体类    public void makeSound(String name) {        System.out.println("Hi," + name + ",si,si~~");    }}class AnimalProxyCglib implements MethodInterceptor {//cglib动态代理    // 要代理的对象    private Object target;    /**     * 创建代理对象     *      * @param target     * @return     */    public Object getInstance(Object target) {        this.target = target;        Enhancer enhancer = new Enhancer();        enhancer.setSuperclass(this.target.getClass());        enhancer.setCallback(this); //回调方法        return enhancer.create(); //创建代理对象    }    @Override     public Object intercept(Object obj, Method method, Object[] args,            MethodProxy proxy) throws Throwable {        Object result = null;        System.out.println("方法调用前操作..");        result = proxy.invokeSuper(obj, args);// 执行被调用方法主体        System.out.println("方法调用后操作..");        return result;    }}public class DynamicProxyCglibDemo {    public static void main(String[] args) {        AnimalProxyCglib proxy = new AnimalProxyCglib(); // 创建一个动态代理的实例        Snake snakeProxy = (Snake) proxy.getInstance(new Snake()); //调用创建代理对象的方法        snakeProxy.makeSound("Tom");//通过代理对象,调用类中的方法    }}

【运行结果】:
方法调用前操作..
Hi,Tom,si,si~~
方法调用后操作..

Java 中的动态代理技术的出现简化了代理模式的实现成本,通过动态字节码生成、反射等技术,可以使得开发人员更为简洁优雅地实现代理模式。动态代理在很多技术框架中都有所使用,如Spring中的AOP面向切面编程,一些RPC框架等。除了Java自带的JDK动态代理以外,也可以使用cglib等类似的库。

动态代理常被应用到以下几种情况中:
1、数据库连接以及事物管理
2、单元测试中的动态 Mock 对象
3、自定义工厂与依赖注入(DI)容器之间的适配器
4、类似 AOP 的方法拦截器

数据库连接以及事物管理
Spring 框架中有一个事物代理可以让你提交/回滚一个事物。方法调用序列如下:

web controller --> proxy.execute(...);  proxy --> connection.setAutoCommit(false);  proxy --> realAction.execute();    realAction does database work  proxy --> connection.commit();

单元测试中的动态 Mock 对象
通过动态代理来动态实现桩(stub),mock 和代理类来进行单元测试。在测试类A的时候如果用到了接口 B,你可以传给 A 一个实现了 B 接口的 mock 来代替实际的 B 接口实现。所有对接口B的方法调用都会被记录,你可以自己来设置 B 的 mock 中方法的返回值。 而且 Butterfly Testing 工具可以让你在 B 的 mock 中包装真实的 B 接口实现,这样所有调用 mock 的方法都会被记录,然后把调用转向到真实的 B 接口实现。这样你就可以检查B中方法真实功能的调用情况。例如:你在测试 DAO 时你可以把真实的数据库连接包装到 mock 中。这样的话就与真实的情况一样,DAO 可以在数据库中读写数据,mock 会把对数据库的读写操作指令都传给数据库,你可以通过 mock 来检查 DAO 是不是以正确的方式来使用数据库连接,比如你可以检查是否调用了 connection.close()方法。这种情况是不能简单的依靠调用 DAO 方法的返回值来判断的。

自定义工厂与依赖注入(DI)容器之间的适配器
依赖注入容器 Butterfly Container 有一个非常强大的特性可以让你把整个容器注入到这个容器生成的 bean 中。但是,如果你不想依赖这个容器的接口,这个容器可以适配你自己定义的工厂接口。你仅仅需要这个接口而不是接口的实现,这样这个工厂接口和你的类看起来就像这样:

public interface IMyFactory {  Bean   bean1();  Person person();  ...}public class MyAction{  protected IMyFactory myFactory= null;  public MyAction(IMyFactory factory){    this.myFactory = factory;  }  public void execute(){    Bean bean = this.myFactory.bean();    Person person = this.myFactory.person();  }}

当 MyAction 类调用通过容器注入到构造方法中的 IMyFactory 实例的方法时,这个方法调用实际先调用了 IContainer.instance()方法,这个方法可以让你从容器中获取实例。这样这个对象可以把 Butterfly Container 容器在运行期当成一个工厂使用,比起在创建这个类的时候进行注入,这种方式显然更好。而且这种方法没有依赖到 Butterfly Container 中的任何接口。

类似 AOP 的方法拦截器
Spring 框架可以拦截指定 bean 的方法调用,你只需提供这个 bean 继承的接口。Spring 使用动态代理来包装 bean。所有对 bean 中方法的调用都会被代理拦截。代理可以判断在调用实际方法之前是否需要调用其他方法或者调用其他对象的方法,还可以在 bean 的方法调用完毕之后再调用其他的代理方法。

参考文献:
http://blog.csdn.net/hla199106/article/details/46832143
http://www.cnblogs.com/hanganglin/p/4485999.html
http://blog.csdn.net/hla199106/article/details/46835049
http://blog.jrwang.me/2015/java-dynamic-proxy-and%20java-reflection/
http://wiki.jikexueyuan.com/project/java-reflection/java-dynamic.html

原创粉丝点击