Java中的反射(通过反射获取类的结构、invoke方法、获取注解)

2023-05-09 16:41:05 浏览数 (2)

1. 创建运行时类的对象

创建运行时类的对象是反射机制应用最多的地方。创建运行时类的对象有两种方式:

方式1:直接调用Class对象的newInstance()方法

要求: 1)类必须有一个无参数的构造器。 2)类的构造器的访问权限需要足够。

方式一的步骤

1)获取该类型的Class对象 2)调用Class对象的newInstance()方法创建对象

方式2:通过获取构造器对象来进行实例化

方式二的步骤

1)通过Class类的getDeclaredConstructor(Class ... parameterTypes)取得本类的指定形参类型的构造器 2)向构造器的形参中传递一个对象数组进去,里面包含了构造器中所需的各个参数。 3)通过Constructor实例化对象。

如果构造器的权限修饰符修饰的范围不可见,可以调用setAccessible(true)

示例代码:

代码语言:javascript复制
import org.junit.Test;

import java.lang.reflect.Constructor;

public class TestCreateObject {
    @Test
    public void test1() throws Exception{
//        Person obj = new Person();//编译期间无法创建

        Class<?> clazz = Class.forName("com.example.ext.demo.Person");
        //clazz代表com.example.ext.demo.Person类型
        //clazz.newInstance()创建的就是Person的对象
        Object obj = clazz.newInstance();
        System.out.println(obj);
    }

    @Test
    public void test2()throws Exception{
        Class<?> clazz = Class.forName("com.example.ext.demo.Person");
        //java.lang.InstantiationException: com.example.ext.demo.Person
        //Caused by: java.lang.NoSuchMethodException: com.example.ext.demo.Person.<init>()
        //即说明Person没有无参构造,就没有无参实例初始化方法<init>
        Object stu = clazz.newInstance();
        System.out.println(stu);
    }

    @Test
    public void test3()throws Exception{
        //(1)获取Class对象
        Class<?> clazz = Class.forName("com.example.ext.demo.Person");
        /*
         * 获取Person类型中的有参构造
         * 如果构造器有多个,我们通常是根据形参【类型】列表来获取指定的一个构造器的
         * 例如:public Person(String title, int num)
         */
        //(2)获取构造器对象
        Constructor<?> constructor = clazz.getDeclaredConstructor(String.class,int.class);

        //(3)创建实例对象
        // T newInstance(Object... initargs)  这个Object...是在创建对象时,给有参构造的实参列表
        Object obj = constructor.newInstance("张三", 18);
        System.out.println(obj);
    }
}

2. 获取运行时类的完整结构

可以获取:包、修饰符、类型名、父类(包括泛型父类)、父接口(包括泛型父接口)、成员(属性、构造器、方法)、注解(类上的、方法上的、属性上的)。

2.1 相关API

代码语言:javascript复制
//1.实现的全部接口
public Class<?>[] getInterfaces()   
//确定此对象所表示的类或接口实现的接口。 

//2.所继承的父类
public Class<? Super T> getSuperclass()
//返回表示此 Class 所表示的实体(类、接口、基本类型)的父类的 Class。

//3.全部的构造器
public Constructor<T>[] getConstructors()
//返回此 Class 对象所表示的类的所有public构造方法。
public Constructor<T>[] getDeclaredConstructors()
//返回此 Class 对象表示的类声明的所有构造方法。

//Constructor类中:
//取得修饰符: 
public int getModifiers();
//取得方法名称: 
public String getName();
//取得参数的类型:
public Class<?>[] getParameterTypes();

//4.全部的方法
public Method[] getDeclaredMethods()
//返回此Class对象所表示的类或接口的全部方法
public Method[] getMethods()  
//返回此Class对象所表示的类或接口的public的方法

//Method类中:
public Class<?> getReturnType()
//取得全部的返回值
public Class<?>[] getParameterTypes()
//取得全部的参数
public int getModifiers()
//取得修饰符
public Class<?>[] getExceptionTypes()
//取得异常信息

//5.全部的Field
public Field[] getFields() 
//返回此Class对象所表示的类或接口的public的Field。
public Field[] getDeclaredFields() 
//返回此Class对象所表示的类或接口的全部Field。

//Field方法中:
public int getModifiers()
//以整数形式返回此Field的修饰符
public Class<?> getType()  
//得到Field的属性类型
public String getName()  
//返回Field的名称。

//6. Annotation相关
get Annotation(Class<T> annotationClass) 
getDeclaredAnnotations() 

//7.泛型相关
//获取父类泛型类型:
Type getGenericSuperclass()
//泛型类型:ParameterizedType
//获取实际的泛型类型参数数组:
getActualTypeArguments()

//8.类所在的包
Package getPackage() 

2.2 获取所有的属性及相关细节

代码语言:javascript复制
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

import org.junit.Test;

import com.example.java1.Person;

public class FieldTest {
	
    @Test
    public void test1(){
        
        Class clazz = Person.class;
        //getFields():获取到运行时类本身及其所有的父类中声明为public权限的属性
    //		Field[] fields = clazz.getFields();
    //
    //		for(Field f : fields){
    //			System.out.println(f);
    //		}
        
        //getDeclaredFields():获取当前运行时类中声明的所有属性,包括 private的属性
        Field[] declaredFields = clazz.getDeclaredFields();
        for(Field f : declaredFields){
            System.out.println(f);
        }
    }
	
	//权限修饰符  变量类型  变量名
    @Test
    public void test2(){
        Class clazz = Person.class;
        Field[] declaredFields = clazz.getDeclaredFields();
        for(Field f : declaredFields){
            //1.权限修饰符
            /*
            * 0x是十六进制
            * PUBLIC           = 0x00000001;  1    1
            * PRIVATE          = 0x00000002;  2	10
            * PROTECTED        = 0x00000004;  4	100
            * STATIC           = 0x00000008;  8	1000
            * FINAL            = 0x00000010;  16	10000
            * ...
            *
            * 设计的理念,就是用二进制的某一位是1,来代表一种修饰符,整个二进制中只有一位是1,其余都是0
            *
            * mod = 17          0x00000011
            * if ((mod & PUBLIC) != 0)  说明修饰符中有public
            * if ((mod & FINAL) != 0)   说明修饰符中有final
            */
            int modifier = f.getModifiers();
            System.out.print(Modifier.toString(modifier)   "t");

//            //2.数据类型
            Class type = f.getType();
            System.out.print(type.getName()   "t");
//
//            //3.变量名
            String fName = f.getName();
            System.out.print(fName);
//
            System.out.println();
        }
    }
}

2.3 获取所有的方法及相关细节

代码语言:javascript复制
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

import org.junit.Test;

import com.example.java1.Person;

public class MethodTest {

    @Test
    public void test1() {

        Class clazz = Person.class;
        // getMethods():获取到运行时类本身及其所有的父类中声明为public权限的方法
        // Method[] methods = clazz.getMethods();
        //
        // for(Method m : methods){
        // System.out.println(m);
        // }

        // getDeclaredMethods():获取当前运行时类中声明的所有方法
        Method[] declaredMethods = clazz.getDeclaredMethods();
        for (Method m : declaredMethods) {
            System.out.println(m);
        }
    }

    // 注解信息
    // 权限修饰符 返回值类型 方法名(形参类型1 参数1,形参类型2 参数2,...) throws 异常类型1,...{}
    @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();
        }
    }
}

2.4 获取其他结构(构造器、父类、接口、包、注解等)

代码语言:javascript复制
package com.example.java2;

import com.example.java1.Person;
import org.junit.Test;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

public class OtherTest {

    /*
    	获取当前类中的所有的构造器(包括私有构造器)
     */
    @Test
    public void test1(){
        Class clazz = Person.class;
        Constructor[] cons = clazz.getDeclaredConstructors();
        for(Constructor c :cons){
            System.out.println(c);
        }
    }
    /*
    	获取运行时类的父类
     */
    @Test
    public void test2(){
        Class clazz = Person.class;
        Class superclass = clazz.getSuperclass();
        System.out.println(superclass);//class com.example.java1.Creature
    }
    /*
    	获取运行时类的所在的包
     */
    @Test
    public void test3(){
        Class clazz = Person.class;
        Package pack = clazz.getPackage();
        System.out.println(pack);

    }
    /*
    	获取运行时类的注解
     */
    @Test
    public void test4(){
        Class clazz = Person.class;
        Annotation[] annos = clazz.getAnnotations();
        for (Annotation anno : annos) {
            System.out.println(anno);
        }
    }

    /*
    	获取运行时类所实现的接口
     */
    @Test
    public void test5(){
        Class clazz = Person.class;
        Class[] interfaces = clazz.getInterfaces();
        for (Class anInterface : interfaces) {
            System.out.println(anInterface);
        }
    }
    /*
    	获取运行时类的带泛型的父类
     */
    @Test
    public void test6(){
        Class clazz = Person.class;
        Type genericSuperclass = clazz.getGenericSuperclass();
        System.out.println(genericSuperclass);//com.example.java1.Creature<java.lang.String>
    }
}

2.5 获取泛型父类信息

示例代码获取泛型父类信息:

代码语言:javascript复制
/* Type:
 * (1)Class
 * (2)ParameterizedType   
 * 		例如:Father<String,Integer>
 * 			ArrayList<String>
 * (3)TypeVariable
 * 		例如:T,U,E,K,V
 * (4)WildcardType
 * 		例如:
 * 		ArrayList<?>
 * 		ArrayList<? super 下限>
 * 		ArrayList<? extends 上限>
 * (5)GenericArrayType
 * 		例如:T[]
 * 	
 */
public class TestGeneric {
    public static void main(String[] args) {
        //需求:在运行时,获取Son类型的泛型父类的泛型实参<String,Integer>
        
        //(1)还是先获取Class对象
        Class clazz = Son.class;//四种形式任意一种都可以
        
        //(2)获取泛型父类
        //  Class sc = clazz.getSuperclass();
        //  System.out.println(sc);
        /*
        * getSuperclass()只能得到父类名,无法得到父类的泛型实参列表
        */
        Type type = clazz.getGenericSuperclass();
        
        // Father<String,Integer>属于ParameterizedType
        ParameterizedType pt = (ParameterizedType) type;
        
        //(3)获取泛型父类的泛型实参列表
        Type[] typeArray = pt.getActualTypeArguments();
        for (Type type2 : typeArray) {
            System.out.println(type2);
        }
    }
}
//泛型形参:<T,U>
class Father<T,U>{
	
}
//泛型实参:<String,Integer>
class Son extends Father<String,Integer>{
	
}

2.6 获取内部类或外部类信息

public Class<?>[] getClasses():返回所有公共内部类和内部接口。包括从超类继承的公共类和接口成员以及该类声明的公共类和接口成员。

public Class<?>[] getDeclaredClasses():返回 Class 对象的一个数组,这些对象反映声明为此 Class 对象所表示的类的成员的所有类和接口。包括该类所声明的公共、保护、默认(包)访问及私有类和接口,但不包括继承的类和接口。

public Class<?> getDeclaringClass():如果此 Class 对象所表示的类或接口是一个内部类或内部接口,则返回它的外部类或外部接口,否则返回null。

Class<?> getEnclosingClass() :返回某个内部类的外部类

代码语言:javascript复制
@Test
public void test5(){
    Class<?> clazz = Map.class;
    Class<?>[] inners = clazz.getDeclaredClasses();
    for (Class<?> inner : inners) {
        System.out.println(inner);
    }
    
    Class<?> ec = Map.Entry.class;
    Class<?> outer = ec.getDeclaringClass();
    System.out.println(outer);
}

2.7 总 结

  1. 了解了反射这么多API,其实在实际的操作中,通过反射获取类的信息的操作代码,并不会经常开发,在框架的设计中才会被频繁使用。
  2. 主要要熟悉java.lang.reflect包的作用,反射机制。

3. 调用运行时类的指定结构

3.1 调用指定的属性

在反射机制中,可以直接通过Field类操作类中的属性,通过Field类提供的set()get()方法就可以完成设置和取得属性内容的操作。

(1)获取该类型的Class对象

代码语言:javascript复制
Class clazz = Class.forName("包.类名");

(2)获取属性对象

代码语言:javascript复制
Field field = clazz.getDeclaredField("属性名");

(3)如果属性的权限修饰符不是public,那么需要设置属性可访问

代码语言:javascript复制
field.setAccessible(true);

(4)创建实例对象:如果操作的是非静态属性,需要创建实例对象

代码语言:javascript复制
Object obj = clazz.newInstance(); //有公共的无参构造

Object obj = 构造器对象.newInstance(实参...);//通过特定构造器对象创建实例对象

(4)设置指定对象obj上此Field的属性内容

代码语言:javascript复制
field.set(obj,"属性值");

如果操作静态变量,那么实例对象可以省略,用null表示

(5)取得指定对象obj上此Field的属性内容

代码语言:javascript复制
Object value = field.get(obj);

如果操作静态变量,那么实例对象可以省略,用null表示

示例代码:

代码语言:javascript复制
package com.example.reflect;

public class Student {
    private int id;
    private String name;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Student{"  
                "id="   id  
                ", name='"   name   '''  
                '}';
    }
}
代码语言:javascript复制
package com.example.reflect;

import java.lang.reflect.Field;

public class TestField {
    public static void main(String[] args)throws Exception {
        //1、获取Student的Class对象
        Class clazz = Class.forName("com.example.reflect.Student");

        //2、获取属性对象,例如:id属性
        Field idField = clazz.getDeclaredField("id");

        //3、如果id是私有的等在当前类中不可访问access的,我们需要做如下操作
        idField.setAccessible(true);

        //4、创建实例对象,即,创建Student对象
        Object stu = clazz.newInstance();

        //5、获取属性值
        /*
         * 以前:int 变量= 学生对象.getId()
         * 现在:Object id属性对象.get(学生对象)
         */
        Object value = idField.get(stu);
        System.out.println("id = "  value);

        //6、设置属性值
        /*
         * 以前:学生对象.setId(值)
         * 现在:id属性对象.set(学生对象,值)
         */
        idField.set(stu, 2);

        value = idField.get(stu);
        System.out.println("id = "  value);
    }
}

3.2 调用指定的方法

(1)获取该类型的Class对象

代码语言:javascript复制
Class clazz = Class.forName("包.类名");

(2)获取方法对象

代码语言:javascript复制
Method method = clazz.getDeclaredMethod("方法名",方法的形参类型列表);

(3)创建实例对象

代码语言:javascript复制
Object obj = clazz.newInstance();

(4)调用方法

代码语言:javascript复制
Object result = method.invoke(obj, 方法的实参值列表);

如果方法的权限修饰符修饰的范围不可见,也可以调用setAccessible(true) 如果方法是静态方法,实例对象也可以省略(写上也没问题),用null代替

示例代码:

代码语言:javascript复制
package com.example.reflect;

import org.junit.Test;

import java.lang.reflect.Method;

public class TestMethod {
    @Test
    public void test()throws Exception {
        // 1、获取Student的Class对象
        Class<?> clazz = Class.forName("com.example.reflect.Student");

        //2、获取方法对象
        /*
         * 在一个类中,唯一定位到一个方法,需要:(1)方法名(2)形参列表,因为方法可能重载
         *
         * 例如:void setName(String name)
         */
        Method setNameMethod = clazz.getDeclaredMethod("setName", String.class);

        //3、创建实例对象
        Object stu = clazz.newInstance();

        //4、调用方法
        /*
         * 以前:学生对象.setName(值)
         * 现在:方法对象.invoke(学生对象,值)
         */
        Object setNameMethodReturnValue = setNameMethod.invoke(stu, "张三");

        System.out.println("stu = "   stu);
        //setName方法返回值类型void,没有返回值,所以setNameMethodReturnValue为null
        System.out.println("setNameMethodReturnValue = "   setNameMethodReturnValue);

        Method getNameMethod = clazz.getDeclaredMethod("getName");
        Object getNameMethodReturnValue = getNameMethod.invoke(stu);
        //getName方法返回值类型String,有返回值,getNameMethod.invoke的返回值就是getName方法的返回值
        System.out.println("getNameMethodReturnValue = "   getNameMethodReturnValue);//张三
    }

    @Test
    public void test02()throws Exception{
        Class<?> clazz = Class.forName("com.example.ext.demo.Student");
        Method printInfoMethod = clazz.getMethod("printInfo", String.class);
        //printInfo方法是静态方法
        printInfoMethod.invoke(null,"北大");
    }
}

3.3 关于setAccessible方法的使用

  • MethodFieldConstructor对象都有setAccessible()方法。
  • setAccessible启动和禁用访问安全检查的开关。
  • 参数值为true则指示反射的对象在使用时应该取消Java语言访问检查。
    • 提高反射的效率。如果代码中必须用反射,而该句代码需要频繁的被调用,那么请设置为true
    • 使得原本无法访问的私有成员也可以访问
  • 参数值为false则指示反射的对象应该实施Java语言访问检查。

4. 读取注解信息

一个完整的注解应该包含三个部分: (1):声明 (2):使用 (3):读取

4.1 声明自定义注解

代码语言:javascript复制
package com.example.annotation;

import java.lang.annotation.*;

@Inherited
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Table {
    String value();
}
代码语言:javascript复制
package com.example.annotation;

import java.lang.annotation.*;

@Inherited
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Column {
    String columnName();
    String columnType();
}
  • 自定义注解可以通过四个元注解@Retention@Target@Inherited@Documented,分别说明它的声明周期,使用位置,是否被继承,是否被生成到API文档中。
  • Annotation 的成员在 Annotation 定义中以无参数有返回值的抽象方法的形式来声明,我们又称为配置参数。返回值类型只能是八种基本数据类型、String类型Class类型enum类型Annotation类型、以上所有类型的数组
  • 可以使用 default 关键字为抽象方法指定默认返回值
  • 如果定义的注解含有抽象方法,那么使用时必须指定返回值,除非它有默认值。格式是“方法名 = 返回值”,如果只有一个抽象方法需要赋值,且方法名为value,可以省略“value=”,所以如果注解只有一个抽象方法成员,建议使用方法名value。

4.2 使用自定义注解

代码语言:javascript复制
package com.example.annotation;

@Table("t_stu")
public class Student {
    @Column(columnName = "sid",columnType = "int")
    private int id;
    @Column(columnName = "sname",columnType = "varchar(20)")
    private String name;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Student{"  
                "id="   id  
                ", name='"   name   '''  
                '}';
    }
}

4.3 读取和处理自定义注解

自定义注解必须配上注解的信息处理流程才有意义。

我们自己定义的注解,只能使用反射的代码读取。所以自定义注解的声明周期必须是RetentionPolicy.RUNTIME

代码语言:javascript复制
package com.example.annotation;

import java.lang.reflect.Field;

public class TestAnnotation {
    public static void main(String[] args) {
        Class studentClass = Student.class;
        Table tableAnnotation = (Table) studentClass.getAnnotation(Table.class);
        String tableName = "";
        if(tableAnnotation != null){
            tableName = tableAnnotation.value();
        }

        Field[] declaredFields = studentClass.getDeclaredFields();
        String[] columns = new String[declaredFields.length];
        int index = 0;
        for (Field declaredField : declaredFields) {
            Column column = declaredField.getAnnotation(Column.class);
            if(column!= null) {
                columns[index  ] = column.columnName();
            }
        }
        
        String sql = "select ";
        for (int i=0; i<index; i  ) {
            sql  = columns[i];
            if(i<index-1){
                sql  = ",";
            }
        }
        sql  = " from "   tableName;
        System.out.println("sql = "   sql);
    }
}

5. 体会反射的动态性

体会1:

代码语言:javascript复制
public class ReflectionTest {

    //体会反射的动态性:动态的创建给定字符串对应的类的对象
    public <T> T getInstance(String className) throws Exception {

        Class clazz = Class.forName(className);

        Constructor constructor = clazz.getDeclaredConstructor();
        constructor.setAccessible(true);
        return (T) constructor.newInstance();

    }

    @Test
    public void test1() throws Exception {
        String className = "com.example.java1.Person";
        Person p1 = getInstance(className);
        System.out.println(p1);
    }
}

体会2:

代码语言:javascript复制
public class ReflectionTest {
    //体会反射的动态性:动态的创建指定字符串对应类的对象,并调用指定的方法
    public Object  invoke(String className,String methodName) throws Exception {
        Class clazz = Class.forName(className);
        Constructor constructor = clazz.getDeclaredConstructor();
        constructor.setAccessible(true);
        //动态的创建指定字符串对应类的对象
        Object obj = constructor.newInstance();

        Method method = clazz.getDeclaredMethod(methodName);
        method.setAccessible(true);
        return method.invoke(obj);
    }

    @Test
    public void test2() throws Exception {
        String info = (String) invoke("com.example.java1.Person", "show");

        System.out.println("返回值为:"   info);

    }
}

体会3:

代码语言:javascript复制
public class ReflectionTest {
	@Test
    public void test1() throws Exception {
        //1.加载配置文件,并获取指定的fruitName值
        Properties pros = new Properties();
        InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("config.properties");
        pros.load(is);
        String fruitStr = pros.getProperty("fruitName");
        //2.创建指定全类名对应类的实例
        Class clazz = Class.forName(fruitStr);
        Constructor constructor = clazz.getDeclaredConstructor();
        constructor.setAccessible(true);
        Fruit fruit = (Fruit) constructor.newInstance();
        //3. 调用相关方法,进行测试
        Juicer juicer = new Juicer();
        juicer.run(fruit);

    }

}

interface Fruit {
	public void squeeze();
}

class Apple implements Fruit {
	public void squeeze() {
		System.out.println("榨出一杯苹果汁儿");
	}
}

class Orange implements Fruit {
	public void squeeze() {
		System.out.println("榨出一杯桔子汁儿");
	}
}

class Juicer {
	public void run(Fruit f) {
		f.squeeze();
	}
}

其中,配置文件【config.properties】存放在当前Module的src下

0 人点赞