初探Spoon(附上用spoon检查HideUtilityClassConstructor)

来源:互联网 发布:sql设置字段默认值 编辑:程序博客网 时间:2024/06/06 00:19

最近收到友人的委托,帮忙研究下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. 所有方法都是静态的
  2. 不需要一个公共的构造方法(并没有任何理由去实例化一个工具类),如何做?
    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.htmlSpoon 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<CtConstructor> 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.0Usage: java <launcher name> [option(s)]Options :   [-h|--help]  [--tabs]        Use tabulations instead of spaces in the generated code (use spaces by        default).  [--tabsize <tabsize>]        Define tabulation size. (default: 4)  [--level <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 <compliance>]        Java source code compliance level (1,2,3,4,5, 6, 7 or 8). (default: 8)  [--encoding <encoding>]        Forces the compiler to use a specific encoding (UTF-8, UTF-16, ...).        (default: UTF-8)  [(-i|--input) <input>]        List of path to sources files.  [(-p|--processors) <processors>]        List of processor's qualified name to be used.  [(-t|--template) <template>]        List of path to templates java files.  [(-o|--output) <output>]        Specify where to place generated java files. (default: spooned)  [--source-classpath <source-classpath>]        An optional classpath to be passed to the internal Java compiler when        building or compiling the input sources.  [--template-classpath <template-classpath>]        An optional classpath to be passed to the internal Java compiler when        building the template sources.  [(-d|--destination) <destination>]        An optional destination directory for the generated class files.        (default: spooned-classes)  [--output-type <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) <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地址,点我点我!

原创粉丝点击