java——反射详解

反射在我们普通程序开发中基本使用不到,但是在我们底层的程序设计中使用特别广泛,例如代理模式、工厂模式等一些设计模式,包括我们使用的开发工具以及各大开源框架底层都使用到了反射的原理。所以掌握了Java的反射机制对我们理解各大开源框架都有很好的帮助。

1.认识反射

反射,从这个“反”字可以看出与我们平时正常的使用逻辑肯定不一样,那么到底什么地方不一样了?想要了解“反”,就得先了解一下“正”的概念。

在正常情况下,如果要使用一个类,必须要经过以下几个步骤:

(1)使用important导入类所在的包(类:java.lang.Class

(2)通过关键字new进行类对象实例化(构造方法:java.lang.reflect.Constructors

(3)产生对象可以使用“对象.属性”进行类中属性的调用(属性:java.lang.reflect.Field)

(4)通过“对象.方法()”调用类中的方法(方法:java.lang.reflect.Method

括号中的红色字体是每个步骤对应反射中使用到的类,如果现在不了解,可以先不用管,后面会一一介绍,这里是为了方便进行比较。

在反射中,使用一个类并不需要导入类的所在包,只要知道类的完整路径就可以知道该类中的所有信息。

反射不需要有明确的类型对象,所有的对象都使用Object表示。可以直接用Object的与反射机制的混合调用类中的方法。

2.获得Class对象

每个类被加载后,系统就会该类生成一个对应的Class对象,通过该Class对象就可以访问到JVM中的这个类。在java程序中获得Class对象通常有如下三种方式。
(1)使用Class类的forName(String clazzName)静态方法。
(2)调用某个类的class属性来获取该类对应的Class对象。
(3)调用某个对象的getClass()方法

2.1class类实例化对象
Class类如果使用forName()方法之后,就可以调用Class类中newInstance()无参构造函数方法进行操作,该方法定义如下:

public T newInstance()
        throws InstantiationException, IllegalAccessException
    {
        if (System.getSecurityManager() != null) {
            checkMemberAccess(Member.PUBLIC, Reflection.getCallerClass(), false);
        }

具体实例如下:

class Student {

    public Student() {
        System.out.println("Student类的构造方法");
    }

    @Override
    public String toString() {
        return "Student类的toString方法";
    }
}
public class ReflectDemo {

    public static void main(String[] args) throws Exception {
        Class<?> cls = Class.forName("reflect.Student");
        // 相当于关键字实例化对象,Object obj = new Student();
        Object obj = cls.newInstance();
        System.out.println(obj);
    }

}

输出结果为:
在这里插入图片描述
通过上面的实例可以看出,调用newInstace()方法时,程序会默认调用Student类的无参构造方法,并且获取到了Student类的实例化对象,可以调用Student类里面的方法属性。

3.利用反射操作构造方法

在Class类中有两个方法可以获取类中的构造方法,分别是:

获取类中所有的构造方法:

public Constructor<?>[] getConstructors() throws SecurityException {
        checkMemberAccess(Member.PUBLIC, Reflection.getCallerClass(), true);
        return copyConstructors(privateGetDeclaredConstructors(true));
    }

获取类中指定的构造方法

public Constructor<T> getConstructor(Class<?>... parameterTypes)
        throws NoSuchMethodException, SecurityException {
        checkMemberAccess(Member.PUBLIC, Reflection.getCallerClass(), true);
        return getConstructor0(parameterTypes, Member.PUBLIC);
    }

这两个方法返回的都是反射包(java.lang.reflect.*)中的Constructor类,该类提供了单个构造方法的信息以及对它的访问权限。

下面以获取String类中的所有构造方法为例,代码如下:

import java.lang.reflect.Constructor;

public class ReflectStringDemo {
    public static void main(String[] args) throws Exception{
        Class<?> cls = Class.forName("java.lang.String");
        //获取所有构造函数
        Constructor<?>[] cons = cls.getConstructors();
        //循环打印
        for (int i = 0; i < cons.length; i++) {
            System.out.println(cons[i]);
        }
    }
}

打印结果:

public java.lang.String(byte[],int,int)
public java.lang.String(byte[],java.nio.charset.Charset)
public java.lang.String(byte[],java.lang.String) throws java.io.UnsupportedEncodingException
public java.lang.String(byte[],int,int,java.nio.charset.Charset)
public java.lang.String(byte[],int,int,java.lang.String) throws java.io.UnsupportedEncodingException
public java.lang.String(java.lang.StringBuilder)
public java.lang.String(java.lang.StringBuffer)
public java.lang.String(byte[])
public java.lang.String(int[],int,int)
public java.lang.String()
public java.lang.String(char[])
public java.lang.String(java.lang.String)
public java.lang.String(char[],int,int)
public java.lang.String(byte[],int)
public java.lang.String(byte[],int,int,int)

上面实例获取了String类中的所有构造方法,包括构造方法中的参数、异常等。

获取所有构造方法看上去并不难,如果想要进行指定构造方法的调用,则必须关注Constructor类,使用newInstance()方法进行实例。

public T newInstance(Object ... initargs)
        throws InstantiationException, IllegalAccessException,
               IllegalArgumentException, InvocationTargetException
    {
        if (!override) {
            if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
                Class<?> caller = Reflection.getCallerClass();
                checkAccess(caller, clazz, null, modifiers);
            }
        }
        if ((clazz.getModifiers() & Modifier.ENUM) != 0)
            throw new IllegalArgumentException("Cannot reflectively create enum objects");
        ConstructorAccessor ca = constructorAccessor;   // read volatile
        if (ca == null) {
            ca = acquireConstructorAccessor();
        }
        @SuppressWarnings("unchecked")
        T inst = (T) ca.newInstance(initargs);
        return inst;
    }

获取指定有参构造方法并且实例化对象实例:

import java.lang.reflect.Constructor;

class Student2 {
    private String name;

    private Integer age;

    public Student2(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "name:" + this.name + ";age:" + this.age;
    }
}


public class ReflectConstructorDemo {
    public static void main(String[] args) throws Exception {
        Class<?> cls = Class.forName("reflect.Student2");
        Constructor<?> con = cls.getConstructor(String.class, Integer.class);
        // 这里就相当于Object obj = new Student2("张三",20);
        Object obj = con.newInstance("张三", 20);
        System.out.println(obj);
    }


}

输出结果:
在这里插入图片描述
通过上面可以看出,如果要实例化一个对象,使用无参构造方法比有参构造方法简单的多,使用无参直接调用newInstance()方法,使用有参则先获取有参构造方法,再通过Constructor中的newInstance()方法,并用指定的初始化参数初始化改实例。很多框架中的底层代码默认都是使用无参构造方法来实例化对象,所以在简单Java类开发中都要明确给出无参构造方法。

通过Class对象可以得到大量的Method、Constructor、Field等对象,这些对象分别代表该类锁包括的方法、构造器、和成员变量等,程序还可以通过这些对象来执行实际的功能,例如调用方法,创建实例。应该查阅API文档开掌握。

4.使用Proxy和InvocationHandler创建动态代理

在Java的java.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口,通过使用这个类和接口可以生成动态代理或动态代理对象。
Proxy提供了两个方法创建动态代理和动态代理实例。看其中一个方法:

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

该方法直接创建一个动态代理对象,该代理对象的实现类实现了interfaces指定的系列接口,执行代理对象的每个方法时都会被替换执行InvocationHandler对象的invoke方法。

实例如下:

interface Person{
    void walk();

    void sayHello(String name);
}

class MyInvokationHandler implements InvocationHandler{

    /**
     * 执行动态代理对象的所有的方法时,都会被替换成执行如下的invoke方法。
     * @param proxy:代表动态代理的对象
     * @param method:代表正在执行的方法
     * @param args:代表调用目标方法时传入的参数
     * @return
     * @throws Throwable
     */

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("----正在执行的方法:" + method);
        if (args != null) {
            System.out.println("下面是执行该方法时传入的实参:");
            for (Object val : args) {
                System.out.println(val);
            }
        }else {
            System.out.println("调用该方法时没有实参");
        }
        return null;
    }
}

public class ProxyTest {
    public static void main(String[] args) {
        //创建一个InvocationHandler对象
        InvocationHandler handler = new MyInvokationHandler();
        //使用指定的InvocationHandler来生成一个动态代理对象
        Person p = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class[]{Person.class}, handler);
        p.walk();
        p.sayHello("jdfd");
    }
}

运行结果如下:
在这里插入图片描述
从运行结果可以看出不管程序执行代理对象的什么方法,实际上都是执行InvocationHandler对象的invoke()方法。

猜你喜欢

转载自blog.csdn.net/weixin_41809206/article/details/89380958