黑马程序员 - 反射和动态代理

类的加载

类的加载:当程序要使用某个类时,如果该类还没有被加载到内存中,则系统会通过加载,

连接,初始化三步实现对这个类的初始化。
加载:将class文件读入内存,并为之创建一个Class对象。
连接:验证 是否有正确的内部结构,并和其他类协调一致。
准备 赋值为类的静态成员分配内存,并使用默认初始化
解析 将类的二进制数据中的符号引用替换为直接引用
初始化:
类加载时机:创建类的实例
访问类的静态变量,或为静态变量赋值
调用类的静态方法
使用反射方式来强制创建某个类或接口对应的java.lang.Class对象
初始化某个类的 子类
直接使用java.exe命令来运行某个主类。
类加载器:负责将.class文件加载到内存,并为之生成对应的Class对象。
类加载器的组成:

Bootstrap ClassLoader 根类加载器。

    也被称为引导类加载器,赋值java核心类的加载器。

Extension ClassLoader 扩展类加载器

    赋值JRE的扩展目录中的jar包的加载。

System ClassLoader 系统加载器

    赋值在JVM启动时加载来自java命令的class文件

类加载器的委托机制:
   1.当Java虚拟机要加载一个类时,到底派出哪个类加载器去加载呢?
     首先当前线程的类加载器去加载线程中的第一个类
     如果类A中引用了类B,Java虚拟机将使用加载类A的类加载器来加载类B
     还可以直接调用ClassLoader.loadClass()方法来指定某个类加载器去加载某个类
   2.每个类加载器加载类时,又先委托给其上级类加载器
     当所有祖宗类加载器没有加载到类,回到发起者类加载器,还加载不了,则抛ClassNotFoundException,
     不是再去找发起者类加载器的儿子,因为没有getChild方法,即使有,那有多个儿子,找哪一个呢?
     对着类加载器的层次结构图和委托加载原理,解释先前将ClassLoaderTest输出成jre/lib/ext目录下的itcast.jar包中后,
     运行结果为:ExtClassLoader原因.
   3.每个ClassLoader本身只能分别加载特定位置和目录中的类,但它们可以委托其他的类加载器去加载类,这就是类加载器的委托模式。
     类加载器一级级委托到BootStrap类加载器,当BootStrap无法加载当前所要加载的类时,
     然后才一级级回退到子孙类加载器去进行真正的加载,当回退到最初的类加载器时,如果它自己也不能完成类的加载,
     那就应报告ClassNotFoundException异常。 
   4.有一道面试题,能不 能自己写个类叫java.lang.System,为了不让我们写System类,类加载采用委托机制,
     这样可以保证爸爸们优先,也就是总是使用爸爸们能找到的类,这样总是使用Java系统提供的System.

java反射机制是在运行状态中,对于任意一个类(class文件),都能知道这个类的所有属性和方法
对于任意一个对象,都能调用它的任意一个方法和属性。
这种动态获取的信息以及动态调用对象的方法的功能称为java反射机制
Class 类 :成员变量 Field
构造方法 Constructor
成员方法 Method

动态调用类中信息就是java的反射。
反射是通过Class类获取字节码文件中的成员变量、构造方法、成员方法,并使用。

要想对字节码文件进行解剖,必须有字节码文件对象
获取字节码文件的具体方法:
1.Object类中的getClass方法
需要明确对象的名称
2.任何数据都具备一个静态属性class,用来获取其对应的Class对象。
相对简单,但是还是要明确用到类中的静态成员,任然不够扩展。
3.只要通过给定的类的字符串名称,就可以获取该类。
可以用Class类中的方法完成。forName()

获取类中的构造函数:
不带参数:通过Class类中的newInstance()生成空参数对象
带参数:通过Class类中的getConstructor(参数类型.class)获取构造器,
再通过Constructor中的newInstance(参数)生成。

获取类中的字段
getField():获取公共字段
getFields():获取公共字段数组
getDeclaredField():获取所有字段(包含私有)
getDeclaredFields():获取所有字段数组(包含私有)

获取指定Class中的公共函数
getMethod(方法名,方法参数的class类型):获取一个公共方法
getMethods():获取公共方法数组
getDeclaredMethod(方法名,方法参数的class类型):获取一个方法(包含私有)
getDeclaredMethods():获取所有方法的数组(包含私有)

反射应用:定义配置文件,通过用反射建立配置文件中的各种对象,实现程序的功能扩展,而不需要改动原有代码。

以下代码演示反射的基本用法

import java.lang.reflect.*;
public class ClassDemo{
    public static void main(String[] args)  throws Exception{   
        //3种获取字节码文件的具体方法
        getClassObject_1();         
        getClassObject_2();
        getClassObject_3();
        //通过Class类的方法产生该类的对象
        createNewObject_1();
        getMethodDemo();     //通过class类获取类中具体方法
    }
    public static void getClassObject_1(){  //方法一
        Person p =new Person();             //新建Person对象
        Class<? extends Person> class1 = p.getClass();//获取该对象的类对象
        Person p1 =new Person();            //新建Person对象
        Class<? extends Person> class2 = p1.getClass();//获取该对象的类对象
        System.out.println("getClassObject_1:"+(class1==class2));//因为两个Person对象都是用的同一个类文件,所以class1==class2
    }
    public static void getClassObject_2(){  //方法二
        Class<Person> class1 = Person.class;//直接靠类名获取类对象
        System.out.println("getClassObject_2:"+class1);
    }
    public static void getClassObject_3() throws ClassNotFoundException { //方法三
        String className = "fanshe.Person";//获取类名,若在包中需添加包名
        Class<?> class1 = Class.forName(className);//获取类名的类对象
        System.out.println("getClassObject_3:"+class1);
    }
    public static void createNewObject_1() throws Exception{    
        //通过Class类的方法产生该类的对象
        String className = "fanshe.Person";      //此处记着带着包名
        Class<?> class1 = Class.forName(className);//获取类名的类对象
        Object obj1 = class1.newInstance();      //生成空参数的新对象
        //当需要生成带参数的新对象时,需要先获得该类的所有构造函数,
        //用getConstructor(Class<?> parameter)
        Constructor<?> con = class1.getConstructor(String.class,int.class);//获取类对象中参数为String和int的构造方法的构造器
        Object obj2 = con.newInstance("mike",40);//根据上面的构造器创建一个参数为"mike"和40的对象
        System.out.println(obj2.toString());     //打印该对象
        Field field1 = class1.getField("age");     //获取类对对象的参数字段信息(只包括公共)
        Field field2 = class1.getDeclaredField("name");//获取类对象的参数字段信息(包括私有)
        //field.setAccessible(true)    //取消私有字段的访问权限检查,暴力访问,不建议使用。
        System.out.println(field1+"  :  "+field2);
        field1.set(obj2,44);           //更改对象中对应字段的值
        System.out.println(obj2.toString());
        Object o1 = field1.get(obj2);  //获取都想着对应字段的值
        System.out.println("age:"+o1);
    }
    public static void getMethodDemo() throws Exception{
        String className = "fanshe.Person";
        Class<?> class1 = Class.forName(className);
        Object obj = class1.newInstance();
        //Method[] methods1 = class1.getMethods();//获取类中的公有方法(包括父类)
        //for (Method method : methods1 ){
        //    System.out.println(method);
        //}
        Method methods2 = class1.getMethod("show",int.class);//输入方法名和参数类型.class,如果无参数则省略
        Method methods3 = class1.getMethod("toString");         //输入方法名,该方法无参数
        //public object invoke (Onject obj,Object...args)
        //返回值是Object接收,第一个参数表示对象是谁,第二个参数表示调用该方法的时机参数
        Object str1 = methods2.invoke(obj,5);       //传入调用对象和方法参数,无参数则省略
        Object str2 = methods3.invoke(obj);            //传入调用对象,该方法无参数
        //String str =(String) methods2.invoke(obj);
        System.out.println(str1);
        System.out.println(str2);
    }
}
class Person{   //定义一个演示类
    private String name;//定义私有成员变量
    public int age;        //定义公共成员变量
    public Person(){}    //定义空参数构造方法
    public Person(String name,int age){//定义带参数构造方法
        this.name = name;
        this.age = age;
    }
    public String toString(){
        return name+";"+age;
    }
    public String show(int x){
        return "invoke方法演示:"+x;
    }
}

运行结果

反射的应用

动态代理
代理:本来应该自己租的事情,却请了别人来做,被请的人就是代理对象
动态代理:在程序运行过程中产生的这个对象
而程序运行过程中产生对象其实就是反射讲解的内容,所以,动态代理就是通过反射来生成一个代理。
在java中java.lang.reflect包下提供了一个Proxy类和一个接口就可以生成动态地理对象,
JDK提供的代理只能针对接口做代理。
Proxy类中方法创建动态代理类对象
public static Object newProxyInstance
(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)throws IllegalArgumentException
最终会调用InvocationHandler
Object invoke(Object proxy, Method method,Object[] args)throws Throwable

InvocationHandler是代理实例的调用处理程序 实现的接口
每个代理实例都具有一个关联的调用处理程序。
对代理实例调用方法时,将对方法调用进行编码并将其指派到它的调用处理程序的 invoke 方法。

需求:创建工具类,在其中实现添加和删除功能。然后用动态代理的方式使其每次运行方法时都能自动调用权限校验和日志记录功能。

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class DynamicClass {
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Tool ul = new UserTool();       //创建演示工具类
        System.out.println("代理前:");
        ul.add();                        //演示工具类中的方法
        ul.delete();
        System.out.println("-------------");
        //创建一个动态代理对象
        //Proxy类中有一个方法可以创建动态代理对象
        //将需要代理的对象传给动态代理对象
        MyInvocationHandler handler = new MyInvocationHandler(ul);
        Tool proxyTool =(Tool) Proxy.newProxyInstance(ul.getClass().getClassLoader(),
                                                            ul.getClass().getInterfaces(),handler);
        System.out.println("代理后:");
        proxyTool.add();                //演示代理后的方法
        proxyTool.delete();
    }
}
//创建一个动态代理类实现Invocation接口 
class MyInvocationHandler implements InvocationHandler{
    private Object obj;
    MyInvocationHandler(Object obj){   //将需要代理的对象传入动态代理对象
        this.obj = obj;
    }
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {            //覆写invoke方法
        System.out.println("权限校验");    //添加自定义方法
        Object result = method.invoke(obj,args);//添加代理对象的方法
        System.out.println("日志记录"); //添加自定义方法
        return result;
    }
}
class UserTool implements Tool{         //创建工具类实现工具接口
    public void add(){                    //覆写工具接口的方法
        System.out.println("添加功能");
    }
    public void delete(){
        System.out.println("删除功能");
    }
}
interface Tool{                            //创建工具接口
    public void add();
    public void delete();
}

运行结果

 

由结果可以看出,通过代理,使代理对象的每个方法运行时都能自动同时运行自定义方法。

posted @ 2015-08-21 15:46  koibiki  阅读(171)  评论(0编辑  收藏  举报