Java应用结构规范

简介: 在Java程序开发中,命名和应用分层无疑是广大后端同胞的两大“痛点”,本文提供一种基于领域模型的轻量级应用分层结构设计,供大家参考。下面按分层结构、分层明细、调用关系、各层规范和通用代码工具展开介绍。

一、分层结构

  • web(前端请求层)

通过调用业务层服务,处理前端的请求。

  • biz(业务层)

提供封装好的能力,并通过对能力进行组装、编排,进行业务逻辑处理。

  • dal(数据层)

对底层数据源进行增删改查操作。

  • client(外部请求层)

定义暴露给其他应用的接口。

  • common(外部公共层)

定义暴露给外部的公共类。

  • facade(外观层)

通过调用业务层服务,处理外部应用的请求。

二、分层明细

web(前端请求层)

子包

描述

controller

对接前端的控制器

model

前端请求相关的实体类

request

前端传入的请求

vo

返回给前端的实体类

convert

controller请求转化为service请求的转化类、

dto转化为vo的转化类

biz(业务层)

子包

描述

service

查询服务和域服务

query

查询服务

fulfilOrder

举例:履约单服务

ability

域能力

fulfilOrder

举例:履约单域能力

manager

对应底层数据模型的通用逻辑处理器

remote

外部服务

message

producer

消息发送器

diamond

动态配置

tair

缓存服务

config

业务层配置项,如:bean配置、hsf配置

common

内部公共类

constansts

仅内部使用的常量

convert

dto和do的转化器、service请求转化为manager请求的转化器

enums

仅内部使用的枚举

model.dto

用于业务处理的实体类载体

model.request

service和ability的请求类

model.option

查询的拓展条件,用于判断返回值的填充内容

utils

工具类

dal(数据层)

子包

描述

mapper

数据处理器

adb

adb的数据处理器

tddl

 xdb的数据处理器

model

前端请求相关的实体类

dataobject

数据实体类

query

数据查询条件

config

数据层配置项,如:mybatis配置、tddl配置、sequence配置

client(外部请求层)

子包

描述

api

暴露给外部的hsf接口

common(外部公共层)

子包

描述

constansts

暴露给外部的常量

enums

暴露给外部的枚举

exception

暴露给外部的异常

model

暴露给外部的实体类

dto

暴露给外部的dto

request

暴露给外部的请求

result

暴露给外部的返回结果

to

暴露给外部的消息实体

facade(外观层)

子包

描述

api

impl

hsf的实现类

convert

dto和外部dto的转化器、外部的请求转化为service请求的转化器

message

listener

消息的监听器

consumer

消息的处理器

start(启动类)

qatest(测试类)


三、调用关系

注意点:

  • 服务和服务直接可以互相调用;
  • 服务可以调用多个域的域能力;
  • 域能力是封装好的最小颗粒度的能力,不可互相调用;
  • 查询服务直接调用manager,不调用域能力;

四、各层规范

web(前端请求层)

  • 定义统一的异常处理切面:处理业务异常和其他运行时异常;


biz(业务层)

  • 内部服务不做异常处理和返回result封装类,异常都抛给web层和facade层处理。
  • 查询服务和其他服务区分开,单独放在一个包中;
  • 能力唯一对应一个域,且是封装好的最小颗粒度的能力。
  • 外部服务要在remote中做好异常处理和封装;
  • 业务层中的common类为仅在应用内部使用的公共类;

dal(数据层)

  • mapper要按不同类型的数据源分开存放,如adb和xdb。


common(外部公共层)

  • common只存放暴露给外部的实体类、常量和枚举;
  • 暴露给外部的dto只保留外部必要的字段,其他字段如feature等不可存在。


facade(外观层)

  • 定义统一的异常处理切面:处理业务异常和其他运行时异常;
  • facade层的hsf实现类只做简单的参数校验和转化,不要写业务逻辑。


五、通用代码和工具

web(前端请求层)

  • 统一异常处理切面
@RestControllerAdvice
public class RestExceptionHandler {
    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler(Exception.class)
    public Result system(HttpServletRequest req, Exception e) {
        AllLoggers.EXCEPTION.error("RestExceptionHandler.system|servlet:{}|method:{}|code:{}|msg:{}",
                req.getServletPath(),req.getMethod(), e.getMessage(), e);
        return Result.error(ResultCode.BASE.SYSTEM_ERROR);
    }
    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler(BusinessException.class)
    public Result business(HttpServletRequest req, BusinessException e) {
        AllLoggers.EXCEPTION.error("RestExceptionHandler.business|servlet:{}|method:{}|code:{}|msg:{}",
                req.getServletPath(),req.getMethod(), e.getMessage(), e);
        return Result.error(e.getErrorCode(), e.getErrorMessage());
    }
}

biz(业务层)

  • 统一日志打印工具类
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public interface AllLoggers {
    /**
     * 应用日志
     */
    Logger APPLICATION = LoggerFactory.getLogger("APPLICATION");
    /**
     * 异常日志
     */
    Logger EXCEPTION = LoggerFactory.getLogger("EXCEPTION");
    /**
     * 业务日志
     */
    Logger BIZ = LoggerFactory.getLogger("BIZ");
    /**
     * hsf日志
     */
    Logger HSF = LoggerFactory.getLogger("HSF");
    /**
     * 入口日志
     */
    Logger MTOP = LoggerFactory.getLogger("MTOP");
}
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <!-- https://github.com/spring-projects/spring-boot/blob/v1.5.13.RELEASE/spring-boot/src/main/resources/org/springframework/boot/logging/logback/defaults.xml -->
    <include resource="org/springframework/boot/logging/logback/defaults.xml" />
    <property resource="application.properties"></property>
    <property name="APP_NAME" value="toms" />
    <property name="LOG_PATH" value="${user.home}/${APP_NAME}/logs" />
    <property name="LOG_FILE" value="${LOG_PATH}/toms-root.log" />
    <appender name="APPLICATION"
        class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_FILE}/toms-root.log</file>
        <encoder>
            <pattern><![CDATA[%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%level] [traceId:%X{EAGLEEYE_TRACE_ID}] [%class:%line] - %m %n ]]> </pattern>
            <charset>UTF-8</charset>
        </encoder>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>${LOG_PATH}/logs_saved/toms-root.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <maxHistory>5</maxHistory>
            <maxFileSize>1GB</maxFileSize>
            <totalSizeCap>20GB</totalSizeCap>
        </rollingPolicy>
    </appender>
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>${CONSOLE_LOG_PATTERN}</pattern>
            <charset>utf8</charset>
        </encoder>
    </appender>
    <!--业务日志-->
    <appender name="TOMS-BIZ-APPENDER"
              class="ch.qos.logback.core.rolling.RollingFileAppender">
        <File>${LOG_PATH}/toms-biz.log</File>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <FileNamePattern>${LOG_PATH}/logs_saved/toms-biz.%d{yyyy-MM-dd}.%i.log</FileNamePattern>
            <maxHistory>5</maxHistory>
            <maxFileSize>2GB</maxFileSize>
            <totalSizeCap>20GB</totalSizeCap>
        </rollingPolicy>
        <encoder>
            <pattern><![CDATA[%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%level] [traceId:%X{EAGLEEYE_TRACE_ID}] [%class:%line] - %m %n ]]> </pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>
    <!--hsf日志-->
    <appender name="TOMS-HSF-APPENDER"
              class="ch.qos.logback.core.rolling.RollingFileAppender">
        <File>${LOG_PATH}/toms-hsf.log</File>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <FileNamePattern>${LOG_PATH}/logs_saved/toms-hsf.%d{yyyy-MM-dd}.%i.log</FileNamePattern>
            <maxHistory>5</maxHistory>
            <maxFileSize>2GB</maxFileSize>
            <totalSizeCap>20GB</totalSizeCap>
        </rollingPolicy>
        <encoder>
            <pattern><![CDATA[%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%level] [traceId:%X{EAGLEEYE_TRACE_ID}] [%class:%line] - %m %n ]]> </pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>
    <!-- 通用错误日志 -->
    <appender name="TOMS-ERROR-APPENDER"
              class="ch.qos.logback.core.rolling.RollingFileAppender">
        <File>${LOG_PATH}/toms-error.log</File>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERROR</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <FileNamePattern>${LOG_PATH}/logs_saved/toms-error.%d{yyyy-MM-dd}.%i.log</FileNamePattern>
            <maxHistory>5</maxHistory>
            <maxFileSize>2GB</maxFileSize>
            <totalSizeCap>10GB</totalSizeCap>
        </rollingPolicy>
        <encoder>
            <pattern><![CDATA[%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%level] [traceId:%X{EAGLEEYE_TRACE_ID}] [%class:%line] - %m %n ]]> </pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>
    <!-- 异常日志 -->
    <appender name="TOMS-EXCEPTION-APPENDER"
              class="ch.qos.logback.core.rolling.RollingFileAppender">
        <File>${LOG_PATH}/toms-exception.log</File>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <FileNamePattern>${LOG_PATH}/logs_saved/toms-exception.%d{yyyy-MM-dd}.log</FileNamePattern>
            <maxHistory>5</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern><![CDATA[%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%level] [traceId:%X{EAGLEEYE_TRACE_ID}] [%class:%line] - %m %n ]]> </pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>
    <logger name="HSF" level="${logback.info.level}" additivity="false">
        <appender-ref ref="TOMS-HSF-APPENDER"/>
    </logger>
    <logger name="BIZ" level="${logback.info.level}" additivity="false">
        <appender-ref ref="TOMS-BIZ-APPENDER"/>
        <appender-ref ref="TOMS-ERROR-APPENDER"/>
    </logger>
    <logger name="EXCEPTION" level="${logback.info.level}" additivity="false">
        <appender-ref ref="TOMS-EXCEPTION-APPENDER"/>
        <appender-ref ref="TOMS-ERROR-APPENDER"/>
    </logger>
    <root level="INFO">
        <appender-ref ref="CONSOLE" />
    </root>
</configuration>
  • 单位转化工具类
public class UnitConvertUtils {
    /**
     * 米和千米的进率
     */
    public static final double RATE_OF_METRE_AND_KILOMETRE = 1000d;
    public static final int INT_RATE_OF_METRE_AND_KILOMETRE = 1000;
    /**
     * 分和元的进率
     */
    public static final double RATE_OF_FEN_AND_YUAN = 100d;
    /**
     * 立方厘米和立方米的进率
     */
    public static final double INT_RATE_OF_CM3_AND_M3 = 1000000d;
    /**
     * 米转千米
     *
     * @param toConvert
     * @return 异常返回null
     */
    public static Double convertMetre2Kilometre(Long toConvert) {
        if (toConvert == null) {
            return null;
        }
        return toConvert / RATE_OF_METRE_AND_KILOMETRE;
    }
    /**
     * 千米转米
     *
     * @param toConvert
     * @return 异常返回null
     */
    public static Long convertKilometre2Metre(Double toConvert) {
        if (toConvert == null) {
            return null;
        }
        BigDecimal bigDecimal = BigDecimal.valueOf(toConvert);
        BigDecimal factorBigDecimal = BigDecimal.valueOf(RATE_OF_METRE_AND_KILOMETRE);
        return bigDecimal.multiply(factorBigDecimal).longValue();
    }
    /**
     * 元转分
     *
     * @param toConvert
     * @return 异常返回null
     */
    public static Long convertYuan2Fen(Double toConvert) {
        if (toConvert == null) {
            return null;
        }
        BigDecimal bigDecimal = BigDecimal.valueOf(toConvert);
        BigDecimal factorBigDecimal = BigDecimal.valueOf(RATE_OF_FEN_AND_YUAN);
        return bigDecimal.multiply(factorBigDecimal).longValue();
    }
    /**
     * 元转分
     *
     * @param toConvert
     * @return 异常返回null
     */
    public static Long convertYuan2Fen(String toConvert) {
        if (toConvert == null) {
            return null;
        }
        BigDecimal bigDecimal = BigDecimal.valueOf(ConvertUtils.convertString2Double(toConvert));
        BigDecimal factorBigDecimal = BigDecimal.valueOf(RATE_OF_FEN_AND_YUAN);
        return bigDecimal.multiply(factorBigDecimal).longValue();
    }
    /**
     * 分转元
     *
     * @param price
     * @return
     */
    public static String convertFen2Yuan(Long price) {
        if (price == null) {
            return null;
        }
        return BigDecimal.valueOf(price).divide(new BigDecimal(RATE_OF_FEN_AND_YUAN)).toString();
    }
    /**
     * 里程米转换为千米
     *
     * @param distance
     * @return
     */
    public static Double meter2Kilometer(Long distance) {
        if (distance == null) {
            return null;
        }
        BigDecimal meter = BigDecimal.valueOf(distance);
        BigDecimal kilometer = meter.divide(new BigDecimal(INT_RATE_OF_METRE_AND_KILOMETRE));
        return kilometer.doubleValue();
    }
    /**
     * 立方厘米转立方米
     *
     * @param volume
     * @return
     */
    public static String convertCm32M3(Long volume) {
        if (volume == null) {
            return null;
        }
        return BigDecimal.valueOf(volume).divide(new BigDecimal(INT_RATE_OF_CM3_AND_M3)).toString();
    }
}



目录
相关文章
|
5天前
|
运维 Java
Java版HIS系统 云HIS系统 云HIS源码 结构简洁、代码规范易阅读
云HIS系统分为两个大的系统,一个是基层卫生健康云综合管理系统,另一个是基层卫生健康云业务系统。基层卫生健康云综合管理系统由运营商、开发商和监管机构使用,用来进行运营管理、运维管理和综合监管。基层卫生健康云业务系统由基层医院使用,用来支撑医院各类业务运转。
27 5
|
22天前
|
移动开发 Java Android开发
构建高效Android应用:探究Kotlin与Java的性能差异
【4月更文挑战第3天】在移动开发领域,性能优化一直是开发者关注的焦点。随着Kotlin的兴起,其在Android开发中的地位逐渐上升,但关于其与Java在性能方面的对比,尚无明确共识。本文通过深入分析并结合实际测试数据,探讨了Kotlin与Java在Android平台上的性能表现,揭示了在不同场景下两者的差异及其对应用性能的潜在影响,为开发者在选择编程语言时提供参考依据。
|
2天前
|
安全 Java 调度
Java线程:深入理解与实战应用
Java线程:深入理解与实战应用
18 0
|
2天前
|
Java
Java中的并发编程:理解和应用线程池
【4月更文挑战第23天】在现代的Java应用程序中,性能和资源的有效利用已经成为了一个重要的考量因素。并发编程是提高应用程序性能的关键手段之一,而线程池则是实现高效并发的重要工具。本文将深入探讨Java中的线程池,包括其基本原理、优势、以及如何在实际开发中有效地使用线程池。我们将通过实例和代码片段,帮助读者理解线程池的概念,并学习如何在Java应用中合理地使用线程池。
|
7天前
|
Java 关系型数据库 MySQL
一套java+ spring boot与vue+ mysql技术开发的UWB高精度工厂人员定位全套系统源码有应用案例
UWB (ULTRA WIDE BAND, UWB) 技术是一种无线载波通讯技术,它不采用正弦载波,而是利用纳秒级的非正弦波窄脉冲传输数据,因此其所占的频谱范围很宽。一套UWB精确定位系统,最高定位精度可达10cm,具有高精度,高动态,高容量,低功耗的应用。
一套java+ spring boot与vue+ mysql技术开发的UWB高精度工厂人员定位全套系统源码有应用案例
|
7天前
|
设计模式 算法 Java
Java中的设计模式及其应用
【4月更文挑战第18天】本文介绍了Java设计模式的重要性及分类,包括创建型、结构型和行为型模式。创建型模式如单例、工厂方法用于对象创建;结构型模式如适配器、组合关注对象组合;行为型模式如策略、观察者关注对象交互。文中还举例说明了单例模式在配置管理器中的应用,工厂方法在图形编辑器中的使用,以及策略模式在电商折扣计算中的实践。设计模式能提升代码可读性、可维护性和可扩展性,是Java开发者的必备知识。
|
7天前
|
安全 Java API
函数式编程在Java中的应用
【4月更文挑战第18天】本文介绍了函数式编程的核心概念,包括不可变性、纯函数、高阶函数和函数组合,并展示了Java 8如何通过Lambda表达式、Stream API、Optional类和函数式接口支持函数式编程。通过实际应用案例,阐述了函数式编程在集合处理、并发编程和错误处理中的应用。结论指出,函数式编程能提升Java代码的质量和可维护性,随着Java语言的演进,函数式特性将更加丰富。
|
8天前
|
Java API 数据库
深入解析:使用JPA进行Java对象关系映射的实践与应用
【4月更文挑战第17天】Java Persistence API (JPA) 是Java EE中的ORM规范,简化数据库操作,让开发者以面向对象方式处理数据,提高效率和代码可读性。它定义了Java对象与数据库表的映射,通过@Entity等注解标记实体类,如User类映射到users表。JPA提供持久化上下文和EntityManager,管理对象生命周期,支持Criteria API和JPQL进行数据库查询。同时,JPA包含事务管理功能,保证数据一致性。使用JPA能降低开发复杂性,但需根据项目需求灵活应用,结合框架如Spring Data JPA,进一步提升开发便捷性。
|
13天前
|
Java
探秘jstack:解决Java应用线程问题的利器
探秘jstack:解决Java应用线程问题的利器
17 1
探秘jstack:解决Java应用线程问题的利器
|
17天前
|
XML JSON JavaScript
Java中XML和JSON的比较与应用指南
本文对比了Java中XML和JSON的使用,XML以自我描述性和可扩展性著称,适合结构复杂、需验证的场景,但语法冗长。JSON结构简洁,适用于轻量级数据交换,但不支持命名空间。在Java中,处理XML可使用DOM、SAX解析器或XPath,而JSON可借助GSON、Jackson库。根据需求选择合适格式,注意安全、性能和可读性。
26 0