2024年1月17日星期三

分享使用多年简单高效的 spring boot 模板工程结构

尽管目前市场主流编程语言有很多,但 Java 仍然是企业的使用首选,不仅是成长多年,积累了不少 Coder,也在于丰富的开源库,而 spring 全家桶仍然非常流行,本篇文章结合作者多年使用,总结的一个简单的 spring boot 模板结构,具有以下优缺点:

优点:

  • 干净清爽,不依赖任何第三方库,适合手工码字人
  • 一个的业务处理入口(Controller),避免入口泛滥在应用中的各个地方
  • 异常处理、响应结果、摘要日志、鉴权部分的预留等等,同样是避免切面膨胀
  • 研发只需关注业务部分代码的开发,不需要管异常、日志、鉴权等非主流程处理的代码
  • 自定义空间大,模板结构保存很多预留位,可自定义扩展

缺点:

  • 适用于处理通用HTTP业务请求,JSON格式的请求响应体,对于websocket或上传下载等需另行定义处理
  • 业务请求的配置路径在 xml 文件中完成,多人开发容易造成代码冲突

1. 类结构图

1.1. 领域对象

下图主要是领域对象的定义,涵盖异常、错误码与服务结果,错误级别、错误码都是为业务异常服务,出现异常的情况,最终会以正常状态服务结果返回,而不是异常堆栈的直接抛出。

20240118101728

为了更快的创建出模板结构,上面数字代表创建顺序,详细的代码可直接点击查看:

  • CommonResultCode,通用的结果定义,用于约束返回内容
  • ErrorCodeConstants,错误码常量定义,应结合错误码规范,其中包含错误码前缀、错误级别与业务域的定义
  • CommonResultCodeEnum,错误码枚举类的定义,因实现了CommonResultCodeErrorCodeConstants,只需定义自己的四位错误码
  • ServiceResult,响应结果,贯穿整条业务请求,其中定义约束了返回结构,应遵循各自的前后端交互规范
  • AbstractBizException,抽象的业务异常对象,由自定义异常继承
  • CustomBizException,自定义的业务异常,需继承AbstractBizException
  • AssertUtils,断言工具类,与AbstractBizException绑定,业务开发中不必if...else...判断,使用此工具直接抛

完成上面领域对象的定义,在 applicationContext.xml 中配置个异常的 Bean 对象,具体可以看AssertUtils类注释,第一步完成。

<bean class="com.xxx.xxx.exception.AssertUtils">
    <property name="exceptionClassName" value="com.xxx.xxx.exception.CustomBizException"/>
</bean>

1.2. 请求委派

领域对象贯穿业务请求的生命周期,对于每个请求的的扭转委派,如下图定义:

20240118135016

尽管已经最小化,为了简单,仍然用了一些工具类的包,提前配置在 pom.xml 文件中,如下:

<!-- JSON格式处理 -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.65</version>
</dependency>

<!-- 工具包 -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.8.1</version>
</dependency>

<!-- 日志 -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>2.0.10</version>
</dependency>

按上图步骤,依次创建的类如下:

至此所有模板代码已创建完成,下面看如何使用。

2. 模板使用

2.1. 编写服务

DelUser,继承AbstractServiceCallback类,必需要实现executeService方法,beforeServiceafterService 可选

import com.alibaba.fastjson.JSONObject;
import org.springframework.stereotype.Component;

@Component("deleteUser")
public class DelUser extends AbstractServiceCallback<ServiceRequest, ServiceResult<String>> {

    /**
     * 前置处理(可选的方法),生命周期在 executeService 执行之前
     * 可以做一些参数校验等操作
     *
     * @param request 服务请求对象
     */
    @Override
    public void beforeService(ServiceRequest request) {
        super.beforeService(request);

        // 参数验证
        JSONObject postJson = request.parseData(JSONObject.class);
        AssertUtils.notNull(postJson, CommonResultCodeEnum.PARAM_ILLEGAL, "post data is null");

        Long id = postJson.getLong("id");
        AssertUtils.notNull(id, CommonResultCodeEnum.PARAM_ILLEGAL, "id is null");
    }

    @Override
    public ServiceResult executeService(ServiceRequest request) {
        // 获取返回对象,最终将在 result 的 T data 中呈现
        String resultData = "ok";

        return ServiceResult.valueOfSuccess(resultData);
    }

    /**
     * 后置处理(可选的方法),生命周期在 executeService 执行完后
     * 可以做回收的操作
     *
     * @param request 服务请求对象
     * @param result {@link ServiceResult}
     */
    @Override
    public void afterService(ServiceRequest request, ServiceResult result) {
        super.afterService(request, result);
    }
}

2.2. 注册服务

将上面的服务注册到request-mapping.xml文件中,下面的 entry key 就是要请求的路径,如/api/user/delete

<bean id="domainServiceProcess" class="com.cainiao.moore.template.DomainServiceProcessImpl">
    <property name="serviceMap">
        <map>
            <entry key="api_user_delete"  value-ref="deleteUser"/>
        </map>
    </property>
</bean>

2.3. 服务访问

启动 spring boot 应用, 使用 curl 或者 postman 这样的工具发起请求

20240118145639

3. Conclusion

整个服务模板,搭建起来废点事,但实际使用中,会发现非常简单,继承服务回调类,注册服务,技术只需要在自己的服务中编写业务代码,完全不需要关注日志怎么打,异常怎么处理,上层都已进行了封装。

仁者见仁,智者见智,适合自己的才是最好的

4. 代码及素材

Retrieving Secret Values Using REST Services in Infisical

Infisical is the open source secret management platform that developers use to centralize their application configuration and secrets like...