跳至主要內容

反射注解

晨光-向大约 10 分钟JavaSpringJavaSpring

反射&注解

1. 反射 reflect重点

1. 反射概述

  • 框架的灵魂
  • 将类的各个组成部分封装为其他对象,这就是反射机制

2. 获取Class类对象的方式

1. 三种方式 3个阶段: 源码,Class,runtime阶段

  1. Class.forName(“全限定类型”); [未加载进内存]

    将字节码文件加载进内存,返回Class对象

    • 应用: 多用于配置文件,将类名定义在配置文件中,读取文件,加载类
  2. 类名.class [加载进内存]

    通过类名的属性class获取

    • 应用: 多用于参数的传递
  3. 对象.getClass()

    getClass()方法在Object类中定义着

    • 对用于对象的获取字节码的方式

2. 结论

  • 同一个字节码文件(*.class)在一次运行过程中,只会被加载一次,不论哪种方式加载进内存,都是同一个.
public static void main(String[] args) throws Exception {
    // 1. Class.forName
    Class<?> cls1 = Class.forName("com.chggx.domain.Person");
    System.out.println(cls1);
    // class com.chggx.domain.Person

    // 2. 类名.class
    Class<Person> cls2 = Person.class;
    System.out.println(cls2);

    // 3. 对象.getClass()
    Person person = new Person();
    Class<? extends Person> cls3 = person.getClass();
    System.out.println(cls3);

    // 4. 比较三个对象
    // ==: 基本类型比较值,引用类型比较地址值
    System.out.println(cls1 == cls2); // true
    System.out.println(cls1 == cls3); // true

}

3. Class对象

1. 获取功能

1. 获取成员变量
  1. field:
    • ① 设置值 set()
    • ②获取值 get()
  2. 忽略安全修饰符,暴力反射 ddd.setAccessible(true);
Field ddd = personClass.getDeclaredField("ddd");
// get() [java.lang.IllegalAccessException]
// 不为public修饰时,忽略访问权限修饰符的安全检查
// 暴力反射
ddd.setAccessible(true);
Object value1 = ddd.get(person);
System.out.println(value1);
修饰符和类型方法描述
FieldgetField(String name)获取所有public修饰的成员变量
Field[]getFields()获取指定名称的 public修饰的成员变量
FieldgetDeclaredField(String name)获取所有的成员变量,不考虑修饰符
Field[]getDeclaredFields()获取指定的成员变量,不考虑修饰符
  1. 代码
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {

    // 0. 获取Person的Class对象
    Class<Person> personClass = Person.class;

    /*
    1. 获取成员变量们
        * Field[] getFields() :获取所有public修饰的成员变量
        * Field getField(String name)   获取指定名称的 public修饰的成员变量
        * Field[] getDeclaredFields()  获取所有的成员变量,不考虑修饰符
        * Field getDeclaredField(String name)
     */
    // 1.1 Field[] getFields() :获取所有public修饰的成员变量
    Field[] fields = personClass.getFields();
    Arrays.asList(fields).forEach(System.out::println);

    System.out.println("----------");
    // 1.2 Field getField(String name)   获取指定名称的 public修饰的成员变量
    Field aaa = personClass.getField("aaa");
    // 操作获取的成员变量 aaa
    // get(): 获取
    Person person = new Person();
    Object value = aaa.get(person);
    System.out.println(value);
    // set(): 设置
    aaa.set(person,"张三");
    System.out.println(person);

    System.out.println("=============");
    // 1.3 Field[] getDeclaredFields()  获取所有的成员变量,不考虑修饰符
    Field[] declaredFields = personClass.getDeclaredFields();
    Arrays.asList(declaredFields).forEach(System.out::println);
    // 1.4 Field getDeclaredField(String name)
    Field ddd = personClass.getDeclaredField("ddd");
    // get() [java.lang.IllegalAccessException]
    // 不为public修饰时,忽略访问权限修饰符的安全检查
    // 暴力反射
    ddd.setAccessible(true);
    Object value1 = ddd.get(person);
    System.out.println(value1);

}
2. 获取构造方法
修饰符和类型方法描述
ConstructorgetConstructor(Class... parameterTypes)
Constructor[]getConstructors()
Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)
Constructor[]getDeclaredConstructors()
  1. Constructor创建对象

    • T newInstance(Object... initargs)
  2. 代码

public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, InstantiationException {

    // 0. 获取Person的Class对象
    Class<Person> personClass = Person.class;

    /*
    2. 获取构造方法们
            * Constructor<?>[] getConstructors()
            * Constructor<T> getConstructor(Class<?>... parameterTypes)
            * Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)
            * Constructor<?>[] getDeclaredConstructors()
     */
   // 1. Constructor<T> getConstructor(Class<?>... parameterTypes)
    Constructor<Person> constructor = personClass.getConstructor(String.class, int.class);
    System.out.println(constructor);

    // 创建对象 T  newInstance(Object... initargs)
    Person person = constructor.newInstance("张三", 23);
    System.out.println(person);

}
3. 获取成员方法
修饰符和类型方法
MethodgetDeclaredMethod(String name, Class... parameterTypes)
Method[]getDeclaredMethods()
MethodgetEnclosingMethod()
MethodgetMethod(String name, Class... parameterTypes)
Method[]getMethods()
  1. 执行方法对象
    • Object invoke(Object obj, Object... args)
  2. 获取方法名---
    • getName();
4. 获取类型
饰符和类型方法
StringgetName()
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {

    // 0. 获取Person的Class对象
    Class<Person> personClass = Person.class;

    /*
    3. 获取成员方法们:
        * Method[] getMethods()
        * Method getMethod(String name, 类<?>... parameterTypes)
        * Method[] getDeclaredMethods()
        * Method getDeclaredMethod(String name, 类<?>... parameterTypes)
     */
    // 1.1
    Method method = personClass.getMethod("eat");
    Person person = new Person();
    // 执行方法: Object    invoke(Object obj, Object... args)
    method.invoke(person);

    // 1.2
    Method method2 = personClass.getMethod("eat", String.class);
    // 执行方法: Object    invoke(Object obj, Object... args)
    method2.invoke(person,"吃饭");

    System.out.println("-----------------------------");

    // 2.1
    Method[] methods = personClass.getMethods();
    // 所有的方法
    Arrays.asList(methods).forEach(method1 -> {
        System.out.println(method);
        // 获取方法名 getName();
        System.out.println(method.getName());
    });
    // 支持暴力反射

    System.out.println("-----------------------------");

    // 3 获取全限定类名 String getName()
    String className = personClass.getName();
    System.out.println(className);

}

4. 案例

  • 需求: 写一个""框架",可以帮我们创建任意类的对象,并执行其中的任意方法

1. 代码 [配置文件形式]

  • Person
public class Person {

    public void eat(){
        System.out.println("eat...");
    }

    public void eat(String food){
        System.out.println(food);
    }
}
  • Student
public class Student {

    public void sleep(){
        System.out.println("sleep...");
    }

}
  • pro.properties
#className=com.chggx.domain.Person
#methodName=eat
className=com.chggx.domain.Student
methodName=sleep
  • ReflectTest
public class ReflectTest {

    public static void main(String[] args) throws Exception {
        // 可以创建任意对象,可以执行任意方法
//        Person person = new Person();
//        person.eat();

        // 反射
        // 1. 加载配置文件
        // 1.1 创建Properties
        Properties properties = new Properties();
        // 1.2 加载配置文件,转换为一个集合
        // 1.2.1 获取class目录下的配置文件
        ClassLoader classLoader = ReflectTest.class.getClassLoader();
        InputStream is = classLoader.getResourceAsStream("pro.properties");
        // 1.3 加载
        properties.load(is);

        // 2. 获取配置文件中定义的数据
        String className = properties.getProperty("className");
        String methodName = properties.getProperty("methodName");

        // 3. 加载该类进内存
        Class<?> cls = Class.forName(className);

        // 4. 创建对象
        Object obj = cls.newInstance();

        // 5. 获取方法对象
        Method method = cls.getMethod(methodName);

        // 6. 执行方法
        method.invoke(obj);
    }

}

2. 注解 annotation

1. 概述

* 概念: 说明程序的。给计算机看的
* 注释: 用文字描述程序的。给程序员看的

* 定义: 注解(Annotation),也叫元数据。一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。
* 概念描述:
	* JDK1.5之后的新特性
	* 说明程序的
	* 使用注解:@注解名称
* 作用分类:
	①编写文档: 通过代码里标识的注解生成文档【生成文档doc文档】
	②代码分析: 通过代码里标识的注解对代码进行分析【使用反射】
	③编译检查: 通过代码里标识的注解让编译器能够实现基本的编译检查【Override】

2. JDK中预定义的一些注解

@Override: 检测被该注解标注的方法是否是继承自父类(接口)的
@Deprecated: 该注解标注的内容,表示已过时
@SuppressWarnings: 压制警告
	* 一般传递参数all  @SuppressWarnings("all")
  • 代码
@SuppressWarnings("all")
public class AnnotationDemo01 {

    @Override
    public String toString() {
        return super.toString();
    }

    /**
     * @Deprecated: 标注已过时
     */
    @Deprecated
    public void show1(){
        // 有缺陷
    }

    /**
     * 压制警告 "all"
     */
    @SuppressWarnings("all")
    public void show2(){
        // 取代show1()
    }

    public void demo(){
        //
        show1();
    }

}

3. 自定义注解

1. 格式

// 元注解
public @interface 注解名称{
    属性列表;
}

2. 本质

本质:注解本质上就是一个接口,该接口默认继承Annotation接口
public interface MyAnnotation extends java.lang.annotation.Annotation {}

3. 属性:接口中的抽象方法

要求:
1. 属性的返回值类型有下列取值
   	 * 基本数据类型
     * String
     * 枚举
     * 注解
     * 以上类型的数组

 2. 定义了属性,在使用时需要给属性赋值
    1. 如果定义属性时,使用default关键字给属性默认初始化值,则使用注解时,可以不进行属性的赋值。
    2. 如果只有一个属性需要赋值,并且属性的名称是value,则value可以省略,直接定义值即可。
    3. 数组赋值时,值使用{}包裹。如果数组中只有一个值,则{}可以省略
1. 案例一
  • 代码
public @interface MyAnnotation {

    // 基本类型
    int anno1();

    // String类型
    String anno2();

    // 枚举
    Person per();

    // 注解
    MyAnnotation2 annotation();
    
    // 数组
    String[] str();

}
  • 赋值
 @MyAnnotation(anno1 = 1, anno2 = "2", per = Person.P1, annotation = @MyAnnotation2,str = {"aaa","bbb"})
public void show2() {
    // 取代show1()
}
2. 案例一
public @interface MyAnnotation3 {

   // 基本类型 
   int value();

    // String类型 默认default数据
    String anno2() default "2";

    // 枚举
    Person per();

    // 注解
    MyAnnotation2 annotation();

}

4. 元注解:用于描述注解的注解

1. @Target: 描述注解能够作用的位置
    * ElementType取值:
        * TYPE: 可以作用于类上
        * METHOD: 可以作用于方法上
        * FIELD: 可以作用于成员变量上
2. @Retention:描述注解被保留的阶段
    * RetentionPolicy取值:
        * SOURCE: 源码
        * CLASS: class
        * RUNTIME: 运行时
    * @Retention(RetentionPolicy.RUNTIME): 当前被描述的注解,会保留到class字节码文件中,并被JVM读取到
3. @Documented: 描述注解是否被抽取到api文档中
4. @Inherited: 描述注解是否被子类继承
1. @Target
/**
 * @Author: CHGGX
 * @Date: 2020/04/28 18:06
 * @Description: <h1> 元注解: 用于描述注解的注解 </h1>
 * 1. @Target:描述注解能够作用的位置
 *     * ElementType取值:
 *         * TYPE:可以作用于类上
 *         * METHOD:可以作用于方法上
 *         * FIELD:可以作用于成员变量上
 * 2. @Retention:描述注解被保留的阶段
 *     * @Retention(RetentionPolicy.RUNTIME):当前被描述的注解,会保留到class字节码文件中,并被JVM读取到
 * 3. @Documented:描述注解是否被抽取到api文档中
 * 4. @Inherited:描述注解是否被子类继承
 */
@Target(value = {ElementType.TYPE,ElementType.METHOD,ElementType.FIELD})
public @interface MyAnnotation4 {

}
@MyAnnotation4
public class Worker {

    @MyAnnotation4
    private String name;

    @MyAnnotation4
    public void eat(){

    }

}
  • 类,属性,方法都可使用
2. @Retention
/**
 * @Author: CHGGX
 * @Date: 2020/04/28 18:06
 * @Description: <h1> 元注解: 用于描述注解的注解 </h1>
 * 1. @Target:描述注解能够作用的位置
 *     * ElementType取值:
 *         * TYPE:可以作用于类上
 *         * METHOD:可以作用于方法上
 *         * FIELD:可以作用于成员变量上
 * 2. @Retention:描述注解被保留的阶段
 *     * RetentionPolicy取值:
 *         * SOURCE: 源码
 *         * CLASS: class
 *         * RUNTIME: 运行时
 *     * @Retention(RetentionPolicy.RUNTIME):当前被描述的注解,会保留到class字节码文件中,并被JVM读取到
 * 3. @Documented:描述注解是否被抽取到api文档中
 * 4. @Inherited:描述注解是否被子类继承
 */
@Target(value = {ElementType.TYPE,ElementType.METHOD,ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface MyAnnotation4 {

}

4. 在程序中是用(解析)注解

  1. 获取注解中定义的属性值
	1. 获取注解定义的位置的对象  (ClassMethod,Field2. 获取指定的注解
		* getAnnotation(Class)
		//其实就是在内存中生成了一个该注解接口的子类实现对象

	            public class ProImpl implements Pro{
	                public String className(){
	                    return "cn.itcast.annotation.Demo1";
	                }
	                public String methodName(){
	                    return "show";
	                }
	            }
	3. 调用注解中的抽象方法获取配置的属性值
  1. 代码

    • 注解类: 取代之前配置的pro.properties配置文件
    /**
     * @Author: CHGGX
     * @Date: 2020/04/28 21:11
     * @Description: <h1> 描述需要执行的类名,和方法名 </h1>
     * 作用于类(TYPE),在运行期执行
     */
    @Target(value = ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Pro {
    
        /**
         * 类名
         */
        String className();
    
        /**
         * 方法名
         */
        String methodName();
    
        /*
          public class ProImpl implements Pro{
                public String className(){
                    return "com.chggx.annotation.Pr1";
                };
                public String methodName(){
                    return "show";
                 };
              }
         */
    
    }
    
    • 应用方法的类
    public class Pr1 {
    
        public void show1(){
            System.out.println("Pr1...show1....");
        }
    
    
    }
    
    public class Pr2 {
    
        public void show2(){
            System.out.println("Pr2...show2....");
        }
    
    }
    
    • 反射测试类 注解实现
    @Pro(className = "com.chggx.annotation.Pr1", methodName = "show1")
    public class ReflectTest {
    
        public static void main(String[] args) throws Exception {
    
            // 1. 解析注解
            // 1.1 获取该类的字节码文件对象
            Class<ReflectTest> rtc = ReflectTest.class;
            // 2 获取上边注解对象
            // 就是在内存中去生成了一个该注解接口的子类实现对象
            Pro annotation = rtc.getAnnotation(Pro.class);
            /*
              public class ProImpl implements Pro{
                    public String className(){
                        return "com.chggx.annotation.Pr1";
                    };
                    public String methodName(){
                        return "show";
                     };
                  }
             */
            // 3.调用注解对象中定义的抽象方法,获取返回值
            // 类名
            String className = annotation.className();
            // 方法名
            String methodName = annotation.methodName();
    
            System.out.println(className);
            System.out.println(methodName);
    
            // 4. 加载该类进内存
            Class<?> cls = Class.forName(className);
    
            // 5. 创建对象
            Object obj = cls.newInstance();
    
            // 6. 获取方法对象
            Method method = cls.getMethod(methodName);
    
            // 7. 执行方法
            method.invoke(obj);
    
    
        }
    
    }
    

5. 案例