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

最近收到友人的委托,帮忙研究下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.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<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.0
Usage: 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地址,点我点我!

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值