java 反射和注解

java反射

1. 什么是反射

反射 (Reflection) 是 Java 的特征之一,它允许运行中的 Java 程序获取自身的信息,并且可以操作类或对象的内部属性。

Oracle 官方对反射的解释是:

Reflection enables Java code to discover information about the fields, methods and constructors of loaded classes, and to use reflected fields, methods, and constructors to operate on their underlying counterparts, within security restrictions.
The API accommodates applications that need access to either the public members of a target object (based on its runtime class) or the members declared by a given class. It also allows programs to suppress default reflective access control.

简而言之,就是在java运行期可以通过反射来获取类的成员或者成员的信息(字段,方法,构造器等)。程序中一般对象的类型都确定下来了,而通过反射机制可以动态的创建对象,并且去调用它的属性,即使这样的对象在编译时期是未知的。

java反射机制的核心就是jvm在运行时去创建对象或调用方法/访问属性,它不需要事先知道(写代码或者编译期)对象是谁

java反射的主要功能:

  1. 在运行时判断任意一个对象所属的类
  2. 在运行时构建任意一个类的对象
  3. 在运行时判断任意一个类所具有的变量和方法(通过反射甚至可以调用private方法)
  4. 在运行时调用任意一个对象的方法
  5. 反编译 .class -> .java

反射主要涉及的类

  1. java.lang .class
  2. java.lang.reflect.field
  3. java.lang.reflect.method
  4. java.lang.reflect.modifier
  5. java.lang.reflect.constructor

2. Class

2.1 什么是Class对象

实际上java的每个类被编译成.class文件的时候,java虚拟机(叫jvm)会自动为这个类生成一个类对象,这个对象保存了这个类的所有信息(成员变量,方法,构造器等),以后这个类要想实例化(也就是创建类的实例或创建类的对象)那么都要以这个class对象为蓝图(或模版)来创建这个类的实例。例如 class<?> c=Class.forName("com.pojo.User"); c就是User的类对象,而 User u=new User();这个u就是以c为模版创建的,其实就相当于u=c.newInstance(); 这个在java的反射里面讲的比较清楚。

2.2 创建Class对象的三种方式

    public static void main(String[] args) throws Exception{
        //创建Class对象的三种方式
        //c1 c2 c3引用保存内存地址指向堆中的对象,该对象代表的是 employee 整个类
        //1.使用 Class.forName("全类名")
        Class<?> c1 = Class.forName("com.iandf.Employee");
        //2.每个类型都有 class 属性
        Class<?> c2 = Employee.class;
        //3.先获取对象,通过对象过去 class
        Employee e = new Employee();
        Class<?> c3 = e.getClass();
        System.out.println(c1 == c2);
        System.out.println(c2 == c3);
    }

测试结果都为true

Class.forName("com.iandf.Employee");对导致类加载,类的静态代码块会执行

2.3 使用Class对象创建实例

    @Test
    public void newInstanceByReflect() throws Exception{
        //通过反射创建对象实例
        Class<?> c1 = Class.forName("com.iandf.bean.Employee");
        //使用class对象创建实例,默认调用无参构造方法,如果没有无参构造方法就会报InstantiationException
        Object o = c1.newInstance();
        System.out.println(o);
    }

2.4 加载配置文件创建对象

通过配置文件创建对象,无需再运行期知道对象是谁,在编译期可以动态的创建对象,更加灵活

@Test
public void newInstanceByFile() throws Exception{
    InputStream in = new FileInputStream("src\com\iandf\reflect.properties");
    Properties properties = new Properties();
    properties.load(in);
    String className = properties.getProperty("className");
    Class<?> c1 = Class.forName(className);
    Object o = c1.newInstance();
    System.out.println(o);
}

NOTE:文件路径问题

getResource 默认是从类路径开始的,所以文件必须放在类路径src

//使用当前线程的类加载器加载资源文件,这个方法可以得到文件的绝对路径,比上一个获取文件路径的方法灵活,以后代码移植更加方便
String filePath = Thread.currentThread().getContextClassLoader().getResource("com/iandf/reflect.properties").getPath();
System.out.println(filePath);
//也可以直接获取文件的流
InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("com/iandf/reflect.properties");

2.5 通过指定的构造器创建对象

通过class对象拿到构造器,然后通过构造器创建对象

@Test
public void createObjectByConstructor() throws Exception{
    Class<?> c1 = Class.forName("com.iandf.bean.Employee");
    Constructor<?> constructor = c1.getConstructor(String.class);
    Object o = constructor.newInstance("huang");
    if (o instanceof Employee){
        Employee employee = (Employee) o;
        employee.work();
    }
}

2.6 Resource Bundle

以前我们需要加载配置文件,需要以下步骤:

  1. 找到文件路径

  2. 获取文件的读取流

  3. 使用properties加载文件流

  4. 获取配置信息

InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("com/iandf/reflect.properties");
Properties properties = new Properties();
properties.load(inputStream);
String className = properties.getProperty("className");
System.out.println(className);

使用resource bundle之后只需要如下步骤

  1. 使用相对于类路径获取bundle对象
  2. 使用bundle对象获取配置信息
ResourceBundle resourceBundle = ResourceBundle.getBundle("com/iandf/reflect");
String className = resourceBundle.getString("className");
System.out.println(className);

需要注意的地方:

  1. 资源文件只能是.properties结尾的
  2. 资源文件必须放在类路径上
  3. 写路径的时候不能加.properties后缀

3. Method Constrcuor Filed

3.1 Method

获取某个Class对象的方法集合,主要有以下几个方法:

  • getDeclaredMethods 方法返回类或接口声明的所有方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法。
public Method[] getDeclaredMethods() throws SecurityException
  • getMethods 方法返回某个类的所有公用(public)方法,包括其继承类的公用方法。
public Method[] getMethods() throws SecurityException
  • getMethod 方法返回一个特定的public方法,其中第一个参数为方法名称,后面的参数为方法的参数对应Class的对象。
public Method getMethod(String name, Class<?>... parameterTypes)
  • getDeclaredMethod 获取任意指定的方法
 public Method getDeclaredMethod(String name, Class<?>... parameterTypes)
@Test
public void getMethods() throws Exception{
    //declaredMethods 返回当前类中所有的方法(包括private),不包括父类继承的方法
    System.out.println("//======================declaredMethods=================================//");
    Class<?> c1 = Class.forName("com.iandf.bean.Employee");
    Method[] declaredMethods = c1.getDeclaredMethods();
    for (Method declaredMethod : declaredMethods) {
        System.out.println(declaredMethod.getName());
    }
    System.out.println("//========================methods===================================//");
    //getMethods: 方法返回类中所有公共的方法,包括父类的方法
    Method[] methods = c1.getMethods();
    for (Method method : methods) {
        System.out.println(method);
    }
    System.out.println("//======================qualifiedMethods=================================//");
    Method showSex = c1.getMethod("showWork");
    System.out.println(showSex.getName());
}

3.2 Constrcuor

和method类似

@Test
public void getConstructors() throws Exception{
    Class<?> c1 = Class.forName("com.iandf.bean.Employee");
    //获取所有的公共构造方法
    System.out.println("//======================getConstructors=================================//");
    Constructor<?>[] constructors = c1.getConstructors();
    for (Constructor<?> constructor : constructors) {
        System.out.println(constructor.getName());
    }
    //获取所有的构造方法
    System.out.println("//==================getDeclaredConstructors=============================//");
    Constructor<?>[] declaredConstructors = c1.getDeclaredConstructors();
    for (Constructor<?> declaredConstructor : declaredConstructors) {
        System.out.println(declaredConstructor.getName());
    }
    //获取指定的构造方法
    Constructor<?> declaredConstructor = c1.getDeclaredConstructor(String.class);
    declaredConstructor.setAccessible(true);
    Employee employee = (Employee) declaredConstructor.newInstance("huang");
    employee.showWork();
}

NOTE: 非public修饰的时候必须用 declaredConstructor.setAccessible(true);

3.3 Filed

和前面两个类似

@Test
public void getFiled() throws Exception{
    Class<?> c1 = Class.forName("com.iandf.bean.Employee");
    Employee employee = (Employee) c1.newInstance();
    System.out.println("//==================getFields=============================//");
    Field[] fields = c1.getFields();
    for (Field field : fields) {
        System.out.println(field.getName());
    }
    System.out.println("//==================getDeclaredFields=============================//");
    Field[] declaredFields = c1.getDeclaredFields();
    for (Field declaredField : declaredFields) {
        //获取字段类型
        Class<?> type = declaredField.getType();
        System.out.println("type -> "+type.getName());
        //获取字段的修饰符
        int modifiers = declaredField.getModifiers();
        System.out.println("modifiers -> "+modifiers);
        System.out.println("modifiers.string -> "+Modifier.toString(modifiers));
        //获取字段的名字
        System.out.println("name -> "+declaredField.getName());
    }

    Field name = c1.getDeclaredField("name");
    name.setAccessible(true);

    Field no = c1.getField("no");
    System.out.println(no.toString());

}

4. 注解

4.1 什么是注解

Java 注解(Annotation)又称 Java 标注,是 JDK5.0 引入的一种注释机制。

Java 语言中的类、方法、变量、参数和包等都可以被标注。和 Javadoc 不同,Java 标注可以通过反射获取标注内容。在编译器生成类文件时,标注可以被嵌入到字节码中。Java 虚拟机可以保留标注内容,在运行时可以获取到标注内容 。 当然它也支持自定义 Java 标注。

4.2 注解的组成

一个注解由一个RetentionPolicy和一个或者多个ElementType组成

img

java.annotation有三个主干类

  1. Annotation接口

    package java.lang.annotation;
    public interface Annotation {
    
        boolean equals(Object obj);
    
        int hashCode();
    
        String toString();
    
        Class<? extends Annotation> annotationType();
    }
    
  2. ElementType

    package java.lang.annotation;
    
    public enum ElementType {
        TYPE,               /* 类、接口(包括注释类型)或枚举声明  */
    
        FIELD,              /* 字段声明(包括枚举常量)  */
    
        METHOD,             /* 方法声明  */
    
        PARAMETER,          /* 参数声明  */
    
        CONSTRUCTOR,        /* 构造方法声明  */
    
        LOCAL_VARIABLE,     /* 局部变量声明  */
    
        ANNOTATION_TYPE,    /* 注释类型声明  */
    
        PACKAGE             /* 包声明  */
    }
    
  3. RetentionPolice

    package java.lang.annotation;
    public enum RetentionPolicy {
        SOURCE,            /* Annotation信息仅存在于编译器处理期间,编译器处理完之后就没有该Annotation信息了  */
    
        CLASS,             /* 编译器将Annotation存储于类对应的.class文件中。默认行为  */
    
        RUNTIME            /* 编译器将Annotation存储于class文件中,并且可由JVM读入,可以被反射读取*/
    }
    

4.3 annotation的定义

@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation1 {
}

说明:

上面的作用是定义一个Annotation,它的名字是MyAnnotation1。定义了MyAnnotation1之后,我们可以在代码中通过“@MyAnnotation1”来使用它。
其它的,@Documented, @Target, @Retention, @interface都是来修饰MyAnnotation1的。下面分别说说它们的含义:

(01) @interface
使用@interface定义注解时,意味着它实现了java.lang.annotation.Annotation接口,即该注解就是一个Annotation。
定义Annotation时,@interface是必须的。
注意:它和我们通常的implemented实现接口的方法不同。Annotation接口的实现细节都由编译器完成。通过@interface定义注解后,该注解不能继承其他的注解或接口。

(02) @Documented
类和方法的Annotation在缺省情况下是不出现在javadoc中的。如果使用@Documented修饰该Annotation,则表示它可以出现在javadoc中。
定义Annotation时,@Documented可有可无;若没有定义,则Annotation不会出现在javadoc中。

(03) @Target(ElementType.TYPE)
前面我们说过,ElementType 是Annotation的类型属性。而@Target的作用,就是来指定Annotation的类型属性。
@Target(ElementType.TYPE) 的意思就是指定该Annotation的类型是ElementType.TYPE。这就意味着,MyAnnotation1是来修饰“类、接口(包括注释类型)或枚举声明”的注解。
定义Annotation时,@Target可有可无。若有@Target,则该Annotation只能用于它所指定的地方;若没有@Target,则该Annotation可以用于任何地方

(04) @Retention(RetentionPolicy.RUNTIME)
前面我们说过,RetentionPolicy 是Annotation的策略属性,而@Retention的作用,就是指定Annotation的策略属性。
@Retention(RetentionPolicy.RUNTIME) 的意思就是指定该Annotation的策略是RetentionPolicy.RUNTIME。这就意味着,编译器会将该Annotation信息保留在.class文件中,并且能被虚拟机读取。
定义Annotation时,@Retention可有可无。若没有@Retention,则默认是RetentionPolicy.CLASS。

4.4 java常用的annotation

java 常用的Annotation:

@Deprecated  -- @Deprecated 所标注内容,不再被建议使用。
@Override    -- @Override 只能标注方法,表示该方法覆盖父类中的方法。
@Documented  -- @Documented 所标注内容,可以出现在javadoc中。
@Inherited   -- @Inherited只能被用来标注“Annotation类型”,它所标注的Annotation具有继承性。
@Retention   -- @Retention只能被用来标注“Annotation类型”,而且它被用来指定Annotation的RetentionPolicy属性。
@Target      -- @Target只能被用来标注“Annotation类型”,而且它被用来指定Annotation的ElementType属性。
@SuppressWarnings -- @SuppressWarnings 所标注内容产生的警告,编译器会对这些警告保持静默。

@Inherited

如果类A被@Inherited标记了,如果有类B继承了类A,那么类B也拥有这个注解。

为了呈现出测试结果,我们将注解标注为@Retention(RetentionPolicy.RUNTIME)

MyAnnotation

@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {

}

类A

@MyAnnotation
public class A {
    public A() {
        System.out.println(A.class.isAnnotationPresent(MyAnnotation.class));
    }
}

类B

public class B extends A{
    public B() {
        System.out.println(B.class.isAnnotationPresent(MyAnnotation.class));
    }
}

测试方法

@Test
public void InheritedTest(){
    B b = new B();
}

测试结果

image-20200830162054093

4.5 annotation的作用

4.5.1 编译检查

Annotation具有“让编译器进行编译检查的作用”。

例如,@SuppressWarnings, @Deprecated和@Override都具有编译检查作用。
(01) 关于@SuppressWarnings和@Deprecated,已经在“第3部分”中详细介绍过了。这里就不再举例说明了。
(02) 若某个方法被 @Override的 标注,则意味着该方法会覆盖父类中的同名方法。如果有方法被@Override标示,但父类中却没有“被@Override标注”的同名方法,则编译器会报错。

4.5.2 在反射中使用Annotation

在反射的Class, Method, Field等函数中,有许多于Annotation相关的接口。
这也意味着,我们可以在反射中解析并使用Annotation。

MyAnnotation,类A,类B

@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
    String[] value() default "iandf";
}

测试方法

@Test
public void useAnnotationByReflect() throws Exception{
    Class<?> c1 = Class.forName("pojo.A");
    Annotation[] annotations = c1.getAnnotations();
    for (Annotation annotation : annotations) {
        System.out.println(annotation.toString());
    }

    MyAnnotation annotation = c1.getAnnotation(MyAnnotation.class);
    System.out.println(Arrays.toString(annotation.value()));
}

测试结果

@annotation.MyAnnotation(value=[iandf, huang])
@java.lang.Deprecated()
[iandf, huang]

4.6 注解的应用

需求:写一个注解使其有如下功能:检测类中是否包含其注解如果包含则必须包含int id这个属性,否则抛出异常

搭建环境

  1. 编写student类

    @MustHasIdProperty
    public class Student {
        int id;
        String name;
    }
    
  2. 编写MustHasIdProperty注解

    @Retention(RetentionPolicy.RUNTIME)
    public @interface MustHasIdProperty {
    
    }
    
  3. 编写实现注解的功能

    public static void main(String[] args) throws Exception {
        Class<?> c1 = Class.forName("pojo.Student");
        boolean hasId = false;
        if (c1.isAnnotationPresent(MustHasIdProperty.class)) {
            Field[] declaredFields = c1.getDeclaredFields();
            for (Field declaredField : declaredFields) {
                if ("id".equals(declaredField.getName())&&"int".equals(declaredField.getType().getName())) {
                    hasId = true;
                    break;
                }
            }
        }
        if (!hasId) {
            throw new NotFoundIDException("被@MustHasIdProperty标记的类必须有int id的属性");
        }
    }
    
  4. 测试结果

    如果把id属性删除则报异常。满足需求

原文地址:https://www.cnblogs.com/iandf/p/13585963.html