JAVA反射

一. 什么是JAVA反射

java反射机制是在程序运行状态中,对于任何一个类都能够知道这个类的属性和方法。对于任何一个对象都能够调用它的任意一个方法。Java反射机制运行程序判断分析任何一个类的结构,包括成员方法和变量,并调用任意一个对象的方法。

二. JAVA反射的实例

在JDK中,主要由以下类来实现Java反射机制,这些类(除了第一个)都位于java.lang.reflect包中

Class类:代表一个类,位于java.lang包下。

Field类:代表类的成员变量(成员变量也称为类的属性)。

Method类:代表类的方法。

Constructor类:代表类的构造方法。

 

Class类

Class类是用来保存运行时类型信息的类。每个类(型)都有一个Class对象。运行程序时,Java虚拟机(JVM)首先检查是否所要加载的类对应的Class对象是否已经加载。如果没有加载,JVM就会根据类名查找.class文件,并将其Class对象载入。基本的 Java 类型(boolean、byte、char、short、int、long、float 和 double)和关键字 void 也都对应一个 Class 对象。一般当某个类的Class对象被载入内存时,它就可以用来创建这个类的所有对象。

常用的获取Class对象的3种方式:

1.使用Class类的静态方法。例如:  

Class.forName("java.lang.String");

2.使用类的.class语法。如:

String.class;

3.使用对象的getClass()方法。如:

String str = "aa";
Class<?> classType1 = str.getClass();

 

Field类

再来看看通过class对象来获取类的属性

private String strName;
    private int sum;
    public String strTest;
      
    public static void main(String[] args) throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
          
        ReflectTest rTest=new ReflectTest();
        Class<?> clsType = ReflectTest.class;
        //获得当前类和父类中的public类型的所有属性
        Field[] fields=clsType.getFields();
        for(Field field:fields)
            System.out.println(field);
    }

通过反射来设置属性值

public class ReflectTest {
      
    private String strName;
    private int sum;
    public String strTest;
      
    public void show(){
        System.out.println(strName);
        System.out.println(""+sum);
        System.out.println(strTest);
    }
      
    public static void main(String[] args) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
          
        ReflectTest rTest=new ReflectTest();
        Class<?> clsType = ReflectTest.class;
        //获取三个属性值
        Field field1=clsType.getDeclaredField("strName");
        Field field2=clsType.getDeclaredField("sum");
        Field field3=clsType.getField("strTest");
        //反射设置对象的属性值
        field1.set(rTest, "james");
        field2.set(rTest, 10);
        field3.set(rTest, "reflect field");
          
        rTest.show();
    }
}

 

Method类

在获取Class对象后,我们还可以获取类所对应的方法,这时就要用到我们的Method类

public class ReflectTest {
  
    private void fun() {
        System.out.println("this is fun()");
    }
  
    private void add(int a, int b) {
        System.out.println("the sum is: " + (a + b) + " ");
    }
  
    public static void main(String[] args) {
        Class<?> clsType = ReflectTest.class;
        // 返回class对象所对应的类或接口中,所声明的所有方法的数组(包括私有方法)
        Method[] methods = clsType.getDeclaredMethods();
        // 打印出所有的方法名
        for (Method method : methods) {
            System.out.println(method);
        }
    }
}

再来看看怎么反射调用对象的方法

public class ReflectTest {
  
    private void fun() {
        System.out.println("this is fun()");
    }
  
    public int add(int a, int b) {
        System.out.println("the sum is: " + (a + b) + " ");
        return a+b;
    }
  
    public static void main(String[] args) throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
        ReflectTest rTest=new ReflectTest();
        Class<?> clsType = ReflectTest.class;
        // 返回对应public类型的方法,第一个参数为方法名,第二个参数为方法的参数列表
        Method method=clsType.getMethod("add"new Class<?>[]{int.class,int.class});
        System.out.println(""+method.invoke(rTest, new Object[]{5,6}));
    }
}

 

Constructor类

在获取Class对象后,我们就可以通过它创建类的对象啦。这就地用到我们的Constructor类啦。有三种创建对象的方式:

1.先获得Class对象,然后通过该Class对象的newInstance()方法直接生成即可:

Class<?> classType = StringBuffer.class;

Object obj = classType.newInstance();

2.当然也可以再获得Class对象后,通过该Class对象获得类的Constructor对象,再通过该Constructor对象的newInstance()方法创建对象

Class<?> clsType=StringBuffer.class;

// 获得Constructor对象,此处获取一个无参数的构造方法的
Constructor<?> constructor=clsType.getConstructor(new Class<?>[]{});

// 通过Constructor对象的构造方法来生成一个对象
Object obj=constructor.newInstance(new Object[]{});

3、如果构造器带有参数,那就不能有上面两个方法来创建对象了,可使用下面这一种方式:

Class<?> clsType = StringBuffer.class;

Constructor<?> constructor = clsType.getConstructor(new Class<?>[] { String.class });

Object obj = constructor.newInstance(new Object[] { "hello, classtest" });

三. JAVA反射的原理

 Class.forName(classname)的执行过程:

实际上是调用了Class类中的 Class.forName(classname, true, currentLoader)方法。参数:name - 所需类的完全限定名;initialize - 是否必须初始化类;loader - 用于加载类的类加载器。currentLoader则是通过调用ClassLoader.getCallerClassLoader()获取当前类加载器的。类要想使用,必须用类加载器加载,所以需要加载器。反射机制,不是每次都去重新反射,而是提供了cache,每次都会需要类加载器去自己的cache中查找,如果可以查到,则直接返回该类。

java的类加载器,它分为BootStrap Class Loader(引导类加载器),Extensions Class Loader (扩展类加载器),App ClassLoader(或System Class Loader),当然少不了Custom ClassLoader(用户自定义类加载器)。其加载过程中会先检查类是否被已加载,检查顺序是自底向上,从Custom ClassLoader到BootStrap ClassLoader逐层检查,只要某个classloader已加载就视为已加载此类,保证此类只所有ClassLoader加载一次。而加载的顺序是自顶向下,也就是由上层来逐层尝试加载此类。

流程图:

类加载器的类加载过程,先检查自己是否已经加载过该类,如果加载过,则直接返回该类,若没有则调用父类的loadClass方法,如果父类中没有,则执行findClass方法去尝试加载此类,也就是我们通常所理解的片面的"反射"了。这个过程主要通过ClassLoader.defineClass方法来完成。defineClass 方法将一个字节数组转换为 Class 类的实例(任何类的对象都是Class类的对象)。这种新定义的类的实例需要使用 Class.newInstance 来创建,而不能使用new来实例化。

在运行期间,如果我们要产生某个类的对象,Java虚拟机(JVM)会检查该类型的Class对象是否已被加载。如果没有被加载,JVM会根据类的名称找到.class文件并加载它。一旦某个类型的Class对象已被加载到内存,就可以用它来产生该类型的所有对象。

四. JAVA反射的应用-动态代理

说动态代理之前,必须先讲讲代理的设计模式。代理模式为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个客户不想或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。代理模式的有三个角色定义:

1)抽象主题接口(Subject)
声明真实对象和代理对象的共同接口。代理模式中,代理类和真实对象都实现了接口。
2)真实主题类(RealSubject)
真正实现业务逻辑的类。
3)代理类(Proxy)
用来代理和封装真实主题。代理对象角色内部含有对真实对象的引用,从而可以操作真实对象。同时,代理对象可以在执行真实对象操作时,附加其他的操作,相当于对真实对象进行封装。通常情况下,代理模式中的每一个代理类在编译之后都会生成一个class文件,代理类所实现的接口和所代理的方法都被固定,这种代理被称之为静态代理(Static Proxy),
缺点:1. 如果需要为不同的真实主题类提供代理类,都需要增加新的代理类,这将导致系统中的类个数急剧增加。
           2. 静态代理类和真实主题类实现了相同的接口,代理类通过真实主题类实现了相同的方法,如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法,增加了代码维护的复杂度。

这就需要使用我们的动态代理了。

动态代理(Dynamic Proxy)在系统运行时动态创建代理类,可以让系统能够根据实际需要来动态创建代理类,让同一个代理类能够代理多个不同的真实主题类。动态代理是一种较为高级的代理模式,它在事务管理、AOP(Aspect-OrientedProgramming,面向方面编程)等领域都发挥了重要的作用。Java语言提供了对动态代理的支持,在Java的动态代理机制中,有两个重要的类或接口,一个是 InvocationHandler接口、另一个则是 Proxy类。Proxy这个类的作用就是用来动态创建一个代理对象,通常使用newProxyInstance 方法创建代理对象。Proxy代理类动态创建代理对象都需要关联到一个InvocationHandler接口,当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由InvocationHandler这个接口的 invoke方法来进行调用。

动态代理的步骤
(1).创建一个实现接口InvocationHandler的类,它必须实现invoke方法
(2).创建被代理的类以及接口
(3).通过Proxy的静态方法
newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h) 创建一个代理
(4).通过代理调用方法

类图

Dynamic Proxy
1)抽象接口
public interface Subject
{
    public void doSomething(String str);
}
2)真实对象
public class RealSubject implements Subject
{
  
    @Override
    public void doSomething(String str)
    {
        // TODO Auto-generated method stub
        System.out.println("do something: " + str);
    }
}
      
3)动态代理Handler类
public class MyInvocationHandler implements InvocationHandler
{
    //被代理的对象
    private Object target=null;
   
    public MyInvocationHandler(Object obj)
    {
        this.target=obj;
    }
   
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
    {
        // TODO Auto-generated method stub
        return method.invoke(target, args);
    }
}
  
  
4)场景类Client
public class Client
{
    public static void main(String[] args)
    {
        //real object
        Subject subject=new RealSubject();
        //handler
        InvocationHandler handler=new MyInvocationHandler(subject);
        //获得代理proxy
        Subject proxy=(Subject) Proxy.newProxyInstance(subject.getClass().getClassLoader(), subject.getClass().getInterfaces(), handler);
   
        System.out.println(proxy.getClass().getName());
        proxy.doSomething("yes");
 }
}
 
posted @ 2017-08-29 15:00  HE_PX  阅读(170)  评论(0编辑  收藏  举报