自定义maven插件,在项目中命令启动springboot并加载当前项目资源
背景
最近在制定团队内公用的基础框架,基于单应用多module的架构思路,使用maven管理项目依赖,在项目中定义了一个springboot模块,该模块依赖具体的业务实现模块,启动后通过扫描路径下的类加载服务,业务开发同事只需要开发具体业务模块即可。
但是在项目管理时,不期望业务开发同事关心和修改基础框架。最开始的做法是让业务开发同事在项目的module管理模块下新建业务module模块(见下描述),在不同分支开发不同的业务,开发自测的时候,需要在springboot模块中依赖具体业务module并启动。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| gm-admin --------------------------------管理后台(springboot)服务,依赖gm-modules中的具体实现 gm-common --gm-common-core ----------------------基础包,含最基础的基类、工具类、异常类等 --gm-common-log ----------------------日志实现包,通过注解,记录web调用参数和结果 --gm-common-ratelimit -----------------限流器实现,若需对用户进行限制则需要依赖gm-common-security模块 --gm-common-redis ---------------------redis缓存依赖和分布式锁工具类 --gm-common-security ------------------基础安全模块,校验和设置用户信息、权限 --gm-common-sftp ----------------------sftp工具 --gm-common-storage -------------------对象存储实现,目前支持本地存储和腾讯oss gm-framework ----------------------------框架模块,主要实现数据源注入等 gm-gateway ------------------------------网关服务,实现报文加解密、限流、路由等 gm-modules --gm-mall -----------------------------商城模块 --gm-partner --------------------------合伙人项目使用,非商城内容 --gm-system ---------------------------系统管理模块,实现系统用户、角色、权限、菜单、机构等业务逻辑 --gm-third ----------------------------第三方服务模块,实现对第三方服务的调用,如短信、积分 --gm-wechat ---------------------------微信模块,实现微信用户授权、生成小程序码等与微信交互逻辑
|
这样的开发模式导致业务开发的同事实际上需要在“框架项目”中进行业务开发,虽然采用了module进行划分,但是在开发中遇到问题总会尝试或者难免会对框架代码进行修改、优化,最终导致冲突等问题。而我想达到的效果是“框架代码”对业务开发同事尽量透明,类似于之前使用dapeng soa框架开发应用时一样,不需要关心容器是怎么跑起来的,只需要关心本业务自身的业务和依赖。
大体思路是:开发一个自定义maven插件,将业务代码在单独的项目中进行开发,需要启动项目时,在项目目录下执行mvn命令,执行maven插件,这个插件会将当前项目的(类)资源和依赖的依赖包添加到类加载器,并启动springboot项目,实现在springboot项目启动当前项目的目的。
实现
具体代码步骤如下供参考:
Maven插件项目pom配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| <packaging>maven-plugin</packaging> <name>gm-maven-plugin</name>
<dependencies> ... <!-- SpringBoot容器 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> <version>2.5.10</version> </dependency> <dependency> <groupId>org.apache.maven</groupId> <artifactId>maven-core</artifactId> <version>3.5.2</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.apache.maven</groupId> <artifactId>maven-plugin-api</artifactId> <version>3.5.2</version> </dependency> <dependency> <groupId>org.apache.maven.plugin-tools</groupId> <artifactId>maven-plugin-annotations</artifactId> <version>3.5.2</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.apache.maven</groupId> <artifactId>maven-project</artifactId> <version>2.2.1</version> </dependency>
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-plugin-plugin</artifactId> <version>3.5</version> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.6.1</version> <configuration> <source>1.8</source> <target>1.8</target> </configuration> </plugin> </plugins> </build> </dependencies>
|
关键代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| @Mojo(name = "run", threadSafe = true, requiresDependencyResolution = ResolutionScope.TEST) public class GmMavenPlugin extends AbstractMojo { /** * 获取项目编译环境类路径 */ @Parameter(defaultValue = "${project}", readonly = true) protected MavenProject project;
@Override public void execute() throws MojoExecutionException {
try { // 获取应用程序的 classpath List<String> classpathElements = project.getRuntimeClasspathElements(); URL[] urls = new URL[classpathElements.size()]; for (int i = 0; i < classpathElements.size(); i++) { urls[i] = new File(classpathElements.get(i)).toURI().toURL(); System.out.println("URL: " + urls[i]); }
// // 创建一个新的 ClassLoader ClassLoader classLoader = URLClassLoader.newInstance(urls, Thread.currentThread().getContextClassLoader()); Class<?> applicationClass = classLoader.loadClass("com......GmAdminApplication"); SpringApplication application = new SpringApplication(applicationClass); application.setResourceLoader(new DefaultResourceLoader(classLoader)); application.setMainApplicationClass(applicationClass); application.run();
// 挂起当前线程 Thread.currentThread().join();
} catch (Exception e) { throw new MojoExecutionException("Failed to start Spring Boot application", e); } } ...
|
编写完成后将该插件install到本地仓库(或推送到远端私库)。
新建一个业务项目,完成业务代码的开发和编译,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13
| @RestController @RequestMapping("/tracking") public class TrackingController {
private static final Logger logger = LoggerFactory.getLogger(TrackingController.class);
@PostConstruct public void postConstruct() { logger.info("--------------------------------------------"); logger.info("Tracking模块控制器被加载..."); logger.info("--------------------------------------------"); } ...
|
然后在项目目录下执行maven命令
1
| mvn compile com.dt26:gm-maven-plugin:2.0.0-SNAPSHOT:run
|
项目即在springboot容器中启动,并可以看到日志如下:
1 2 3 4 5 6 7 8 9 10
| ... [INFO] -------------------------------------------- [INFO] Tracking 模块控制器被加载... [INFO] -------------------------------------------- [INFO] Exposing 1 endpoint(s) beneath base path '/actuator' [INFO] Mapped URL path [/v2/api-docs] onto method [springfox.documentation.swagger2.web.Swagger2Controller#getDocumentation(String, HttpServletRequest)] [INFO] Will not secure any request [INFO] Starting ProtocolHandler ["http-nio-8080"] [INFO] Tomcat started on port(s): 8080 (http) with context path '/admin' ...
|
整体的需求就算满足了,剩下就是优化代码使得更优雅。当前只能实现开发时启动项目,在实际打包发布到测试环境和生产时,仍然需要gm-admin项目中引入业务代码模块并打包成可执行包。这一步后续可以考虑使用maven命令等方式自动完成。
参考:https://www.cnblogs.com/coder-chi/p/11305498.html