最近收到友人的委托,帮忙研究下spoon,尝试下用spoon来实现一个工具类的检查,于是做了个demo,以供参考。
首先说下工具类的一些规范约束,又或者是最佳实践,以下是checkstyle网站上面的定义:
http://checkstyle.sourceforge.net/config_design.html#HideUtilityClassConstructor
HideUtilityClassConstructor Description
Since Checkstyle 3.1
Makes sure that utility classes (classes that contain only static
methods or fields in their API) do not have a public constructor.Rationale: Instantiating utility classes does not make sense. Hence
the constructors should either be private or (if you want to allow
subclassing) protected. A common mistake is forgetting to hide the
default constructor.If you make the constructor protected you may want to consider the
following constructor implementation technique to disallow
instantiating subclasses:public class StringUtils // not final to allow subclassing {
protected StringUtils() {
// prevents calls from subclass
throw new UnsupportedOperationException();
}public static int count(char c, String s) { // ... } }
其实宗旨很简单,对于一个工具类来说最好的做法是:
不需要一个公共的构造方法(并没有任何理由去实例化一个工具类),如何做?
1)隐藏默认的构造方法(如果在类的修饰前是public 则默认构造函数访问权限是 public ,如果 没有显示采用public修饰,则 默认构造函数的访问权限是 friendly),将其设置为private
2)如果构造方法是protected的,则建议在构造方法里面抛出异常,以防止子类被实例化
protected StringUtils() {
// prevents calls from subclass
throw new UnsupportedOperationException();
}
同时也科普下访问权限的作用域:
下图引用自博文:
http://blog.csdn.net/fatherican/article/details/6876596
那么,什么是spoon?上官网介绍:
http://spoon.gforge.inria.fr/architecture_test.html
Spoon is a library to analyze, transform, rewrite, transpile Java source code (incl Java 8). It parses source files to build a well-designed AST with powerful analysis and transformation API.
简单滴说,就是使用了抽象语法树AST(abstract syntax tree)的方式去拆解一个java源码(有点像反射机制做的东西?),并提取里面的元素,后续做一些我们想要做的操作,例如代码格式检查,xxx,xxx,xxx(反正我只用到格式检查)….
然后看到关于codean alysis的介绍:
http://spoon.gforge.inria.fr/first_analysis_processor.html
不错,这正是我想要的,我看了下例子,主要说的是检查try {…} catch {…}代码块里面的catch代码块是不是没有做任何的处理,那既然能检查try catch代码块,是不是也能检查方法的作用域呢?我把 spoon的jar包下了下来,反编译看了下源码,果然有!
只要在processor的类上集成了AbstractProcessor ,注意枚举类型是CtClass的话,就会在解析到构造函数的时候,就会触发processor的处理
public class ClassProcessor extends AbstractProcessor<CtClass>
接着完善processor,实现父类的方法public void process(CtClass paramE),然后后用paramE.hasModifier来判断方法的作用域,是不是很简单?! So easy! 妈妈再也不用担心我的代码不规范了!
/**
* If this class is only a utility class, you should make the class final and define a private constructor
*/
@Override
public void process(CtClass paramE) {
System.out.println(">>>>>>>>> Start to check the [" + paramE.getActualClass().getName() +"] Class definition <<<<<<<<<<<<<<");
if(!paramE.hasModifier(ModifierKind.FINAL)) {
System.out.println("Utility Class is recommanded to be defined as FINAL!!!");
System.out.println();
}else {
System.out.println("Utility Class is FINAL now. : )");
}
System.out.println(">>>>>>>>> Start to check the Class definition <<<<<<<<<<<<<<");
Set set = paramE.getConstructors();
int counter = 0;
for(CtConstructor constructor : set) {
if(constructor.hasModifier(ModifierKind.PUBLIC) || constructor.hasModifier(ModifierKind.PROTECTED)) {
getFactory().getEnvironment().report(this, Level.WARN, paramE, "Utility Class Constructor is accessible!");
counter ++;
System.out.println("------------------- " +counter+ " ---------------------");
System.out.println("Utility Class Constructor is accessible! It should be defined as private! Constructor is :");
System.out.println(constructor.toString());
System.out.println("------------------------------------------");
}
}
}
接着在main函数调用new Launcher().run(params), 就大功告成了
public static void main( String[] args )
{
String[] params = {"-i","J:\\eclipse-workspace\\Spoon\\src\\main\\java\\com\\nathan\\Spoon\\Utils","-p","com.nathan.Spoon.ClassProcessor"};
new Launcher().run(params);
}
至于new Launcher().run参数的定义,可以参考源代码可以看到
/* */ public void printUsage() {
/* 121 */ this.commandLineArgs = new String[] { "--help" };
/* 122 */ processArguments();
/* */ }
可以输入help来查看参数定义,很贴心有木有,心都要化了:
String[] params = {"--help"};
new Launcher().run(params);
结果很清晰了,就是说,你需要输入什么参数,首先在前面告知你需要输入参数的类型,如
[(-i|–input) ]
List of path to sources files.
[(-p|–processors) ]
List of processor’s qualified name to be used.:
Spoon version 5.7.0
Usage: java name> [option(s)]
Options :
[-h|--help]
[--tabs]
Use tabulations instead of spaces in the generated code (use spaces by
default).
[--tabsize ]
Define tabulation size. (default: 4)
[--level ]
Level of the ouput messages about what spoon is doing. Default value is
ALL level. (default: OFF)
[--with-imports]
Enable imports in generated files.
[--compliance ]
Java source code compliance level (1,2,3,4,5, 6, 7 or 8). (default: 8)
[--encoding ]
Forces the compiler to use a specific encoding (UTF-8, UTF-16, ...).
(default: UTF-8)
[(-i|--input) ]
List of path to sources files.
[(-p|--processors) ]
List of processor's qualified name to be used.
[(-t|--template) ]
List of path to templates java files.
[(-o|--output)
Specify where to place generated java files. (default: spooned)
[--source-classpath ]
An optional classpath to be passed to the internal Java compiler when
building or compiling the input sources.
[--template-classpath ]
An optional classpath to be passed to the internal Java compiler when
building the template sources.
[(-d|--destination) ]
An optional destination directory for the generated class files.
(default: spooned-classes)
[--output-type ]
States how to print the processed source code:
nooutput|classes|compilationunits (default: classes)
[--compile]
Compiles the resulting classes (after transformation) to bytecode.
[--precompile]
[experimental] Enable pre-compilation of input source files before
processing. The compiled classes will be added to the classpath.
[--buildOnlyOutdatedFiles]
Set Spoon to build only the source files that have been modified since
the latest source code generation, for performance purpose. Note that
this option requires to have the --ouput-type option not set to none.
This option is not appropriate to all kinds of processing. In particular
processings that implement or rely on a global analysis should avoid
this option because the processor will only have access to the outdated
source code (the files modified since the latest processing).
[--lines]
Set Spoon to try to preserve the original line numbers when generating
the source code (may lead to human-unfriendly formatting).
[-x|--noclasspath]
Does not assume a full classpath
[-g|--gui]
Show spoon model after processing
[-r|--no-copy-resources]
Disable the copy of resources from source to destination folder.
[-c|--enable-comments]
Adds all code comments in the Spoon AST (Javadoc, line-based comments),
rewrites them when pretty-printing.
[(-f|--generate-files) ]
Only generate the given fully qualified java classes (separated by ':'
if multiple are given).
[-a|--disable-model-self-checks]
Disables checks made on the AST (hashcode violation, method's signature
violation and parent violation). Default: false.
接下来我们看一下代码的执行效果,console的输出,格式不太好看,凑合凑合:
>>>>>>>>> Start to check the [com.nathan.Spoon.Utils.DateUtil] Class definition <<<<<<<<<<<<<<
Utility Class is recommanded to be defined as FINAL!!!
>>>>>>>>> Start to check the Class definition <<<<<<<<<<<<<<
------------------- 1 ---------------------
Utility Class Constructor is accessible! It should be defined as private! Constructor is :
public DateUtil() {
super();
}
------------------------------------------
------------------- 2 ---------------------
Utility Class Constructor is accessible! It should be defined as private! Constructor is :
public DateUtil(java.lang.String utilName) {
super();
this.utilName = utilName;
}
------------------------------------------
>>>>>>>>> Start to check the [com.nathan.Spoon.Utils.DateUtil2] Class definition <<<<<<<<<<<<<<
Utility Class is FINAL now. : )
>>>>>>>>> Start to check the Class definition <<<<<<<<<<<<<<
------------------- 1 ---------------------
Utility Class Constructor is accessible! It should be defined as private! Constructor is :
protected DateUtil2() {
super();
}
------------------------------------------
最后附上源代码,学无止境,不要在不可以做更多事情的时候却什么都不做,共勉!
Spoon demo的 githun地址,点我点我!