var code = “12433d02-b242-4fd2-937d-750761a365ea”

说明:本博文有参考一些技术博主的思路,据实践内容及代码持续总结更新中。

五个分层维度:SpringBoot工程分层实战

1 分层思想

计算机领域有一句话:计算机中任何问题都可通过增加一个虚拟层解决。这句体现了分层思想重要性,分层思想同样适用于Java工程架构。

分层优点是每层只专注本层工作,可以类比设计模式单一职责原则,或者经济学比较优势原理,每层只做本层最擅长的事情。

分层缺点是层之间通信时,需要通过适配器,翻译成本层或者下层可以理解的信息,通信成本有所增加。

我认为工程分层需要从五个维度思考:

(1) 单一

每层只处理一类事情,满足单一职责原则

(2) 降噪

信息在每一层进行传输,满足最小知识原则,只向下层传输必要信息

(3) 适配

每层都需要一个适配器,翻译信息为本层或者下层可以理解的信息

(4) 业务

业务对象可以整合业务逻辑,例如使用充血模型整合业务

(5) 数据

数据对象尽量纯净,尽量不要聚合业务

1.2 九层结构

综上所述SpringBoot工程可以分为九层:

  • 工具层:util
  • 整合层:integration
  • 基础层:infrastructure
  • 服务层:service
  • 领域层:domain
  • 门面层:facade
  • 控制层:controller
  • 客户端:client
  • 启动层:boot

2 分层详解

创建测试项目user-demo-service:

user-demo-service
-user-demo-service-boot
-user-demo-service-client
-user-demo-service-controller
-user-demo-service-domain
-user-demo-service-facade
-user-demo-service-infrastructure
-user-demo-service-integration
-user-demo-service-service
-user-demo-service-util

2.1 util
工具层承载工具代码

不依赖本项目其它模块

只依赖一些通用工具包

user-demo-service-util
-/src/main/java
-date
-DateUtil.java
-json
-JSONUtil.java
-validate
-BizValidator.java

2.2 infrastructure
基础层核心是承载数据访问,entity实体对象承载在本层。

2.2.1 项目结构
代码层分为两个领域:

player:运动员
game:比赛
每个领域具有两个子包:

entity
mapper
user-demo-service-infrastructure
-/src/main/java
-player
-entity
-PlayerEntity.java
-mapper
-PlayerEntityMapper.java
-game
-entity
-GameEntity.java
-mapper
-GameEntityMapper.java
-/src/main/resources
-mybatis
-sqlmappers
-gameEntityMappler.xml
-playerEntityMapper.xml

复制代码

2.2.2 本项目间依赖关系

infrastructure只依赖工具模块

<dependency>
<groupId>com.test.javafront</groupId>
<artifactId>user-demo-service-util</artifactId>
</dependency>

2.2.3 核心代码

创建运动员数据表:

CREATE TABLE `player` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
`player_id` varchar(256) NOT NULL COMMENT '运动员编号',
`player_name` varchar(256) NOT NULL COMMENT '运动员名称',
`height` int(11) NOT NULL COMMENT '身高',
`weight` int(11) NOT NULL COMMENT '体重',
`game_performance` text COMMENT '最近一场比赛表现',
`creator` varchar(256) NOT NULL COMMENT '创建人',
`updator` varchar(256) NOT NULL COMMENT '修改人',
`create_time` datetime NOT NULL COMMENT '创建时间',
`update_time` datetime NOT NULL COMMENT '修改时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8

运动员实体对象,gamePerformance字段作为string保存在数据库,这体现了数据层尽量纯净,不要整合过多业务,解析任务应该放在业务层:

public class PlayerEntity {
private Long id;
private String playerId;
private String playerName;
private Integer height;
private Integer weight;
private String creator;
private String updator;
private Date createTime;
private Date updateTime;
private String gamePerformance;
}

运动员Mapper对象:

@Repository
public interface PlayerEntityMapper {
int insert(PlayerEntity record);
int updateById(PlayerEntity record);
PlayerEntity selectById(@Param("playerId") String playerId);
}

2.3 domain

2.3.1 概念说明

领域层是DDD流行兴起之概念

可以通过三组对比理解领域层

领域对象 VS 数据对象
领域对象 VS 业务对象
领域层 VS 业务层

(1) 领域对象 VS 数据对象

数据对象字段尽量纯净,使用基本类型

public class PlayerEntity {
private Long id;
private String playerId;
private String playerName;
private Integer height;
private Integer weight;
private String creator;
private String updator;
private Date createTime;
private Date updateTime;
private String gamePerformance;
}

以查询结果领域对象为例

领域对象需要体现业务含义

public class PlayerQueryResultDomain {
private String playerId;
private String playerName;
private Integer height;
private Integer weight;
private GamePerformanceVO gamePerformance;
} public class GamePerformanceVO {
// 跑动距离
private Double runDistance;
// 传球成功率
private Double passSuccess;
// 进球数
private Integer scoreNum;
}

(2) 领域对象 VS 业务对象

业务对象同样会体现业务,领域对象和业务对象有什么不同呢?其中一个最大不同是领域对象采用充血模型聚合业务。

运动员新增业务对象:

public class PlayerCreateBO {
private String playerName;
private Integer height;
private Integer weight;
private GamePerformanceVO gamePerformance;
private MaintainCreateVO maintainInfo;
}

运动员新增领域对象:

public class PlayerCreateDomain implements BizValidator {
private String playerName;
private Integer height;
private Integer weight;
private GamePerformanceVO gamePerformance;
private MaintainCreateVO maintainInfo; @Override
public void validate() {
if (StringUtils.isEmpty(playerName)) {
throw new BizException(ErrorCodeBizEnum.ILLEGAL_ARGUMENT);
}
if (null == height) {
throw new BizException(ErrorCodeBizEnum.ILLEGAL_ARGUMENT);
}
if (height > 300) {
throw new BizException(ErrorCodeBizEnum.ILLEGAL_ARGUMENT);
}
if (null == weight) {
throw new BizException(ErrorCodeBizEnum.ILLEGAL_ARGUMENT);
}
if (null != gamePerformance) {
gamePerformance.validate();
}
if (null == maintainInfo) {
throw new BizException(ErrorCodeBizEnum.ILLEGAL_ARGUMENT);
}
maintainInfo.validate();
}
}

(3) 领域层 VS 业务层

领域层和业务层都包含业务,二者不是替代关系,而是互补关系。业务层可以更加灵活组合不同领域业务,并且可以增加流控、监控、日志、权限,分布式锁等控制,相较于领域层功能更为丰富。

2.3.2 项目结构

代码层分为两个领域:

player:运动员
game:比赛

每个领域具有三个子包:

domain:领域对象
event:领域事件
vo:值对象
user-demo-service-domain
-/src/main/java
-base
-domain
-BaseDomain.java
-event
-BaseEvent.java
-vo
-BaseVO.java
-MaintainCreateVO.java
-MaintainUpdateVO.java
-player
-domain
-PlayerCreateDomain.java
-PlayerUpdateDomain.java
-PlayerQueryResultDomain.java
-event
-PlayerUpdateEvent.java
-vo
-GamePerformanceVO.java
-game
-domain
-GameCreateDomain.java
-GameUpdateDomain.java
-GameQueryResultDomain.java
-event
-GameUpdateEvent.java
-vo
-GameSubstitutionVO.java

2.3.3 本项目间依赖关系
domain依赖本项目两个模块:

util
client

之所以依赖client模块是因为领域对象聚合了业务校验,以下信息需要暴露至外部:

BizException
ErrorCodeBizEnum
<dependency>
<groupId>com.test.javafront</groupId>
<artifactId>user-demo-service-util</artifactId>
</dependency>
<dependency>
<groupId>com.test.javafront</groupId>
<artifactId>user-demo-service-client</artifactId>
</dependency>

2.3.4 核心代码

以运动员修改领域对象为例:

// 运动员修改领域对象

public class PlayerUpdateDomain extends BaseDomain implements BizValidator {
private String playerId;
private String playerName;
private Integer height;
private Integer weight;
private String updator;
private Date updatetime;
private GamePerformanceVO gamePerformance;
private MaintainUpdateVO maintainInfo; @Override
public void validate() {
if (StringUtils.isEmpty(playerId)) {
throw new BizException(ErrorCodeBizEnum.ILLEGAL_ARGUMENT);
}
if (StringUtils.isEmpty(playerName)) {
throw new BizException(ErrorCodeBizEnum.ILLEGAL_ARGUMENT);
}
if (null == height) {
throw new BizException(ErrorCodeBizEnum.ILLEGAL_ARGUMENT);
}
if (height > 300) {
throw new BizException(ErrorCodeBizEnum.ILLEGAL_ARGUMENT);
}
if (null == weight) {
throw new BizException(ErrorCodeBizEnum.ILLEGAL_ARGUMENT);
}
if (null != gamePerformance) {
gamePerformance.validate();
}
if (null == maintainInfo) {
throw new BizException(ErrorCodeBizEnum.ILLEGAL_ARGUMENT);
}
maintainInfo.validate();
}
}

// 比赛表现值对象

public class GamePerformanceVO implements BizValidator {

    // 跑动距离
private Double runDistance;
// 传球成功率
private Double passSuccess;
// 进球数
private Integer scoreNum; @Override
public void validate() {
if (null == runDistance) {
throw new BizException(ErrorCodeBizEnum.ILLEGAL_ARGUMENT);
}
if (null == passSuccess) {
throw new BizException(ErrorCodeBizEnum.ILLEGAL_ARGUMENT);
}
if (Double.compare(passSuccess, 100) > 0) {
throw new BizException(ErrorCodeBizEnum.ILLEGAL_ARGUMENT);
}
if (null == runDistance) {
throw new BizException(ErrorCodeBizEnum.ILLEGAL_ARGUMENT);
}
if (null == scoreNum) {
throw new BizException(ErrorCodeBizEnum.ILLEGAL_ARGUMENT);
}
}
}

// 修改人值对象

public class MaintainUpdateVO implements BizValidator {

    // 修改人
private String updator;
// 修改时间
private Date updateTime; @Override
public void validate() {
if (null == updator) {
throw new BizException(ErrorCodeBizEnum.ILLEGAL_ARGUMENT);
}
if (null == updateTime) {
throw new BizException(ErrorCodeBizEnum.ILLEGAL_ARGUMENT);
}
}
}

2.4 service

2.4.1 项目结构

user-demo-service-service
-/src/main/java
-player
-adapter
-PlayerServiceAdapter.java
-event
-PlayerMessageSender.java
-service
-PlayerService.java
-game
-adapter
-GameServiceAdapter.java
-event
-GameMessageSender.java
-service
-GameService.java

2.4.2 本项目间依赖关系
service依赖本项目四个模块:

util
domain
integration
infrastructure
<dependency>
<groupId>com.test.javafront</groupId>
<artifactId>user-demo-service-domain</artifactId>
</dependency>
<dependency>
<groupId>com.test.javafront</groupId>
<artifactId>user-demo-service-infrastructure</artifactId>
</dependency>
<dependency>
<groupId>com.test.javafront</groupId>
<artifactId>user-demo-service-util</artifactId>
</dependency>
<dependency>
<groupId>com.test.javafront</groupId>
<artifactId>user-demo-service-integration</artifactId>
</dependency>

2.4.3 核心代码

以运动员编辑服务为例:

// 运动员服务

public class PlayerService {

    @Resource
private PlayerEntityMapper playerEntityMapper;
@Resource
private PlayerMessageSender playerMessageSender;
@Resource
private PlayerServiceAdapter playerServiceAdapter; public boolean updatePlayer(PlayerUpdateDomain player) {
AssertUtil.notNull(player, new BizException(ErrorCodeBizEnum.ILLEGAL_ARGUMENT));
player.validate();
PlayerEntity entity = playerServiceAdapter.convertUpdate(player);
playerEntityMapper.updateById(entity);
playerMessageSender.sendPlayerUpdatemessage(player);
return true;
}
}

// 运动员消息服务

public class PlayerMessageSender {

    @Resource
private PlayerServiceAdapter playerServiceAdapter; public boolean sendPlayerUpdatemessage(PlayerUpdateDomain domain) {
PlayerUpdateEvent event = playerServiceAdapter.convertUpdateEvent(domain);
log.info("sendPlayerUpdatemessage event={}", event);
return true;
}
}

// 服务适配器

public class PlayerServiceAdapter {

    // domain -> entity
public PlayerEntity convertUpdate(PlayerUpdateDomain domain) {
PlayerEntity player = new PlayerEntity();
player.setPlayerId(domain.getPlayerId());
player.setPlayerName(domain.getPlayerName());
player.setWeight(domain.getWeight());
player.setHeight(domain.getHeight());
if (null != domain.getGamePerformance()) {
player.setGamePerformance(JacksonUtil.bean2Json(domain.getGamePerformance()));
}
String updator = domain.getMaintainInfo().getUpdator();
Date updateTime = domain.getMaintainInfo().getUpdateTime();
player.setUpdator(updator);
player.setUpdateTime(updateTime);
return player;
} // domain -> event
public PlayerUpdateEvent convertUpdateEvent(PlayerUpdateDomain domain) {
PlayerUpdateEvent event = new PlayerUpdateEvent();
event.setPlayerUpdateDomain(domain);
event.setMessageId(UUID.randomUUID().toString());
event.setMessageId(PlayerMessageType.UPDATE.getMsg());
return event;
}
}

2.5 intergration
本项目可能会依赖外部服务,那么将外部DTO转换为本项目可以理解的对象,需要在本层处理。

2.5.1 项目结构
假设本项目调用了用户中心服务:

user-demo-service-intergration
-/src/main/java
-user
-adapter
-UserClientAdapter.java
-proxy
-UserClientProxy.java

复制代码
2.5.2 本项目间依赖关系
intergration依赖本项目两个模块:

util
domain

之所以依赖domain模块,是因为本层需要将外部DTO转换为本项目可以理解的对象,这些对象就放在domain模块。

<dependency>
<groupId>com.test.javafront</groupId>
<artifactId>user-demo-service-domain</artifactId>
</dependency>
<dependency>
<groupId>com.test.javafront</groupId>
<artifactId>user-demo-service-util</artifactId>
</dependency>

2.5.3 核心代码
现在我们将外部对象UserClientDTO

转换为本项目领域对象UserInfoDomain

(1) 外部服务

// 外部对象

public class UserInfoClientDTO implements Serializable {
private String id;
private String name;
private Date createTime;
private Date updateTime;
private String mobile;
private String cityCode;
private String addressDetail;
}

// 外部服务

public class UserClientService {

    // RPC模拟
public UserInfoClientDTO getUserInfo(String userId) {
UserInfoClientDTO userInfo = new UserInfoClientDTO();
userInfo.setId(userId);
userInfo.setName(userId);
userInfo.setCreateTime(DateUtil.now());
userInfo.setUpdateTime(DateUtil.now());
userInfo.setMobile("test-mobile");
userInfo.setCityCode("test-city-code");
userInfo.setAddressDetail("test-address-detail");
return userInfo;
}
}

(2) 本项目领域对象

domain模块新增user领域:

user-demo-service-domain
-/src/main/java
-user
-domain
-UserDomain.java
-vo
-UserAddressVO.java
-UserContactVO.java

user领域对象代码:

// 用户领域

public class UserInfoDomain extends BaseDomain {
private UserContactVO contactInfo;
private UserAddressVO addressInfo;
}

// 地址值对象

public class UserAddressVO extends BaseVO {
private String cityCode;
private String addressDetail;
}

// 联系方式值对象

public class UserContactVO extends BaseVO {
private String mobile;
}

(3) 适配器

public class UserClientAdapter {

    // third dto -> domain
public UserInfoDomain convertUserDomain(UserInfoClientDTO userInfo) {
UserInfoDomain userDomain = new UserInfoDomain();
UserContactVO contactVO = new UserContactVO();
contactVO.setMobile(userInfo.getMobile());
userDomain.setContactInfo(contactVO); UserAddressVO addressVO = new UserAddressVO();
addressVO.setCityCode(userInfo.getCityCode());
addressVO.setAddressDetail(userInfo.getAddressDetail());
userDomain.setAddressInfo(addressVO);
return userDomain;
}
}

(4) 调用外部服务

public class UserClientProxy {

    @Resource
private UserClientService userClientService;
@Resource
private UserClientAdapter userClientAdapter; public UserInfoDomain getUserInfo(String userId) {
UserInfoClientDTO user = userClientService.getUserInfo(userId);
UserInfoDomain result = userClientAdapter.convertUserDomain(user);
return result;
}
}

2.6 facade + client

设计模式中有一种Facade模式,称为门面模式或者外观模式。这种模式提供一个简洁对外语义,屏蔽内部系统复杂性。

client承载数据对外传输对象DTO,facade承载对外服务,这两层必须满足最小知识原则,无关信息不必对外透出。

这样做有两个优点:

简洁性:对外服务语义明确简洁
安全性:敏感字段不能对外透出

2.6.1 项目结构

(1) client

user-demo-service-client
-/src/main/java
-base
-dto
-BaseDTO.java
-error
-BizException.java
-BizErrorCode.java
-event
-BaseEventDTO.java
-result
-ResultDTO.java
-player
-dto
-PlayerCreateDTO.java
-PlayerQueryResultDTO.java
-PlayerUpdateDTO.java
-enums
-PlayerMessageTypeEnum.java
-event
-PlayerUpdateEventDTO.java
-service
-PlayerClientService.java

(2) facade

user-demo-service-facade
-/src/main/java
-player
-adapter
-PlayerFacadeAdapter.java
-impl
-PlayerClientServiceImpl.java
-game
-adapter
-GameFacadeAdapter.java
-impl
-GameClientServiceImpl.java

2.6.2 本项目间依赖关系
client不依赖本项目其它模块,这一点非常重要,因为client会被外部引用,必须保证这一层简洁和安全。

facade依赖本项目三个模块:

domain
client
service
<dependency>
<groupId>com.test.javafront</groupId>
<artifactId>user-demo-service-domain</artifactId>
</dependency>
<dependency>
<groupId>com.test.javafront</groupId>
<artifactId>user-demo-service-client</artifactId>
</dependency>
<dependency>
<groupId>com.test.javafront</groupId>
<artifactId>user-demo-service-service</artifactId>
</dependency>

2.6.3 核心代码

(1) DTO

以查询运动员信息为例,查询结果DTO只封装最关键字段,例如运动员ID、创建时间、修改时间等业务不强字段就无须透出:

public class PlayerQueryResultDTO implements Serializable {
private String playerName;
private Integer height;
private Integer weight;
private GamePerformanceDTO gamePerformanceDTO;
}

(2) 客户端服务

public interface PlayerClientService {
public ResultDTO<PlayerQueryResultDTO> queryById(String playerId);
}

(3) 适配器

public class PlayerFacadeAdapter {

    // domain -> dto
public PlayerQueryResultDTO convertQuery(PlayerQueryResultDomain domain) {
if (null == domain) {
return null;
}
PlayerQueryResultDTO result = new PlayerQueryResultDTO();
result.setPlayerId(domain.getPlayerId());
result.setPlayerName(domain.getPlayerName());
result.setHeight(domain.getHeight());
result.setWeight(domain.getWeight());
if (null != domain.getGamePerformance()) {
GamePerformanceDTO performance = convertGamePerformance(domain.getGamePerformance());
result.setGamePerformanceDTO(performance);
}
return result;
}
}

(4) 服务实现

public class PlayerClientServiceImpl implements PlayerClientService {

    @Resource
private PlayerService playerService;
@Resource
private PlayerFacadeAdapter playerFacadeAdapter; @Override
public ResultDTO<PlayerQueryResultDTO> queryById(String playerId) {
PlayerQueryResultDomain resultDomain = playerService.queryPlayerById(playerId);
if (null == resultDomain) {
return ResultCommonDTO.success();
}
PlayerQueryResultDTO result = playerFacadeAdapter.convertQuery(resultDomain);
return ResultCommonDTO.success(result);
}
}

2.7 controller

facade服务实现可以作为RPC提供服务,controller则作为本项目HTTP接口提供服务,供前端调用。

controller需要注意HTTP相关特性,敏感信息例如登陆用户ID不能依赖前端传递,登陆后前端会在请求头带一个登陆用户信息,服务端需要从请求头中获取并解析。

2.7.1 项目结构

user-demo-service-controller
-/src/main/java
-config
-CharsetConfig.java
-controller
-player
-PlayerController.java
-game
-GameController.java

2.7.2 本项目依赖关系

controller依赖本项目一个模块:

facade

根据依赖传递原理同时依赖以下模块:

domain
client
service
<dependency>
<groupId>com.test.javafront</groupId>
<artifactId>user-demo-service-facade</artifactId>
</dependency>

2.7.3 核心代码

@RestController
@RequestMapping("/player")
public class PlayerController { @Resource
private PlayerClientService playerClientService; @PostMapping("/add")
public ResultDTO<Boolean> add(@RequestHeader("test-login-info") String loginUserId, @RequestBody PlayerCreateDTO dto) {
dto.setCreator(loginUserId);
ResultCommonDTO<Boolean> resultDTO = playerClientService.addPlayer(dto);
return resultDTO;
} @PostMapping("/update")
public ResultDTO<Boolean> update(@RequestHeader("test-login-info") String loginUserId, @RequestBody PlayerUpdateDTO dto) {
dto.setUpdator(loginUserId);
ResultCommonDTO<Boolean> resultDTO = playerClientService.updatePlayer(dto);
return resultDTO;
} @GetMapping("/{playerId}/query")
public ResultDTO<PlayerQueryResultDTO> queryById(@RequestHeader("test-login-info") String loginUserId, @PathVariable("playerId") String playerId) {
ResultCommonDTO<PlayerQueryResultDTO> resultDTO = playerClientService.queryById(playerId);
return resultDTO;
}
}

2.8 boot

boot作为启动层,只有启动入口代码

2.8.1 项目结构

所有模块代码均必须属于com.user.demo.service子路径

user-demo-service-boot
-/src/main/java
-com.user.demo.service
-MainApplication.java

2.8.2 本项目间依赖
boot引用本项目所有模块

util
integration
infrastructure
service
domain
facade
controller
client

2.8.3 核心代码

@MapperScan("com.user.demo.service.infrastructure.*.mapper")
@SpringBootApplication
public class MainApplication {
public static void main(final String[] args) {
SpringApplication.run(MainApplication.class, args);
}
}

3 文章总结

我们再次回顾分层五个思考维度:

(1) 单一

每层只处理一类事情,例如util只承载工具对象,integration只处理外部服务,每层职责单一且清晰

(2) 降噪

如无必要无增实体,例如查询结果DTO只透出最关键字段,例如运动员ID、创建时间、修改时间等业务不强字段无须透出

(3) 适配

service、facade、intergration层都存在适配器,翻译信息为本层或者下层可以理解的信息

(4) 业务

业务对象可以通过充血模型聚合业务,例如在业务对象中聚合业务校验逻辑

(5) 数据

数据对象要纯净,例如通过string类型保存比赛表现,数据层无需解析

Spring Boot 项目五维度九层次分层架构实现实践研究——持续更新中的更多相关文章

  1. 用Gradle构建Spring Boot项目

    相比起Maven的XML配置方式,Gradle提供了一套简明的DSL用于构建Java项目,使我们就像编写程序一样编写项目构建脚本.本文将从无到有创建一个用Gradle构建的Spring Boot项目, ...

  2. 让Spring Boot项目启动时可以根据自定义配置决定初始化哪些Bean

    让Spring Boot项目启动时可以根据自定义配置决定初始化哪些Bean 问题描述 实现思路 思路一 [不符合要求] 思路二[满足要求] 思路三[未试验] 问题描述 目前我工作环境下,后端主要的框架 ...

  3. 将spring boot项目部署到tomcat容器中

    一. 我这里用的环境 tomcat: tomcat 8 jdk: jdk 7 spring boot 版本: 1.5 二. 将创建好的spring boot项目做如下修改 2.1. 修改打包形式 在p ...

  4. Spring Boot 项目实战(五)集成 Dubbo

    一.前言 上篇介绍了 Redis 的集成过程,可用于解决热点数据访问的性能问题.随着业务复杂度的提高,单体应用越来越庞大,就好比一个类的代码行数越来越多,分而治之,切成多个类应该是更好的解决方法,所以 ...

  5. spring boot / cloud (五) 自签SSL证书以及HTTPS

    spring boot / cloud (五) 自签SSL证书以及HTTPS 前言 什么是HTTPS? HTTPS(全称:Hyper Text Transfer Protocol over Secur ...

  6. 从零部署Spring boot项目到云服务器(正式部署)

    上一篇文章总结了在Linux云服务器上部署Spring Boot项目的准备过程,包括环境的安装配置,项目的打包上传等. 链接在这里:http://www.cnblogs.com/Lovebugs/p/ ...

  7. Java Spring Boot VS .NetCore (九) Spring Security vs .NetCore Security

    Java Spring Boot VS .NetCore (一)来一个简单的 Hello World Java Spring Boot VS .NetCore (二)实现一个过滤器Filter Jav ...

  8. Spring Boot 2 (五):Docker Compose + Spring Boot + Nginx + Mysql 实践

    Spring Boot 2 (五):Docker Compose + Spring Boot + Nginx + Mysql 实践 Spring Boot + Nginx + Mysql 是实际工作中 ...

  9. Spring Boot(十五):spring boot+jpa+thymeleaf增删改查示例

    Spring Boot(十五):spring boot+jpa+thymeleaf增删改查示例 一.快速上手 1,配置文件 (1)pom包配置 pom包里面添加jpa和thymeleaf的相关包引用 ...

  10. Spring Boot项目简单上手+swagger配置+项目发布(可能是史上最详细的)

    Spring Boot项目简单上手+swagger配置 1.项目实践 项目结构图 项目整体分为四部分:1.source code 2.sql-mapper 3.application.properti ...

随机推荐

  1. slice 切片数组测试记录【GO 基础】

    〇.测试前准备 本文是在 GO 环境下测试记录系列之一,GO 基本环境部署步骤将略过,直接上代码. 下面是常用命令:[初始化 + 运行 + 编译] // {GOPATH} 环境变量值, example ...

  2. http-server -S 开启 https 服务

    下载 openssl Win64 OpenSSL v1.1.1k Light http://slproweb.com/download/Win64OpenSSL_Light-1_1_1k.exe 一路 ...

  3. 选择单词后 按 ctrl + space 单词发音

    需求: 在ide或其他地方,经常有单词发音不是很确定,但并不要很详细 就听个单词发音. 确定快捷键: 左手单手操作,我键盘上貌似就 左边的ctrl和空格键 还没有设定 翻译软件: 使用 pc端的 欧路 ...

  4. Pod中断预算 PodDisruptionBudget(PDB)

    PodDisruptionBudget(PDB)是Kubernetes中的一个资源对象,用于确保在进行维护.升级或其他操作时,系统中的Pod不会被意外中断或终止.PDB提供了一种机制,通过限制在给定时 ...

  5. 反转链表——java

    给定一个链表,请你将链表反转过来. 举例:原链表:1→2→3→4→5→null 反转链表:5→4→3→2→1→null 代码: package algorithm_niuke; public clas ...

  6. 在Blazor中使用Chart.js快速创建图表

    前言 BlazorChartjs是一个在Blazor中使用Chart.js的库(支持Blazor WebAssembly和Blazor Server两种模式),它提供了简单易用的组件来帮助开发者快速集 ...

  7. python 图片转文字小工具

    应群友要求,要做一个图片转文字的格式,因为有些人的简历中只有一张图片要提取他里面的文字就不好办了. 于是就有了下面这个小工具: 功能:选择要识别的图片后,识别出来后存到.txt文本中. 实现原理,基于 ...

  8. debian12 出现Waiting for suspend/resume device ... Begin: Running /scripts/local-block ... done.

    /etc/initramfs-tools/conf.d/resume里对应的交换分区的uuid不正确 删除/etc/initramfs-tools/conf.d/resume 再运行 sudo upd ...

  9. Java封装xml格式参数请求第三方接口

    Java封装xml格式参数请求第三方接口 1.引用包 import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers ...

  10. Python爬取imdb电影数据并存储到mysql数据库

    数据获取方式:微信搜索关注[靠谱杨阅读人生]回复[电影].整理不易,资源付费,谢谢支持. Python爬虫代码: 1 import re 2 import time 3 import tracebac ...