java注解的原理剖析

一、元注解

1、@Target(作用目标)

标注注解作用的范围
类型 说明
ElementType.TYPE 类,接口(包括注解类型)或enum声明
ElementType.FIELD 字段,枚举常量
ElementType.METHOD 方法声明
ElementType.PARAMETER 参数声明
ElementType.CONSTRUCTOR 构造器声明
ElementType.LOCAL_VARIABLE 局部变量声明
ElementType.PACKAGE 包声明
ElementType.LOCAL_VARIABLE 局部变量
ElementType.ANNOTATION_TYPE 注解

2、@Retention(保留策略)

表示在什么级别保存该注解信息
类型 说明
RetentionPolicy.SOURCE 注解仅存在于源码中,在class字节码文件中不包含
RetentionPolicy.CLASS 默认的保留策略,注解会在class字节码文件中存在,但运行时无法获得
RetentionPolicy.RUNTIME 注解会在class字节码文件中存在,在运行时可以通过反射获取到首 先要明确生命周期长度 SOURCE < CLASS < RUNTIME ,所以前者能作用的地方后者一定也能作用。一般如果需要在运行时去动态获取注解信息,那只能用 RUNTIME 注解;如果要在编译时进行一些预处理操作,比如生成一些辅助代码(如 ButterKnife),就用 CLASS注解;如果只是做一些检查性的操作,比如 @Override 和 @SuppressWarnings,则可选用 SOURCE 注解。

3、@Documented

将此注解包含在 javadoc 中 ,它代表着此注解会被javadoc工具提取成文档。在doc文档中的内容会因为此注解的信息内容不同而不同。相当与@see,@param 等。

4、@Inherited

Inherited 是继承的意思,但是他并不是说直接本身可以继承,而是说一个超类被@Inheritedh注解,

二、反射

此处为何会讲解反射,因为注解和反射是密切相关的,没有反射,无法完成注解功能的解析。

1、概念

class
Method
Field
Constructor

2、反射核心类

java.lang.Class;     类
java.lang.reflect.Constructor;    构造器方法
java.lang.reflect.Field;    属性
java.lang.reflect.Method;    方法
java.lang.reflect.Modifier;    修饰符

3、类加载三个阶段

1、源代码(source)
    原代码不是指java文件,是class字节码文件
2、类加载(class)
    class字节码文件经类加载器classloader加载到虚拟机内存中,类加载器解析class文件生成Class类型的对象
3、运行时(runtime)
    newInstance()根据java类型生成对象

4、 获取class类对象三种方法

1、Class class=Class.forName("com.xx.xx.xx.Person");
2、Class<Person> class=Person.class;
3、Person p=new Person();
   Class class=p.getClass();

三、解析注解(源码剖析)

写一个注解,自定义返回自己的toString方法。返回自己想要的格式

完整代码可以去此项目下查看cloud-annotation

1、创建自定义注解

注解名称 功能
@JsonFormat 格式化功能
@JsonIgnore 忽略功能
@JsonProperty 属性名
/**
 * 作用于属性上,用户按要求格式化日期
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface JsonFormat {
    String pattern() default "yyyy-MM-dd HH:mm:ss";
}
/**
 * 作用在属性上,用于忽略一些属性
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface JsonIgnore {
}
/**
* 属性别名
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface JsonProperty {
    String value(); //使用的时候必须给值

2、注解定义好后自定义方法toJSONString(类似于toString)方法

package com.xwb.springcloud.annotation.json;

import java.lang.reflect.Field;
import java.text.SimpleDateFormat;
import java.util.Date;

@SuppressWarnings("all")
public class JSON {
    public static String toJSONString(Object object) {
        StringBuffer json = new StringBuffer();
        try {
            //1、得到object的Class对象
            Class<?> clazz = object.getClass();
            //2、得到class里面的所有属性
            Field[] fields = clazz.getDeclaredFields();
            //3、循环遍历所有属性病打破属性的访问权限
            int index = 0;
            //得到属性长度
            int length = fields.length;
            json.append("{");
            for (Field field : fields) {
                index++;
                //判断属性上是否有忽略的注解
                if (field.isAnnotationPresent(JsonIgnore.class)) {
                    continue;
                }
                //私有变量可读
                field.setAccessible(true);
                //4得到属性名作为json的key
                String name = field.getName();
                //5、得到属性值作为json的value
                Object value = field.get(object);
                //判断属性上是否有更改名字的注解
                if (field.isAnnotationPresent(JsonProperty.class)) {
                    //有的话取出注解上的名字
                    JsonProperty annotation = field.getAnnotation(JsonProperty.class);
                    name = annotation.value();
                }
                json.append("\"" + name + "\"");
                if (value instanceof String) {
                    json.append(":\"" + value.toString() + "\"");
                } else if (value instanceof Date) {
                    //此处如果是日期的话,则判断是否有日起格式化的注解
                    Date date = (Date) value;
                    if (field.isAnnotationPresent(JsonFormat.class)) {
                        JsonFormat format = field.getAnnotation(JsonFormat.class);
                        json.append(":\"" + parseDateToStr(date, format.pattern()) + "\"");
                    } else {
                        json.append(":\"" + date.getTime() + "\"");
                    }
                } else {
                    json.append(":" + value.toString());
                }
                if (index != length) {
                    json.append(",");
                }
            }
            json.append("}");
            return json.toString();
        } catch (Exception e) {
            System.out.println(e.getCause());
        }
        return null;
    }
    public static String parseDateToStr(Date date, String format) {
        SimpleDateFormat sdf = new SimpleDateFormat(format);
        return sdf.format(date);
    }
}

说明:

定义的注解,没有任何作用,但是想让注解发挥它的作用,就要定义相应的方法去解析他,然后让他发挥作用
谁定义的注解谁去解析。而不是说自己定义的注解让java去解析,他是不识别的

3、调用

public class TestJSON {
    public static void main(String[] args) {
        User user = new User(1, "张三", "宁夏银川", new Date());
        String s = JSON.toJSONString(user);
        System.out.println(s);
    }
}

4、输出结果

{“user_name”:“张三”,“user_address”:“宁夏银川”,“birth”:“2021-11-12”}

java注解的原理剖析

示例

总结:注解本身没有任何作用,只是申明的而已,具体起作用是定义该注解的开发者想让其起到什么功能,然后再对应解析的方法中运用反射获取类、属性、方法、参数…上是否有该注解,有的话(xxx instanceof xxx)根据开发者自己的想法去实现该功能方法。
查找源码中的类或者方法使用的快捷键是【ctrl+n

1、spring的@RequestBody注解

spring-webmvc-5.2.2.RELEASE.jar包下的RequestResponseBodyMethodProcessor类进行解析
关于该注解的详细说明请观看此博客@RequestBody

java注解的原理剖析

2、mybatis的@Mapper注解

mybatis-3.4.4.jar包下的MapperAnnotationBuilder类parse方法RequestResponseBodyMethodProcessor
关于@Mapper请看此博客@Mapper

java注解的原理剖析

3、spring的@Configuration

spring-context-5.2.2.RELEASE.jar包下的ConfigurationClassPostProcessor类
源码具体详解可以查看此博主博客@Configuration

java注解的原理剖析

4、springcloud的@FeignClient注解

spring-cloud-openfeign-core-2.2.1.RELEASE.jar包的FeignClientsRegistrar类
关于@FeignClient的源码解析请看此博客@FeignClient

java注解的原理剖析

上一篇:2018 Multi-University Training Contest 6


下一篇:Java自定义注解