一、注解简介:

1.1.什么是“注解”:

​ 在我们编写代码时,一定看到过这样的代码:

class Student {
private String name; @Override
public String toString(String str) {//编译错误!
return "Student name = " + name;
}
}

​ 其中的@Override,就是一个“注解”,@Override一般出现在重写equals()或者toString()方法的上边,意思是告诉编译器:下边的代码是重写父类方法的。这时编译器会按照“重写”的语法严格检查下面的方法,如果不符合重写语法,将会编译错误。

​ "注解"作为一种“标记”,被写在源码中,不会改变程序的执行流程。它通常由“注解解析工具”来解析,而“注解解析器”可以随Java编译器启动,也可以独立启动,来解析注解,并以此可以做一些事情。

1.2.注解的分类:

源码注解:

​ 注解只在源码中,编译成class文件后就不存在了。

编译时注解:

​ 注解在源码和.class文件中都存在(如:JDK内置系统注解)

运行时注解:

​ 在运行阶段还起作用,甚至会影响运行逻辑的注解(如:JUnit的@Test)

1.3.注解的作用

​ 注解的作用非常广泛,注解可以被用在类、属性、构造方法、成员方法、局部变量等位置,用于对这些元素进行说明。由“注解解析工具”解析后,可以生成文档、进行代码分析、编译检查等。

​ 本例将会实现一个用作"编译检查“的注解,以及一个"注解解析器"。"注解解析器"将会随着javac编译器一同启动来对使用了注解的类进行编译,并检查类名、字段名、方法名是否以大写、小写字符开头,如果违反了规则,编译时将会报错。

二、自定义注解:

2.1.定义注解的基本语法

​ “注解”本质上是一个“类”,我们可以根据自己的需要定义自己的注解。

​ 定义注解的语法很简单:

public @interface CheckWord{
...
}

​ "注解”编译后会生成.class文件。但这是一个非常简单的注解,它可以被用在任何位置,而且编译器遇到这种注解也不做任何事情。例如:

@CheckWord
public class Student {
@CheckWord
public Student() {
} @CheckWord
private String name; @CheckWord
public void study() {
}
}

下面我们先使用“元注解”来规定这个注解可以被用在哪里。

2.2.元注解

​ “元注解”也是一种“注解”,它是已经实现好的。必须用在“注解”的定义上,它可以规定注解可以用在哪里,以及可以存在于源码中,或者class中,或者运行时。

常用的“元注解”有两个:

​ 1).@Target : 规定注解可以用在哪里。常用的取值被定义在枚举java.lang.annotation.ElementType中:

​ ElementType.TYPE:类和接口上

​ ElementType.FIELD: 用在成员变量上

​ ElementType.METHOD: 用在方法上

​ ElementType.PARAMETER: 用在参数上

​ ElementType.CONSTRUCTOR: 用在构造方法上

​ ElementType.LOCAL_VARIABLE: 用在局部变量上

​ 2).@Retention : 规定注解可以存在于哪里。常用的取值被定义在枚举java.lang.annotation.RetentionPolicy中:

​ RetentionPolicy.SOURCE: 规定注解只存在于Java源代码中, 编译生成的字节码文件中就不存在了。

​ RetentionPolicy.CLASS: 规定注解存在于Java源代码、 编译以后的字节码文件中, 但JVM运行时,不会被加载到内存。

​ RetentionPolicy.RUNTIME: 规定注解存在于Java源代码中、 编译以后的字节码文件中、 运行时内存中, 程序可以通过反射获取该注解。

​ 例如:修改我们的注解,规定它只能用在"类","字段",“方法”上,并且可以存在于“源码中”:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; @Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.SOURCE)
public @interface CheckWord { }

​ 如果再编译之前的Student类,会发现用在"构造方法"上的@CheckWord会编译错误,因为我们规定了它只能用在"类","字段","方法"上。

2.3.定义注解的属性:

1.“注解”中可以定义一些属性,“注解解析器”可以根据“属性”的不同,分别做不同的事情。

​ 例如@Target注解中的ElementType.TYPE就是此注解的一个属性,它是一个"枚举"类型。

​ 下面让我们来看看怎样定义属性,然后再解析这些属性。

​ 注解中定义属性的语法:数据类型 属性名() [deafult 值];

​ 1.其中“数据类型”可以是:

​ 1).所有基本类型;

​ 2).String;

​ 3).Class;

​ 4).枚举;

​ 5).注解;

​ 6).以上任一类型的数组

​ 2.属性名():属性名可以自由设定,要遵循Java标识符的命名规则;其中的一对()是必须的。

​ 3.[default 值]:为此属性设置的默认值。

2.本例中由于只检查大小写,为了规范取值,所以定义一个"枚举"类型的属性。

​ 1).先定义枚举:

public enum StartsWith {
UPPERCASE, LOWERCASE
}

​ 此枚举定义了两个值:UPPERCASE表示:大写;LOWERCASE表示:小写。

​ 2).修改"CheckWord"注解的代码:

@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.SOURCE)
public @interface CheckWord {
StartsWith value();
}

​ 说明:

​ a.StartsWith表示"数据类型",是一个"枚举"类型。

​ b.value表示"属性名",在使用此注解时,此属性的可取值只有StartsWith.UPPERCASE和StartsWith.LOWERCASE两个。

​ c.此属性没有设置"默认值",在使用此注解时必须要设置此属性的值。如下面的代码:

​ 3).修改"Student"类的代码:

@CheckWord(StartsWith.UPPERCASE)
public class Student {
@CheckWord(StartsWith.LOWERCASE)
private String stuName;
@CheckWord(StartsWith.LOWERCASE)
public void show() {
}
}

2.4注解解析器:

​ 1."注解解析器"通常是随着注解一起定义的,用于解析"注解",并做一些事情。本例的"注解解析器"用于与javac编译器一起启动,编译Student类时,检查各元素的名称是否按要求以指定的大写、小写字母开头。

​ 2.自定义"注解解析器"需要继承AbstractProcessor类,并重写process()方法,完整的"注解解析器"代码如下:

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic;
import java.util.Set; @SupportedAnnotationTypes("CheckWord")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class MyProcessor extends AbstractProcessor { @Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
//获取所有使用了@CheckWord注解的元素
Set<? extends Element> annoEle = roundEnv.getElementsAnnotatedWith(CheckWord.class);
// 遍历这些元素
for (Element e : annoEle) {
//获取元素名称,可能是:类名、属性名、方法名
String name = e.getSimpleName().toString();
//获取这个名字的第一个字母
char c = name.charAt(0);
//获取这个元素上的@CheckWord注解对象
CheckWord anno = e.getAnnotation(CheckWord.class);
//获取这个注解的value属性的值,它是一个StartsWith枚举类型
StartsWith sw = anno.value();
//判断属性值是否设置为:StartsWith.UPPERCASE,但名字的首字母是小写
if (sw == StartsWith.UPPERCASE && Character.isLowerCase(c)) {
//向控制台打印异常信息
this.processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "名称:" + name + " 首字母应该大写!");
return false;
}
//判断属性值是否设置为:StartsWith.LOWERCASE,但名字的首字母是大写
if (sw == StartsWith.LOWERCASE && Character.isUpperCase(c)) {
this.processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "名称:" + name + " 首字母应该小写!");
return false;
} }
return true;
}
}

​ 此代码的细节大家可以根据注释一点一点研究。一些类:TypeElement,RoundEnvironment,Element等的一些方法大家可以在API手册中查找。

​ 其它说明:

​ @SupportedAnnotationTypes("CheckWord") : 表示只处理CheckWord注解。

​ @SupportedSourceVersion(SourceVersion.RELEASE_8) : 表示支持JDK1.8。

2.5.编译和测试:

1.在编译前,我们看一下完整的代码清单:请确保以下的四个类在同一个目录下

​ 1).枚举类:

public enum StartsWith {
UPPERCASE, LOWERCASE
}

​ 2).自定义注解类:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; @Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.SOURCE)
public @interface CheckWord {
StartsWith value();
}

​ 3).使用了CheckWord注解的Student类:

@CheckWord(StartsWith.UPPERCASE)
public class Student {
@CheckWord(StartsWith.LOWERCASE)
private String StuName; @CheckWord(StartsWith.LOWERCASE)
public void show() { }
}

​ 4).注解解析器类:

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic;
import java.util.Set; @SupportedAnnotationTypes("CheckWord")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class MyProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
Set<? extends Element> annoEle = roundEnv.getElementsAnnotatedWith(CheckWord.class);
for (Element e : annoEle) {
String name = e.getSimpleName().toString();
char c = name.charAt(0); CheckWord anno = e.getAnnotation(CheckWord.class);
StartsWith sw = anno.value();
if (sw == StartsWith.UPPERCASE) {
if (Character.isLowerCase(c)) {
this.processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "名称:" + name + " 首字母应该大写!");
return false;
}
}
if (sw == StartsWith.LOWERCASE) {
if (Character.isUpperCase(c)) {
this.processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "名称:" + name + " 首字母应该小写!");
return false;
}
}
}
return true;
}
}

2.启动命令行,使用javac依次进行编译:

​ javac StartsWith.java

​ javac CheckWord.java

​ javac MyProcessor.java(如果报错: 编码GBK的不可映射字符,是因为代码中的中文,可以使用javac -encoding UTF-8 MyProcessor.java进行编译)

​ 接下来使用MyProcessor解析器编译Student:

​ javac -processor MyProcessor Student.java

​ 执行命令后,会有错误提示:

​ 错误: 名称:StuName 首字母应该小写!

​ 1 个错误

三、总结:

​ 源码级注解的应用非常广泛,例如:进行代码检查、生成新类、生成文件。本文实现了基本的代码检查,用于检查类中的元素是否按照要求进行首字母大写或者小写。也可以根据需要,验证是否全部大写,或者全部小写。希望大家通过本案例能够了解源码级注解的编写及使用。

关于JAVA中源码级注解的编写及使用的更多相关文章

  1. 浅谈java中源码常见的几个关键字(native,strictfp,transient,volatile)

    最近看源码总发现一些没见过的关键字,今天就来整理一下native,strictfp,transient,volatile native 本地 native是与C++联合开发的时候用的!java自己开发 ...

  2. 【Java编程实战】Metasploit_Java后门运行原理分析以及实现源码级免杀与JRE精简化

    QQ:3496925334 文章作者:MG1937 CNBLOG博客ID:ALDYS4 未经许可,禁止转载 某日午睡,迷迷糊糊梦到Metasploit里有个Java平台的远控载荷,梦醒后,打开虚拟机, ...

  3. MapReduce的ReduceTask任务的运行源码级分析

    MapReduce的MapTask任务的运行源码级分析 这篇文章好不容易恢复了...谢天谢地...这篇文章讲了MapTask的执行流程.咱们这一节讲解ReduceTask的执行流程.ReduceTas ...

  4. TaskTracker任务初始化及启动task源码级分析

    在监听器初始化Job.JobTracker相应TaskTracker心跳.调度器分配task源码级分析中我们分析的Tasktracker发送心跳的机制,这一节我们分析TaskTracker接受JobT ...

  5. 【转】Java HashMap 源码解析(好文章)

    ­ .fluid-width-video-wrapper { width: 100%; position: relative; padding: 0; } .fluid-width-video-wra ...

  6. 源码级强力分析hadoop的RPC机制

    分析对象: hadoop版本:hadoop 0.20.203.0 必备技术点: 1. 动态代理(参考 :http://weixiaolu.iteye.com/blog/1477774 )2. Java ...

  7. Java集合源码分析(四)Vector<E>

    Vector<E>简介 Vector也是基于数组实现的,是一个动态数组,其容量能自动增长. Vector是JDK1.0引入了,它的很多实现方法都加入了同步语句,因此是线程安全的(其实也只是 ...

  8. Java集合源码分析(三)LinkedList

    LinkedList简介 LinkedList是基于双向循环链表(从源码中可以很容易看出)实现的,除了可以当做链表来操作外,它还可以当做栈.队列和双端队列来使用. LinkedList同样是非线程安全 ...

  9. Java集合源码分析(二)ArrayList

    ArrayList简介 ArrayList是基于数组实现的,是一个动态数组,其容量能自动增长,类似于C语言中的动态申请内存,动态增长内存. ArrayList不是线程安全的,只能用在单线程环境下,多线 ...

随机推荐

  1. less实现if else

    less没有我们平常使用的if,else条件判断,而是用when来实现这种用法 1.比如我们要设置宽度 宽度可以百分比,也可以是像素,当是百分比时做对应处理,当是px时做另一种处理,这时候就需要用wh ...

  2. springmvc接收json数据的常见方式

    经常使用Ajax异步请求来进行数据传输,传的数据是json数据,json数据又有对象,数组.所有总结下springmvc获取前端传来的json数据方式:1.以RequestParam接收前端传来的是j ...

  3. hdfs/hbase 程序利用Kerberos认证超过ticket_lifetime期限后异常

    问题描述 业务需要一个长期运行的程序,将上传的文件存放至HDFS,程序启动后,刚开始一切正常,执行一段时间(一般是一天,有的现场是三天),就会出现认证错误,用的JDK是1.8,hadoop-clien ...

  4. 【Spring Boot 源码解读】之 【为何引入了 Jedis 依赖最后用的还是 Lettuce 客户端?】

    1.Spring Boot 2.x 的两种 Redis 客户端 首先,我们都知道,从 Spring Boot 2.x 开始 Lettuce 已取代 Jedis 成为首选 Redis 的客户端.当然 S ...

  5. [apue] 作为 daemon, 启动 Unix Domain Socket 侦听失败?

    前段时间写一个传递文件句柄的小 demo,有 server 端.有 client 端,之间通过 Unix Domain Socket 通讯. 在普通模式下,双方可以正常建立连接,当server端作为d ...

  6. UAF——use after free

    本文系pwn2web原创,转载请说明出处 UAF 漏洞,英文原名use after free,该漏洞简洁的可以概括为 分配一块内存 free该内存但不回收,构成悬垂指针 再次构造分配同样大小的内存,按 ...

  7. maven-assembly-plugin入门

    愿文地址:https://www.jianshu.com/p/e8585a991e8e 当你使用 Maven 对项目打包时,你需要了解以下 3 个打包 plugin,它们分别是 plugin func ...

  8. XSS Cheat Sheet

    Basic and advanced exploits for XSS proofs and attacks. Work in progress, bookmark it. Technique Vec ...

  9. 缓冲区溢出实例(二)--Linux

    原理:crossfire 1.9.0 版本接受入站 socket 连接时存在缓冲区溢出漏洞. 工具: 调试工具:edb: ###python在漏洞溢出方面的渗透测试和漏洞攻击中,具有很大的优势   实 ...

  10. spring mvc 框架运行机制 + 数据绑定原理

    spring mvc 运行主要的组件: 1 前端控制器 (dispatchservlet) 相当于一个重要处理器,它用来调用其他功能模块来分工的效应一次请求,主要起调度的作用. 2. handler ...