第8章 Java的反射机制、Java的代理机制、类加载

来源:互联网 发布:知乎社区 法律 编辑:程序博客网 时间:2024/06/05 18:17

本文是 JAVA程序设计进阶课程 和《深入理解Java 7 ——核心技术与最佳实践》第2章 Java语言的动态性 学习笔记

Java的反射机制

使用反射API的一个重要好处是可以绕过Java语言中默认的访问控制权限。

  • Java类型信息
    获取Java运行时的类型信息有两种方法
    RTTI(Run-Time Type Identification) 所有的类型检测都是在运行时进行正确性
    检查的
  • Java反射机制的定义
    Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和
    方法;对于任意一个对象,都能够调用它的方法和属性;这种动态获取信息以及动
    态调用对象的功能称为Java语言的发射机制。
  • 类 Class
    Calss 类是Java一个基础类,每装载一个新类的时候,Java虚拟机就会在Java堆中,
    创建一个Class的实例,这个实例就代表这个Class类型,通过实例获取类型信息。该类
    中的一些方法如下:
    Method[]getMethods()    Filed[]getFileds()    Constructor<?>[]    getDeclaredConstructors()
  • Object 类中的方法
    hashCode()    equals()    clone()    toString()    notify()    wait()
  • 利用Class类来创建实例
    • 创建Class类的一个对象,返回一个类的引用
      Class cls = Class.forName(”xxx“);//返回一个类型
    • 通过类的引用创建实例
      cls.newInstace();//通过newInstace创建实例,一般调用默认构造函数
package reflect;/** * 利用Class 类来创建实例 传统方法 */class Airplane {    @Override    public String toString() {        return "in airplane";    }}public class CreateInstance {    public static void main(String[] args) throws Exception {        Class cla = null;        Object ap ;//      创建Class类的一个对象,描述了类Airplane 注意加上包名        cla = Class.forName("reflect.Airplane");        System.out.println(cla);        ap =cla.newInstance();//创建实例的另外一种办法        System.out.println(ap.toString());    }}
  • 使用反射API获取参数长度可变的构造方法
    相关的方法有4个,其中getConstructors用来获取所有的公开构造方法的列表,
    getConstructor则根据参数类型来获取公开的构造方法。另外两个对应方法
    getDeclaredConstructors和getDeclaredConstructor的作用类似,只不过它们
    会获取类中真正声明的构造方法,而忽略从父类中继承下来的构造方法。得到了
    表示构造方法的java.lang.reflect.Constructor对象之后,就可以获取关于构
    造方法的更多信息,以及通过newInstance方法创建出新的对象。
package reflect;import java.lang.reflect.Constructor;import java.lang.reflect.InvocationTargetException;/** * 通过反射API可以获取到Java类中的构造方法。 * 通过构造方法可以在运行时动态地创建Java对象,而不只是通过new操作符来进行创建。 * @author wangbaofu */public class GetConstructor {    public GetConstructor(String... names) {        System.out.println(names.length);    }    public GetConstructor(String names) {        System.out.println(names);    }    public GetConstructor() {        System.out.println("null");    }    public static void useGetConstructor() throws NoSuchMethodException,            SecurityException, InstantiationException, IllegalAccessException,            IllegalArgumentException, InvocationTargetException {        Constructor<GetConstructor> constructor = GetConstructor.class                .getDeclaredConstructor(String[].class);        constructor.newInstance((Object) (new String[] { "A", "B", "C" }));        // 获取默认构造方法        Constructor<GetConstructor> constructor2 = GetConstructor.class                .getDeclaredConstructor();        constructor2.newInstance();//      获取所有的公开构造方法的列表        Constructor<?>[] constructors = GetConstructor.class                .getDeclaredConstructors();        for (Constructor<?> constructor3 : constructors) {            System.out.println(constructor3.getParameterCount());        }        constructor2.newInstance();    }    public static void main(String[] args) {        try {            useGetConstructor();        } catch (NoSuchMethodException | SecurityException                | InstantiationException | IllegalAccessException                | IllegalArgumentException | InvocationTargetException e) {            // TODO Auto-generated catch block            e.printStackTrace();        }    }}
  • 通过反射API可以获取到类中公开的静态域和对象中的实例域。
package reflect;import java.lang.reflect.Field;/** * 通过反射API可以获取到类中公开的静态域和对象中的实例域。 得到表示域的java.lang.reflect.Field类的对象之后, * 就可以获取和设置域的值。与上面的构造方法类似,Class类中也有4个方法 用来获取域, * 分别是getFields、getField、getDeclaredFields和getDeclaredField,  其含义与获取构造方法的4个方法类似。 *  * 使用静态域时不需要提供具体的对象实例,使用null即可。 Field类中除了 操作Object的get和set方法之外,还有操作基本类型的对应方法, * 包括getBoolean/setBoolean、getByte/setByte、getChar/setChar、 * getDouble/setDouble、getFloat/setFloat、getInt/setInt和getLong/setLong等。 * ,对域的获取和设置都比较简单。但是只能对类中的公开域进行操作。 私有域没有办法通过反射API获取到,也无法进行操作 Class * reflect.GetFile can not access a member of class reflect.FieldContainer with * modifiers "private" *  */public class GetFile {    public static void useField() throws Exception, Exception {        Field fieldCount = FieldContainer.class.getDeclaredField("count");        fieldCount.set(null, 48);        Field fieldName = FieldContainer.class.getDeclaredField("name");        FieldContainer fieldContainer = new FieldContainer();        fieldName.set(fieldContainer, "Bob");        Field fieldName2 = FieldContainer.class.getField("tel");        Field[] fields = FieldContainer.class.getFields();        Field[] fields3 = FieldContainer.class.getDeclaredFields();        for (Field field : fields3) {            System.out.println(field);        }        /**         * public java.lang.String reflect.FieldContainer.name public         * java.lang.String reflect.FieldContainer.sex public static int         * reflect.FieldContainer.count public java.lang.String         * reflect.FieldContainer.tel         */        Field[] fields4 = FieldContainer.class.getFields();        for (Field field : fields4) {            System.out.println(field);        }        /**         * fields4 public java.lang.String reflect.FieldContainer.sex public         * static int reflect.FieldContainer.count public java.lang.String         * reflect.FieldContainer.tel public java.lang.String         * reflect.FiledSupper.father         */        for (Field field : fields) {            System.out.println(fields);        }        /**         * fields [Ljava.lang.reflect.Field;@3d4eac69         * [Ljava.lang.reflect.Field;@3d4eac69         * [Ljava.lang.reflect.Field;@3d4eac69         * [Ljava.lang.reflect.Field;@3d4eac69         */        fieldName2.set(fieldContainer, "1883939393");        System.out.println(fieldName2);        /**         * public java.lang.String reflect.FieldContainer.tel         */        System.out.println(fieldContainer);        /**         * name: Bob sex:null count:48 tel:1883939393         */    }    public static void main(String[] args) throws Exception {        useField();    }}class FieldContainer extends FiledSupper {    public String name;    public String sex;    public static int count;    public String tel;    @Override    public String toString() {        return "name: " + name + " sex:" + sex + " count:" + count + " tel:"                + tel;    }}
  • 使用反射API获取和使用公开和私有方法
package reflect;import java.lang.reflect.Method;/** * 使用反射API获取和使用公开和私有方法 * Class类中也有4个方法用来获取方法, * 分别是getMethods、getMethod、getDeclaredMethods和getDeclaredMethod *   这4个方法的含义类似于获取构造方法和域的对应方法。 *   在 得到了表示方法的java.lang.reflect.Method类的对象之后,就可以查询该方法的详细信息 *   比如方法的参数和返回值的类型等。最重要的是可以通过invoke方法来传入实际参数并调用该方法。 */public class getMethod {    public static void useMethod() throws Exception, Exception {        MethodContainer mc = new MethodContainer();        Method publicMethod = MethodContainer.class                .getDeclaredMethod("testMethod");        publicMethod.invoke(mc);        Method privateMethod = MethodContainer.class.getDeclaredMethod(                "testMethod2", String.class);        privateMethod.setAccessible(true);        privateMethod.invoke(mc, "ss");    }    public static void main(String[] args) {        try {            useMethod();        } catch (Exception e) {            e.printStackTrace();        }    }}class MethodContainer {    public void testMethod() {        System.out.println("null");    }    private void testMethod2(String str) {        System.out.println("2");    }}
  • 使用反射API操作数组
package reflect;import java.lang.reflect.Array;/** * 使用反射API操作数组 * 使用反射API对数组进行操作的方式不同于一般的Java对象,是通过专门的 * java.lang.reflect.Array这个实用工具类来实现的 Array类中提供的方法包括创建数组和操作数组中的元素。 * newInstance方法用来创建新的数组 *第一个参数是数组中元素的类型,后面的参数是数组的维度信息。比如names是 *一个长度为10的一维String数组 * matrix1是一个3×3×3的三维数组。由于matrix2的元素类型是int[], */public class UseArray {    public void useArray() {        String[] names = (String[]) Array.newInstance(String.class, 10);        names[0] = "Hello";        Array.set(names, 1, "World");        String str = (String) Array.get(names, 0);        int[][][] matrixl = (int[][][]) Array.newInstance(int.class, 3, 3, 3);        matrixl[0][0][0] = 1;        int[][][] matrix2 = (int[][][]) Array.newInstance(int[].class, 3, 4);        matrix2[0][0] = new int[10];        matrix2[0][1] = new int[3];        matrix2[0][0][1] = 1;    }}
  • 异常类
    Java 7为所有与反射操作相关的异常类添加了一个新的父类java.lang.ReflectiveOperationException。
    在处理与反射相关的异常的时候,可以直接捕获这个新的异常。

Java的代理机制

代理模式

在某些情况下,一个客户不想或者不能直接引用另一个对象,而代理对可以在客户端和目标对象之间起到中介的作用。
代理模式的作用是:为其他对象提供一种代理以控制对这个对象的访问。
代理模式一般涉及到的角色:
抽象角色:声明真实对象和代理对象的共同接口。
代理角色:代理对象角色内部含有对真实对象的引用,从而可以直接操作真实对象,同时代理对象提供与真实对象相同的接口以便在任何时刻都能够代替真实对象。同时,代理对象可以在执行真实对象操作时,附加其他的操作,相当于对真实对象进行封装。
真实角色:代理角色所代表的真实对象,是我们最终要引用的对象。

静态代理例子

package reflect;/** * 静态代理的例子 *  * @author wangbaofu *  */public class Client {    // 客户端    public static void main(String[] args) {        Subject subject = new ProxtSubject();        subject.request();    }}// 真实对象和代理对象的共同接口abstract class Subject {    public abstract void request();}// 真实角色class RealSubject extends Subject {    @Override    public void request() {        System.out.println("From Real Subject!");    }}// 代理角色class ProxtSubject extends Subject {    // 代理角色对象内部含有对真实对象的引用    private RealSubject prRealSubject;    @Override    public void request() {        // 在真实角色操作之前所附加的操作        preRequest();        if (null == prRealSubject) {            prRealSubject = new RealSubject();        }        // 真实操作完成后进行的操作        postRequest();    }    private void postRequest() {        System.out.println("postRequest()");    }    private void preRequest() {        System.out.println("preRequest()");    }}输出:preRequest()postRequest()
  • 静态代理的优缺点:
    优点:业务只需要关注业务逻辑本身,保证了业务类的重用性。
    缺点:代理对象的接口服务于一种类型的对象,如果要代理的方法很多,势必要为每一种方法都进行代理,静态代理在程序规模上稍大时就无法胜任了。
    如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。

Java动态代理

**java.lang.reflect.Proxy**这是Java动态代理机制的主类,它提供了一组静态方法来为一组接口动态地生成代理类及其对象。//该方法用于获取关联指定类加载器和一组接口的动态代理类的类对象**static Class getProxyClass(ClassLoader loader,Class []interface )**//该方法用于判断指定类对象是否是一个动态代理类**static boolean isProxyClass(Class cl)**//该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类的实例**static Object newProxyInstance(ClassLoader loader,Class[]interfaces,InvocationHandler h)**

java.lang.reflect.InvocationHandler
这是调用处理器接口,它定义了一个invoke方法,用于集中处理在动态代理类对象上的方法调用,通常在给方法中实现对委托类的代理访问
Obeject invoke(Object proxy,Method method ,Object []args)
//该方法负责集中处理处理动态代理类上的所有方法调用。第一个参数是代理类实例,第二个参数是被调用的方法对象,第三份方法是调用的参数。调用处理器根据这三个参数进行预处理或者分派到委托实例上执行
实例:

package reflect;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;import javax.jws.Oneway;/** * Java 动态代理实例 *  */public class Clinet2 {    public static void main(String[] args) {        RealSubject2 rs = new RealSubject2();// 在这里指定代理类        InvocationHandler ds = new DynamicSubject(rs);        Class cls = rs.getClass();// 以下是一次性生成代理        Subject2 subject2 = (Subject2) Proxy.newProxyInstance(                cls.getClassLoader(), cls.getInterfaces(), ds);        subject2.request();    }}// 抽象角色interface Subject2 {    public void request();}// 真实角色 :实现了Subject 的request()方法class RealSubject2 implements Subject2 {    public RealSubject2() {    }    @Override    public void request() {        System.out.println("From real subject.");    }}// 代理角色,必须继承InvocationHandlerclass DynamicSubject implements InvocationHandler {    private Object sub;    public DynamicSubject() {    }    public DynamicSubject(Object object) {        sub = object;    }    @Override    public Object invoke(Object proxy, Method method, Object[] args)            throws Throwable {        System.out.println(" before caling " + method);        method.invoke(sub, args);        System.out.println(" after caling " + method);        return null;    }}/** * 输出结果 *  before caling public abstract void reflect.Subject2.request()From real subject. after caling public abstract void reflect.Subject2.request() */
  • 动态代理的特点:
    :如果所代理的接口都是public的,那么它将被定义为顶层包(即包路径为空),如果所代理的接口中有非public的接口,那么它将被定义在该接口所在包,这样的设计的目的是为了最大程度的保证动态代理类不会因为包管理的问题二无法被定义并访问;
    类修饰符:该代理类具有final和public修饰符,意味着它可以被所有的类访问,但是不能被再度继承;
    类名:格式是“$ProxyN”,其中N是一个逐一递增的阿拉伯数字,代表Proxy类第N次生成的动态代理类,值得注意的一点是,并不是每次调用Proxy的静态方法穿件动态代理类都会使得N增加,原因是如果对同一组接口
    (包括接口排列顺序相同)试图重复创建动态代理类,它会很聪明地返回先前已经创建好的代理类的对象,而不会再尝试去创建一个全新的代理类,这样可以节省不必要的代码重复生成,提高了代理类的创建效率。
    动态代理的优点和缺点
    优点:动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转义到调用处理器一个集中的方法中处理(InvocationHandler.invoke)。这样,在接口方法数量比较多的时候,可以进行灵活处理,而不需要像代理那样每一个方法进行中转。
    缺点:Proxy已经设计的非常优美,美中不足仅支持interface代理。

类加载

  • JVM类加载的种类:
    JVM自带的默认加载器
    1.根据加载器:boostrap,由C++编写,所有的Java程序无法获得。
    2.扩展类加载器:由Java编写。
    3.系统类、应用类加载器:由Java 编写。
    用户自定义的类加载器
    Java.lang.ClassLoader的子类,用户可以定制类的加载方式。每一个类都包含了加载它的ClassLoader的一个引用–getClassLoader()。
    如果返回的是null,证明加载它的ClassLoader是跟加载器boostrap。
    类的加载方式

    • 本地编译好的class文件直接加载
    • 网络加载:java.net.URLClassLoader可以加载仂指定的类
    • 从jar、zip等等压缩文件加载类,自动解析jar文件找到class 文件去加载。

    类加载的步骤

    • 加载 、连接、验证、准备、解析

    加载顺序
    根加载器->扩展类加载器 ->应用类加载器 -> 用户自定义类加载器
    如果到最后一层都再加载不了,就出现ClassNotFoundException异常。
    ClassLoader加载Class的过程
    第1步:检测此Class是否载入过(即在cache中是否有此Class),如果有跳到第8步,如果没有到第2步
    第2步:如果parent classloader不存在(没有parent一定是bootstrap classloader了),则跳到第4步。
    第3步:请求parent classloader载入,如果成功到第8步
    第4步:请求Jvm从bootstrap classloader 中载入,如果成功到第8步。
    第5步:寻求Class文件(从与此classLoader相关的类路径中寻找)。如果找不到则跳到第8步。
    第6步 :从文件中载入Class,到第8步
    第7步:抛出ClassNotFoundException
    第8步:返回Class

0 0