Java的反射机制

Java
351
0
0
2023-09-13
标签   Java反射

我们通过前面几篇文章的学习已经了解了类加载机制和类加载器,当一个类被类加载器加载到内存之后,在内存中就会存在一份类的Class实例,这个Class里面包含了类的所有信息。

Class里面的信息即是我们这篇文章要学习的反射。

什么是反射

当类加载器将一个类加载完成后,方法区中就产生了一个Class类型的对象,一个类只有一个Class对象,则个Class对象中包含了完整的类信息,我们可以通过Class对象获取类中的所有信息。这种功能我们就称为”反射“。反射是动态语言的关键。

Java的反射机制

Class类和 java . lang .reflect类库一起对反射的概念进行了支持,java.lang.reflect类库包含了Field、 method 和 constructor 类,这些类的对象是JVM在运行时创建的,用于表示未知类里对应的成员。

Java的反射机制

反射的作用

反射的作用主要体现在运行时对象的灵活处理:

  • 判断一个对象所属的类
  • 获取类的成员变量、方法和 构造器
  • 获取类的注解信息
  • 动态代理

反射好处多多,当然也是有缺陷的,反射的主要缺陷就是在运行时的性能损耗,但是大部分框架(比如spring)都是加了缓存的,我们无需太过担心啦!

反射API用法

我们首先创建一个Person类:里面包含了成员变量,构造器,方法和注解,并且实现了Serializable接口。

 //类注解
@Component
public class Person implements Serializable {
     //成员变量
        public String name;

        protected  Integer  age;

         private  String sex;
     //有参构造
        public Person(String name, Integer age, String sex) {
                this.name = name;
                this.age = age;
                this.sex = sex;
        }

        public Person(String name, Integer age) {
                this.name = name;
                this.age = age;
        }
     //无参构造
        public Person() {
                System.out.println("这是一个无参构造器");
        }

        public String getName() {
                return name;
        }

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

        public Integer getAge() {
                return age;
        }

        public void setAge(Integer age) {
                this.age = age;
        }

        public String getSex() {
                return sex;
        }

        public void setSex(String sex) {
                this.sex = sex;
        }
     //方法注解
        @Override
        public String  toString () {
                return "Person{" +
                            "name='" + name + ''' +
                            ", age=" + age +
                            ", sex='" + sex + ''' +
                        '}';
        }
     //私有方法
         @Deprecated
        private void showAge(Integer age) throws RuntimeException {
                System.out.println("My age is " + age);
        }
     //开放方法
        public void showName(String name) {
                System.out.println("My name is " + name);
        }
}

Person类被应用类加载器加载之后,就会在方法区中存在唯一的Class对象,那么接下来我们就通过类库的API了解如果获取类结构信息。

Class类是源数据,我们一切的处理都是在获取Class之后。

获取Class的方式有4种

1)调用运行时类的属性.class。

2)通过运行时类的对象调用getClass方法。

3)调用Class的静态方法forName,指定类的全限定名。

4)获取类的类加载器,调用方法loadClass,指定类的全限定名。

 Class clazz = Person.class;
Class clazz = new Person().getClass();
Class clazz = Class.forName("com.study.test.code.girl.base.reflect.Person");
Class clazz = ReflectionTest1.class.getClassLoader().loadClass("com.study.test.code.girl.base.reflect.Person");

System.out.println(clazz);
System.out.println(clazz);
System.out.println(clazz);
System.out.println(clazz);

上述获取Class的方法得到的都是Person对象。但是除了class之外,还有其他的类型存在Class信息:

 //类
Class<Thread> threadClass = Thread.class;
//接口
Class<Iterable> iterableClass = Iterable.class;
//注解
Class<Override> overrideClass = Override.class;
//枚举
Class<EnumTest> enumTestClass = EnumTest.class;
//数组
Class<int[]> aClass = int[].class;
//基本数据类型
Class<Integer> integerClass = int.class;
//void
Class<Void> voidClass = void.class;

获取到Class信息之后,我们就可以使用API操作Class对象啦。

newInstance():创建对应的运行时类对象,内部调用运行时类的无参构造器。

如果找不到无参构造器将抛出异常NoSuchMethodException,如果无参构造器无法访问(比如权限为私有)则抛出异常IllegalAccessException。

所以如果使用newInstance()创建类对象,类必须包含无参构造器,且满足调用权限(一般为public)。

    Class<Person> clazz = Person.class;
   Person person = clazz.newInstance();
   System.out.println(person);

运行结果:

 这是一个无参构造器
Person{name='null', age=null, sex='null'}

getPackage():获取类所在的全限定包路径。

getAnnotations():获取类的注解信息,在切面开发中使用比较多。

  Class<Person> clazz = Person.class;
/**
 * 获取所在包
 */System.out.println(clazz.getPackage());

/**
 * 类注解
 */Annotation[] annotations = clazz.getAnnotations();
Arrays.stream(annotations).forEach(System.out::println);

运行结果:

 package com.study.test.code.girl.base.reflect
@org.springframework.stereotype.Component(value=)

getFields():获取当前运行时类及其所有父类中声明为public权限的属性信息。

getDeclaredFields():获取当前运行类当中声明的所有属性(不限制权限),不包含父类。

getDeclaredField(fileldName):获取指定的属性信息,通过set()或者get()获取或者设置属性值。如果属性权限为private时需要setAccessible(true)再对属性赋值。

我们还可以根据拿到的Field获取属性的名称、权限描述符、数据类型等信息。

 Class<Person> clazz = Person.class;
/**
 * 获取运行类属性
 */System.out.println("********************限制权限得到的结果******************************");
Field[] fields = clazz.getFields();
Arrays.stream(fields).forEach(field ->System.out.println(field));
System.out.println("********************不限制权限得到的结果******************************");
Field[] declaredFields = clazz.getDeclaredFields();
Arrays.stream(declaredFields).forEach(field ->
        {
                System.out.print(field);
                System.out.print(" 权限修饰符为:" + Modifier.toString(field.getModifiers()));
                System.out.print(" 数据类型为:" + field.getType());
                System.out.println(" 属性名称为:" + field.getName());
        }
);
//设置属性信息
Field age = clazz.getDeclaredField("age");
age.setAccessible(true);
age.set(person,);
age.setAccessible(false);
System.out.println(person.toString());

运行结果:

 ********************限制权限得到的结果******************************
public java.lang.String com.study.test.code.girl.base.reflect.Person.name
********************不限制权限得到的结果******************************
public java.lang.String com.study.test.code.girl.base.reflect.Person.name 权限修饰符为:public 数据类型为:class java.lang.String 属性名称为:name
private java.lang.Integer com.study.test.code.girl.base.reflect.Person.age 权限修饰符为:private 数据类型为:class java.lang.Integer 属性名称为:age
private java.lang.String com.study.test.code.girl.base.reflect.Person.sex 权限修饰符为:private 数据类型为:class java.lang.String 属性名称为:sex
Person{name='null', age=, sex='null'}

getMethods():获取当前运行时类及其所有父类中声明为public权限的方法。

getDeclaredMethods():获取当前运行类当中声明的所有方法(不限制权限),不包含父类。

getDeclaredMethod(methodName,args):获取指定的方法,并指定参数列表,因为你可能重写了方法。invoke()即调用这个方法。

我们可以根据获取的Method获取方法的名称、权限修饰符、方法参数数组、方法注解数组、返回值类型以及方法抛出的异常信息等。

 Class<Person> clazz = Person.class;
/**
 * 获取运行类方法
 */System.out.println("********************权限为public的方法******************************");
Method[] methods = clazz.getMethods();
Arrays.stream(methods).forEach(method -> System.out.println(method));
System.out.println("********************所有方法******************************");
Method[] declaredMethods = clazz.getDeclaredMethods();
Arrays.stream(declaredMethods).forEach(method -> {
        System.out.print(method);
        System.out.print(" 方法名:" + method.getName());
        System.out.print(" 权限修饰符:" + Modifier.toString(method.getModifiers()));
        System.out.print(" 方法参数:" + Arrays.stream(method.getParameters()).findFirst());
        System.out.print(" 方法注解:" +  Arrays.stream(method.getAnnotations()).findFirst());
        System.out.print(" 返回值类型:" + method.getReturnType());
        System.out.println(" 异常信息:" + Arrays.stream(method.getExceptionTypes()).findFirst());

});
//调用方法
Method showAge = clazz.getDeclaredMethod("showAge", Integer.class);
showAge.setAccessible(true);
showAge.invoke(person, 18);

运行结果:(方法结果比较多,抽样展示)

 *******************权限为public的方法******************************
public java.lang.String com.study.test.code.girl.base.reflect.Person.toString()
public java.lang.String com.study.test.code.girl.base.reflect.Person.getName()
public void com.study.test.code.girl.base.reflect.Person.setName(java.lang.String)
public java.lang.Integer com.study.test.code.girl.base.reflect.Person.getAge()
public void com.study.test.code.girl.base.reflect.Person.setAge(java.lang.Integer)
public java.lang.String com.study.test.code.girl.base.reflect.Person.getSex()
public void com.study.test.code.girl.base.reflect.Person.showName(java.lang.String)
public void com.study.test.code.girl.base.reflect.Person.setSex(java.lang.String)
public final void java.lang. Object .wait() throws java.lang.InterruptedException
public final void java.lang.Object.wait(long,intthrows java.lang.InterruptedException
public final native void java.lang.Object.wait(longthrows java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
public native int java.lang.Object.hashCode()
public final native java.lang.Class java.lang.Object.getClass()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()
********************所有方法******************************
private void com.study.test.code.girl.base.reflect.Person.showAge(java.lang.Integer) throws java.lang.RuntimeException 方法名:showAge 权限修饰符:private 方法参数:Optional[java.lang.Integer age] 方法注解:Optional[@java.lang.Deprecated()] 返回值类型:void 异常信息:Optional[class java.lang.RuntimeException]
 My age is 

getConstructors():获取当前运行时类及其所有父类中声明为public权限的构造器。

getDeclaredConstructors():获取当前运行类当中声明的所有构造器,不包含父类。

我们可以根据Constructor获取构造器的名称、权限修饰符、参数数组、注解、异常信息等。

 Class<ArrayList> clazz = ArrayList.class;
/**
 * 构造器
 */Constructor[] constructors = clazz.getConstructors();
Arrays.stream(constructors).forEach(constructor -> System.out.println(constructor));

Constructor[] declaredConstructors = clazz.getDeclaredConstructors();
Arrays.stream(declaredConstructors).forEach(constructor -> {
        System.out.print(constructor);
        System.out.print(" 名称:" + constructor.getName());
        System.out.print(" 权限修饰符:" + Modifier.toString(constructor.getModifiers()));
        System.out.print(" 参数:" + Arrays.stream(constructor.getTypeParameters()).findFirst());
        System.out.print(" 注解:" +  Arrays.stream(constructor.getAnnotations()).findFirst());
        System.out.println(" 异常信息:" + Arrays.stream(constructor.getExceptionTypes()).findFirst());
});
//获取指定构造器
 Constructor constructor = clazz.getConstructor(String.class, Integer.class);     
 Person person = (Person) constructor.newInstance("JavaCodeGirl"18);
 System.out.println(person.toString());

运行结果:

 ********************权限为public的构造器******************************
public com.study.test.code.girl.base.reflect.Person()
public com.study.test.code.girl.base.reflect.Person(java.lang.String,java.lang.Integer)
public com.study.test.code.girl.base.reflect.Person(java.lang.String,java.lang.Integer,java.lang.String)
********************所有构造器******************************
public com.study.test.code.girl.base.reflect.Person() 名称:com.study.test.code.girl.base.reflect.Person 权限修饰符:public 参数:Optional.empty 注解:Optional.empty 异常信息:Optional.empty
public com.study.test.code.girl.base.reflect.Person(java.lang.String,java.lang.Integer) 名称:com.study.test.code.girl.base.reflect.Person 权限修饰符:public 参数:Optional[java.lang.String name] 注解:Optional.empty 异常信息:Optional.empty
public com.study.test.code.girl.base.reflect.Person(java.lang.String,java.lang.Integer,java.lang.String) 名称:com.study.test.code.girl.base.reflect.Person 权限修饰符:public 参数:Optional[java.lang.String name] 注解:Optional.empty 异常信息:Optional.empty
Person{name='JavaCodeGirl', age=, sex='null'}

以ArrayList为例我们可以获取运行时类的父类信息。

getSuperclass():获取当前运行时类的父类。

getGenericSuperclass():获取当前运行时类的泛型父类。

 Class<ArrayList> clazz = ArrayList.class;
/**
  * 获取父类信息
  */ System.out.println("*******************获取所有父类信息******************************");
 Class<? super ArrayList> superclass = clazz.getSuperclass();
 System.out.println(superclass);
 System.out.println("*******************获取泛型的父类信息******************************");
 Type genericSuperclass = clazz.getGenericSuperclass();
 System.out.println(genericSuperclass);

运行结果:

 *******************获取所有父类信息******************************
class java.util.AbstractList
*******************获取泛型的父类信息******************************
java.util.AbstractList<E>

getInterfaces():取当前运行时类实现的接口。

getGenericInterfaces():当前运行时类实现的泛型接口。

 Class<ArrayList> clazz = ArrayList.class;
/**
 * 获取接口信息
 */System.out.println("*******************获取所有接口信息******************************");
Class<?>[] interfaces = clazz.getInterfaces();
Arrays.stream(interfaces).forEach(System.out::println);
System.out.println("*******************获泛型接口信息******************************");
Type[] genericInterfaces = clazz.getGenericInterfaces();
Arrays.stream(genericInterfaces).forEach(System.out::println);

运行结果:

 *******************获取所有接口信息******************************
interface java.util.List
interface java.util.RandomAccess
interface java.lang.Cloneable
interface java.io.Serializable
*******************获泛型接口信息******************************
java.util.List<E>
interface java.util.RandomAccess
interface java.lang.Cloneable
interface java.io.Serializable

学完这些API,我们平时工作中是怎么使用的呢?勾勾给大家描述一个场景,看看你会怎么做。

反射实战

在一个请求中,我们需要针对不同的类型(type)做不同的业务处理,如果你做开发会怎么设计呢?

策略+模板方法,我猜你肯定会这么说。

没错,勾勾也是这么用的,那么此时就有一个问题:你是如何根据type找到对应的策略呢?千万不要用if…else。

勾勾是这么做的:

1)自定义注解;

 @Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface BizDealAnnotation {
        String value();
}

2)策略类添加注解,定义唯一的value值。

 @Slfj
@BizDealAnnotation("stock_sale")
@Component
public class StockSaleHandler extends AbstractHandler {
        
}

3)项目启动时,根据注解扫描得到value值和类信息,将这两个值分别作为key和value存储到map中。

 Map<String, Object> dealBeans = applicationContext.getBeansWithAnnotation(BizDealAnnotation.class);
// 被事务代理,获取目标类上的注解信息
for (Object serviceObject : dealBeans.values()) {
        Class<?> targetClass = AopUtils.getTargetClass(serviceObject);
        //做一些规范性的判断
        if (StandardDealService.class.isAssignableFrom(targetClass) && !Modifier.isAbstract(targetClass.getModifiers())) {
                StandardDealService dealService = (StandardDealService) serviceObject;
                //通过反射获取指定的注解
                BizDealAnnotation annotation = targetClass.getAnnotation(BizDealAnnotation.class);
                if (annotation != null) {
                        String value = annotation.value();
                        //map存值
                        StandardDealServiceRegister.addDealBean(value, dealService);
                }
        }
}

4)使用时通过type找到map中对应的策略类。

总结

反射即是根据Class获取类的结构信息,我们可以使用Constructor创建新的对象,使用get()和set()方法读取和修改和Field对象关联的字段信息,用invoke方法调用与Method对象关联的方法。

当通过反射与未知对象通信时,JVM必须已经加载了这个类的Class对象。

反射的API比较简单,大家学完可以发现它的规律,在开发中使用最多是注解,大家重点掌握即可。

我是勾勾,一直在努力的程序媛,感谢您的点赞、转发和关注。

我们下篇文章见!