Java 注解

Java注解是Java5引入的重要语言特性之一,它可以提供描述程序所需元数据信息,而这些信息是无法使用Java语言进行表达的。

注解的引入可以使我们能够使用编译器来验证格式,并存储程序额外信息。

注解又称元数据,为我们在代码中添加信息提供了一种方法,使得我们能够在稍后某个时刻方便的访问这些数据。

注解在一定程度上是将元数据和源代码文件结合在一起,而无需使用额外的配置文件进行管理。

注解的语法很简单,除了@符号的使用,基本与Java固有的写法一致,Java5中内置了三个注解:

  1. @Override
    表示当前方法将覆盖父类中的方法,如果方法名拼错或方法签名与被覆盖的方法不一致,编译器会发出错误提示
  2. @Deprecated
    标记当前方法、类、参数为已过期,当使用已过期方法时,编译器会发出Warning提示
  3. @SuppressWarning
    关闭编译器Warning信息

大多数情况下,程序员主要是定义自己的注解,并编写自己的处理器来处理他们,以达到既定的目的。

1. 定义注解

使用Java内置元注解对自定义注解进行注解,并在需要添加元数据的地方添加自定义注解。

1.1. 元注解

Java中内置了四种元注解,专职负责注解其他注解

  1. @Target
    表示该注解可以用于哪些地方,可用的ElementType有:
    • TYPE 类、接口、枚举等类型
    • FIELD 属性说明
    • METHOD 类方法
    • PARAMETER 参数声明
    • CONSTRUCTOR 构造函数
    • LOCAL_VARIABLE 本地变量
    • ANNOTATION_TYPE 注解类型
    • PACKAGE 包
    • TYPE_PARAMETER 类型参数(泛型)1.8
    • TYPE_USE 类型 1.8
  2. @Retention
    表明在哪个级别保存该注解信息,可选的RetentionPolicy有:
    • SOURCE
      注解对编译器生效,但被编译器丢弃
    • CLASS
      注解在Class文件中生效,但被JVM丢失
    • RUNTIME
      在运行时保留注解,因此通过反射机制可以拿到注解信息
  3. @Document
    将此注解包含到JavaDoc中
  4. @Inherited
    允许子类继承父类的注解

1.2. 自定义注解

1.2.1. 定义注解

下面是一个自动生成DTO的注解,可见注解的定义看起来很像接口定义,事实上,与接口定义一样,注解也会编译成class文件。

除@符外注解定义就像一个空接口,其中@Target和@Retention尤为重要,@Target用于定义注解将用于什么地方,@Retention用来定义该注解在哪个级别上可用。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
@Inherited
@Documented
public @interface GenDTO {
    String pkgName();
}

此注解,使用在类型之上,作用于SOURCE,允许子类继承,并将其包含到Javadoc中。

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.SOURCE)
@Inherited
@Documented
public @interface GenDTOIgnore {
}

此注解为标记注解,没有设置注解元素,只做标记来用。

1.2.2. 注解元素

注解上一般会包含一些元素以表示某值,当处理注解时,程序或工具可用利用这些值。

GenDTO注解上有一个String元素pkgName,用于记录元信息,注解元素可以用的类型包括:

  • 所有基本类型(int、float、boolean等)
  • String
  • Class
  • Enum
  • Annotation
  • 以上类型的数组

如果使用其他类型,编译器会报错。

1.2.3. 注解默认值

编译器对元素的默认值有些过分苛刻。首先元素不能有不确定的值,及元素要么提供默认值,要么在使用的时候提供元素的值;其次,对于非基本类型的元素,都不能以null作为默认值。

这种约束使得处理器很难处理元素值缺失的情况,因为在注解中所有的元素都存在,并且都有相应的值。为了绕开这个限制,我们通常会定义一些特殊的值,如空字符串或负数,以表示该值不存在。

1.2.4. 注解不支持继承

不能用extends来继承某个@interface,这是一件比较遗憾的事。

2. 使用注解

从语法角度看,注解的使用方式几乎与修饰符(public、static、void等)的使用一模一样,一个比较大的区别是注解上可以设置注解元素,以便注解处理器使用。

@GenDTO(pkgName = "com.example.annotation.dto")
public class TestEntity {
    private String name;
    private Long id;
    private Integer age;
    private Date birthday;

    @GenDTOIgnore
    private List<String> address;

    public String getName() {
        return name;
    }

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

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public Integer getAge() {
        return age;
    }

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

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    public List<String> getAddress() {
        return address;
    }

    public void setAddress(List<String> address) {
        this.address = address;
    }
}

3. 定义注解处理器

常见的注解处理方案主要包括应用于SOURCE阶段的apt(annotation proccess tools)和应用于RUNTIME阶段的反射。

3.1. apt处理器

注解处理工具apt,是Sun公司为了帮助注解处理过程而提供的工具。apt和javac一样,被设计为操作Java源文件,而不是编译后的类。

默认情况下,apt会在处理完源文件后编译他们,如果在系统构建的过程中自动创建了一些新的源文件,该文件会在新一轮的注解处理中接受检查。该工具一轮轮的处理,直到不在有新的源文件产生为止,然后在编译所有的源文件。

我们定义的每一个注解都需要自己的处理器,apt工具可以将多个注解处理器组合起来,已完成比较复杂的应用场景。

在使用apt生成的注解处理器时,我们无法使用Java的反射机制,因为我们操作的是Java源文件,而不是编译后的类。

基于apt的注解处理器开发,主要包括两个步骤:

  1. 实现Processor接口定制自己的注解处理器
  2. 添加注解处理器相关配置

3.1.1. 自定义Processor

自定义Processor,需要提供一个公共的无参构造函数,框架工具通过该构造函数实例化Processor,并由工具统一调用。

Processor 交互流程

框架工具与Processor的交互流程如下:

  1. 如果不使用现有的Processor对象,则通过Processor的无参构造函数创建新的实例对象
  2. 工具调用init方法对Processor进行初始化
  3. 工具调用getSupportedAnnotationTypes、getSupportedOptions 和 getSupportedSourceVersion方法,了解注解具体的应用信息。这些方法只在每次运行时调用一次,并非每个处理都调用。
  4. 如果满足上述条件,调用Processor对象的process方法,进行注解处理。

Processor 核心方法

Processor核心方法如下:

  1. void init(ProcessingEnvironment processingEnv) 用处理器环境初始化 Processor
  2. Set<String> getSupportedAnnotationTypes() 返回此 Processor 支持的注释类型的名称
  3. SourceVersion getSupportedSourceVersion() 返回此注释 Processor 支持的最新的源版本
  4. Set<String> getSupportedOptions() 返回此 Processor 识别的选项
  5. boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) 处理先前产生的类型元素上的注释类型集,并返回这些注释是否由此 Processor处理
  6. Iterable<? extends Completion> getCompletions(Element element, AnnotationMirror annotation, ExecutableElement member, String userText) 向工具框架返回某一注释的建议 completion 迭代

AbstractProcessor

一般情况下,我们很少直接实现Processor,而是继承AbstractProcessor,并在它基础上进行扩展。
AbstractProcessor对Processor接口进行了扩展,以方便扩展。

AbstractProcessor核心方法:

  1. void init(ProcessingEnvironment processingEnv)
    环境初始化方法,将processingEnv字段设置为 processingEnv 参数的值
  2. Set<String> getSupportedAnnotationTypes() 如果 processor类是使用SupportedAnnotationTypes注释的,则返回一个不可修改的集合,该集合具有与注释相的字符串集
  3. SourceVersion getSupportedSourceVersion()如果 processor类是使用SupportedSourceVersion注释的,则返回注释中的源版本
  4. Set<String> getSupportedOptions() 如果 processor类是使用SupportedOptions注释的,则返回一个不可修改的集合,该集合具有与注释相的字符串集
  5. Iterable<? extends Completion> getCompletions(Element element, AnnotationMirror annotation, ExecutableElement member, String userText) 返回一个空的 completion 迭代

总体来说,AbstractProcessor主要做了以下几件事:

  • init时保存ProcessingEnvironment
  • 使用注解方式实现getSupported***相关方法
  • getCompletions返回新迭代

至此,对于Processor的实现主要集中在procosse方法。

ProcessingEnvironment

ProcessingEnvironment主要用于Processor初始化,将上下文信息传递到Processor中,以方便后续操作。

核心方法如下:

  1. Elements getElementUtils() 返回用来在元素上进行操作的某些实用工具方法的实现
  2. Filer getFiler() 返回用来创建新源、类或辅助文件的 Filer
  3. Locale getLocale() 返回当前语言环境;如果没有有效的语言环境,则返回null
  4. Messager getMessager() 返回用来报告错误、警报和其他通知的 Messager
  5. Map<String,String> getOptions()返回传递给注释处理工具的特定于 processor 的选项
  6. SourceVersion getSourceVersion() 返回任何生成的源和类文件应该符合的源版本
  7. Types getTypeUtils()返回用来在类型上进行操作的某些实用工具方法的实现

process

这相当于每个处理器的主函数main()。你在这里写你的扫描、收集和处理注解的代码,以及生成Java文件。

一般情况下,process方法的处理逻辑如下:

  1. 提取注解中的元信息
  2. 生成java代码,通过拼接字符串或使用成熟的代码生成器生成java代码
  3. 将java代码通过filer写回工具,等待编译器处理

3.1.2. Processor配置

自定义处理器最终会打包成jar文件,由其他项目在编译环节调用,为了让编译器能识别自定义的Processor,需要使用SPI技术添加相关配置信息。

在META-INF/services目录下,新建javax.annotation.processing.Processor文件,每行一个,将注解器添加到该文件。

如javax.annotation.processing.Processor文件内容:

xxxx.GenCodeBasedEnumConverterProcessor
xxx.GenDTOProcessor
xxx.GenUpdaterProcessor

该文件打包至输出jar中,然后由编译器在编译环节使用。

3.2 反射处理器

Java5中对反射相关的接口进行扩展,用以通过反射机制获取RUNTIME阶段的注解信息,从而实现基于反射的注解处理器。

3.2.1 Annotation

Java使用Annotation类来表示程序中的注解,及所有注解都是Annotation接口的子类。

3.2.2. AnnotatedElement

Java新增AnnotatedElement接口,该接口代表程序中可以接受注解的程序元素,并提供统一的Annotation访问方式,赋予API通过反射获取Annotation的能力,当一个Annotation类型被定义为运行时后,该注解才能是运行时可见,当class文件被装载时被保存在class文件中的Annotation才会被虚拟机读取。

AnnotatedElement接口是所有注解元素(Class、Method、Field、Package和Constructor)的父接口,所以程序通过反射获取了某个类的AnnotatedElement对象之后,程序就可以调用该对象的下列方法来访问Annotation信息:

  1. <T extends Annotation> T getAnnotation(Class<T> annotationClass)返回程序元素上存在的、指定类型的注解,如果该类型注解不存在,则返回null
  2. Annotation[] getAnnotations() 返回该程序元素上存在的所有注解
  3. boolean is AnnotationPresent(Class<?extends Annotation> annotationClass) 判断该程序元素上是否包含指定类型的注解,存在则返回true,否则返回false
  4. Annotation[] getDeclaredAnnotations() 返回直接存在于此元素上的所有注释。与此接口中的其他方法不同,该方法将忽略继承的注释。(如果没有注释直接存在于此元素上,则返回长度为零的一个数组)该方法的调用者可以随意修改返回的数组;这不会对其他调用者返回的数组产生任何影响。

AnnotatedElement子类涵盖所有可以出现Annotation的地方,其中包括:

  1. Constructor 构造函数
  2. Method 方法
  3. Class 类型
  4. Field 字段
  5. Package 包
  6. Parameter 参数
  7. AnnotatedParameterizedType 泛型
  8. AnnotatedTypeVariable 变量
  9. AnnotatedArrayType 数组类型
  10. AnnotatedWildcardType

4. Java注解实战

4.1. 基于apt的代码生成器

现在系统都有严格的分层标准,每层负责不同的职责,以最大程度的解耦,其中最为核心的应该是Domain层(也称领域层),其上是application或service层,为了保证domain层的安全性,不允许直接将其进行暴露,最常用的一种方式便是,将其转化为DTO在并外提供服务。

其中Domain对象与DTO之间结构同步,耗费了很大的人力资源,而且大多情况下都是些机械性的代码,没有什么太大的挑战。而这个场景便是代码生成器所擅长的领域。

4.1.1. 设计目标

通过基于注解的代码生成器,根据Domain对象的结构,自动生成BaseDomainDTO作为父类,用于维护通用的结构,DomainDTO从BaseDomainDTO中继承,在享受通用结构的同时,为特殊需求提供扩展点。

4.1.2. 自定义注解

自定义注解包括GenDTO和GenDTOIgnore。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
@Inherited
@Documented
public @interface GenDTO {
    String pkgName();
}

标记于Domain类上,用于说明该类需要生成BaseDomainDTO

@Target({ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.SOURCE)
@Inherited
@Documented
public @interface GenDTOIgnore {
}

注解与Domain中的字段上,在生成BaseDomainDTO时,忽略该字段

4.1.3. 自定义注解处理器

自定义注解处理器,在编译过程中,读取注解信息,并完成BaseDomainDTO的代码生成。

public abstract class BaseProcessor<A extends Annotation> extends AbstractProcessor {
    /**
     * 需要处理的注解类(GenDTO)
     */
    private final Class aClass;
    /**
     * 用于回写新生成的java文件
     */
    private Filer filer;

    private Messager messager;

    public BaseProcessor(Class<A> aClass) {
        this.aClass = aClass;
    }

    /**
     *
     * @return
     */
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        return Sets.newHashSet(aClass.getCanonicalName());
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        this.filer = processingEnv.getFiler();
        this.messager = processingEnv.getMessager();
    }



    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        // 获取所有标记该注解的元素
        Set<Element> elements = roundEnv.getElementsAnnotatedWith(this.aClass);
        for (Element element : ElementFilter.typesIn(elements)){
            A a = (A) element.getAnnotation(this.aClass);
            // 循环处理每一个标记对象
            foreachClass(a, element, roundEnv);
        }
        return false;
    }

    protected abstract void foreachClass(A a, Element element, RoundEnvironment roundEnv);

    /**
     * 获取元素的所有字段信息
     * @param element
     * @param filter
     * @return
     */
    protected Set<TypeAndName> findFields(Element element, Predicate<Element> filter){
        return ElementFilter.fieldsIn(element.getEnclosedElements()).stream()
                .filter(filter)
                .map(variableElement -> new TypeAndName(variableElement))
                .collect(Collectors.toSet());
    }

    /**
     * 获取元素中所有的Getter方法
     * @param element
     * @param filter
     * @return
     */
    protected Set<TypeAndName> findGetter(Element element, Predicate<Element> filter){
        return ElementFilter.methodsIn(element.getEnclosedElements()).stream()
                .filter(filter)
                .filter(executableElement -> isGetter(executableElement.getSimpleName().toString()))
                .map(executableElement -> new TypeAndName(executableElement))
                .collect(Collectors.toSet());

    }

    private boolean isGetter(String s) {
        return s.startsWith("id") || s.startsWith("get");
    }

    /**
     * 获取基于注解的忽略过滤器
     * @param iClass
     * @param <I>
     * @return
     */
    protected <I extends Annotation> Predicate<Element> filterForIgnore(Class<I> iClass){
        return new IgnoreFilter<I>(iClass);
    }

    /**
     * 忽略过滤器,如果元素上添加了Ignore注解,对其进行忽略
     * @param <I>
     */
    protected static class IgnoreFilter<I extends Annotation> implements Predicate<Element>{
        private final Class<I> iClass;

        public IgnoreFilter(Class<I> iClass) {
            this.iClass = iClass;
        }

        @Override
        public boolean test(Element variableElement) {
            return variableElement.getAnnotation(iClass) == null;
        }
    }


    /**
     * 生成Java文件
     * @param typeSpecBuilder
     * @param pkgName
     */
    protected void createJavaFile(TypeSpec.Builder typeSpecBuilder, String pkgName) {
        try {
            JavaFile javaFile = JavaFile.builder(pkgName, typeSpecBuilder.build())
                    .addFileComment(" This codes are generated automatically. Do not modify!")
                    .build();
            javaFile.writeTo(filer);
            System.out.print(javaFile);
            this.messager.printMessage(WARNING, javaFile.toString());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Value
    protected static class TypeAndName{
        private final String name;
        private final TypeName type;

        public TypeAndName(VariableElement variableElement) {
            this.name = variableElement.getSimpleName().toString();
            this.type = TypeName.get(variableElement.asType());

        }

        public TypeAndName(ExecutableElement executableElement) {
            this.name = getFieldName(executableElement.getSimpleName().toString());
            this.type = TypeName.get(executableElement.getReturnType());
        }

        private String getFieldName(String s) {
            String r = null;
            if (s.startsWith("get")){
                r = s.substring(3, s.length());
            }else if (s.startsWith("is")){
                r = s.substring(2, s.length());
            }else {
                r = s;
            }
            return r.substring(0, 1).toLowerCase() + r.substring(1, r.length());
        }
    }
}

BaseProcessor继承自AbstractProcessor,对通用行为进行封装。

@AutoService(Processor.class)
public class GenDTOProcessor extends BaseProcessor<GenDTO> {
    public GenDTOProcessor() {
        super(GenDTO.class);
    }

    @Override
    protected void foreachClass(GenDTO genDTO, Element element, RoundEnvironment roundEnv) {
        String className = "Base" + element.getSimpleName().toString() + "DTO";

        Set<TypeAndName> typeAndNames = Sets.newHashSet();
        // 获取元素中字段信息
        Set<TypeAndName> fields = findFields(element, filterForIgnore(GenDTOIgnore.class));
        typeAndNames.addAll(fields);

        // 获取元素中的Getter信息
        Set<TypeAndName> getters = findGetter(element, filterForIgnore(GenDTOIgnore.class));
        typeAndNames.addAll(getters);

        // 生成Java类, 类名为className, 添加Data注解,并使用public、abstract关键字描述
        TypeSpec.Builder typeSpecBuilder = TypeSpec.classBuilder(className)
                .addAnnotation(Data.class)
                .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT);

        // 生成构造函数
        MethodSpec.Builder cMethodSpecBuilder = MethodSpec.constructorBuilder()
                .addParameter(TypeName.get(element.asType()), "source")
                .addModifiers(Modifier.PROTECTED);

        for (TypeAndName typeAndName : typeAndNames) {
            // 声明BaseDomainDTO中的字段信息,Setter方法设置为private,Getter方法设置为public
            FieldSpec fieldSpec = FieldSpec.builder(typeAndName.getType(), typeAndName.getName(), Modifier.PRIVATE)
                    .addAnnotation(AnnotationSpec.builder(Setter.class)
                            .addMember("value", "$T.PRIVATE", AccessLevel.class)
                            .build())
                    .addAnnotation(AnnotationSpec.builder(Getter.class)
                            .addMember("value", "$T.PUBLIC", AccessLevel.class)
                            .build())
                    .build();
            typeSpecBuilder.addField(fieldSpec);

            String fieldName = typeAndName.getName().substring(0, 1).toUpperCase() + typeAndName.getName().substring(1, typeAndName.getName().length());

            // 构造函数中添加设置语句
            cMethodSpecBuilder.addStatement("this.set$L(source.get$L())", fieldName, fieldName);
        }

        // 将构造函数添加到类中
        typeSpecBuilder.addMethod(cMethodSpecBuilder.build());

        // 生成Java文件
        String pkgName = genDTO.pkgName();
        createJavaFile(typeSpecBuilder, pkgName);

    }
}

生成BaseDomainDTO的核心逻辑全部在GenDTOProcessor中,详见内部注解。

有一个点需要特殊注意,GenDTOProcessor类上添加了@AutoService(Processor.class)注解,这是google auto-service中的注解,用于完成Processor服务的自动注册,及在META-INF/services/javax.annotation.processing.Processor文件中添加com.example.annotation.dto.GenDTOProcessor配置内容。

4.1.4. 使用注解

根据实际需求,在对于的class中添加注解即可。

/**
 * 设置BaseTestEntityDTO所存在的包
 */
@GenDTO(pkgName = "com.example.annotation.dto")
public class TestEntity {
    private String name;
    private Long id;
    private Integer age;
    private Date birthday;

    /**
     * 忽略该字段
     */
    @GenDTOIgnore
    private List<String> address;

    public String getName() {
        return name;
    }

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

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public Integer getAge() {
        return age;
    }

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

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    @GenDTOIgnore
    public List<String> getAddress() {
        return address;
    }

    public void setAddress(List<String> address) {
        this.address = address;
    }
}

执行maven编译,自动生成BaseTestEntityDTO,并自动编译。
生成代码如下:

//  This codes are generated automatically. Do not modify!
package com.example.annotation.dto;

import com.example.annotation.entity.TestEntity;
import java.lang.Integer;
import java.lang.Long;
import java.lang.String;
import java.util.Date;
import java.util.List;
import lombok.AccessLevel;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;

@Data
public abstract class BaseTestEntityDTO {
  
  @Setter(AccessLevel.PRIVATE)
  @Getter(AccessLevel.PUBLIC)
  private Integer age;

  @Setter(AccessLevel.PRIVATE)
  @Getter(AccessLevel.PUBLIC)
  private Date birthday;

  @Setter(AccessLevel.PRIVATE)
  @Getter(AccessLevel.PUBLIC)
  private Long id;

  @Setter(AccessLevel.PRIVATE)
  @Getter(AccessLevel.PUBLIC)
  private String name;

  protected BaseTestEntityDTO(TestEntity source) {
    this.setAge(source.getAge());
    this.setBirthday(source.getBirthday());
    this.setId(source.getId());
    this.setName(source.getName());
  }
}

此时便可以据此创建TestEntityDTO。

@Data
public class TestEntityDTO extends BaseTestEntityDTO{
    private List<String> address;
    
    private TestEntityDTO(TestEntity source) {
        super(source);
        this.address = Lists.newArrayList(source.getAddress());
    }
}

对于需要特殊处理的属性,进行特殊处理。如address的copy。

4.2. Spring基于反射的RequestMapping

Spring一直是java注解的使用大户,特别是RUNTIME级别、基于反射的注解,几乎无处不在。

4.2.1. 设计目标

模拟SpringMVC中的RequestMapping注解,收集path与method的映射关系。

4.2.2. 自定义注解

自定义RequestMappiing注解。

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface RequestMapping {
    String path() default "";
}

Retention设置为RUNTIME,以通过反射获取注解信息。

4.2.3. 自定义注解处理器

public class RequestMappingParser<T> {
    private final Class<T> tClass;

    public RequestMappingParser(Class<T> tClass){
        this.tClass = tClass;
    }

    public Set<ParserResult> parse(){
        RequestMapping cRequestMapping = this.tClass.getAnnotation(RequestMapping.class);
        String rootPath = cRequestMapping != null ? cRequestMapping.path() : "";
        if (!rootPath.startsWith("/")){
            rootPath = "/" + rootPath;
        }

        Set<ParserResult> parserResults = Sets.newHashSet();
        for (Method method : this.tClass.getMethods()){
            RequestMapping mRequestMapping = method.getAnnotation(RequestMapping.class);
            if (mRequestMapping == null){
                continue;
            }

            String mPath = mRequestMapping.path();
            if ("".equals(mPath)){
                continue;
            }
            String path = null;
            if (mPath.startsWith("/")){
                path = rootPath + mPath;
            }else {
                path = rootPath + "/" + mPath;
            }
            parserResults.add(new ParserResult(path, method));
        }
        return parserResults;
    }


    @Data
    static class ParserResult{
        private final String path;
        private final Method method;
    }
}

4.2.4. 使用注解

@RequestMapping(path = "/root")
public class RequestMappingObject {

    @RequestMapping(path = "method")
    public void method(){

    }

    @RequestMapping(path = "method1")
    public void method1(){

    }

    @RequestMapping(path = "method2")
    public void method2(){

    }

    @RequestMapping(path = "method3")
    public void method3(){

    }

    @RequestMapping(path = "method4")
    public void method4(){

    }
}

4.2.5. 处理结果

public class RequestMappingTest {
    @Test
    public void print(){
        new RequestMappingParser<>(RequestMappingObject.class)
                .parse()
                .forEach(parserResult -> {
                    System.out.print(parserResult.getPath());
                    System.out.print("-->");
                    System.out.print(parserResult.getMethod());
                    System.out.println();
                });
    }
}

运行结果如下:

/root/method2-->public void com.example.annotation.reflect.RequestMappingObject.method2()
/root/method3-->public void com.example.annotation.reflect.RequestMappingObject.method3()
/root/method-->public void com.example.annotation.reflect.RequestMappingObject.method()
/root/method4-->public void com.example.annotation.reflect.RequestMappingObject.method4()
/root/method1-->public void com.example.annotation.reflect.RequestMappingObject.method1()

5. 总结

总体来说,注解从两个层面简化了硬编码的工作量,给一些反复工作提供了另一种解决方案。基于注解的代码生成器便是这个领域的一大利器;注解与反射的结合,可以在运行时获取注解元数据,给程序的动态扩展提供了一个想象空间,特别是Spring等框架,将其用到了极致。

文章所用代码已经上传至码云,如有需要请自行下载:https://gitee.com/litao851025/books/tree/master/annotation-demo

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,406评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,732评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,711评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,380评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,432评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,301评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,145评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,008评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,443评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,649评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,795评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,501评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,119评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,731评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,865评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,899评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,724评论 2 354

推荐阅读更多精彩内容