🍜SpringBoot框架②
目录
- 🍜SpringBoot框架②
- 一、SpringBoot 整合 Mybatis
- 1、环境搭建
- ①、依赖导入
- ②、项目目录(跟SSM差不多)
- 2、实现增删查改(Rest风格编写crud)
- ①三层开发以及映射代码
- ②异常处理(自定义工具类)
- ③目标对象Bean和启动类Starter
- ④测试结果(因内容过多,不做过多细化统一处理)
- 3、PostMan 工具下载与使用
- 4、分页条件查询操作
- ①、代码实现
- ②、Rest测试结果(使用PostMan)
- 二、SpringBoot-API && 测试单元
- 1、API 文档构建工具 - Swagger2
- ①、环境配置和配置类
- ②、常用API注解
- ③、对源代码使用API注解
- 2、 SpringBoot 单元测试
- ①、引入依赖
- ②、测试Service层和Controller层
- 三、分布式缓存和热部署
- 1、分布式缓存 Ehcache 整合
- ①、环境配置
- ②、缓存注解的介绍
- ③、通过缓存用户ID查询测试
- 2、SpringBoot 应用热部署
- ①、环境的配置
- ②、热部署测试
- 四、全局异常与事务控制、数据校验
- 1、事务控制
- 2、全局异常控制
- 3、Springboot数据校验
一、SpringBoot 整合 Mybatis
1、环境搭建
- 依赖的导入和插件引入,以及项目的目录结构(这里使用的是Mysql数据库)
①、依赖导入
pom.xml:>>>>
<!--springboot项目依赖-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
</parent>
----------------------------------------------------
<!-- web项目依赖-->
<dependency> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- web项目依赖-->
<dependency> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- mybatis 集成 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.1</version>
</dependency>
<!-- springboot 分页插件 -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.2.13</version>
</dependency>
<!-- mysql 驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- c3p0 数据源 -->
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.5</version>
</dependency>
<!-- 日志依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
②、项目目录(跟SSM差不多)
不罗嗦上代码~
2、实现增删查改(Rest风格编写crud)
- 首先实现简单的三层开发内容,以及Sql映射
- Rest不理解的可以看我之前的文章SSM整合
①三层开发以及映射代码
⭐mapper/UserMapper.java接口
public interface UserMapper {
//查询
User selectOne(String userName);
User selectId(int userId);
//插入
int insert(User user);
//删除
int delete(int userId);
}
⭐service/UserService.java接口和UserServiceImp.java实现类
//------------------接口------------------------------
public interface UserService {
User findOne(String name);
User findId(Integer id);
void add(User user);
void drop(Integer id);
}
//-----------------实现类-----------------------------
@Service
public class UserServiceImp implements UserService{
@Autowired(required = false)
private UserMapper userMapper;
//通过用户名查找
@Override
public User findOne(String name) {
return userMapper.selectOne(name);
}
//通过id查询用胡
@Override
public User findId(Integer id) {
return userMapper.selectId(id);
}
//添加插入
@Override
public void add(User user) {
//可能是空数据引入插件
AccessUtil.isOK(StringUtils.isBlank(user.getUserName()),"用户名不能为空");
//判断用户id
AccessUtil.isOK(StringUtils.isBlank(user.getUserPwd()),"密码不能为空");
//查询是否重复
User users=findOne(user.getUserName());
AccessUtil.isOK(users!=null,"用户名不能重复");
//判断插入是否成共
AccessUtil.isOK(userMapper.insert(user)<1,"插入失败");
}
//删除
@Override
public void drop(Integer id) {
AccessUtil.isOK(id==null || findId(id)==null,"删除记录不存在~");
//判断是否删除成功
int num=userMapper.delete(id);
AccessUtil.isOK(num<1,"删除失败!");
}
}
⭐controller/UserController.java
@RestController
public class SpringTest {
@Autowired
private UserService userService;
@GetMapping("test")
public String test(){
return "is ok";
}
//查询
@GetMapping("select/{name}")
public User selectOne(@PathVariable("name") String name){
return userService.findOne(name);
}
//查询
@GetMapping("selectid/{userId}")
public User selectid(@PathVariable("userId") Integer userId){
return userService.findId(userId);
}
//插入
@GetMapping("insert")
public CheckMsg insert(User user){
CheckMsg cm=new CheckMsg();
//捕获异常
try{
userService.add(user);
cm.setCode(100);
cm.setMsg("插入成功");
}catch (CheckException ce){
cm.setCode(ce.getCode());
cm.setMsg(ce.getMsg());
}catch (Exception e){
cm.setMsg(e.getMessage());
}
return cm;
}
//删除
@RequestMapping("delete/{userId}")
public CheckMsg delete(@PathVariable("userId") int userId){
CheckMsg cm=new CheckMsg();
//捕获异常
try{
userService.drop(userId);
cm.setCode(100);
cm.setMsg("删除成功");
}catch (CheckException ce){
cm.setCode(ce.getCode());
cm.setMsg(ce.getMsg());
}catch (Exception e){
cm.setMsg(e.getMessage());
}
return cm;
}
@GetMapping("page")
public PageInfo<User> pageSelect(PageQuery pq){
return userService.PageSelect(pq);
}
}
⭐resources/mappers/UserMapper.xml:
<?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.yjxxt.mapper.UserMapper">
<!-- 插入-->
<insert id="insert">
insert into user (userName,userPwd) values (#{userName},#{userPwd})
</insert>
<!-- 删除操作-->
<delete id="delete">
delete from user where userId=#{userId}
</delete>
<!-- 查询-->
<select id="selectOne" resultType="com.yjxxt.bean.User">
select * from user where userName=#{userName}
</select>
<!-- Id查询-->
<select id="selectId" resultType="com.yjxxt.bean.User">
select * from user where userId=#{userId}
</select>
</mapper>
⭐以上便实现了简单的增删查改,还差一点~~~
②异常处理(自定义工具类)
- 引入依赖StringUtils 工具类
<!-- StringUtils 工具类,需要引入 commons-lang3 依赖。-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
⭐自定义异常类:exceptions/CheckException.java
public class CheckException extends RuntimeException{
private int code=101;
private String msg="发生异常》》》》";
//重载构造器,get和set方法自己不全~!!!
public CheckException() {
super("发生异常》》》》");
}
public CheckException(int code){
super("发生异常》》》》");
this.code=code;
}
public CheckException(String msg){
super(msg);
this.msg=msg;
}
public CheckException(String msg,int code){
super(msg);
this.msg=msg;
this.code=code;
}
}
⭐com.yjxxt.util/CheckMsg.java自定义状态码对象:
public class CheckMsg {
//状态码
private int code=100;
private String msg="插入成功";
//自动补全set 、get方法
}
⭐com.yjxxt.util/AccessUtil.java对异常的间接调用
public class AccessUtil {
//静态方法进行数据审核
public static void isOK(boolean bl,String msg){
if(bl){
throw new CheckException(msg);
}
}
}
③目标对象Bean和启动类Starter
Bean/User.java这里字段与数据库最好同步
public class User implements Serializable {
private String userName;
private Integer userId;
private String userPwd;
}
启动类:>>>>>
@SpringBootApplication
@MapperScan("com.yjxxt.mapper")//扫描mapper下内容
public class Starter {
public static void main(String[] args) {
SpringApplication.run(Starter.class,args);
}
}
④测试结果(因内容过多,不做过多细化统一处理)
简单的浏览器测试一个:
根据用户名查询:
Rest风格URL插入,以及异常的展示:
总体上即完成了Mybatis的集成,也实现了异常的处理,还有PostMan辅助软件进行Rest风格URL测试,最后呢,想到此,应该有人发现貌似修改这个能力没有编写,这里直接省略,与插入的编写类似,不做多解释~~
3、PostMan 工具下载与使用
在企业 web 应用开发中,对服务器端接口进行测试,通常借助接口测试工具,这里使用 Postman 接口测试工具来对后台 restful 接口进行测试。
Postman 工具下载地址 : https://www.getpostman.com/apps ,选中对应平台下载即可。
下载安装后,启动 Postman 根据后台接口地址发送响应请求即可对接口进行测试。
4、分页条件查询操作
- 其实就是通过Sql里的limit+动态sql+PageInfo类+PageHelper类实现
①、代码实现
⭐自定义:query包/PageQuery.java
public class PageQuery {
//第几页
private Integer pageNum;
//显示几条
private Integer pageTotle;
//查找的用户户名
private String userName;
//get set toString 构造器记得生成!!!此处略
}
⭐mapper/UserMapper.java
public interface UserMapper {
//分页查找
List<User> pageQuery(PageQuery pg);
}
⭐service/UserService.java和UserServiceImp.java
public interface UserService {
PageInfo<User> PageSelect(PageQuery pq);
}
//--------------------UserServiceImp.java------------
@Service
public class UserServiceImp implements UserService{
@Autowired(required = false)
private UserMapper userMapper;
//分页查找
@Override
public PageInfo<User> PageSelect(PageQuery pq) {
//进行分页初始化分页规则
PageHelper.startPage(pq.getPageNum(),pq.getPageTotle());
List<User> luser=userMapper.pageQuery(pq);
//进行分页
PageInfo<User> pagelist=new PageInfo<User>(luser);
System.out.println(pagelist.isHasNextPage());
System.out.println(pagelist.getPageNum());
System.out.println(pagelist.isHasPreviousPage());
return pagelist;
}
}
⭐controller/UserController.java
@RestController
public class SpringTest {
@Autowired
private UserService userService;
@GetMapping("page")
public PageInfo<User> pageSelect(PageQuery pq){
return userService.PageSelect(pq);
}
}
mappers/UserMapper.java: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.yjxxt.mapper.UserMapper">
<!-- 分页查找-->
<select id="pageQuery" resultType="com.yjxxt.bean.User">
-- 如果没有输入userName参数,则查询全部数据
-- select * from user where userName like %{userName}%
select * from user
<where>
<if test="userName!=null and userName!=''">
and userName like "%${userName}%"
</if>
</where>
</select>
</mapper>
②、Rest测试结果(使用PostMan)
pageNum:第几页,pageTotle:显示几条,userName:查询用户的名字
浏览器:
二、SpringBoot-API && 测试单元
1、API 文档构建工具 - Swagger2
- 由于 Spring Boot 能够快速开发、便捷部署等特性,通常在使用 Spring Boot 构建 Restful 接口应用时考虑到多终端的原因,这些终端会共用很多底层业务逻辑,因此我们会抽象出这样一层来同时服务于多个移动端或者Web 前端。对于不同的终端公用一套接口 API 时,对于联调测试的时候就需要知道后端提供的接口 API列表文档,对于服务端开发人员来说就需要编写接口文档,描述接口的调用地址、参数结果等,这里借助第三方构建工具 Swagger2 来实现 API 文档生成功能。
①、环境配置和配置类
- 导入依赖
<!--API文档依赖-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<!-- API生成依赖-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
API配置类config/swagger2.java
package com.yjxxt.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@Configuration
@EnableSwagger2
public class Swagger2 {
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("com.yjxxt.controller"))
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("用户管理接口API文档")
.version("1.0")
.build();
}
}
②、常用API注解
- @Api
@Api:用在请求的类上,说明该类的作用
tags="说明该类的作用"
----------------------------------------------
@Api(tags="APP用户注册Controller")
- @ApiOperation
@ApiOperation:"用在请求的方法上,说明方法的作用"
value="说明方法的作用"
notes="方法的备注说明"
-----------------------------------------------
@ApiOperation(value="用户注册",notes="手机号、密码都是必填项,年龄是选填项,但必须是数字")
- @ApiImplicitParams
@ApiImplicitParams:用在请求的方法上,包含一组参数说明
@ApiImplicitParam:用在 @ApiImplicitParams 注解中,指定一个请求参数的配置信息
name:参数名
value:参数的汉字说明、解释
required:参数是否必须传
paramType:参数放在哪个地方
· header --> 请求参数的获取:@RequestHeader
· query --> 请求参数的获取:@RequestParam
· path(用于restful接口)--> 请求参数的获取:@PathVariable
· body(不常用)
· form(不常用)
dataType:参数类型,默认String,其它值dataType="Integer"
defaultValue:参数的默认值
@ApiImplicitParams({
@ApiImplicitParam(name="mobile",value="手机 号",required=true,paramType="form"),
@ApiImplicitParam(name="password",value="密 码",required=true,paramType="form"),
@ApiImplicitParam(name="age",value="年 龄",required=true,paramType="form",dataType="Integer"
) })
- @ApiResponses
@ApiResponses:用于请求的方法上,表示一组响应
@ApiResponse:用在@ApiResponses中,一般用于表达一个错误的响应信息
code:数字,例如400
message:信息,例如"请求参数没填好"
response:抛出异常的类
---------------------------------------------
@ApiOperation(value = "select请求", notes = "多个参数,多种的查询参数类型")
@ApiResponses({
@ApiResponse(code=400, message="请求参数没填好"),
@ApiResponse(code=404, message="请求路径没有或页面跳转路径不对")
})
- @ApiModel
@ApiModel:用于响应类上,表示一个返回响应数据的信息
(这种一般用在post创建的时候,使用@RequestBody这样的场景, 请求参数无法使用@ApiImplicitParam注解进行描述的时候)
@ApiModelProperty:用在属性上,描述响应类的属性
@ApiModel(description= "返回响应数据")
public class RestMessage implements Serializable{
@ApiModelProperty(value = "是否成功")
private boolean success=true;
@ApiModelProperty(value = "返回对象")
private Object data;
@ApiModelProperty(value = "错误编号")
private Integer errCode;
@ApiModelProperty(value = "错误信息")
private String message;
/* getter/setter */
}
③、对源代码使用API注解
User.java类加入API注解
@ApiModel(description = "响应结果-用户信息")
public class User implements Serializable {
@ApiModelProperty(value = "用户名")
private String userName;
@ApiModelProperty(value = "用户id",example = "0")
private Integer userId;
@ApiModelProperty(value = "用户密码")
private String userPwd;
}
controller层/UserController.java
//此处只给这三个方法增夹了API注解,其余类似
@RestController
public class SpringTest {
@Autowired
private UserService userService;
@ApiOperation(value = "测试springboot环境是否正常")
@GetMapping("test")
public String test(){
return "is ok";
}
//查询
@ApiOperation(value = "根据用户名查询用户信息")
@ApiImplicitParam(name = "name",value = "查询参数",required = true,paramType = "path")
@GetMapping("select/{name}")
public User selectOne(@PathVariable("name") String name){
return userService.findOne(name);
}
//查询
@ApiOperation(value = "根据用户ID获取用户信息")
@ApiImplicitParam(name = "userId",value = "查询参数",required = true,paramType = "path")
@GetMapping("selectid/{userId}")
@CachePut(value = "users", key = "#userId")
public User selectid(@PathVariable("userId") Integer userId){
return userService.findId(userId);
}
}
启动项目,访问地址浏览器访问 :http://localhost:8081/springboot/swagger-ui.html
2、 SpringBoot 单元测试
做过 web 项目开发的对于单元测试都并不陌生了,通过它能够快速检测业务代码功能的正确与否,SpringBoot 框架对单元测试也提供了良好的支持,来看 SpringBoot 应用中单元测试的使用。
①、引入依赖
<!-- 单元测试-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
②、测试Service层和Controller层
src/Test/java/com/yjxxt/bean/ServiceTest.java
@RunWith(SpringRunner.class)
@SpringBootTest(classes = {Starter.class})
public class ServiceTest {
@Autowired
private UserService userService;
@Before
public void before(){
System.out.println("before........");
}
@Test
public void test(){
System.out.println(userService.findId(4));
}
@After
public void after(){
System.out.println("after........");
}
}
–
新建测试类ControllerTest.java
@RunWith(SpringRunner.class)
@SpringBootTest(classes = {Starter.class})
@AutoConfigureMockMvc
public class ControllerTest {
private Logger log = LoggerFactory.getLogger(ControllerTest.class);
@Autowired
private MockMvc mockMvc;
//用户列表查询
@Test
public void apiTest01() throws Exception{
// 构建请求
MockHttpServletRequestBuilder request = MockMvcRequestBuilders
.get("/page")
.contentType("text/html")
// 指定请求的contentType头信息
.accept(MediaType.APPLICATION_JSON);
// 指定请求的Accept头信息
// 发送请求,获取请求结果
ResultActions perform = mockMvc.perform(request);
// 请求结果校验
perform.andExpect(MockMvcResultMatchers.status().isOk());
// 表示执行完成后返回相应的结果
MvcResult mvcResult = perform.andReturn();
// 得到执行后的响应
MockHttpServletResponse response = mvcResult.getResponse();
log.info("响应状态:{}", response.getStatus());
log.info("响应内容:{}", response.getContentAsString());
}
// 用户名记录查询
@Test
public void apiTest02() throws Exception{
MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.get("/select/wang"))
. andExpect(MockMvcResultMatchers.status().isOk()).andReturn();
log.info("响应状态:{}",mvcResult.getResponse().getStatus());
log.info("响应内容:{}",mvcResult.getResponse().getContentAsString());
}
}
三、分布式缓存和热部署
1、分布式缓存 Ehcache 整合
EhCache 是一个比较成熟的 Java 缓存框架,最早从 hibernate 发展而来, 是进程中的缓存系统,它提供了用内存,磁盘文件存储,以及分布式存储方式等多种灵活的 cache 管理方案,快速简单。
Spring Boot 对 Ehcache 的使用提供支持,所以在 Spring Boot 中只需简单配置即可使用 Ehcache 实现数据缓存处理。
①、环境配置
- 依赖导入
<!-- 缓存依赖Ehcache -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
</dependency>
- ehcache.xml配置文件
<ehcache name="mycache">
<!--
如果不使用磁盘存储,只需要将diskStore注释掉即可;
如果使用,需要在ehcache.xml文件中的ehcahce元素下的定义一个diskStore元素并指定其path属性。
-->
<diskStore path="C:\java\cache"/>
<!--
name:缓存名称。
maxElementsInMemory:缓存最大数目
maxElementsOnDisk:硬盘最大缓存个数。
eternal:对象是否永久有效,一但设置了,timeout将不起作用。
overflowToDisk:是否保存到磁盘,当系统宕机时
timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。
仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,表示可闲置时间无穷大。
timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。
最大时间介于创建时间和失效时间之间。
仅当eternal=false对象不是永久有效时使用,默认是0,也就是对象存活时间无穷大。
diskPersistent:是否缓存虚拟机重启期数据
Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。
默认是30MB。每个Cache都应该有自己的一个缓冲区。
diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。
memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,会根据指定的策略去清理内存
默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。
clearOnFlush:内存数量最大时是否清除。
memoryStoreEvictionPolicy:
可选策略有:
LRU(最近最少使用,默认策略)
Less Frequently Used,就是例子中使用的策略,就是一直以来最少被使用的。
FIFO(先进先出)
first in first out,这个是大家最熟的,先进先出。
LFU(最少访问次数)
Least Recently Used,最近最少使用的。
缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,
那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。
-->
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
maxElementsOnDisk="10000000"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
</defaultCache>
<cache
name="users"
eternal="false"
maxElementsInMemory="100"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="0"
timeToLiveSeconds="300"
memoryStoreEvictionPolicy="LRU"/>
</ehcache>
②、缓存注解的介绍
- @CacheConfig
用于标注在类上,可以存放该类中所有缓存的公有属性,比如设置缓存的名字
@CacheConfig(cacheNames = "users")
public interface UserService {。。。}
- @Cacheable
//用于读取数据的方法上
@Cacheable(value = "user", key = "#id")
User selectUserById(final Integer id);
- @CachePut
//用于写数据的方法上
@CachePut(value = "user", key = "#user.id")
public User save(User user) { users.add(user); return user; }
- @CacheEvict
//用于移除数据的方法上
@CacheEvict(value = "user", key = "#id")
void delete(final Integer id);
- @CacheConfig
//组合多个 Cache 注解使用
@Caching(
put = {
@CachePut(value = "user", key = "#user.id"),
@CachePut(value = "user", key = "#user.username"),
@CachePut(value = "user", key = "#user.age")
}
}
自动装配时,如果一个接口的实现类不止一个,则用@Resource指定名字装配,一个则使用@Autowired装配
③、通过缓存用户ID查询测试
UserService.java/selectid方法
@Service
public class UserServiceImp implements UserService{
@Autowired(required = false)
private UserMapper userMapper;
//通过id查询用胡
@Override
@CachePut(value = "users", key = "#id")
public User findId(Integer id) {
return userMapper.selectId(id);
}
}
第一次查询
删除控制台的查询信息,再次刷新页面查询
2、SpringBoot 应用热部署
- 热部署,就是在应用正在运行的时候升级软件(增加业务/修改bug),却不需要重新启动应用。
- 在原理上是使用了两个 ClassLoader,一个 ClassLoader 加载那些不会改变的类(第三方 Jar 包),另一个ClassLoader 加载会更改的类,称为 restart ClassLoader,这样在有代码更改的时候,原来的restart ClassLoader 被丢弃,重新创建一个 restart ClassLoader,由于需要加载的类相比较少,所以实现了较快的重启时间。
①、环境的配置
- 引入依赖 DevTools同时在 plugin 中添加 devtools 生效标志
<!-- 热部署依赖DevTools 的坐标 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<!--当前这个项目被继承之后,这个不向下传递-->
<optional>true</optional>
</dependency>
<!--打包工具-->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<fork>true</fork>
<!-- 如果没有该配置,热部署的devtools不生效 -->
</configuration>
</plugin>
devtools 可以实现页面热部署(即页面修改后会立即生效,这个可以直接在 application.properties文件中配置 spring.thymeleaf.cache=false 来实现),实现类文件热部署(类文件修改后不会立即生效),实现对属性文件的热部署。即devtools 会监听 classpath 下的文件变动,并且会立即重启应用(发生在保存时机),注意:因为其采用的虚拟机机制,该项重启是很快的。配置了后在修改 java 文件后也就支持了热启动,不过这种方式是属于项目重启(速度比较快的项目重启),会清空 session 中的值,也就是如果有用户登陆的话,项目重启后需要重新登陆。
- 配置application.yml文件
## 热部署配置
devtools:
restart:
enabled: true
# 设置重启的目录,添加目录的文件需要restart
additional-paths: src/main/java
# 解决项目自动重新编译后接口报404的问题
poll-interval: 3000
quiet-period: 1000
- IDEA配置
自动编译配置File -> Settings -> Compiler -> Build Project automatically
Registry 属性修改 ctrl + shift + alt + /,选择Registry,勾上 Compiler autoMake allow when app running
②、热部署测试
修改接口代码 控制台打印接收的 userName 参数,ctrl+f9 键重新编译,浏览器访问
四、全局异常与事务控制、数据校验
1、事务控制
这里 Spring Boot 集成了 mybatis 框架,mybatis 底层数据访问层实现基于 jdbc 来实现,所以在Spring Boot 环境下对事务进行控制,事务实现由 Spring Boot 实现并自动配置,在使用时通过注解方式标注相关方法加入事务控制即可。
在相关方法上加上注解@Transactional()即可
@Transactional(propagation = Propagation.REQUIRED)
2、全局异常控制
- 首先编写一个全局异常类GlobalExceptionHandler
- @ExceptionHandler:
该注解在 Spring 3.X 版本引入,在处理异常时标注在方法级别,代表当前方法处理的异常类型有哪些具体应用以 Restful 接口为例,测试保存用户接口。 - @ControllerAdvice:
该注解组合了 @Component 注解功能,最常用的就是作为全局异常处理的切面类,同时通过该注解可以指定包扫描的范围。@ControllerAdvice 约定了几种可行的返回值,如果是直接返回 model 类的话,需要使用@ResponseBody 进行 json 转换
//全局异常处理类
@ControllerAdvice
public class GlobalExceptionHandler{
/*** 全局异常处理 返回json*/
@ExceptionHandler(value = Exception.class)
@ResponseBody
public CheckMsg exceptionHandler(Exception e){
CheckMsg resultInfo = new CheckMsg();
resultInfo.setCode(300);
resultInfo.setMsg("操作失败!");
if(e instanceof CheckException){
CheckException pe = (CheckException) e;
resultInfo.setMsg(pe.getMsg());
resultInfo.setCode(pe.getCode());
}
return resultInfo;
}
}
这里对我们的全局异常类进行测试,将ID查询直接报异常看是否能够捕获到
//查询
@ApiOperation(value = "根据用户ID获取用户信息")
@ApiImplicitParam(name = "userId",value = "查询参数",required = true,paramType = "path")
@GetMapping("selectid/{userId}")
@CachePut(value = "users", key = "#userId")
public User selectid(@PathVariable("userId") Integer userId){
//报异常
AccessUtil.isOK(true,"测试一下......");
return null;
}
3、Springboot数据校验
常用注解校验如下:
案列演示:》》》
①、对User类添加注解
@ApiModel(description = "响应结果-用户信息")
public class User implements Serializable {
@NotBlank(message = "用户名不能为空!")
@ApiModelProperty(value = "用户名")
private String userName;
@ApiModelProperty(value = "用户id",example = "0")
private Integer userId;
@NotBlank(message = "用户ID不能为空!")
@Length(min = 6, max = 10,message = "密码长度至少6位但不超过10位!")
@ApiModelProperty(value = "用户密码")
private String userPwd;
}
//set和get方法略
②、UserController.java插入方法增加@Valid和修改
//插入
@PostMapping("insert")
public CheckMsg insert(@Valid User user){
CheckMsg cm=new CheckMsg();
// //捕获异常
// try{
userService.add(user);
cm.setCode(100);
cm.setMsg("插入成功");
// }catch (CheckException ce){
// cm.setCode(ce.getCode());
// cm.setMsg(ce.getMsg());
// }catch (Exception e){
// cm.setMsg(e.getMessage());
// }
return cm;
}
测试结果:》》》
完结撒花~~~~