Java 基础之反射

什么是反射

 反射就是借助 Reflection API 在运行期间,获取任何类的内部结构,并可以操作这些内部结构,包括但不限于属性、方法等。
 个人的理解:简单点儿说就是通常情况下,我们得先 new 一个某个类的对象,然后通过对象去设置属性值,调用方法等。而除此方式外,我们还可以通过另一种方式去实现这些操作,即借助某些特定的 API,通过这些 API 去操作属性、方法的过程,就可以简单的理解为反射。
 这些 API 包括:

  • java.lang.Class: 代表一 个类
  • java.lang.reflect.Method: 代表类的方法
  • java.lang.reflect.Field: 代表类的成员变量
  • java.lang.reflect.Constructor: 代表类的构造器

理解 Class 类并获取 Class 的实例

什么是 Class 类

 注意和小写的 class 可不是一个东西,小写的 class 是一个关键字。Class 是一个类,它的一个实例就表示加载到内存中的一个类,也可以说一个 Class 实例就对应着一个 .class 字节码文件。我们要想操作某个类的属性或者方法,必须先获取这个类对应的 Class 类的一个实例,然后通过这个实例才能再去获取类中的内部结构,可以说 Class 类的实例就是反射的源头。

获取 Class 实例的四种方式

① 通过类的 class 属性获取
 已知具体的类,就可以通过该类的 class 属性获取,该方法最为安全可靠,程序性能最高。
Class clazz = String.class;

② 通过 getClass() 方法获取
 此方法的前提是先有一个类的对象,然后调用该对象的 getClass() 方法
Person p1 = new Person();
Class clazz = p1.getClass();

③ 通过 Class 类的静态方法获取
 需要知道一个类的全路径类名
Class clazz = Class.forName(“java.lang.String”);

④ 通过 ClassLoader 获取
ClassLoader cl = this.getClass().getClassLoader();
Class clazz4 = cl.loadClass(“类的全类名”);

Class 类的实例除可以代表一个运行时类外,还可以代表以下结构:

  • class:外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类
  • interface:接口
  • []:数组
  • enum:枚举
  • annotation:注解@interface
  • primitive type:基本数据类型
  • void

类的加载过程

在这里插入图片描述

  • 加载
    将 .class 字节码文件加载到内存中,将静态数据转换成方法区的运行时数据结构,并生成一个代表此类的 Class 对象,这个过程需要类加载器参与。

  • 链接:将 Java 类的二进制代码合并到 JVM 的运行状态之中
    验证:确保加载的类信息符合 JVM 规范,例如:以 cafe 开头,没有安全方面的问题
    准备:为静态变量分配内存(在方法区中)并设置默认值
    解析:虚拟机常量池的符号引用替换为直接引用

  • 初始化
    执行类构造器 () 方法的过程,此过程会为静态变量赋值(显示赋值或在静态代码块中赋值,谁在前面先执行谁)。
    如果在初始化的时候,发现这个类的父类也没有进行初始化,则会先初始化其父类。
    在这里插入图片描述
    何时会发生类的初始化?

  • 类的主动引用(一定会发生类的初始化)
    ① 当虚拟机启动,先初始化main方法所在的类
    ② new 一个类的对象
    ③ 调用类的静态成员(除了final常量)和静态方法
    ④ 使用java.lang.reflect包的方法对类进行反射调用
    ⑤ 当初始化一个类,如果其父类没有被初始化,则先会初始化它的父类

  • 类的被动引用(不会发生类的初始化)
    ① 当访问一个静态域时,只有真正声明这个域的类才会被初始化。也就是说当通过子类引用父类的静态变量,不会导致子类初始化
    ② 通过数组定义类引用,不会触发此类的初始化
    ③ 引用常量不会触发此类的初始化(常量在链接阶段就存入调用类的常量池中了)
    在这里插入图片描述

ClassLoader 的理解

 ClassLoader 见名知意,就是用来加载类的。
 不过一旦某个类被加载到类加载器中,它将会缓存一段时间。

1)ClassLoader 的分类

  • Bootstap ClassLoader:引导类加载器。C++ 编写的,JVM 自带的加载器,用来加载 Java 的核心类库,该加载器无法获取
  • Extension ClassLoader:扩展类加载器。用来加载 jre/lib/ext 目录下的 jar 包中的类
  • System ClassLoader:系统类加载器。用来加载我们自己写的类

2)ClassLoader 的另一个作用
  ClassLoader 还可以用来加载配置文件,且默认配置文件在 module 的 src 目录下。

Properties pros =  new Properties();
// 此时的文件默认在当前的module下。
// 读取配置文件的方式一:
// FileInputStream fis = new  FileInputStream("jdbc.properties");
// FileInputStream fis = new  FileInputStream("src\\jdbc1.properties");
// pros.load(fis);

// 读取配置文件的方式二:使用ClassLoader
// 配置文件默认识别为:当前 module 的 src 下
ClassLoader classLoader =  ClassLoaderTest.class.getClassLoader();
InputStream is =  classLoader.getResourceAsStream("jdbc1.properties");
pros.load(is);

String user = pros.getProperty("user");
String password =  pros.getProperty("password");
System.out.println("user = " + user +  ",password = " + password);

使用反射创建对象

1)调用空参构造器

Class<Person> clazz = Person.class;
Person obj = clazz.newInstance();

2)调用指定的构造器

//1. 根据全类名获取对应的Class 对象
String name = “com.java.Person";
Class clazz = null;
clazz = Class.forName(name);
//2. 调用指定参数结构的构造器,生成Constructor 的实例
Constructor con = clazz.getConstructor(String.class,Integer.class);
//3. 通过Constructor 的实例创建对应类的对象,并初始化类属性
Person p2 = (Person) con.newInstance("Peter",20);
System.out.println(p2);

使用反射获取运行时类的完整结构

我们可以通过反射获取类的 构造器、属性、方法、父类、父类的泛型、接口、异常、注解、所在的包等。

获取构造器

public Constructor<T>[] getConstructors():获取当前运行时类中声明为 public 的构造器
public Constructor<T>[] getDeclaredConstructors():获取当前运行时类中声明的所有构造器

通过反射获取类的构造器之后,还可以通过 Constructor 类获取构造器的修饰符、名称、参数的类型:
获取修饰符:public int getModifiers();
获取构造器的名称:public String getName();
获取参数的类型:public Class<?>[] getParameterTypes();

获取属性

public Field[] getFields():获取当前运行时类及其父类中声明为 public 访问权限的属性
public Field[] getDeclaredFields():获取当前运行时类中声明的所有属性(不包含父类中声明的属性)

得到 Field 之后,还可以获取属性的修饰符、类型、名称:
以整数形式获取修饰符:public int getModifiers()

int modifier  = f.getModifiers();
// 得到整数之后,转换成以字符串呈现
System.out.print(Modifier.toString(modifier) + "\t");

获取属性类型:public Class<?> getType()
获取属性名称:public String getName()

获取方法

public Method[] getMethods():获取当前运行时类及其所父类中声明为public权限的方法
public Method[] getDeclaredMethods():获取当前运行时类中声明的所方法(不包含父类中声明的方法)

/*
@Xxxx
权限修饰符  返回值类型  方法名(参数类型1 形参名1,...) throws XxxException{}
*/
@Test
public void test2(){
    Class clazz = Person.class;
    Method[] declaredMethods = clazz.getDeclaredMethods();
    for(Method m : declaredMethods){
        //1.获取方法声明的注解
        Annotation[] annos = m.getAnnotations();
        for(Annotation a : annos){
            System.out.println(a);
        }

        //2.权限修饰符
        System.out.print(Modifier.toString(m.getModifiers()) + "\t");

        //3.返回值类型
        System.out.print(m.getReturnType().getName() + "\t");

        //4.方法名
        System.out.print(m.getName());
        System.out.print("(");
        
        //5.形参列表
        Class[] parameterTypes = m.getParameterTypes();
        if(!(parameterTypes == null && parameterTypes.length == 0)){
            for(int i = 0;i < parameterTypes.length;i++){
                if(i == parameterTypes.length - 1){
                    System.out.print(parameterTypes[i].getName() + " args_" + i);
                    break;
                }
                System.out.print(parameterTypes[i].getName() + " args_" + i + ",");
            }
        }

        System.out.print(")");

        //6.抛出的异常
        Class[] exceptionTypes = m.getExceptionTypes();
        if(exceptionTypes.length > 0){
            System.out.print("throws ");
            for(int i = 0;i < exceptionTypes.length;i++){
                if(i == exceptionTypes.length - 1){
                    System.out.print(exceptionTypes[i].getName());
                    break;
                }
                System.out.print(exceptionTypes[i].getName() + ",");
            }
        }
        System.out.println();
    }
}

获取父类

public Class<? Super T> getSuperclass()

获取带

Type genericSuperclass = clazz.getGenericSuperclass();

获取父类的泛型

Class clazz = Person.class;

Type genericSuperclass = clazz.getGenericSuperclass();
ParameterizedType paramType = (ParameterizedType) genericSuperclass;

// 获取泛型类型
Type[] actualTypeArguments = paramType.getActualTypeArguments();

// System.out.println(actualTypeArguments[0].getTypeName());
System.out.println(((Class)actualTypeArguments[0]).getName());

获取接口

public Class<?>[] getInterfaces()

获取注解

Annotation[] annotations = clazz.getAnnotations();

获取所在的包

Package getPackage()

使用反射获取运行时类的指定结构

1)调用指定的构造器

Class clazz = Person.class;

//private Person(String name)
/*
    1.获取指定的构造器
    getDeclaredConstructor():参数:指明构造器的参数列表
*/

Constructor constructor =  clazz.getDeclaredConstructor(String.class);

//2.保证此构造器是可访问的(如果构造器是私有的,则必须让构造器是可访问的)
constructor.setAccessible(true);

//3.调用此构造器创建运行时类的对象
Person per = (Person)  constructor.newInstance("Tom");
System.out.println(per);

2)调用指定的属性

Class clazz = Person.class;

// 创建运行时类的对象
Person p = (Person) clazz.newInstance();

// 1. getDeclaredField(String fieldName):获取运行时类中指定变量名的属性
Field name = clazz.getDeclaredField("name");

// 2.保证当前属性是可访问的
name.setAccessible(true);

// 3.设置指定对象的属性值
name.set(p,"Tom");

// 4.获取指定对象的属性值
System.out.println(name.get(p));

3)调用指定的方法

Class clazz = Person.class;

// 创建运行时类的对象
Person p = (Person) clazz.newInstance();

/*
    1.获取指定的某个方法
    getDeclaredMethod(String name, Class<?>... parameterTypes)
    参数1 :指明获取的方法的名称  
    参数2:指明获取的方法的形参列表
*/
Method show = clazz.getDeclaredMethod("show", String.class);
//2.保证当前方法是可访问的
show.setAccessible(true);

/*
    3. 调用方法的invoke(Object obj, Object... args)
    参数1:方法的调用者  
    参数2:给方法形参赋值的实参
    invoke() 的返回值即为对应类中调用的方法的返回值。
*/
Object returnValue = show.invoke(p,"CHN"); //String nation = p.show("CHN");
System.out.println(returnValue);

System.out.println("*************如何调用静态方法*****************");
// private static void showDesc()

Method showDesc = clazz.getDeclaredMethod("showDesc");
showDesc.setAccessible(true);
// 如果调用的运行时类中的方法没返回值,则此invoke()返回null
// Object returnVal = showDesc.invoke(null);
Object returnVal = showDesc.invoke(Person.class);
System.out.println(returnVal); //null

说明:

  • 若原方法无参数,则 invoke() 的第二个参数可以不写(第一个参数必须要写)
  • 若原方法无返回值,则 invoke() 返回 null
  • 若是静态方法,则形参可以写 null 或 此类对应的 Class 的实例

反射的应用:动态代理

 代理模式要求代理类与被代理类都实现共同的接口,这是前提。
 动态代理与静态代理最大的不同就是,我们不需要再创建代理类,而可以通过 jdk 动态地为我们生成代理类的对象。我们只需要提供 接口、被代理类,以及多了一个 InvocationHandler 接口的实现类。
 我们通过 InvocationHandler 中的 invoke() 方法,去调用被代理类的方法,还可以在此方法中进行一些其他的操作。原理就是:jdk 帮我们生成的代理类的字节码文件中,维护了一个 InvocationHandler 类型的引用,所以我们在生成代理类的对象时,要传入一个此接口的实现类对象。而调用代理类中的任何方法,都会转发到 InvocationHandler 类中的 invoke() 方法,所以我们执行代理类的方法时,实际执行的是 InvocationHandler 中的 invoke() 方法。

先定义接口

interface Human {
    String getBelief();
    void eat(String food);
}

创建被代理类

class SuperMan implements Human{

    @Override
    public String getBelief() {
        return "I believe I can fly!";
    }

    @Override
    public void eat(String food) {
        System.out.println("我喜欢吃" + food);
    }
}

创建一个实现了 InvocationHandler 接口的实现类

class MyInvocationHandler implements InvocationHandler{

    private Object obj;

    public void bind(Object obj){
        this.obj = obj;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object invoke = method.invoke(obj, args);
        return invoke;
    }
}

创建一个工厂,用于生成代理对象

class ProxyFactory{
    public static Object createProxy(Object obj){

        MyInvocationHandler myInvocationHandler = new MyInvocationHandler();

        myInvocationHandler.bind(obj);

        return Proxy.newProxyInstance(obj.getClass().getClassLoader(),obj.getClass().getInterfaces(),myInvocationHandler);
    }
}

创建测试类

public class ProxyTest {
    @Test
    public void testProxy(){

        SuperMan superMan = new SuperMan();

        // 生成了一个代理类对象
        Human proxy = (Human) ProxyFactory.createProxy(superMan);

        // 执行代理类中的任何方法时,都会去执行 invoke() 方法
        String belief = proxy.getBelief();
        System.out.println(belief);

        proxy.eat("四川麻辣烫");
    }
}

看完上面的例子,不知道大家有没有疑问,反正我目前有两个疑问:
 1. 调用代理类的方法的时候,怎么就去执行 MyInvocationHandler 类中 invoke 方法了呢?可参考下面的文章:
Java JDK 动态代理(AOP)使用及实现原理分析
 2. invoke(Object proxy, Method method, Object[] args) 方法中的第一个参数 proxy 根本没有用到,那为什么还需要它呢?可参考下面的文章:
InvocationHandler中invoke方法中的第一个参数proxy的用途

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值