Springboot项目最佳实践

Posted by Wh0ami-hy on April 25, 2023

1. Spring Boot通用项目结构

在Java 的web开发中,使用了Spring MVC 框架之后,我们的代码组织形式变成了controller,service,dao这种形式

以Spring Boot 构建的项目应该总体分为三大层

  • 项目根目录/src/main/java:放置项目Java源代码
  • 项目根目录/src/main/resources:放置项目静态资源和配置文件
  • 项目根目录/src/test/java:放置项目测试用例代码

1.1. /src/main/java目录下的结构

不同项目和团队实践不一样,稍许有区别,但整体安排应该差不多。如果是多模块的项目的话,下面的结构应该只对应其中一个模块,其他模块的代码组织也大致差不多

|_annotation:放置项目自定义注解
|_aspect:放置切面代码
|_config:放置配置类
|_constant:放置常量、枚举等定义
   |__consist:存放常量定义
   |__enums:存放枚举定义
|_controller:放置控制器代码
|_filter:放置一些过滤、拦截相关的代码
|_mapper:放置数据访问层代码接口
|_model:放置数据模型代码
   |__entity:放置数据库实体对象定义
   |__dto:存放数据传输对象定义
   |__vo:存放显示层对象定义
|_service:放置具体的业务逻辑代码(接口和实现分离)
   |__intf:存放业务逻辑接口定义
   |__impl:存放业务逻辑实际实现
|_util:放置工具类和辅助代码

1.2. /src/main/resources目录下的结构

主要存放静态配置文件和页面静态资源等东西

|_mapper:存放mybatis的XML映射文件(如果是mybatis项目)
|_static:存放网页静态资源,比如下面的js/css/img
   |__js:
   |__css:
   |__img:
   |__font:
   |__等等
|_template:存放网页模板,比如thymeleaf/freemarker模板等
   |__header
   |__sidebar
   |__bottom
   |__XXX.html等等
|_application.yml       基本配置文件
|_application-dev.yml   开发环境配置文件
|_application-test.yml  测试环境配置文件
|_application-prod.yml  生产环境配置文件

1.3. 控制器(controller)

controller 接收请求,并将请求转发至业务处理对象

接收业务请求处理结果,并将结果分发到响应页面

不执行实际的业务逻辑,调用service 接口中的方法去完成

控制器应该围绕用例、业务能力来设计

controller层一般返回封装的结果类Result

默认情况下,控制器是单例

@RestController
@RequestMapping("/user")
public class UserController {
    // 注入service接口
    @Autowired
    private UserService userService;
    // 查询所有用户信息
    @RequestMapping(value = "/all", method = RequestMethod.GET)
    public List<User> test(){
        return userService.queryUsers();
    }
}

1.4. 业务层(service)

service,用于执行业务逻辑,如计算、验证、授权等

一般由两部分组成 一个Java接口和一个实现类

serviceImpl是实现具体业务逻辑的,通常是把mapper和业务进行整合

service层返回什么主要看controller层需要什么,一般返回操作的执行结果(成功、失败)、处理后的业务数据(如经过计算、过滤或转换的数据)、异常或错误信息

最好围绕业务功能、领域、用例来构建服务,合理的使用单一职责原则

AccountServiceUserService 这样的服务,比起 DatabaseServiceValidationService 这样的会更合适一些

定义接口 UserService

public interface UserService {
    // 对应于mapper层接口中定义的方法
    List <User> queryUsers();
}

定义接口实现类 UserServiceImpl

@Component
public class UserServiceImpl implements UserService {
	// 注入mapper接口
    @Autowired
    private UserMapper userMapper;
    @Override
    public List<User> queryUsers() {
        return userMapper.queryUsers();
    }
}

1.5. DAO层(mapper)

实现数据层的操作

dao(Data Access Objects)层也叫mapper层,用于从数据库或其他数据存储介质中读取和写入数据

先设计mapper接口,然后在xml配置文件中进行配置

定义接口 UserMapper

@Mapper
public interface UserMapper {
    List<User> queryUsers();
}

在xml中写SQL语句

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.hy.mapper.UserMapper">
    <!--id表示接口当中定义的方法. resultType,表示数据库查询的结果.-->
    <select id="queryUsers" resultType="com.hy.entity.User" parameterType="com.hy.entity.User">
        select * from login
    </select>
</mapper>

mapper层返回什么主要看service层需要什么

1.6. 工具类(utils)

utils,用于提供常用的实用程序方法,如日期格式化、字符串处理、加密解密等

1.7. 配置类(config)

config,拓展功能、修改默认功能

1.8. 拦截器(interceptor)

interceptor

1.9. 数据模型定义(model)

model包中放的都是POJO类,一个POJO类一般实现了JavaBean的标准,要在各个实体类中实现序列化接口,避免在微服务中出现错误

entity是数据库实体对象定义,定义各个属性以及各个属性的getter()和setter()方法,与数据库表结构一一对应

《阿里巴巴Java开发手册》中定义如下(实际项目开发时,没有必要刻意照搬去定义这么多层对象)

DO(Data Object):与数据库表结构一一对应,通过DAO层向上传输数据源对象

DTO(Data Transfer Object):数据传输对象,Service层向外传输的对象

BO(Business Object):业务对象。由Service层输出的封装业务逻辑的对象

AO(Application Object):应用对象。在Web层与Service层之间抽象的复用对象模型,极为贴近展示层,复用度不高

VO(View Object):显示层对象,通常是Web向模板渲染引擎层传输的对象

Query:数据查询对象,各层接收上层的查询请求。注意超过2个参数的查询封装,禁止使用Map类来传输

实际开发中一般只涉及如下两种数据模型就够用了

DTO/VO 泛指控制层与服务层之间的数据传输对象

DO/Entity 泛指服务层与DAO层间的数据传输对象

1.10. 各层间调用关系

要遵循Controller–>Service接口–>ServiceImpt实现类–>Mapper接口->SQL->DataBase的调用模式

每当我们拿到一个新的项目到手时,只要按照这个思路去看别人项目的代码,应该基本都是能理得顺的

controller调用service接口,serviceimpl实现类调用mapper接口并实现其他业务

在Service接口有多个ServiceImpl实现类的情况,就需要指定参数名来选择哪个ServiceImpt实现类

Service层此时有两个接口实现类
    @Service("PCIImpt1")
    class PCIImpt1 imeplements PCI{}

    @Service("PCIimpt2")
    class PCIImpt2 imeplements PCI{}

Controller层
    @Resource(name="PCIimpt2")  //填PCIimpt1,注入PCIimpt1实现类,填PCIimpt2,则注入PCIimpt2实现类
    private PCI pci;    //注入接口以Resource手动指定接收

controller层调用了service层接口中的方法,service层由service对应的接口和实现类组成,serviceImpl实现service的相关接口同时完成相关的业务逻辑处理

service层的实现类serviceImpl 再调用mapper层的接口,进行业务逻辑应用的处理

mapper层的接口在对应的xml配置文件中进行配置、实现以及关联,mapper层的任务就是向数据库发送sql语句,完成数据的处理任务

springmvc各层调用

1.11. 编写顺序

先设计数据库表,然后定义好model,之后再实现其他部分

2. Spring Boot的最佳实践

2.1. 使用自定义BOM来维护第三方依赖

可以借鉴Spring IO Platform来编写自己的基础项目platform-bom,所有的业务模块项目应该以BOM的方式引入。这样在升级第三方依赖时,就只需要升级这一个依赖的版本而已

<dependencyManagement>
   <dependencies>
       <dependency>
           <groupId>io.spring.platform</groupId>
           <artifactId>platform-bom</artifactId>
           <version>Cairo-SR3</version>
           <type>pom</type>
           <scope>import</scope>
       </dependency>
   </dependencies>
</dependencyManagement>

在项目中,为了比避免在pom.xml文件中重复导包的问题,可以自己创建一个父工程,将所有的依赖版本统一,避免出现jar包冲突和每个项目都要导包的问题。在创建项目时只需要继承父工程即可。类似于spring boot的父工程

2.2. 使用自动化配置

自动化配置是Spring Boot的一部分,它可以简化你的代码并使之工作。当在类路径上检测到特定的jar文件时,自动配置就会被激活。

使用它的最简单方法是依赖Spring Boot Starters

如果想与Redis进行集成

<dependency>  
   <groupId>org.springframework.boot</groupId>  
   <artifactId>spring-boot-starter-data-redis</artifactId>  
</dependency>

可为开发中常见的问题,创建自己的自动配置,即手动实现一个 starter

2.3. 正确设计代码目录结构

├── main
│   ├── java
│   │   └── com
│   │       └── bestpractice
│   │           ├── config 存放配置类
│   │           ├── constants 放置常量、枚举等定义
│   │           ├── controller 放置控制器代码
│   │           ├── dao 放置数据访问层代码接口
│   │           ├── exception
│   │           ├── filter 放置一些过滤、拦截相关的代码
│   │           ├── manager 放置数据访问层代码接口
│   │           ├── model
│   │           │   ├── entity 放置数据库实体对象定义
│   │           │   ├── dto 存放数据传输对象定义
│   │           │   └── vo 存放显示层对象定义
│   │           ├── service 放置具体的业务逻辑代码(接口和实现分离)
│   │           │   ├── impl 存放业务逻辑实际实现
│   │           ├── task
│   │           ├── utils 放置工具类和辅助代码
│   │           └── BestPracticeApplication.java
│   │
│   └── resources

2.4. 保持Controller层的简洁

了解设计REST API的最佳实践

Controller层里可以做参数校验、异常抛出等操作,但建议不要放太多业务逻辑,业务逻辑尽量放到Service层代码中去做

2.5. 围绕业务功能构建Service

Service层做实际业务逻辑,完整的业务逻辑包含验证、缓存等

功能模块Service之间引用时,建议不要渗透到DAO层(或者mapper层),基于Service层进行调用和复用比较合理

业务逻辑层Service和数据库DAO层的操作对象不要混用。Controller层的数据对象不要直接渗透到DAO层(或者mapper层);同理数据表实体对象Entity也不要直接传到Controller层进行输出或展示。

2.6. 使用全局异常处理

Spring Boot提供了两种主要方法:

使用 HandlerExceptionResolver 定义全局异常处理策略

在控制器上添加 @ExceptionHandler 注解,这在某些特定场景下使用可能会很有用

2.7. 使用 slf4j 日志

应该使用日志框架进行日志记录,而不是使用System.out.println()手动执行。建议将 Slf4j 与 Spring Boot 中默认的日志框架 logback 一起使用。可以使用 Lombok的 @Slf4j 注释非常轻松地创建日志记录器

在Spring Boot中完成,几乎没有配置。只需获取该类的记录器实例:

Logger logger = LoggerFactory.getLogger(MyClass.class);

2.8. 代码测试

测试切片,你可以根据需要仅连接部分应用程序

2.9. 使用Swagger接口文档工具

在大多数情况下,其他应用程序将通过REST API 调用你的应用程序。因此我们需要维护一份API文档。文档应该由代码生成。

2.10. 使数据库独立于核心业务逻辑之外

理想情况下,你不希望服务知道它正在与哪个数据库通信,这需要一些抽象来封装对象的持久性

一些优秀的持久层框架可以帮助我们做到这些

2.11. 使用Spring Initializr来开始一个新的Spring Boot项目

使用Initializr创建应用程序可确保你获得经过测试和验证的依赖项,这些依赖项适用于Spring自动配置。

2.12. 使用 Lombok

Lombok 是一个 Java 库,可用于减少代码并允许我们使用其注释编写干净的代码

2.13. 避免空指针异常

  • 为了避免 NullPointerException,我们可以使用 java.util 包中的 Optional。
  • 我们还可以使用空安全库。例如:Apache Commons StringUtils
  • 对已知对象调用 equals() 和 equalsIgnoreCase() 方法。
  • 使用 valueOf() 而不是 toString()
  • 使用基于 IDE 的 @NotNull 和 @Nullable 注释

    2.14. 使用集合框架的最佳实践

  • 对我们的数据集使用适当的集合。
  • 将 forEach 与 Java 8 功能结合使用,并避免使用旧版 for 循环。
  • 使用接口类型而不是实现。
  • 使用 isEmpty() 而不是 size() 以获得更好的可读性。
  • 不返回空值,可以返回空集合。
  • 如果我们使用对象作为要存储在基于哈希的集合中的数据,则应重写 equals() 和 hashCode() 方法。

    2.15. 使用分页

使用物理分页

2.16. 使用自定义响应对象


本站总访问量