java 命名代码检查-注解处理器
命名代码检查
根据 <Java 语言规范( 第 3 版 ) > 中第6.8节的要求, Java 程序命名应当符合下列格式的书写规范:
- 类 ( 或接口 ) : 符合驼式命名法, 首字母大写.
- 方法 : 符合驼式命名法,首字母小写
- 字段 :
- 类或实例变量 : 符合驼式命名法 , 首字母小写
- 常量 : 要求全部有大写字母或下划线构成, 并且第一个字符不能是下划线.
要通过注解处理器的API 实现一个编译器插件 , 首先需要了解这组 API 的基本知识.我们实现注解处理器的代码需要继承抽象类 javax.annotation.processing.AbstractProcessor ,这个抽象类中只有一个必须覆盖的abstract方法 : "process()" 它是 javac 编译器在执行注解处理器代码时需要调用的过程 , 我们可以从这个方法的第一个参数 'annotations' 中获取到此注解处理器所要求处理的注解集合,从第二个参数 'roundEnv' 中访问到当前这个 round 中得语法树节点, 每个语法树节点在这里表示为一个 Element , 在 JDK1.6 新增的 javax.lang.model 包中定义了16类 Element , 包括了 Java 代码中最常用的元素,
如 : '包( PACKAGE ) , 枚举 ( ENUM ) , 类 ( CLASS ) , 注解 (ANNOTATION_TYPE) , 接口 (INTERFACE ) , 枚举值 ( ENUM_VARIABLE ) ,字段 ( FIELD ) , 参数 ( PARAMETER ) , 本地变量 ( LOCAL_VARIABLE ) , 异常 ( EXCEPTOIN_PARAMETER ) ,方法 ( METHOD ) , 构造函数 (CONSTRUCTOR ) 静态块语句 ( STATIC_INIT ,即 static {} 块 ) ,实例语句块 (INSTANCE_INIT, 即{}块) , 参数化类型 ( TYPE_PARAMETER ) , 和未定义的其他语法树节点 ( OTHER ) ;
除了 process () 方法的传入参数之外, 还有一个很常用的实例变量' processingEnv' ,它是 AbstractProcessor 中的一个 protected 变量, 在注解处理器代码可以直接访问到它,它代表了注解处理器框架提供了一个上下文环境,眼创建新的代码,向编译器输出信息,获取其他工具类等都需要用到这个实例变量,
注解处理器除了 process 方法及其参数外,还有两个可以配合使用的 Annotations :@SupportedAnnotationTypes 和@SupportedSourceVersion 前者代表这个注解处理器对哪些注解感兴趣,可以使用 '*' 作为通配符表示对所有的感兴趣.后者指出这个注解处理器可以处理哪些版本的 Java 代码.
每个注解处理器在运行的时候都是单例的.如果不需要改变生成语法树的内容, process() 方法就可以返回一个值为 false 的布尔值,通知编译器这个 round 中得代码未发生变化, 无需构造新的 JavaCompiler 实例,在这里只对程序命名进行检查 , 不需要改变语法树的内容, 因此process() 方法的返回值都是 false
实例如下:
AbstractProcessor
package annotation.processing; import java.util.EnumSet;
import java.util.Set; import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
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.ExecutableElement;
import javax.lang.model.element.Name;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.util.ElementScanner6;
import javax.tools.Diagnostic.Kind; // 使用*表示支持所有的Annotations
/**
* 以下代码出自 《深入理解Java虚拟机:JVM高级特性与最佳实践》
*
*/
@SupportedAnnotationTypes("*")
// 表示只对 JDK 1.6 的 Java 源码感兴趣
@SupportedSourceVersion(value = SourceVersion.RELEASE_6)
public class NameCheckProcessor extends AbstractProcessor {
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
this.nameCheck = new NameCheck(processingEnv);
} private NameCheck nameCheck; @Override
public boolean process(Set<? extends TypeElement> annotations,
RoundEnvironment roundEnv) {
if (!roundEnv.processingOver()) {
for (Element element : roundEnv.getRootElements()) {
nameCheck.check(element);
}
}
return false;
} /**
* 程序名称规范的编译器插件 如果程序命名不合规范,将会输出一个编译器的Warning信息
*
* @author kevin
*
*/
static class NameCheck {
Messager messager = null;
public NameCheckScanner nameCheckScanner; private NameCheck(ProcessingEnvironment processingEnv) {
messager = processingEnv.getMessager();
nameCheckScanner = new NameCheckScanner(processingEnv);
} /**
* 对Java程序明明进行检查,根据《Java语言规范(第3版)》6.8节的要求,Java程序命名应当符合下列格式:
* <ul>
* <li>类或接口:符合驼式命名法,首字母大写。
* <li>方法:符合驼式命名法,首字母小写。
* <li>字段:
* <ul>
* <li>类,实例变量:符合驼式命名法,首字母小写。
* <li>常量:要求全部大写
* </ul>
* </ul>
*
* @param element
*/
public void check(Element element) {
nameCheckScanner.scan(element);
} /**
* 名称检查器实现类,继承了1.6中新提供的ElementScanner6<br>
* 将会以Visitor模式访问抽象语法数中得元素
*
*
*/
static class NameCheckScanner extends ElementScanner6<Void, Void> {
Messager messager = null; public NameCheckScanner(ProcessingEnvironment processingEnv) {
this.messager = processingEnv.getMessager();
} /**
* 此方法用于检查Java类
*/
@Override
public Void visitType(TypeElement e, Void p) {
scan(e.getTypeParameters(), p);
checkCamelCase(e, true);
super.visitType(e, p);
return null;
} /**
* 检查传入的Element是否符合驼式命名法,如果不符合,则输出警告信息
*
* @param e
* @param b
*/
private void checkCamelCase(Element e, boolean initialCaps) {
String name = e.getSimpleName().toString();
boolean previousUpper = false;
boolean conventional = true;
int firstCodePoint = name.codePointAt(0);
if (Character.isUpperCase(firstCodePoint)) {
previousUpper = true;
if (!initialCaps) {
messager.printMessage(Kind.WARNING, "名称:" + name
+ " 应当以小写字母开头", e);
return;
}
} else if (Character.isLowerCase(firstCodePoint)) {
if (initialCaps) {
messager.printMessage(Kind.WARNING, "名称:" + name
+ " 应当以大写字母开头", e);
return;
}
} else {
conventional = false;
}
if (conventional) {
int cp = firstCodePoint;
for (int i = Character.charCount(cp); i < name.length(); i += Character
.charCount(cp)) {
cp = name.codePointAt(i);
if (Character.isUpperCase(cp)) {
if (previousUpper) {
conventional = false;
break;
}
previousUpper = true;
} else {
previousUpper = false;
}
}
}
if (!conventional) {
messager.printMessage(Kind.WARNING, "名称:" + name
+ "应当符合驼式命名法(Camel Case Names)", e);
}
} /**
* 检查方法命名是否合法
*/
@Override
public Void visitExecutable(ExecutableElement e, Void p) {
if (e.getKind() == ElementKind.METHOD) {
Name name = e.getSimpleName();
if (name.contentEquals(e.getEnclosingElement()
.getSimpleName())) {
messager.printMessage(Kind.WARNING, "一个普通方法:" + name
+ " 不应当与类名重复,避免与构造函数产生混淆", e);
checkCamelCase(e, false);
}
}
super.visitExecutable(e, p);
return null;
} /**
* 检查变量是否合法
*/
@Override
public Void visitVariable(VariableElement e, Void p) {
/* 如果这个Variable是枚举或常量,则按大写命名检查,否则按照驼式命名法规则检查 */
if (e.getKind() == ElementKind.ENUM_CONSTANT
|| e.getConstantValue() != null
|| heuristicallyConstant(e)) {
checkAllCaps(e);
} else {
checkCamelCase(e, false);
}
super.visitVariable(e, p);
return null;
} /**
* 大写命名检查,要求第一个字符必须是大写的英文字母,其余部分可以下划线或大写字母
*
* @param e
*/
private void checkAllCaps(VariableElement e) {
String name = e.getSimpleName().toString();
boolean conventional = true;
int firstCodePoint = name.codePointAt(0);
if (!Character.isUpperCase(firstCodePoint)) {
conventional = false;
} else {
boolean previousUnderscore = false;
int cp = firstCodePoint;
for (int i = Character.charCount(cp); i < name.length(); i += Character
.charCount(cp)) {
cp = name.codePointAt(i);
if (cp == (int) '_') {
if (previousUnderscore) {
conventional = false;
break;
}
previousUnderscore = true;
} else {
previousUnderscore = false;
if (!Character.isUpperCase(cp)
&& !Character.isDigit(cp)) {
conventional = false;
break;
}
} }
}
if (!conventional) {
messager.printMessage(Kind.WARNING, "常量:" + name
+ " 应该全部以大写字母" + "或下划线命名,并且以字符开头", e);
}
} /**
* 判断一个变量是否是常量
*
* @param e
* @return
*/
private boolean heuristicallyConstant(VariableElement e) {
if (e.getEnclosingElement().getKind() == ElementKind.INTERFACE) {
return true;
} else if (e.getKind() == ElementKind.FIELD
&& e.getModifiers()
.containsAll(
EnumSet.of(
javax.lang.model.element.Modifier.FINAL,
javax.lang.model.element.Modifier.STATIC,
javax.lang.model.element.Modifier.PUBLIC))) {
return true;
}
return false;
}
}
}
}
测试代码:
BADLY_NAMED_CODE
package annotation.processing;
public class BADLY_NAMED_CODE { enum Colors {
Red, Blue, Green;
}
static final int FORTY_TWO =42; public static int NOT_A_CONSTANT = FORTY_TWO; protected void Badly_Named_Code() {
return;
} public void NOTcamelCASEmethodNAME() {
return;
} }
执行过程如下:
bogon:Desktop mjorcen$ javac annotation/processing/BADLY_NAMED_CODE.java
bogon:Desktop mjorcen$ javac annotation/processing/NameCheckProcessor.java
bogon:Desktop mjorcen$ javac -processor annotation.processing.NameCheckProcessor annotation/processing/BADLY_NAMED_CODE.java
警告: 来自注释处理程序 'annotation.processing.NameCheckProcessor' 的受支持 source 版本 'RELEASE_6' 低于 -source '1.7'
annotation/processing/BADLY_NAMED_CODE.java:2: 警告: 名称:BADLY_NAMED_CODE应当符合驼式命名法(Camel Case Names)
public class BADLY_NAMED_CODE {
^
annotation/processing/BADLY_NAMED_CODE.java:5: 警告: 常量:Red 应该全部以大写字母或下划线命名,并且以字符开头
Red, Blue, Green;
^
annotation/processing/BADLY_NAMED_CODE.java:5: 警告: 常量:Blue 应该全部以大写字母或下划线命名,并且以字符开头
Red, Blue, Green;
^
annotation/processing/BADLY_NAMED_CODE.java:5: 警告: 常量:Green 应该全部以大写字母或下划线命名,并且以字符开头
Red, Blue, Green;
^
annotation/processing/BADLY_NAMED_CODE.java:9: 警告: 名称:NOT_A_CONSTANT 应当以小写字母开头
public static int NOT_A_CONSTANT = FORTY_TWO;
^
6 个警告
....
以上内容出自:
《深入理解Java虚拟机:JVM高级特性与最佳实践》
将这个处理器注册到 Eclipse 上,我建立如下 META-INF 文件:
META-INF/services/javax.annotation.processing.Processor:
annotation.processing.NameCheckProcessor
这里只包含了处理器实现类的类名。我不确定你是否可以在这里列出多个处理器。
就这样。现在导出一个 jar 文件,并且在你需要用到这个处理器的工程上导入这个文件。
第二步:建立一个使用你的处理器的工程
In the properties for your new project go to Java Compiler -> Annotation Processing
Check the “Enable Project Specific Settings” and make sure “Enable annotation processing” is checked. I also changed the generated source directory to a name which didn’t start with a dot so it wouldn’t be hidden in the package explorer (files or directories which start with a dot are by default filtered away in eclipse).在工程的属性中找到 Java Compiler -> Annotation Processing 查看 “Enable Project Specific Settings” 确认 “Enable annotation processing” 被选中。为了不让他在包浏览器中隐藏,我还修改了 generated source directory ,去掉了开始的点(Eclipse 会将文件名以点开始的文件或文件夹过滤掉)。
然后,转到 Java Compiler -> Annotation Processing -> Factory Path 你可以在这里导入处理器的 jar 文件。不可以使用工程引用。
点击 “Advanced” 按钮,会显示一个对话框,列出了 META-INF/services/javax.annotation.processing.Processor 文件中的内容。选择它并按OK。
第三步:Build!
完成了。这是在我的工程里显示的样子:
java 命名代码检查-注解处理器的更多相关文章
- [原创]Java静态代码检查工具介绍
[原创]Java静态代码检查工具介绍 一 什么是静态代码检查? 静态代码分析是指无需运行被测代码,仅通过分析或检查源程序的语法.结构.过程.接口等来检查程序的正确性,找出代码隐藏的错误和缺陷,如参数 ...
- 最近开始研究PMD(一款采用BSD协议发布的Java程序代码检查工具)
PMD是一款采用BSD协议发布的Java程序代码检查工具.该工具可以做到检查Java代码中是否含有未使用的变量.是否含有空的抓取块.是否含有不必要的对象等.该软件功能强大,扫描效率高,是Java程序员 ...
- FindBugs:Java 静态代码检查
在使用 Jenkins 构建 Java Web 项目时候,有一项叫做静态代码检查,是用内置的 findBugs 插件,对程序源代码进行检查,以分析程序行为的技术,应用于程序的正确性检查. 安全缺陷检测 ...
- 【开发技术】java中代码检查checkStyle结果分析
编写Javadoc代码在Java代码的类.函数.数据成员前中输入/**回车,Eclipse能够自动生成相应的Javadoc代码.可以在后面添加相关的文字说明. Type is missing a ja ...
- Java注解处理器(转)
Java中的注解(Annotation)是一个很神奇的东西,特别现在有很多Android库都是使用注解的方式来实现的.一直想详细了解一下其中的原理.很有幸阅读到一篇详细解释编写注解处理器的文章.本文的 ...
- Java注解处理器使用详解
在这篇文章中,我将阐述怎样写一个注解处理器(Annotation Processor).在这篇教程中,首先,我将向您解释什么是注解器,你可以利用这个强大的工具做什么以及不能做什么:然后,我将一步一步实 ...
- Java注解处理器
Java注解处理器 2015/03/03 | 分类: 基础技术 | 0 条评论 | 标签: 注解 分享到:1 译文出处: race604.com 原文出处:Hannes Dorfmann Java ...
- Java注解解析-搭建自己的注解处理器(CLASS注解使用篇)
该文章是继Java注解解析-基础+运行时注解(RUNTIME)之后,使用注解处理器处理CLASS注解的文章.通过完整的Demo例子介绍整个注解处理器的搭建流程以及注意事项,你将知道如何去搭建自己的注解 ...
- JAVA 插入注解处理器
JDK1.5后,Java语言提供了对注解(Annotation)的支持 JDK1.6中提供一组插件式注解处理器的标准API,可以实现API自定义注解处理器,干涉编译器的行为. 在这里,注解处理器可以看 ...
随机推荐
- 举例详解CSS中的继承
CSS的继承是由所使用的样式属性定义的.换句话说,当你查看CSS属性backgruound-color,你会看到一栏「继承性」,也许你几乎没有在意过它,但是它还是十分有用的.什么是CSS继承 每一个元 ...
- Oracle数据库中的Function调用参数问题
在工作中用到了Oracle数据库,需要调用Oracle的Function,Function返回的游标和结果都是通过参数来获取的 比如Function定义如下: , intype, ininttype) ...
- Objective-C特点
1.兼容性 OC是一种面向对象的C语言,在OC的代码中可以有C和C++语句,它可以调用C函数,也可以通过C++对象访问方法. 2.字符串 OC通常不使用C语言风格的字符串.大多数情况下(CoreFou ...
- SQL中查看数据库各表的大小
SQL中查看数据库各表的大小 编写人:CC阿爸 2014-6-17 在日常SQL数据库的操作中,如何快速的查询数据库中各表中数据的大小. 以下有两种方法供参考: 第一种: create table # ...
- mariadb一些命令介绍及mariadb架构图和索引
mariadb> SHOW GLOBAL VARIABLES; 全局变量影响服务器的全局操作 mariadb> SHOW [SESSION] VARIABLES; 客户端变量,只对当前会话 ...
- Win10下IIS配置图解、MVC项目发布图解、IIS添加网站图解
Win10下IIS配置 .找到控制面板:[开始]菜单鼠标右击,打开[控制面板] .打开控制面板,点击[程序],点击[启用或关闭Windows功能] 下一步,点击[启用虎关闭Windows功能] . 开 ...
- 9.css背景
这节也是关于盒模型的扩展,而我个人喜欢把盒模型想象成图画.元素的尺寸调整就是画布大小的调整,边框的设置就是画框的镶嵌.但是,作为一个最终要将画作展现到美术馆(网页)的艺术家来说,仅仅是这样还是不够的, ...
- static关键字的作用
static可以用来定义静态成员变量.静态函数.静态代码块. 静态成员变量的语法特点 定义方法:在成员变量前面加上static class Person{ static int i; //静态成员变量 ...
- afddaf
//import javax.swing.*; import javax.swing.JFrame; import javax.swing.JButton; import javax.swing.JL ...
- Oracle Database Links解析
什么是Database Links呢? 首先我们阐述下它的作用:使用户可以通过一个数据库访问到另外一个远程数据库. 那么Database Link是存储着远程数据库的连接信息. 如下图所示: 用户Sc ...