Maven插件开发(二)

在上一篇博客中我们讲解了简单的自定义插件开发工作,今天我们继续讲解一下自定义插件开发中可能涉及到的其他内容。比如传参、自定义类加载、扩展maven-compiler-plugin的processor等功能。

在前面一篇博客中我们简单入门了解了Maven插件开发的相关流程,具体操作是读取文件然后显示出来。如果忘了,可以先去复习一下——自定义Maven插件开发(一)

在实际应用中我们可以通过maven插件结合模板引擎来生成代码。当然我们也可以通过maven插件来生成一些额外的代码,比如,像querydsl这种来生成带Q的实体类对象等。

还是直接实战更快,比如这里我们通过使用FreeMarker在指定的目录下生成基本的entity实体对象。

一、FreeMarker

既然使用FreeMarker来实际操作,那么在使用之前,我们就来介绍一下什么是FreeMarker。

在官方文档介绍中说FreeMarker是一款模板引擎: 即一种基于模板和要改变的数据, 并用来生成输出文本(HTML网页,电子邮件,配置文件,源代码等)的通用工具。 它不是面向最终用户的,而是一个Java类库,是一款程序员可以嵌入他们所开发产品的组件。

也就是类似于Python中的Jinja2,PHP中的Smarty这类工具。

模板编写为FreeMarker Template Language (FTL)。它是简单的,专用的语言, 不是像PHP那样成熟的编程语言。 那就意味着要准备数据在真实编程语言中来显示,比如数据库查询和业务运算, 之后模板显示已经准备好的数据。在模板中,你可以专注于如何展现数据, 而在模板之外可以专注于要展示什么数据。

freemarker

因为我们的重点内容不是FreeMarker,所以关于更多的内容与语法可以参考官网

二、实战

接下来我们还是直接实战,新建code-maven-plugin项目,类似第一节这里就不多说了。因为我们需要通过FreeMarker去生成模板代码,所以在resources下新建entity.ftl文件用于代码模板生成。

这里entity.ftl的文件很简单,就是一个简单的Entity类,set/get方法则是通过lombok生成。代码如下:

package ${config.pkg}.${config.entityPkg};

import lombok.Data;
import lombok.EqualsAndHashCode;

@Data
@EqualsAndHashCode(callSuper = false)
public class ${entityName} {

    <#list params as param>
        private ${param.fieldType} ${param.fieldName};

    </#list>

}

这里我们就是简单使用了一下FreeMarker的基本自定义参数,当然FreeMarker的用法也非常多,还可以实现自定义函数等多种用法,可以具体查看文档

接着我们编写CodeMojo代码,其实和上一章差不多,只是这里我们将FreeMarker结合起来了。代码如下:

package net.anumbrella;


import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import org.apache.commons.io.FileUtils;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;

import java.io.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static freemarker.template.Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS;


@Mojo(name = "code", defaultPhase = LifecyclePhase.GENERATE_SOURCES)
public class CodeMojo extends AbstractMojo {

    private static final Configuration FREE_MARKER_CONFIGURATION = new Configuration(DEFAULT_INCOMPATIBLE_IMPROVEMENTS);


    private String basePkgDir;


    @Parameter(defaultValue = "${project}", required = true, readonly = true)
    MavenProject project;

    @Parameter
    private Config config;


    public void execute() throws MojoExecutionException, MojoFailureException {
        initParams();
        generatorCode();
    }

    private void initParams() {
        basePkgDir = project.getBuild().getSourceDirectory() + File.separator + (config.getPkg() + ".").replaceAll("\\.", "/");
    }

    private void generatorCode() throws MojoFailureException {
        try {
            String name = "entity";
            InputStream source = getTemplate(name);
            StringWriter writer = new StringWriter();
            File targetFile = new File(basePkgDir + File.separator + config.getEntityPkg().toLowerCase(), "Person.java");
            Template template = new Template(name, new InputStreamReader(source, "utf-8"), FREE_MARKER_CONFIGURATION);

            Map<String, Object> data = new HashMap<String, Object>();
            data.put("entityName", "Person");
            List<Map<String, String>> paramsList = new ArrayList<Map<String, String>>();
            Map<String, String> IdMap = new HashMap<String, String>();
            IdMap.put("fieldType", "Integer");
            IdMap.put("fieldName", "id");
            paramsList.add(IdMap);
            Map<String, String> nameMap = new HashMap<String, String>();
            nameMap.put("fieldType", "String");
            nameMap.put("fieldName", "username");
            paramsList.add(nameMap);
            Map<String, String> passwordMap = new HashMap<String, String>();
            passwordMap.put("fieldType", "String");
            passwordMap.put("fieldName", "password");
            paramsList.add(passwordMap);
            data.put("params", paramsList);
            data.put("config", config);
            template.process(data, writer);
            String content = writer.getBuffer().toString();
            FileUtils.writeStringToFile(targetFile, content, "utf-8", false);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (TemplateException e) {
            e.printStackTrace();
        }
    }

    private InputStream getTemplate(String type) {
        String name = type + ".ftl";
        return this.getClass().getResourceAsStream("/" + name);
    }
}

其中@Parameter这个是在插件代码中使用的参数,这个是可以使用Maven中内置参数的,也就是不需要我们手动传入的。我们知道

Maven共有6类属性:

内置属性(Maven预定义,用户可以直接使用)

  • ${basedir}表示项目根目录,即包含pom.xml文件的目录;

  • ${version}表示项目版本;

  • ${project.basedir}${basedir};

  • ${project.baseUri}表示项目文件地址;

  • ${maven.build.timestamp}表示项目构件开始时间;

  • ${maven.build.timestamp.format}表示属性${maven.build.timestamp}的展示格式,默认值为yyyyMMdd-HHmm;

POM属性(使用pom属性可以引用到pom.xml文件对应元素的值)

  • ${project.build.directory}表示主源码路径;

  • ${project.build.sourceEncoding}表示主源码的编码格式;

  • ${project.build.sourceDirectory}表示主源码路径;

  • ${project.build.finalName}表示输出文件名称;

  • ${project.version}表示项目版本,与${version}相同;

比如@Parameter(property = "project.basedir", readonly = true),就是表示项目的具体地址,更多其他Maven属性可以查看Maven属性文档,其他灵活用法也可以参考官方的示例

当然plugin中还有很多其他的注解,关于插件中其他注解信息可以查看文档

如下面我们传入一个自定义的Config对象,Cofig类如下:

package net.anumbrella;

/**
 * @auther anumbrella
 */

public class Config {

    private String pkg;

    /**
     * entity的包名
     */
    private String entityPkg = "entity";


    public String getPkg() {
        return pkg;
    }

    public void setPkg(String pkg) {
        this.pkg = pkg;
    }

    public String getEntityPkg() {
        return entityPkg;
    }

    public void setEntityPkg(String entityPkg) {
        this.entityPkg = entityPkg;
    }
}
 @Parameter
 private Config config;

在插件里面,我们有一个通过对象传参的情况,这种情况在pom.xml里面,需要如下配置即可。

<config>
  <pkg>test</pkg>
  <entityPkg>model</entityPkg>
</config>

同样的,我们执行mvn clean install,然后再具体项目中引入plugin。

如下,我在另一个项目中使用自定义的插件,在pom.xml中引入。

    <plugin>
         <groupId>net.anumbrella</groupId>
         <artifactId>code-maven-plugin</artifactId>
         <version>1.0-SNAPSHOT</version>
         <configuration>
             <config>
                 <pkg>net.anumbrella.oauth2</pkg>
             </config>
         </configuration>
     </plugin>

因为我的项目包命名为net.anumbrella.oauth2,所以这里传入的参数为具体包名。制定好plugin配置信息,然后执行mvn code:code
mvn

可以发现在entity目录下生成了我们预先配置好的Person.java文件。
code

这里只是简单生成了一个代码,比如我们可以扩展根据连接数据库查询到相关表,然后表的字段映射处理,就可以生成需要的Entity类了。同时对于基础框架,也可以定义好项目框架结构直接生成相关代码,比如service层,rest层,dao层等。

三、扩展
1、自定义ClassLoader

比如我们这里像要实现querydsl这种在每个entity类中,去扫描获取到带有注解@Entity的类,然后生成带Q的entity。这个要如何实现,也许你会说我们能到获取到项目地址,然后扫描类根据拿到的当前类去操作,比如Class.getAnnotations()这种方法。
比如,还有我们想获取多个继承最顶层的泛型的类型,一般我们会使用

     ParameterizedType parameterizedType = (ParameterizedType) temp.getGenericSuperclass();
     Type[] actualType = parameterizedType.getActualTypeArguments();

去获取父类泛型定义。但是很遗憾maven插件在执行时,所有类并没有加载到JVM里面,所以你没法通过class去操作一些列想要的办法。

那怎么办?这里我们使用URLClassLoader,因为我们能够获取到目录下的文件,所以我们手动加载到JVM里面去。

我们在plugin里面初始化变量时,初始化ClassLoader。


    @Parameter(defaultValue = "${project.compileClasspathElements}", readonly = true, required = true)
    private List<String> compilePath;
    
    @Parameter(defaultValue = "${plugin}", readonly = true)
    PluginDescriptor pluginDescriptor;

   // 初始化classloader
    List<URL> pathUrls = new ArrayList<>();
    for (String mavenCompilePath : project.getCompileClasspathElements()) {
        pathUrls.add(new File(mavenCompilePath).toURI().toURL());
    }
    URL[] urlsForClassLoader = pathUrls.toArray(new URL[pathUrls.size()]);
    classloader = new URLClassLoader(urlsForClassLoader, Thread.currentThread().getContextClassLoader());

同时需要加入上面两个@Parameter,但是有时候可能依赖不能完全加载,所以还得更改Mojo如下:

@Mojo(name = "code", defaultPhase = LifecyclePhase.GENERATE_SOURCES, threadSafe = true,
        requiresDependencyResolution = ResolutionScope.COMPILE

主要是为了添加requiresDependencyResolution来解决依赖不能加载情况。

2、将插件绑定在生命周期

每次我们如果要执行生成代码,除了在命令中执行mvn code:code外,我们还可以将plugin绑定到maven的生命执行周期中去。比如:

   <plugin>
       <groupId>net.anumbrella</groupId>
       <artifactId>code-maven-plugin</artifactId>
       <version>1.0-SNAPSHOT</version>
       <configuration>
           <config>
               <pkg>net.anumbrella.oauth2</pkg>
           </config>
       </configuration>
       <executions>
           <execution>
               <id>code</id>
               <goals>
                   <goal>code</goal>
               </goals>
               <phase>compile</phase>
           </execution>
       </executions>
   </plugin>

这里我们将code代码生成绑定在maven的compile生命周期中,所以每次执行mvn compile就都会执行plugin命令操作。

由于篇幅过长,扩展maven-compiler-plugin的processor的功能将在下章节讲解。

代码实例:Chapter2

参考

  • http://maven.apache.org/plugin-developers/index.html
  • http://freemarker.foofun.cn/index.html
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值