Java 9 模块化特性学习:一个使用Gradle构建模块化项目的实践指南

Java 9 模块化特性学习:一个Gradle模块化项目实践

前言

不知怎么的,现今的程序员往往被当成加班工种的典范,而我随着工龄的不断增加,作为码农的我码带码的时间却可以做到越来越少?(狗头)
时间的节省一部分归功于之前写过的代码,后续就可以拿来参考复用;一部分归功于现今的开源技术,有很多优秀的开源API可以直接依赖,拿来即用;一部分归功于不断提高的写总结和写文档的能力,编程思路梳理清楚了,自然而然的写代码埋坑和不断填坑的时间就会减少;还有一部分归功于现今的AI技术,比如GitHub Copilot 和当下比较火的 chatGPT,目前 GitHub Copilot 可以帮助我减少写那些逻辑简单的重复代码的时间 ;chatGPT 则被我用做私人资料查询库,有什么知识点需要,都可以先用chatGPT查一下,然后它会给我提供比较满意的答案。比起使用百度,google 在众多文章中淘金,chatGPT 提供的答案往往可以做到一针见血,是相当不错的工作伴侣。
而节省码带码时间最重要的一点,就是作为一个程序员一定要去写更优秀的代码。一个可拓展的,复用性高的,可维护性好的优秀代码,可以节省出程序员百分之五十的做重复需求的时间。而如果将这些时间再用于code review ,去提高自己的代码质量,并同时提高自己的编程水平和编程思路,那么做无聊纯粹码代码工作的时间就会越来越少,而用于探索新知识,新技术的时间就会增多,搞编程的乐趣慢慢涌现出来,突然想起快乐星球头上一根天线的天线宝宝。。。嘿嘿,头像就是它了。
前言写了这么多却没有进入正题,是我觉得作为一个打工人,思考清楚自己每天都在做什么是至关重要的。作为程序员我们每天是在不断埋坑,填坑的车轱辘里面打转?还是每天在为自己后续的编程路上添砖加瓦?思考过后,希望大家一起共勉。

项目简介

本次要介绍的项目,是最近我做Code Review 的一个小项目getPush。这个项目一开始是我在2022年2月写的,主要做文件获取,文件解析,文件生成,文件发送这四件事情。项目上线后,多个城市部署使用,基本没有什么运维问题,我觉得算我比较满意的作品。最近一个需求中又正好需要用到这个程序,我便借这个机会再给这个项目优化一下。
而这次优化,改动比较大,我便直接新建了一个项目,之前使用的maven 构建,这次替换成了Gradle 构建,并使用了Java 17 以及Java 9 特性中的模块化技术去搭建这个项目。本次Code Review 的目的有以下几点:

  • 实现业务代码,技术代码的完全分离,为软件开源做准备
  • 使用Module 技术去掉代码中的无效依赖,减少代码包字节大小,为后续将项目作为热插拔插件使用做准备
  • 代码结构调整,减少代码重复率;SonarLint 代码检查,减少程序中问题代码
  • 文档完善,编写更加利于用户使用的规范文档
  • 前端优化,准备使用React 编写前端,并增加页面交互更利于用户自己配置和使用这个项目

本次项目分享的重点不在于项目,而是这个项目是如何使用 gradle 和模块化的,所以这次分享中不含有项目的概要设计,详细设计和操作文档。

项目结构简介

getPush项目结构简介
项目主要分为五个module

  1. common-data 数据仓储

  2. common-template 项目核心module,因为对于文件获取,文件解析,文件生成,文件发送这四个功能,我使用了模板设计模式,所以起名叫template

  3. common-util 项目工具包

  4. web 前后端交互接口的后端部分

  5. biz 具体业务封装,里面会再根据不同的业务需求封装不同的module子项目

    如果项目实现开源的话,用户需要自己去实现 biz module,去完成指定业务需求;对于文件获取,文件发送,只需在前端添加配置即可实现;对于文件解析,可以使用common-util 提供的Bean反射方法,只需编写接收数据的Bean 对象即可实现文件解析。而文件生成则需要用户自己继承template的生成接口,然后编写文件生成的业务逻辑。getpush程序提供了完善的日志,去记录每条任务处理结果,同时getpush也提供了手动处理页面和批次处理文件的方法。

项目设计简介

在这里插入图片描述

gradle配置简介

get-push总项目的 build.gradle 配置
plugins {
    id 'java'
    id 'io.spring.dependency-management' version '1.1.0'
    id 'org.javamodularity.moduleplugin' version '1.8.1-SNAPSHOT' apply false
}

plugins {
    id 'java'
    id 'io.spring.dependency-management' version '1.1.0'
    id 'org.javamodularity.moduleplugin' version '1.8.1-SNAPSHOT' apply false
}

group = 'com.rtzn'
version = '1.0.0'
sourceCompatibility = '17'

subprojects {

    group = 'com.rtzn'
    version = '1.0.0'
    sourceCompatibility = '17'

    apply plugin: "org.javamodularity.moduleplugin"
    apply plugin: 'io.spring.dependency-management'

    java {
        modularity.inferModulePath = true
    }

    repositories {
        mavenLocal()
        maven { name "Alibaba"; url "https://maven.aliyun.com/repository/public"}
        maven { name "Alibaba-gradle-plugin"; url "https://maven.aliyun.com/repository/gradle-plugin/" }
        mavenCentral()
    }

    buildscript {
        repositories {
            mavenLocal()
            maven { name "Alibaba"; url 'https://maven.aliyun.com/repository/public' }
        }
    }

    test {
        useJUnitPlatform()

        testLogging {
            events 'PASSED', 'FAILED', 'SKIPPED'
        }
    }

    dependencies {
        compileOnly 'org.projectlombok:lombok:1.18.22'
        annotationProcessor 'org.projectlombok:lombok:1.18.22'
        implementation 'cn.hutool:hutool-all:5.8.11'
        implementation 'org.slf4j:slf4j-api:1.7.32'
        implementation 'ch.qos.logback:logback-classic:1.2.9'
        testImplementation 'org.junit.jupiter:junit-jupiter-api:5.3.1'
        testImplementation 'org.junit.jupiter:junit-jupiter-params:5.3.1'
        testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.3.1'
    }
}
  1. plugins
  • java 插件:为Java项目提供了一些标准的任务和配置,例如编译Java代码、运行单元测试、打包Java类库等等。此外,Java插件还定义了一些约定,例如默认的源代码和资源目录结构,以及编译输出的默认位置。
  • dependency-management 插件:它的作用是简化构建过程中对第三方依赖的管理。
  • org.javamodularity.moduleplugin: 使用这个插件,可以通过在Gradle构建脚本中定义模块、声明模块之间的依赖关系、导出模块的API等,来轻松创建和管理Java 9及更高版本中的模块化应用程序
  1. subprojects :定义了所有子项目的公共配置内容
  • group 组,version 版本,sourceCompatibility jdk版本
  • apply plugin:定义子模块的公共插件
  • modularity.inferModulePath=true:Java运行时环境将自动推断模块路径,它会根据应用程序中的类路径和模块路径的默认规则,计算出模块路径中应该包含哪些模块。
  • repositories:仓储库,仓储库会按顺序加载,首先加载mavenLocal() 本地maven 库,然后本地库取不到依赖的再去加载 maven { name “Alibaba”; url “https://maven.aliyun.com/repository/public”} 阿里Maven镜像地址;
    maven { name “Alibaba-gradle-plugin”; url “https://maven.aliyun.com/repository/gradle-plugin/” } 阿里Gradle插件镜像地址;最后再去查 mavenCentral() maven 中央仓库。
  • buildscript 声明构建时先从本地库下载依赖,然后再从阿里Maven镜像地址下载依赖。
  • test:指定测试任务的行为和配置
    • useJUnitPlatform():指定使用JUnit 5平台来运行测试
    • testLogging:指定测试日志记录的级别和输出。在这个例子中,只输出测试结果为“PASSED”、“FAILED”或“SKIPPED”的测试日志记录。
  • dependencies 定义子项目的公共依赖
get-push总项目的 settings.gradle 配置
rootProject.name = 'getPush-new'
include('common-data', 'common-util', 'common-template','web','biz')
include('biz:bizsubproject')
  • rootProject.name:本项目名称
  • include(‘common-data’, ‘common-util’, ‘common-template’,‘web’,‘biz’):定义包含的子项目
  • include(‘biz:bizsubproject’) :定义子项目中包含的子项目
get-push子项目common-template的 build.gradle 配置
plugins {
    id 'java-library'
}
dependencies {
    implementation project(path: ':common-util')
    implementation project(path: ':common-data')
    implementation 'com.alipay.sdk:alipay-sdk-java:3.0.52.ALL'
    implementation 'org.apache.httpcomponents:httpclient:4.5.14'
    implementation('org.springframework:spring-context:6.0.6') {
        exclude group: 'org.springframework', module: 'spring-jcl'
    }
    implementation('org.springframework:spring-jdbc:6.0.6') {
        exclude group: 'org.springframework', module: 'spring-jcl'
    }
    implementation 'com.baomidou:mybatis-plus-core:3.5.3.1'
    implementation 'com.baomidou:mybatis-plus-extension:3.5.3.1'
    implementation 'com.jcraft:jsch:0.1.55'
}
  • plugins:java-library:当应用java-library插件时,Gradle将默认按照Java库的约定进行配置和构建
  • implementation project(path: ‘:common-util’): 依赖引用另一个子项目 common-util
  • implementation(‘org.springframework:spring-jdbc:6.0.6’) {exclude group: ‘org.springframework’, module: ‘spring-jcl’}:引用 spring-jdbc 6.0.6,但不引用其 spring-jcl 依赖。spring-jcl依赖用于实现Spring框架中的日志抽象层,而我程序的日志使用了logback依赖;在模块化项目中这两个依赖不能同时引用。
get-push子项目common-template的 settings.gradle 配置
rootProject.name = 'common-template'

模块化的必要java类 module-info

common-template 的 module-info 示例
module common.template.main {
    // 依赖的 二方库
    requires common.util.main;
    requires common.data.main;
    // 依赖的 三方库
    requires spring.context;
    requires hutool.all;
    requires alipay.sdk.java;
    requires org.apache.httpcomponents.httpclient;
    requires org.apache.httpcomponents.httpcore;
    requires spring.jdbc;
    requires com.baomidou.mybatis.plus.core;
    requires com.baomidou.mybatis.plus.extension;
    requires jsch;

    // 提供反射类
    opens com.rtzn.commontemplate.config;
    opens com.rtzn.commontemplate.pojo.vo;

    // 提供接口
    provides com.rtzn.commontemplate.service.connect.ConnectService
            with com.rtzn.commontemplate.service.connect.ConnectByFTPServiceImpl, com.rtzn.commontemplate.service.connect.ConnectByHTTPServiceImpl,
            com.rtzn.commontemplate.service.connect.ConnectBySFTPServiceImpl, com.rtzn.commontemplate.service.connect.AlipayConnectImpl;
    provides com.rtzn.commontemplate.service.generate.GenerateFileService
            with com.rtzn.commontemplate.service.generate.GenerateFileServiceImpl;
    provides com.rtzn.commontemplate.service.handle.HandleFileService
            with com.rtzn.commontemplate.service.handle.HandleFileServiceImpl;

    exports com.rtzn.commontemplate.service.template;
    exports com.rtzn.commontemplate.config;
    exports com.rtzn.commontemplate.pojo.vo;
    exports com.rtzn.commontemplate.service.generate;
    exports com.rtzn.commontemplate.service.handle;


}
module-info 基本语法
  • module:定义一个Java模块
  • requires: 声明当前模块所依赖的其他模块
  • exports:指定当前模块中哪些可以被其他模块访问
  • opens:指定当前模块中哪些包可以被其他模块反射访问
  • uses:声明当前模块使用的服务接口
  • provides: 声明当前模块提供的服务实现类
  • with:与provides 一起使用,指定服务实现类的实现方式

FAQ

不同子模块的 service ,mapper 如何实例化为 spring 容器中的bean对象,从而实现跨模块服务调用

通常,我们使用@ComponentScan注解来配置Spring应用程序的组件扫描路径;使用@MapperScan注解来配置MyBatis应用程序的Mapper接口扫描路径。
示例:

package com.rtzn.web;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;

@SpringBootApplication
@ComponentScan(basePackages = {"com.rtzn.web.*", "com.rtzn.data.service", "com.rtzn.commontemplate.service",
        "com.rtzn.demo.service"})
@MapperScan(basePackages = {"com.rtzn.data.mapper", "com.rtzn.demo.mapper"})
public class WebApplication {

    public static void main(String[] args) {
        SpringApplication.run(WebApplication.class, args);
    }
}

mabatis-plus 的 mapper.xml 路径多模块配置

mybatis-plus:
  # Mapper.xml 文件位置 Maven 多模块项目的扫描路径需以 classpath*: 开头
  mapperLocations: classpath*:/mapperxml/*.xml

问题

  1. 目前Gradle plugin中没有找到一款像 mavenHelper的插件来更方便的进行 gradle 的依赖管理,有什么关于gradle 进行依赖管理的推荐吗?
  2. 再进行 java application 模块化的过程中,遇到了很多项目模块化后无法访问API依赖的难题 。比如
    because module common.data.main does not read unnamed module @0x301ec38b
    需要在build.gradle 中加入
run {
    jvmArgs = ['--illegal-access=permit', '--add-opens', 'com.rtzn.web.WebApplication=ALL-UNNAMED']
}

module java.base does not "opens java.lang" to module spring.core
需要在启动java 命令时加入 --add-opens java.base/java.lang=spring.core
我想找到一个完善的 gradle 模块化项目已供我参考,来进行后续项目的优化和提高,有什么好的项目推荐吗?

学习链接

gradle 学习

  • gradle 官网文档:https://docs.gradle.org/current/userguide/userguide.html
    模块化学习
  • oracle 官网文档:https://docs.oracle.com/javase/9/docs/api/java/lang/module/package-summary.html

总结

时光荏苒,小编写完这篇文章已经过了3个小时,时光回溯,小编重构这个项目可是花了整整一周时间,期间还不忘利用业余时间,王者上了巅峰1900百分。而阅读文章的你可能仅仅两三分钟就看完了一篇文章,阅读文章是咱们技术学习的一个方式,但我认为不是最好的方式。
在我看来,学习一门技术最好的方式就是先详细阅读它的官网文档,而阅读官网文档最好的方式就是不用翻译软件,而直接原文去阅读这个官网正品文档。所以,各位有缘再见,小编要去学英语了。good bye!see you nala。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值