首页 > 技术文章 > 注解和反射

sgKurisu 2021-03-07 21:27 原文

注解和反射是所有框架的底层实现机制

注解

注解入门

注解都在java.lang.annotation中

注解作用:

  • 对程序做出解释(和注释相同)
  • 被其他程序读取

基本格式:@+注释名,还可以添加一些参数值,如@SuppressWarnings(value="unchecked")

内置注解

  • @Override,定义在java.lang.Override中,表示重写父类方法
  • @Deprecated,定义在java.lang.Deprecated中,表示该方法(或类、属性)不推荐使用,但仍可使用
  • @SuppressWarnings("all"),用来抑制编译器的警告信息

元注解

元注解为注解其他注解,即解释其他注解

有四种:

  • @Target:描述注解的使用范围
  • @Retention:描述注解的生命周期,(SOURCE<CLASS<RUNTIME)
  • @Document:说明该注解被包含在JavaDoc中
  • @Inherit:子类可继承父类中的该注解

@Target:

源码

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {    
    ElementType[] value();
}

可发现,可传递枚举类型的参数value,根据PERCENTILE__博客@Target注解详解

@Target(ElementType.TYPE) //接口、类、枚举
@Target(ElementType.FIELD) //字段、枚举的常量
@Target(ElementType.METHOD) //方法
@Target(ElementType.PARAMETER) //方法参数
@Target(ElementType.CONSTRUCTOR) //构造函
@Target(ElementType.LOCAL_VARIABLE)//局部变量
@Target(ElementType.ANNOTATION_TYPE)//注解
@Target(ElementType.PACKAGE) ///包
package com.annotation;

import java.lang.annotation.*;

//测试元注解
public class Demo2 {
    @MyAnnotation
    public void test () {

    }
}

//定义一个注解
@Target({ElementType.TYPE,ElementType.METHOD}) //该注解可用到什么地方
@Retention(RetentionPolicy.RUNTIME)  //注解生命周期
@Documented   //是否将该注解生成到JavaDoc中
@Inherited   //子类可继承父类的注解
@interface MyAnnotation {

}

自定义注解

@interface

package com.annotation;

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

//自定义注解
public class Demo3 {
    //@TestAnnotation(name = "AAA")
    @TestAnnotation(name = "A", school = "B", id = 1) //注解没有顺序
    public void test () {

    }
}

@Target({ElementType.TYPE,ElementType.METHOD}) //可以在类或方法上使用
@Retention(RetentionPolicy.RUNTIME)
@interface TestAnnotation {
    //注解的参数,格式:参数类型+参数名+()
    String name() default "A"; //加default后,提供默认值
    int age() default 1;
    int id();
    String school();
}

反射

概述

动态语言:在运行时可改变自身结构

静态语言:运行时不可改变自身结构,Java不是动态语言

Reflection(反射)是Java动态语言的关键

正常方式和反射方式的区别:

反射优点:

  • 可进行动态创建对象和编译,具有灵活性

反射缺点:

  • 影响性能,和直接执行相同的操作(new)相比效率较低

Object类的getClass可返回一个Class对象,Class是反射Reflection的根源,想要动态加载的类必须获得相应Class对象

package com.reflect;

//反射
public class Demo1 {
    public static void main(String[] args) throws ClassNotFoundException {
        Class<?> c1 = Class.forName("com.reflect.Person");//位置
        System.out.println(c1);

        Class<?> c2 = Class.forName("com.reflect.Person");
        Class<?> c3 = Class.forName("com.reflect.Person");
        System.out.println(c2 == c3);
        System.out.println(c2.hashCode());
    }
}
class Person {...}

获得Class的方式

Class常用方法:

Class cl = Person.class; //效率更高
Class cl = person.getClass;
Class cl = Class.getName("com.sgkurisu.Person");
//第四种:基本数据类型包装类中的TYPE属性,可返回Class类型数据
Class<Integer> c1 = Integer.TYPE;
//第五种:使用getSuperClass获得父类的Class
Class<?> c2 = c1.getSuperclass();
package com.reflect;

//Class类的创建方法
public class Demo2 {
    public static void main(String[] args) throws ClassNotFoundException {
        Student student = new Student("AB", 11);
        System.out.println(student.hashCode());

        Class<? extends Student> c1 = student.getClass();
        Class<Student> c2 = Student.class;
        Class<?> c3 = Class.forName("com.reflect.Student");
        System.out.println(c1.hashCode() + "\n" + c2.hashCode() + "\n" + c3.hashCode());
        //基本数据类型的包装类中TYPE属性
        Class<Integer> c4 = Integer.TYPE;
        System.out.println(c4);
        //获得父类类型
        Class<?> c5 = c1.getSuperclass();
        System.out.println(c5);
    }
}
class Student {...}

所有类型的Class对象

哪些类有Class对象:

  • class:外部类、成员内部类、静态内部类、局部内部类、匿名内部类
  • interface:接口
  • []:数组
  • enum:枚举
  • annotation:注解
  • primitive type:基本数据类型的包装类
  • void
Class<Object> c1 = Object.class;
Class<Comparable> c2 = Comparable.class;
Class<String[]> c3 = String[].class; //当数组长度不同时,Class对象仍相同,即hashCode相同
Class<Override> c4 = Override.class;
Class<Element> c5 = Element.class;
Class<Integer> c6 = Integer.class;
Class<Void> c7 = void.class;
Class<Class> c8 = Class.class;

类加载内存分析

类的加载过程分为:

  • 加载:将class字节码文件加载到内存中,将静态数据转化为方法区运行时的数据结构,并生成该类的java.lang.Class对象
  • 链接:将Java的二进制代码合并到JVM的运行状态中
    • 准备:是否符合JVM规范,语法问题等
    • 验证:为static对象分配内存空间和初始值,在方法区中进行分配
    • 解析:常量名替换为地址
  • 初始化:
    • 执行类构造器clinit()方法,将静态变量、方法合并
    • 若父类未初始化,则现将父类初始化
    • 保证clint()方法线程安全
package com.reflect;
/**
 * 类加载内存分析
 * 1、在方法区中产生Demo4类和Test类的基本数据
 * 2、在堆中加载产生Demo4和Test的Class对象
 * 3、链接,为静态变量m分配内存空间和初始值=0
 * 4、初始化,执行clint方法,合并静态变量、方法,即clinit(){
 *                                                    System.out.println("静态代码块");
 *                                                    m = 10;
 *                                                    m = 5; //和先后顺序有关
 *                                                  }
 */
public class Demo4 {
    public static void main(String[] args) {
        Test test = new Test();
        System.out.println(Test.m);
    }
}

class Test {
    static {
        System.out.println("静态代码块");
        m = 10;
    }
    static int m = 5;
    
    public Test(){
        System.out.println("构造方法");
    }
}

以上程序类加载内存分析

分析类的初始化

何时类会被初始化:类的主动引用

  • 首先初始化main方法所在的类
  • new一个对象的类
  • 调用类中的静态成员或静态方法
  • 反射调用,java.lang.reflect
  • 当初始化一个类时,若父类未被初始化,则先初始化父类

类的被动引用,不会发生类的初始化:

  • 访问静态域时,只有真正声明这个域的类才会被初始化,例如,当使用子类访问父类的静态变量时,不会子类初始化
  • 数组定义类的引用,不会初始化
  • 引用常量final也不会初始化(常量在链接阶段已存入类的常量池中)
package com.reflect;

//测试类的初始化
public class Demo5 {
    static {
        System.out.println("Main");
    }
    public static void main(String[] args) throws ClassNotFoundException {
        //主动引用会被初始化
        //Son son = new Son();
        //Class.forName("com.reflect.Son");
        //int c = Son.c;

        //被动引用不会初始化
        //int a = Son.a;
        //Son[] son = new Son[5];
        int b = Son.b;
    }
}

class Father {
    static int a = 1;
    static {
        System.out.println("父类");
    }
}

class Son extends Father {
    static final int b = 2;
    static int c = 3;
    static {
        System.out.println("子类");
    }
}

类加载器

类加载器作用:将.class文件加载到内存中,并将静态数据转换为方法区的数据结构,然后在堆中生成这个类的Class对象,作为方法区中类数据的访问入口。

类缓存:一旦某个类被加载到类加载器中,将会维持一段时间,JVM垃圾回收机制可回收这些Class对象

JVM定义了以下类型的类加载器:

  • 引导类加载器:用C++编写,是JVM自带类加载器,负责Java平台核心库,加载核心类库。该加载器无法直接获取
  • 扩展类加载器ExtClassLoader:将jre/lib/ext下的jar包装入工作库
  • 系统类加载器AppClassLoader:将已知目录下的类和jar包装入工作库,是最常用的类加载器

引导类加载器是扩展类加载器的父类,扩展类加载器是系统类加载器的父类

package com.reflect;

public class Demo6 {
    public static void main(String[] args) throws ClassNotFoundException {
        //系统类加载器
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
        System.out.println(systemClassLoader);

        //系统类加载器的父类-->扩展类加载器
        ClassLoader parent = systemClassLoader.getParent();
        System.out.println(parent);

        //扩展类加载器的父类-->引导类加载器
        ClassLoader parent1 = parent.getParent();
        System.out.println(parent1);        //无法获得引导类加载器,因此输出为null

        //当前类的类加载器,即当前类是由哪个类加载器加载的
        ClassLoader classLoader = Class.forName("com.reflect.Demo6").getClassLoader();
        System.out.println(classLoader);

        //系统内部的类是由哪个类加载器加载的
        ClassLoader classLoader1 = Class.forName("java.lang.Object").getClassLoader();
        System.out.println(classLoader1);

        //系统类加载器可加载类的路径
        String property = System.getProperty("java.class.path");
        System.out.println(property);
        /*
        输出为:
        F:\JDK\jre\lib\charsets.jar;
        F:\JDK\jre\lib\deploy.jar;
        F:\JDK\jre\lib\ext\access-bridge-64.jar;
        F:\JDK\jre\lib\ext\cldrdata.jar;
        F:\JDK\jre\lib\ext\dnsns.jar;
        F:\JDK\jre\lib\ext\jaccess.jar;
        F:\JDK\jre\lib\ext\jfxrt.jar;
        F:\JDK\jre\lib\ext\localedata.jar;
        F:\JDK\jre\lib\ext\nashorn.jar;
        F:\JDK\jre\lib\ext\sunec.jar;
        F:\JDK\jre\lib\ext\sunjce_provider.jar;
        F:\JDK\jre\lib\ext\sunmscapi.jar;
        F:\JDK\jre\lib\ext\sunpkcs11.jar;
        F:\JDK\jre\lib\ext\zipfs.jar;
        F:\JDK\jre\lib\javaws.jar;
        F:\JDK\jre\lib\jce.jar;
        F:\JDK\jre\lib\jfr.jar;
        F:\JDK\jre\lib\jfxswt.jar;
        F:\JDK\jre\lib\jsse.jar;
        F:\JDK\jre\lib\management-agent.jar;
        F:\JDK\jre\lib\plugin.jar;
        F:\JDK\jre\lib\resources.jar;
        F:\JDK\jre\lib\rt.jar;
        E:\code\basic\out\production\javabasic;
        E:\code\basic\javabasic\src\com\lib\commons-io-2.8.0.jar;
        F:\IDEA\IntelliJ IDEA Community Edition 2020.3.2\lib\idea_rt.jar

         */
    }
}

双亲委派机制

根据IT烂笔头的博客通俗易懂的双亲委派机制

利用这种机制,可防止用户替换系统级别的类,例如String.java,防止危险代码的植入

获取类运行时的结构

利用反射可获取类运行时的字段(Field),方法(Method),构造器(Constructor),父类(SuperClass),接口(Interface),注解(Annotation)

package com.reflect;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

//获取类的信息
public class Demo7 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException {
        Class<?> c1 = Class.forName("com.reflect.Student"); //Demo2中的Student类

        System.out.println(c1.getName()); //包名+类名
        System.out.println(c1.getSimpleName()); //类名

        Field[] f1 = c1.getFields(); //仅能获得public属性
        Field[] f2 = c1.getDeclaredFields(); //可获得所有属性
        for (Field field : f1) {
            System.out.println(field);
        }
        for (Field field : f2) {
            System.out.println(field);
        }
        Field name = c1.getDeclaredField("name"); //获得指定属性,使用c1.getField("name")方法,仅能获得指定public属性
        System.out.println(name);

        Method[] m1 = c1.getMethods(); //获得本类及父类的所有public方法
        for (Method method : m1) {
            System.out.println(method);
        }
        Method[] m2 = c1.getDeclaredMethods(); //获得本类所有方法,包括私有方法
        for (Method method : m2) {
            System.out.println(method);
        }
        //获得指定方法
        Method m3 = c1.getMethod("getName", null);//null表示获取的该方法没有参数
        Method m4 = c1.getMethod("setName", String.class);//setName方法的参数为String类型
        System.out.println(m3);
        System.out.println(m4);

        Constructor<?>[] constructor1 = c1.getConstructors();
        for (Constructor<?> constructor : constructor1) {
            System.out.println(constructor);
        }
        Constructor<?>[] constructor2 = c1.getDeclaredConstructors();
        for (Constructor<?> constructor : constructor2) {
            System.out.println(constructor);
        }
        //获得指定构造器
        Constructor<?> constructor3 = c1.getConstructor(String.class, int.class); //括号中为构造器参数类型
        System.out.println(constructor3);

    }
}

动态创建对象(创建和操作对象)

有了类的Class后,可用Class.newInstance()方法创建类的对象(和new类似),本质是调用无参构造器。需满足

  • 该类需要有一个无参构造器
  • 需要有类的无参构造器权限

当类中没有无参构造器时,可采用构造器来创建对象

  1. 通过Class类中的getDeclaredConstructor(Class...)方法获得有参构造器
  2. 使用构造器的newInstance(...)创建对象,括号中的参数为类有参构造器中的参数,需要传形参
  3. 使用Constructor实例化对象
package com.reflect;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class Demo8 {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
        Class<?> c1 = Class.forName("com.reflect.Student");

        //Student student = (Student) c1.newInstance(); //本质是调用无参构造器,没有无参构造时,将会出错
        //System.out.println(student.toString());

        Constructor<?> constructor = c1.getDeclaredConstructor(String.class, int.class);
        Object s1 = constructor.newInstance("AA", 11); //创建为Object类,因此不可调用Student的方法
        System.out.println(s1);

        //通过反射调用普通方法
        //通过反射获取一个方法
        Method method = c1.getDeclaredMethod("setName", String.class);
        method.invoke(s1, "ABCD"); //invoke为激活,参数为(对象, 激活方法所传的形参)
        System.out.println(s1); //创建是s1是为Object,因此不可调用Student的getName方法,需要强制转换为Student类

        //通过反射操作属性
        Field name = c1.getDeclaredField("name");
        //使用反射无法操作私有属性,但通过name.setAccessible(true)方法,可操作类中的所有属性
        name.setAccessible(true);
        name.set(s1, "CDA");
        System.out.println(s1);
    }
}

Field,Method和Constructor都有setAccessible安全监测方法,当参数值为true时,表示取消权限安全检查,参数为false表示开启权限安全检查

性能分析

package com.reflect;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

//性能分析
public class Demo9 {
    //普通方式
    public void test1() {
        Student student = new Student("A", 11);
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < 100000000; i++) {
            student.getName();
        }
        long endTime = System.currentTimeMillis();
        System.out.println("普通方式时间:"+ (endTime - startTime));
    }
    //反射方式
    public void test2() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Student student = new Student("A", 11);
        Class<? extends Student> studentClass = student.getClass();
        Method getName = studentClass.getDeclaredMethod("getName");

        long startTime = System.currentTimeMillis();
        for (int i = 0; i < 100000000; i++) {
            getName.invoke(student);
        }
        long endTime = System.currentTimeMillis();
        System.out.println("反射方式时间:"+ (endTime - startTime));
    }

    //关闭检测的反射方式
    public void test3() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Student student = new Student("A", 11);
        Class<? extends Student> studentClass = student.getClass();
        Method getName = studentClass.getDeclaredMethod("getName");
        getName.setAccessible(true);

        long startTime = System.currentTimeMillis();
        for (int i = 0; i < 100000000; i++) {
            getName.invoke(student);
        }
        long endTime = System.currentTimeMillis();
        System.out.println("反射方式时间:"+ (endTime - startTime));
    }

    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
        new Demo9().test1();
        new Demo9().test2();
        new Demo9().test3();

    }
}

反射操作泛型

  • Parameterized:表示一种参数化类型,例如Collection
  • GenericArrayType:表示元素类型是参数化类型或类型变量的数组类型

操作过程如下:

  • 通过Class.getMethod方法获得Method
  • 通过Method的getGenericParameterTypes方法获得参数类型
  • 通过Method的getGenericReturnType方法获得返回值类型
  • 用xxxx instanceof ParameterizedType判断是否属于结构化参数类型,若是,则通过getActualTypeArguments返回实际的参数类型
package com.reflect;

import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;

public class Demo10 {

    public void test(Map<String, Integer> map, List<Student> list) {

    }

    public Map<String, Student> test1() {
        return null;
    }

    public static void main(String[] args) throws NoSuchMethodException {
        Method test = Demo10.class.getMethod("test", Map.class, List.class);
        Type[] genericParameterTypes = test.getGenericParameterTypes(); //getGenericParameterTypes获得参数类型
        for (Type genericParameterType : genericParameterTypes) { //对该方法的参数类型进行输出
            System.out.println(genericParameterType);

            if (genericParameterType instanceof ParameterizedType) { //是否该参数类型为结构化参数类型
                Type[] actualTypeArguments = ((ParameterizedType) genericParameterType).getActualTypeArguments(); //getActualTypeArguments获取实际的参数类型
                for (Type actualTypeArgument : actualTypeArguments) {
                    System.out.println(actualTypeArgument);
                }
            }

        }
        System.out.println("============================");
        Method test1 = Demo10.class.getMethod("test1");
        Type genericParameterTypes1 = test1.getGenericReturnType(); //获取返回值类型
        if (genericParameterTypes1 instanceof ParameterizedType) { //是否该参数类型为结构化参数类型
            Type[] actualTypeArguments = ((ParameterizedType) genericParameterTypes1).getActualTypeArguments(); //getActualTypeArguments获取实际的参数类型
            for (Type actualTypeArgument : actualTypeArguments) {
                System.out.println(actualTypeArgument);
            }
        }

    }
}

反射操作注解

ORM:对象关系映射

数据库中

  • 类映射为表
  • 属性映射为字段
  • 对象映射为记录

操作过程:

  • 类注解:Class.getAnnotation(xxx)方法参数值为所需注解的Class,同样也有getAnnotations方法
  • 字段注解:先获得所需字段的Field,再通过Field的getAnnotation(xxx)方法,xxx为所需字段注解的Class,然后再通过返回的注解直接可获得注解的值
package com.reflect;

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

//反射操作注解
public class Demo11 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
        Class<?> c1 = Class.forName("com.reflect.Student2");

        //获取注解
        Annotation[] annotations = c1.getAnnotations();
        for (Annotation annotation : annotations) {
            System.out.println(annotation);
        }
        //获取类的注解value的值
        test1 annotation = c1.getAnnotation(test1.class);
        System.out.println(annotation.value());
        //获取字段注解的值
        //先获取字段
        Field field = c1.getDeclaredField("name");
        test2 annotation1 = field.getAnnotation(test2.class);//参数为注解的Class
        System.out.println(annotation1.name());
        System.out.println(annotation1.type());
        System.out.println(annotation1.length());
    }

}

@test1(value = "Student2")
class Student2 {

    @test2(name = "name", type = "String", length = 10)
    private String name;
    @test2(name = "age", type = "int", length = 20)
    private int age;

    public Student2(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

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

    @Override
    public String toString() {
        return "Student2{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

//注解

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

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface test2 {
    String name();
    String type();
    int length();
}

推荐阅读