05 注解与反射 & JVM

注解与反射

jdk5.0 之后的技术, 分为: 内置注解,自定义注解.

注解并不是程序本身, 可以对程序作出解释(类似注释), 但是还可以被其他程序/编译器读取.  注解类似一个说明, 这个说明可以被其他类读到.

这些注解本身放在这没有意义,需要有其他的程序来处理这些注解才有实际意义.

根据注解的提示, 外部程序根据注解的提示, 可以通过反射加载不同的类的实例, 从而实现不同的功能.

比如, @Override 就是一个注解, 提示给编译器, 这个方法是重写了父类方法, 比如你写 toString(), 写一个@Override, 如果你改写方法名为 toString12() 这时就会报错, 因为你写了@Override 注解, 编译器回去查看父类中是否有相同的方法, 如果你去掉了 @Override, 那么就不会有任何报错, 因为编译器认为你新写了一个方法名为 toString12()

元注解

元注解: 对注解做进一步解释, 也可以理解为注解的范围, 这个是堆注解的进一步说明, 比如 @Retention(source), @Override 这个注解就是用的这个范围, 就是源码范围.

@Target, 描述注解的使用范围,@Target(value=ElementType.method)  这个注解是用来修饰方法的

@Retention, 注解保留策略, (source, class, runtime), 如果是source, 那么这个注解就只保留在源码级别, 而如果是class 会被加载到类了,如果是runtime,这个注解可以被编译器读取(反射).  

source: 源码级别,举例 @Override,  很明显在你编辑代码时, 这个注解生效, 所以, 比如 toString12(), 会提示你父类中没有该方法.

class: 真正把注解编译到 class 文件中. (感觉使用不多, 因为这样可能就写"死"了, 失去了动态的意义.)

runtime: 运行时注解, 可以被编译器或其他运行时读取. 这也是反射的主要用处. 所以这种应该是我们用的最多的.

反射, 解析注解通常由框架已经做好了,你所要做的就是定义注解和使用注解(把注解注册到对应位置).

自定义注解

@Target(value=ElementType.method)

@Retention(source)

public @interface MyAnnotation01{

  // 注解里有参数类型和参数名

  // String 是参数类型, studentName 是参数名

  // String studentName() default "";

  // 如果注解里只有一个参数, 通常定义为 value

}

反射读取注解, 举例模拟 ORM

完成注解使用需要3步:

1. 定义注解本身

2. 在类中使用注解

3. 通过解析程序把注解读出来, 一般解析注解会有框架做完.

下边详细的代码:

1. 定义注解本身

package com.leon.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

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


package com.leon.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(value=ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyField {
    String columnName();
    String type();
    int length();
}

2. 在类中插入注解

package com.leon.annotation;

// 因为我们的类实际上在 ORM 中对应的一个表, 所以这里注解的参数是一个String, 表示一个表名字, tb_student 就是表名
@MyTable("tb_student")
public class MyStudent {
    
    @MyField(columnName="id", type="int", length = 10)
    private int id;
    @MyField(columnName="sname", type="varchar", length = 10)
    private String studentName;
    @MyField(columnName="age", type="int", length = 3)
    private int age;
    
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getStudentName() {
        return studentName;
    }
    public void setStudentName(String studentName) {
        this.studentName = studentName;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    
}

3. 通过解析, 读出注解, 并加以使用

package com.leon.annotation;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;

// 使用反射, 读取注解信息, 模拟处理注解信息
// 通过注解信息, 实际上就可以创建一个表了
// 从注解中得出, 表名字是 tb_student
// 列的名字和类型, 比如: columnName=sname, type=varchar, length=10
// 这些信息都有了, 就可以拼写 SQL 语句了, 然后就可以动态的创建表了, 通过 JDBC
public class MyDemo {
    public static void main(String[] args) {
        try {
            Class clazz = Class.forName("com.leon.annotation.MyStudent");
            Annotation[] annotations = clazz.getAnnotations();
            for (Annotation annotation : annotations) {
                System.out.println(annotation);    // 输出 @com.leon.annotation.MyTable(value=tb_student)
            }
            // 获得属性的注解
            try {
                Field f = clazz.getDeclaredField("studentName");
                MyField myfield = f.getAnnotation(MyField.class);
                System.out.println(myfield);    // 输出 @com.leon.annotation.MyField(columnName=sname, type=varchar, length=10)
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            } catch (SecurityException e) {
                e.printStackTrace();
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}
 反射机制

可以运行时加载, 探知,使用编译期间完全未知的类.

程序已经跑起来了,在运行时,依然可以再加载一些你只知道相关名字的类.对于任意一个已加载的类, 都能够知道这个类的所有属性和方法,对于任意一个对象,都能够调用它的任意一个方法和属性;

String s = "com.abc.User";    Class c = Class.forName(s);     这样也是可以的 (这就是照镜子的过程)

Class c = Class.forName("com.abc.User");  这个双引号中是一个类的名称, 这个值是可以改的,比如你改成Student,那么程序运行时,就动态的加载了Student 这个类. 加载之后, 这个类的对象就在堆内存中创建了这个对象。这个对象就像一面镜子一样,通过这面镜子可以看到对应类的全部信息,就可以动态的调用相关方法和属性了。

一个类只对应一个反射对象,多次调用 Class c2 = Class.forName(s); 实际上返回的都是同一个对象(因为这不是创建新的对象,而是调用对象)

还有获取方法:

Class strClazz = String.class;   

Class strClazz = path.getClass();     当然, 获取到的对象肯定是同一个对象.

有一些反射的 api 可以被调用, 比如 strClazz.getFileds()    返回属性 等等.

反射的理解

1. 之前类模板都是由 JVM 运行的 class 文件产生的, 现在是在运行过程中, 谁需要, 谁来创建这个类模板.

2. 另外, 在运行中创建的对象(模子是外部给出的), 这个对象可以通过反射(类似照镜子的功能), 将这些对象里的信息拿出来使用, 比如这些对象里的方法.

所以, 反射相当于运行时, 根据运行的状态, 来传入不同的类的模板, 根据这个类的模板, 来动态反射出来这个模板的对象, 然后使用该对象的方法的过程.

我们知道反射是在动态运行时使用的, 那反射如何知道代码中哪些程序可以被反射调用呢? 哪些是 JVM 原生创建的呢? 这时就需要注解

注解的意义就是给JVM 在运行时提示, 这部分内容是可以被"反射的".

而目前来看, 在 springBoot 里, 注解已经由大神写好了,我们只需要写注解, 就是在我们的应用中, 哪个部分用到哪些反射, 通过注解给出提示.

比如 Maven, 也是通过这种反射的机制来做的.(应该是)

JVM 类加载

加载: class 字节码内容加载到内存. 

链接: 将 java 类的二进制代码合并到 JVM 的运行状态之中.

初始化: 

类加载器

 引导类加载器 是用 C来写的, 其他的用的是 java.

原文地址:https://www.cnblogs.com/moveofgod/p/12490893.html