文章目录
Springboot
快速入门
创建项目
- 官网
- IDEA
编写启动器和TestDemo
建议启动类以
*Application
的命名结尾,并放置在顶层包(cn.hxj.projectname.*.Application)的下面,方便扫描注解扫描
// 当接收外部的web请求时会考虑给标有该注解类
// @RestController = @Controller + @@ResponseBody 源码也是这2个注解的组合
@RestController
// 近似于 @ComponentScan + @EnableAutoConfiguration(可以使用这2个注解替代)
@SpringBootApplication
//建议放在顶层包下
public class SpringbootTemplateApplication {
@RequestMapping("/")
String home() {
System.out.println("Hello Springboot");
return "Hello World!";
}
public static void main(String[] args) {
// SpringApplication.run(SpringbootTemplateApplication.class, args);
SpringApplication app = new SpringApplication(SpringbootTemplateApplication.class);
app.setBannerMode(Banner.Mode.OFF);
app.run(args);
// new SpringApplicationBuilder()
// // .bannerMode(Banner.Mode.OFF) // 推荐使用yaml配置
// .sources(SpringbootTemplateApplication.class)
// .run(args);
}
}
默认pom文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.1</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>cn.hxj</groupId>
<artifactId>springboot-template</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboot-template</name>
<description>Demo project for Spring Boot study</description>
<properties>
<java.version>13</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<!-- to create an executable jar, we need to add the spring-boot-maven-plugin to our pom.xml. -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
图标控制
自定义新图标
在resources目录下新增banner.txt,图案生成可搜索“ASCII 图案生成”,或直接使用该网站
banner.txt
Application Version: ${application.version}${application.formatted-version}
Spring Boot Version: ${spring-boot.version}${spring-boot.formatted-version}
///
// why-use step template top-to-bottom //
///
控制图标显示
代码设置
SpringApplication app = new SpringApplication(SpringbootTemplateApplication.class);
app.setBannerMode(Banner.Mode.OFF);
app.run(args);
// 流式编程
// new SpringApplicationBuilder()
// // .bannerMode(Banner.Mode.OFF) // 推荐使用yaml配置
// .sources(SpringbootTemplateApplication.class)
// .run(args);
application配置文件设置
spring:
main:
banner-mode: "console"
日志
日志门面:slf4j
日志实现:log4j、logback、log4j2、java.utl.logging
springboot默认使用:slf4j + logback
slf4j可以整合其他日志,通过桥接器等模式
日志技术一般使用了:门面模式、工厂模式等设计模式
日志可以用来替代打印语句
日志等级
debug:替代原来的输出语句
info:执行的方法流程等信息
warn:警告信息
error:替代try-catch里的打印信息
springboot配置logback日志
logback-spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<!--
每隔30s自动检查配置文件是否改变,改变的话重新加载配置
configuration: scan="true" scanPeriod="5 seconds"
-->
<configuration>
<!-- 这个是引入springboot的defaults.xml配置 -->
<!--<include resource="org/springframework/boot/logging/logback/defaults.xml"/>-->
<conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter" />
<conversionRule conversionWord="wex" converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter" />
<conversionRule conversionWord="wEx" converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter" />
<property name="CONSOLE_LOG_PATTERN"
value="${CONSOLE_LOG_PATTERN:-%clr(%d{${LOG_DATEFORMAT_PATTERN:-HH:mm}}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(:){faint} %m %n %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
<property name="CONSOLE_LOG_CHARSET" value="${CONSOLE_LOG_CHARSET:-default}"/>
<property name="FILE_LOG_PATTERN" value="${FILE_LOG_PATTERN:-%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd HH:mm:ss.SSS}} ${LOG_LEVEL_PATTERN:-%5p} ${PID:- } --- [%t] %-40.40logger{39} : %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
<property name="FILE_LOG_CHARSET" value="${FILE_LOG_CHARSET:-default}"/>
<!-- 自定义 -->
<property name="log_dir" value="logs"/>
<appender name="consoleAppender" class="ch.qos.logback.core.ConsoleAppender">
<target>System.out</target> <!-- 默认标准输出,可以改为err -->
<encoder>
<charset>UTF-8</charset>
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
<!--<pattern>[%p] %d{yyyy-MM-dd HH:mm:ss} %m %n</pattern>-->
</encoder>
</appender>
<appender name="rollingFileAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log_dir}/roll_logback.log</file>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${FILE_LOG_PATTERN}</pattern>
<!--<pattern>[%p] %d{yyyy-MM-dd HH:mm:ss} %m %n</pattern>-->
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!-- 按天或文件大小进行拆分, 加上.gz/.zip就是压缩包 -->
<fileNamePattern>${log_dir}/rolling.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
<!--<maxHistory>30</maxHistory>-->
<!-- 按文件大小拆分 -->
<maxFileSize>8MB</maxFileSize>
<!-- 最大容量 -->
<totalSizeCap>1GB</totalSizeCap>
<!-- 清除 -->
<cleanHistoryOnStart>true</cleanHistoryOnStart>
</rollingPolicy>
<!-- 过滤器 -->
<!--
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>ERROR</level>
</filter>
-->
</appender>
<appender name="asyncAppender" class="ch.qos.logback.classic.AsyncAppender">
<!-- 指定某个 appender 启用异步模式 -->
<appender-ref ref="consoleAppender"/>
<appender-ref ref="rollingFileAppender"/>
</appender>
<root level="info"> <!-- 一般开发info,上线error -->
<!-- <appender-ref ref="asyncAppender" />--> <!-- 小项目没必要异步 -->
<appender-ref ref="consoleAppender"/>
<appender-ref ref="rollingFileAppender"/>
</root>
<!-- 优先级高于root -->
<logger name="cn.hxj.springboottemplate.dao" level="DEBUG"/>
<logger name="cn.hxj.springboottemplate.controller" level="DEBUG"/>
<logger name="cn.hxj.springboottemplate.service" level="DEBUG"/>
</configuration>
springboot使用日志(配合IDEA-live-templates)
一般都使用slf4j的日志门面api,而不使用logback的api,为了便于日志实现的切换
由于springboot提供了其他日志的转移器jar,所以可以把其他日志实现整合到slf4j+logback
中
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@RestController
@RequestMapping("/hello")
public class HelloController {
private static final Logger logger = LoggerFactory.getLogger(HelloController.class);//logc 补全
@GetMapping("/sayHi")
public String sayHi(int i) {
logger.info("进入{}类的{}方法", "HelloController", "sayHi");//logm 补全
logger.info("i = " + i);// logv 补全
logger.info("i = " + i);// logp 补全
try {
throw new Exception("抛出异常");
} catch (Exception e) {
logger.error(e.getMessage());
}
logger.info("结束执行sayHi方法");
return "Hello";
}
}
模板配置
切换配置文件
application.
为主配置文件,application-*.
为子配置文件
application.yaml
spring:
profiles:
# 值为application-*的*
active: dev
application-dev.yaml,同理可以配置test
、prod
环境的切换
# 注意必须是以 application-*.yaml 这种格式
spring:
main:
banner-mode: "console"
server:
port: 8080
servlet:
context-path: /springboot-template
Freemarker & Thymeleaf 视图技术集成
Freemarker视图集成(建议使用)
SpringBoot内部支持Freemarker视图技术的集成,并提供了自动化配置类FreeMarkerAutoConfiguration,借助自动化配置可以很方便的集成Freemarker基础到SpringBoot环境中。这里借助入i ]项目引入Freemarker环境配置。
-
Starter坐标引入
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-freemarker</artifactId> </dependency>
-
添加Freemarker配置信息
Freemarker默认默认视图路径为resources/templates目录(由自动化配置类FreemarkerProperties决定),该目录可以进行在application.yml中进行修改spring: freemarker: # 点击该key就可以进入源码看到默认路径为 "classpath:/templates/" template-loader-path: classpath:/templates/freemarker-view/ # 使用和jsp一样,这里就不赘述了 # 设置视图后缀 默认值为 ftlh suffix: .ftl # 响应类型 默认值如下 content-type: text/html # 设置文件编码 默认值如下 charset: utf-8
IDEA的注释要按ctrl + shift + /
Thymeleaf视图集成
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
thymeleaf:
prefix: classpath:/templates/thymeleaf-view/
cache: false # 开发是方便调试
静态资源访问
其它资源不可以通过浏览器(外部)直接访问
注意Maven经常不编译静态资源,这时可以把target目录删除再编译
Web静态资源的默认存储位置
"classpath:/META-INF/resources/"
"classpath:/resources/" // 注意这个的意思是可以在resources目录下新建一个resources放静态资源,而不是resources可以访问
"classpath:/static/"
"classpath:/public/"
可以通过配置文件找到实现的类
spring:
web:
resources:
static-locations:
修改静态资源位置
spring:
web:
resources:
# 如果有需要建议追加而不是替换
# 点进入可以发现是一个数组,所以可以使用value可以使用数组的形式
static-locations:
- "classpath:/META-INF/resources/"
- "classpath:/resources/"
- "classpath:/static/"
- "classpath:/public/"
- "classpath:/my-static/"
Spring打包与部署
当项目开发完毕进行部署上线时,需要对项目进行打包操作,入门中构建的项目属于普通应用,由于SpringBoot内嵌Tomcat容器,所有打包后的jar包默认可以自行运行。
Jar包部署
自定义名称
<build>
<finalName>springboot-template</finalName>
</build>
配置打包命令
idea下配置clean compile package -Dmaven.test.skip=true
执行打包命令,target 目录得到待部署的项目文件。
该命令的意思是清理 编译 打包并跳过测试
选中jar包复制绝对路径,命令行执行java -jar a/b/c/xxx.jar
即可
另一种打包方法
$ mvn package # to create an executable jar, we need to add the spring-boot-maven-plugin to our pom.xml
$ java -jar target/myapplication-0.0.1-SNAPSHOT.jar # 运行springboot打包的jar项目
$ mvn spring-boot:run # 或者直接运行不打包
War包部署
修改pom.xml
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging> <!-- 默认打包方式是jar -->
仅在运行时使用内嵌的tomcat
<!-- 仅在运行使用内部tomcat -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
修改启动器类
添加容器启动加载文件(类似于读取web.xml),这里通过继承Springooterletnitialier类并重写configure方法来实现,在部署项目时指定外部Tomcat读取项目入口方法。
配置打包命令
idea下配置clean compile package -Dmaven.test.skip=true
执行打包命令,target 目录得到待部署的项目文件。
整合Mybatis
修改pom文件
<!-- 集成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>
<!-- druid数据源 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.6</version>
</dependency>
<!-- 字符串检验工具类 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
修改配置文件
spring:
mvc:
hiddenmethod:
filter:
enabled: true # 转换表单请求为put or delete
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
# 字符串多行书写方法
url: "jdbc:mysql://localhost:3306/springboot_template\
?useSSL=true&\
useUnicode=true&\
characterEncoding=utf8&\
serverTimezone=Asia/Shanghai"
username: root
password: root
# mybatis配置
mybatis:
mapper-locations: classpath:/mappers/*.xml
type-aliases-package: cn.hxj.springboottemplate.domain
configuration:
# 下滑线转驼峰
map-underscore-to-camel-case: true
# pageHelper
pagehelper:
helper-dialect: mysql # 方言,各种SQL语言的分页语法不尽相同
配置扫描包路径
@MapperScan("cn.hxj.springboottemplate.dao")
public class SpringbootTemplateApplication {
// run a app
}
编程测试代码
@RestController
@RequestMapping("/user")
public class UserController {
UserService userService;
// 只有一个构造方法,不用@Autowried也可以自动注入
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping("/name/{userName}")
public User findUserByUserName(@PathVariable String userName) {
return userService.findUserByUserName(userName);
}
}
@Service
// @SuppressWarnings("all")
public class UserService {
// 注意不能使用@Autowried,因为会找不到bean
// 原因:IDEA无法在编辑时获取对应的bean
@Resource
UserMapper userMapper;
public User findUserByUserName(String userName) {
return userMapper.queryUserByUserName(userName);
}
}
// 如果要使用@Authwired注解,就要在这里加上@Repository
public interface UserMapper {
User queryUserByUserName(String userName);
}
CRUD & 分页查询
分页对象
package cn.hxj.springboottemplate.query;
public class UserQuery {
private Integer curPage = 1; // 当前页
private Integer pageSize = 10; // 每页显示条数
private String userName; // 查询条件:用户名
}
持久层对象
package cn.hxj.springboottemplate.po;
public class User implements Serializable {
Integer userId;
String userName;
String userPwd;
}
ResultInfo
对象
package cn.hxj.springboottemplate.vo;
public class ResultInfo {
private Integer code = 200; // 状态码,成功默认200
private String msg = "操作成功"; // 提示信息
private Object result;
}
校验工具类
package cn.hxj.springboottemplate.utils;
public class AssertUtil {
private AssertUtil() {}
public static void isTrue(Boolean flag, String msg) {
if(flag) {
throw new ParamsException(msg);
}
}
}
参数异常类
package cn.hxj.springboottemplate.exceptions;
public class ParamsException extends RuntimeException {
private Integer code = 300;
private String msg = "参数异常!";
public ParamsException() {
super("参数异常!");
}
public ParamsException(String msg) {
super("参数异常!");
this.msg = msg;
}
public ParamsException(Integer code, String msg) {
super(msg);
this.code = code;
this.msg = msg;
}
// getter & setter & tostring
}
前端
<a href="user/name/zhangsan">提交get请求</a>
<hr/>
<form method="post" action="user">
<input type="text" name="userName">
<input type="password" name="userPwd">
<input type="submit" value="提交post请求">
</form>
<hr/>
<form method="post" action="user">
<input type="hidden" name="_method" value="put">
<input type="text" name="userId">
<input type="text" name="userName">
<input type="password" name="userPwd">
<input type="submit" value="提交put请求">
</form>
<hr/>
<form method="post" action="user/userId/1">
<input type="hidden" name="_method" value="delete">
<!-- <input type="text" name="userId"> -->
<input type="submit" value="提交delete请求">
</form>
<hr/>
<a href="user/page?pageSize=2&curPage=2">分页请求</a>
<hr/>
controller
@RestController
@RequestMapping("user")
public class UserController {
UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping("name/{userName}")
public User findUserByUserName(@PathVariable String userName) {
return userService.findUserByUserName(userName);
}
@GetMapping("userId/{userId}")
public User findUserByUserId(@PathVariable Integer userId) {
return userService.findUserByUserId(userId);
}
@PostMapping
public ResultInfo insert(User user) {
ResultInfo resultInfo = new ResultInfo();
try {
userService.insert(user);
} catch (ParamsException e) {
e.printStackTrace();
resultInfo.setCode(e.getCode());
resultInfo.setMsg(e.getMsg());
} catch (Exception e) {
e.printStackTrace();
resultInfo.setCode(500);
resultInfo.setMsg("用户添加失败!");
}
return resultInfo;
}
@PutMapping
public ResultInfo update(User user) {
ResultInfo resultInfo = new ResultInfo();
try {
userService.update(user);
} catch (ParamsException e) {
e.printStackTrace();
resultInfo.setCode(e.getCode());
resultInfo.setMsg(e.getMsg());
} catch (Exception e) {
e.printStackTrace();
resultInfo.setCode(500);
resultInfo.setMsg("用户更新失败!");
}
return resultInfo;
}
@DeleteMapping("userId/{userId}")
public ResultInfo delete(@PathVariable Integer userId) {
ResultInfo resultInfo = new ResultInfo();
try {
userService.delete(userId);
} catch (ParamsException e) {
e.printStackTrace();
resultInfo.setCode(e.getCode());
resultInfo.setMsg(e.getMsg());
} catch (Exception e) {
e.printStackTrace();
resultInfo.setCode(500);
resultInfo.setMsg("用户删除失败!");
}
return resultInfo;
}
@GetMapping("page")
public PageInfo<User> findByPage(UserQuery userQuery) {
return userService.findByPage(userQuery);
}
}
Service
@Service
// @SuppressWarnings("all")
public class UserService {
// @Resource
UserMapper userMapper;
public UserService(UserMapper userMapper) {
this.userMapper = userMapper;
}
public User findUserByUserName(String userName) {
return userMapper.selectByUserName(userName);
}
public User findUserByUserId(Integer userId) {
return userMapper.selectByUserId(userId);
}
public void insert(User user) {
AssertUtil.isTrue(StringUtils.isBlank(user.getUserName()), "用户名不能为空");
AssertUtil.isTrue(StringUtils.isBlank(user.getUserPwd()), "密码不能为空");
AssertUtil.isTrue(userMapper.selectByUserName(user.getUserName()) != null, "用户名已存在");
AssertUtil.isTrue(userMapper.insert(user) < 1, "用户添加失败");
}
public void update(User user) {
// 更新时必须传入用户id,假设该id是当前登录用户的id
AssertUtil.isTrue(StringUtils.isBlank(user.getUserName()), "用户名不能为空");
AssertUtil.isTrue(StringUtils.isBlank(user.getUserPwd()), "密码不能为空");
AssertUtil.isTrue(user.getUserId() == null, "账号未登录");
User temp = userMapper.selectByUserName(user.getUserName());
AssertUtil.isTrue(temp != null && !temp.getUserId().equals(user.getUserId()), "用户名已存在");
AssertUtil.isTrue(userMapper.update(user) < 1, "更新失败");
}
public void delete(Integer userId) {
AssertUtil.isTrue(userId == null || userMapper.selectByUserId(userId) == null, "用户不存在");
AssertUtil.isTrue(userMapper.delete(userId) < 1, "删除失败");
}
public PageInfo<User> findByPage(UserQuery userQuery) {
PageHelper.startPage(userQuery.getCurPage(), userQuery.getPageSize());
return new PageInfo<User>(userMapper.selectByPage(userQuery));
}
}
dao
@Repository
public interface UserMapper {
User selectByUserName(String userName);
User selectByUserId(Integer userId);
int insert(User user);
int update(User user);
int delete(Integer userId);
List<User> selectByPage(UserQuery userQuery);
}
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="cn.hxj.springboottemplate.dao.UserMapper">
<select id="selectByUserName" parameterType="String" resultType="User">
select user_id, user_name, user_pwd
from tb_user
where user_name = #{userName}
</select>
<select id="selectByUserId" parameterType="int" resultType="User">
select user_id, user_name, user_pwd
from tb_user
where user_id = #{userId}
</select>
<insert id="insert" parameterType="User">
insert into tb_user(user_name, user_pwd)
values (#{userName}, #{userPwd})
</insert>
<update id="update" parameterType="User">
update tb_user
set user_name = #{userName},
user_pwd = #{userPwd}
where
user_id = #{userId}
</update>
<delete id="delete" parameterType="int">
delete from tb_user
where user_id = #{userId}
</delete>
<select id="selectByPage" resultType="User">
select user_id, user_name, user_pwd
from tb_user
<where>
<if test="userName != null and userName.trim().length() > 0">
and user_name like concat('%', #{userName}, '%')
</if>
</where>
</select>
</mapper>
PageHelper实现原理
玩转spring全家桶在0x3fhxj2中,可以看看,非常nice
当然PageHelper的原理得找mybatis插件的实现原理
API文档构建工具- Swagger2
由于Spring Boot能够快速开发、便捷部署等特性,通常在使用Spring Boot构建Restful接应用时考虑到多终端的原因,这些终端会共用很多底层业务逻辑,因此我们会抽象出这样一层来同时服务于多个移动端或者Web前端。对于不同的终端公用一套接口API时,对于联调测试的时候就需要知道后端提供的接口API列表文档,对于服务端开发人员来说就需要编写接口文档,描述接口的调用地址、参数结果等,这里借助第三方构建I具Swagger2来实现API文档生成功能。
修改pom.xml文件
<!-- API构建 -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
添加配置类
package cn.hxj.springboottemplate.config;
@Configuration
@EnableSwagger2
public class Swagger2 {
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("cn.hxj.springboottemplate.controller"))
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("用户管理接口API文档")
.version("1.0")
.build();
}
}
Swagger2常用注解
@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(不常用)
from(不常用)
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: 用在@ApiResponse中,一般表单一个错误的响应消息
code: 数字,例如:404
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 code;
@ApiModelProperty(value="错误信息")
private String message;
}
示例
Controller层
@Api(tags = "用户模块")
@RestController
@RequestMapping("user")
public class UserController {
UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@ApiOperation(value = "根据用户名查找用户记录", notes = "用户名不能为空")
@ApiImplicitParam(name = "userName", value = "用户名", required = true,
dataType = "String", // 参数类型,默认String
paramType = "path") // 表示参数从路径上获取
@GetMapping("name/{userName}")
public User findUserByUserName(@PathVariable String userName) {
return userService.findUserByUserName(userName);
}
@ApiOperation(value = "根据用户ID查找用户记录", notes = "用户ID不能为空")
@ApiImplicitParam(name = "userId", value = "用户ID", required = true,
dataType = "Integer", // 参数类型,默认String
paramType = "path") // 表示参数从路径上获取
@GetMapping("userId/{userId}")
public User findUserByUserId(@PathVariable Integer userId) {
return userService.findUserByUserId(userId);
}
@ApiOperation(value = "添加用户", notes = "用户不能为空")
@ApiImplicitParam(name = "user", value = "用户实体类",
dataType = "User", // 参数类型,默认String
paramType = "form") // 表示参数从表单上获取
@PostMapping
public ResultInfo insert(User user) {
ResultInfo resultInfo = new ResultInfo();
try {
userService.insert(user);
} catch (ParamsException e) {
e.printStackTrace();
resultInfo.setCode(e.getCode());
resultInfo.setMsg(e.getMsg());
} catch (Exception e) {
e.printStackTrace();
resultInfo.setCode(500);
resultInfo.setMsg("用户添加失败!");
}
return resultInfo;
}
@ApiOperation(value = "更新用户", notes = "用户不能为空")
@ApiImplicitParam(name = "user", value = "用户实体类",
dataType = "User", // 参数类型,默认String
paramType = "form") // 表示参数从表单上获取
@PutMapping
public ResultInfo update(User user) {
ResultInfo resultInfo = new ResultInfo();
try {
userService.update(user);
} catch (ParamsException e) {
e.printStackTrace();
resultInfo.setCode(e.getCode());
resultInfo.setMsg(e.getMsg());
} catch (Exception e) {
e.printStackTrace();
resultInfo.setCode(500);
resultInfo.setMsg("用户更新失败!");
}
return resultInfo;
}
@ApiOperation(value = "根据用户ID删除用户记录", notes = "用户ID不能为空")
@ApiImplicitParam(name = "userId", value = "用户ID", required = true,
dataType = "Integer", // 参数类型,默认String
paramType = "path") // 表示参数从路径上获取
@DeleteMapping("userId/{userId}")
public ResultInfo delete(@PathVariable Integer userId) {
ResultInfo resultInfo = new ResultInfo();
try {
userService.delete(userId);
} catch (ParamsException e) {
e.printStackTrace();
resultInfo.setCode(e.getCode());
resultInfo.setMsg(e.getMsg());
} catch (Exception e) {
e.printStackTrace();
resultInfo.setCode(500);
resultInfo.setMsg("用户删除失败!");
}
return resultInfo;
}
@ApiOperation(value = "根据分页条件查询用户记录", notes = "默认第1页,一页10条记录")
@ApiImplicitParam(name = "userQuery", value = "用户查询对象",
dataType = "UserQuery", // 参数类型,默认String
paramType = "form") // 表示参数从路径上获取
@GetMapping("page")
public PageInfo<User> findByPage(UserQuery userQuery) {
return userService.findByPage(userQuery);
}
}
实体类
@ApiModel(description = "用户实体类")
public class User implements Serializable {
@ApiModelProperty(name = "userId", value = "用户ID", notes = "唯一")
Integer userId;
@ApiModelProperty(name = "userName", value = "用户名", notes = "唯一")
String userName;
String userPwd;
}
SpringBoot应用热部署
什么是热部署?
热部署,就是在应用正在运行的时候升级软件(增加业务/修改bug),却不需要重新启动应用。
大家都知道在项目开发过程中,常常会改动页面数据或者修改数据结构,为了显示改动效果,往往需要重启应用查看改变效果,其实就是重新编译生成了新的Class文件,这个文件里记录着和代码等对应的各种信息,然后Class文件将被虚拟机的ClassLoader加载。而热部署正是利用了这个特点,它监听到如果有Class文件改动了,就会创建一个新的 Claass oader进行加载该文件,经过一系列的过程,最终将结果呈现在我们眼前,Spring Boot通过配置DevTools工具来达到热部署效果。
在原理上是使用了两个ClassLoader,一个ClassLoader加载那些不会改变的类(第三方Jar包),另一个ClassLoader加载会更改的类,称为restart ClassLoader,这样在有代码更改的时候,原来的restart ClassLoader被丢弃,重新创建一个restart ClassLoader,由于需要加载的类相比较少,所以实现了较快的重启时间。
热部署环境配置与测试
配置DevTools环境
devtools可以实现页面热部署(即页面修改后会立即生效,这个可以直接在application.properties文件中spring.thymeleaf.cache=false来实现),实现类文件热部署(类文件修改后不会立即生效),实现对属性文件的热部署。即devtools会监听classpath下的文件变动,并且会立即重启应用(发生在保存时机),注意:因为其采用的虚拟机机制,该项重启是很快的。配置了后在修改java 文件后也就支持了热启动,不过这种方式是属于项目重启(速度比较快的项目重启),会清空session中的值,也就是如果有用户登陆的话,项目重启后需要重新陆。
默认情况下,/META-INF/maven, /META-INF/resources, /resources, /static, /templates, /public 这些文件夹下的文件修改不会使应用重启,但是会重新加载( devtools内嵌了一一个LiveReload server,当资源发生改变时,浏览器刷新)
<!-- 热部署依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<!-- 当这个项目被继承后,这个不向下传递 -->
<optional>true</optional>
</dependency>
<!-- 热部署生效 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<fork>true</fork> <!-- 如果没有配置,默认热部署不生效 -->
</configuration>
</plugin>
全局配置文件配置
在application.yml中配置spring.devtools.restart.enabled=false,此时restart 类加载器还会初始化,但不会监视文件更新。
spring:
# 热部署配置
devtools:
restart:
enabled: true
# 设置重启的目录,添加目录的文件需要restart
additional-paths: src/main/java
# 解决项目自动重新编译后接口报404的问题
poll-interval: 3000
quiet-period: 1000
IDEA修改配置
当我们修改了Java类后,IDEA默认是不自动编译的,而spring boot devtools又是监测classpath下的文件发生变化才会重启应用,所以需要设置IDEA的自动编译。
- 静态自动编译
- File -> Settings -> Compiler -> Build Project automatically(注意要在other settings配置,不然会只作用于当前项目)
- 动态自动编译
- Registry属性修改,ctrl+ shift + alt+/,选择Registry,勾上Compiler autoMake allow when app running
- 修改配置
配置取消自动保存
因为IDEA如果设置自动编译又自动保存,那在启动时修改项目就会一直编译,所以关了自动保存比较好
示例
@GetMapping("name/{userName}")
public User findUserByUserName(@PathVariable String userName) {
System.out.println("热部署");
System.out.println("不会自动部署, 因为去掉了自动保存");
// 通过修改打印信息看是否能实现热部署
// System.out.println("没有热部署");
// System.out.println("更新classes");
return userService.findUserByUserName(userName);
}
修改后按ctrl+s保存就会热部署,如果没生效就点下锤子
单元测试
建议Service层测试写单元测试,在对应的类按ctrl+shift+T就可以生成一个测试类
至于Controller层可以使用PostMan或浏览器或Swagger2等方法测试
Spring Boot 2.2.0 版本开始引入 JUnit5 作为单元测试默认库,当前我使用的版本是2.5.1,所以相较于Junit4写法会有所变动
添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
示例
Service层
@SpringBootTest
class UserServiceTest {
@Autowired
private UserService userService;
@BeforeEach
void setUp() {
}
@AfterEach
void tearDown() {
}
@Test
void insert() throws Exception {
userService.insert(new User("junit5+springboot2.2以上", "123abc"));
}
}
Controller层
@SpringBootTest(classes = {SpringbootTemplateApplication.class})//classes可以省略
@AutoConfigureMockMvc
class UserControllerTest {
@Autowired
private MockMvc mockMvc;
private static final Logger logger = LoggerFactory.getLogger(UserControllerTest.class);
@Test
void findUserByUserName() throws Exception {
MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.get("/user/name/admin")).
andExpect(MockMvcResultMatchers.status().isOk()).
andReturn();
MockHttpServletResponse response = mvcResult.getResponse();
logger.info("响应状态: {} ", response.getStatus());
logger.info("响应内容: {} ", response.getContentAsString());
}
@Test
void findByPage() throws Exception {
// 构建请求
MockHttpServletRequestBuilder request = MockMvcRequestBuilders.get("/user/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();
logger.info("响应状态: {} ", response.getStatus());
logger.info("响应内容: {} ", response.getContentAsString());
}
}
分布式缓存Ecache整合
EhCache是一个比较成熟的Java缓存框架,最早从hibernate发展而来,是进程中的缓存系统, 它提供了用内存,磁盘文件存储,以及分布式存储方式等多种灵活的cache管理方案,快速简单。
Spring Boot对Ehcache的使用提供支持,所以在Spring Boot中只需简单配置即可使用Ehcache实现数据缓存处理。
SpringCache相关注解
SpringBoot缓存实现内部使用SpringCache实现缓存控制,这里集成Ehcache实际上是对SpringCache抽象的其中一种实现,这里在使用Ehcache实现缓存控制时相关注解说明如下:
@CacheConfig
用于标注在类上,可以存放该类中所有缓存的公有属性,比如设置缓存的名字。
@CacheConfig(cacheNames = "users")
public interface UserService { }
配置了该注解的数据访问对象中返回的内容将存储在名为users的缓存对象中,我们也可以不适应该注解,直接通过@Cacheable自己配置缓存集的名字来定义。
@Cacheable
应用到读取数据的方法上,即可缓存的方法,如查找方法,先从缓存中读取,如果没有再调用响应的方法获取数据,然后把数据添加到缓存中。
该注解主要有下面几个参数:
- value, cacheNames: 两个等同的参数( cacheNames为Spring 4新增,作为value的别名),用于指定缓存存储的集合名。由于Spring 4中新增了@CacheConfig,因此在Spring 3中原本必须有的value属性,也成为非必需项了。
- key: 缓存对象存储在Map集合中的key值,非必需, 缺省按照函数的所有参数组合作为key值,若自己配置需使用SpEL表达式,比如: @Cacheable(key= “#p0”):使用函数第一个参数作为缓存的key值,更多关于SpEL表达式的详细内容可参考官方文档。
- condition: 缓存对象的条件,非必需,也需使用SpEL表达式, 只有满足表达式条件的内容才会被缓存,比如: @Cacheable(key = “#p0”, condition = “#p0.length() ❤️”),表示只有当第一个参数的长度小于3的时候才 会被缓存。
- unless: 另外一个缓存条件参数,非必需,需使用SpEL表达式。它不同于condition参数的地方在于它的判断时机,该条件是在函数被调用之后才做判断的,所以它可以通过对result进行判断。
- keyGenerator: 用于指定key生成器,非必需。若需要指定一 个自定义的key生成器,我们需要去实现
org.springframework.cache.interceptor.KeyGenerator
接口,并使用该参数来指定。需要注意的是:该参数与key是互斥的。 - cacheManager: 用于指定使用哪个缓存管理器,非必需。只有当有多个时才需要使用
- cacheResolver: 用于指定使用那个缓存解析器,非必需。需通过
org.springframework.cache.interceptor.CacheResolver
接口来实现自己的缓存解析器,并用该参数指定。
@Cacheable(cacheNames="user", key="#id")
User selectUserById(final Integer id);
@CachePut
应用到写数据的方法上,如新增、修改方法,调用方法时会自动把相应的数据放入缓存,@CachePut的参数与@Cacheable类似
@CachePut(cacheNames="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) ;
除了同@Cacheable一样的参数之外,@CacheEvict 还有下面两个参数:
- allEntries: 非必需,默认为false。当为true时,会移除所有数据
- beforeInvocation: 非必需,默认为false, 会在调用方法之后移除数据。当为true时,会在调用方法之前移除数据。
@Caching
组合多个Cache注解使用
// 将id --> user, username --> user, age --> user进行缓存
@Caching(
put = {
@CachePut(value = "user", key = "#user.id"),
@CachePut(value = "user", key = "#user.username"),
@CachePut(value = "user", key = "#user.age"),
}
)
环境搭建
pom.xml依赖添加
<!-- Spring-boot 缓存支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<!-- 缓存实现 Ehcache -->
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
</dependency>
配置文件添加
resources目录下新增ehcache.xml文件
<ehcache name= "mycache">
<!--
如果不使用磁盘存储,只需要将diskStore注释掉即可;
如果使用,需要在ehcache . xml文件中的ehcahce元素下的定义一个diskStore元素并指定其path属性。
-->
<diskStore path="D:\hxj\dev\workspace\springboot\springboot-template"/>
<!--
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" />
<cache name= "users"
eternal="false"
maxElementsInMemory="100"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="0"
timeToLiveSeconds="300"
memoryStoreEvictionPolicy="LRU"/>
</ehcache>
修改全局配置文件
spring:
# ehcache配置文件
cache:
ehcache:
config: ehcache.xml
启动缓存
@EnableCaching
public class SpringbootTemplateApplication {
}
JavaBean对象实现序列化
这样才能进行磁盘存储
public class User implements Serializable {
}
示例
用户查询
@Service
public class UserService {
// 注意不要导错包,是springframework的包
@Cacheable(cacheNames = "users", key = "#userId")
public User findUserByUserId(Integer userId) {
return userMapper.selectByUserId(userId);
}
}
用户分页查询
@Cacheable(cacheNames = "users",
key = "#userQuery.userName + '-' + #userQuery.curPage+ '-' + #userQuery.pageSize")
public PageInfo<User> findByPage(UserQuery userQuery) {
// 分页参数
PageHelper.startPage(userQuery.getCurPage(), userQuery.getPageSize());
// 分页对象
return new PageInfo<User>(userMapper.selectByPage(userQuery));
}
用户更新 & 删除缓存
@CachePut(cacheNames = "users", key = "#user.userId") // 必须有返回值存入缓存
public User update(User user) {
// 更新时必须传入用户id,假设该id是当前登录用户的id
AssertUtil.isTrue(StringUtils.isBlank(user.getUserName()), "用户名不能为空");
AssertUtil.isTrue(StringUtils.isBlank(user.getUserPwd()), "密码不能为空");
AssertUtil.isTrue(user.getUserId() == null, "账号未登录");
User temp = userMapper.selectByUserName(user.getUserName());
AssertUtil.isTrue(temp != null && !temp.getUserId().equals(user.getUserId()), "用户名已存在");
AssertUtil.isTrue(userMapper.update(user) < 1, "更新失败");
return user;
}
@CacheEvict(cacheNames = "users", key = "#userId")
public void delete(Integer userId) {
AssertUtil.isTrue(userId == null || userMapper.selectByUserId(userId) == null, "用户不存在");
AssertUtil.isTrue(userMapper.delete(userId) < 1, "删除失败");
}
定时调度集成-Quartz
在日常项目运行中,我们总会有需求在某一时间段周期性的执行某个动作。比如每天在某个时间段导出报表,或者每隔多久统计一次现在在线的用户量等。
在Spring Boot中有Java自带的java.util.Timer类,也有强大的调度器Quartz,还有SpringBoot自带的Scheduled来实现。Scheduled 在Spring3.X引入,默认SpringBoot自带该功能,使用起来也很简单,在启动类级别添加@EnableScheduling注解即可引入定时任务环境。但遗憾的是Scheduled默认不支持分布式环境,这里主要讲解Quartz时钟调度框架与Spring Boot集成。
环境整合
添加jobs 包,定义待执行job任务。实现Job接口,并且在execute 方法中实现自己的业务逻辑。
package cn.hxj.springboottemplate.jobs;
public class MyFirstJob implements Job {
private static final Logger logger = LoggerFactory.getLogger(MyFirstJob.class);
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
logger.info("{} Hello Quartz", sdf.format(new Date()));
}
}
构建调度配置类
创建JobDetail 实例并定义Trigger 注册到scheduler,启动scheduler 开启调度
package cn.hxj.springboottemplate.config;
@Configuration
public class QuartzConfig {
// 定义Job实例
@Bean
public JobDetail jobDetail() {
// JobBuilder:定义和创建JobDetail实例的接口
return JobBuilder.newJob(MyFirstJob.class).storeDurably().build();
}
@Bean
public Trigger trigger1() {
// 构造特定行为的调度
SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(1) // 每秒执行一次
.repeatForever();// 无限重复
// 生成调度触发器
return TriggerBuilder.newTrigger()
.withIdentity("trigger1", "group1")
.withSchedule(scheduleBuilder)
.forJob(jobDetail())
.build();
}
@Bean
public Trigger trigger2() {
return TriggerBuilder.newTrigger()
.withIdentity("trigger2", "group1")
// 5 秒执行一次
.withSchedule(CronScheduleBuilder.cronSchedule("0/5 * * * * ? *"))
.forJob(jobDetail())
.build();
}
}
全局异常 & 事务控制
Spring Boot事务支持
在使用Jdbc作为数据库访问技术时,Spring Boot框架定义了基于jdbc的PlatformTransactionManager接口的实现DataSourceTransactionManager,并在Spring Boot应用启动时自动进行配置。如果使用jpa的话Spring Boot同样提供了对应实现。
这里Spring Boot集成了mybatis框架,mybatis 底层数据访问层实现基于jdbc来实现,所以在Spring Boot环境下对事务进行控制,事务实现由Spring Boot实现并自动配置,在使用时通过注解方式标注相关方法加入事务控制即可。
@Transactional(propagation = Propagation.REQUIRED)
public void insert(User user) {
AssertUtil.isTrue(StringUtils.isBlank(user.getUserName()), "用户名不能为空");
AssertUtil.isTrue(StringUtils.isBlank(user.getUserPwd()), "密码不能为空");
AssertUtil.isTrue(userMapper.selectByUserName(user.getUserName()) != null, "用户名已存在");
AssertUtil.isTrue(userMapper.insert(user) < 1, "用户添加失败");
}
@Transactional(propagation = Propagation.REQUIRED)
@CachePut(cacheNames = "users", key = "#user.userId") // 必须有返回值存入缓存
public User update(User user) {
// 更新时必须传入用户id,假设该id是当前登录用户的id
AssertUtil.isTrue(StringUtils.isBlank(user.getUserName()), "用户名不能为空");
AssertUtil.isTrue(StringUtils.isBlank(user.getUserPwd()), "密码不能为空");
AssertUtil.isTrue(user.getUserId() == null, "账号未登录");
User temp = userMapper.selectByUserName(user.getUserName());
AssertUtil.isTrue(temp != null && !temp.getUserId().equals(user.getUserId()), "用户名已存在");
AssertUtil.isTrue(userMapper.update(user) < 1, "更新失败");
return user;
}
@Transactional(propagation = Propagation.REQUIRED)
@CacheEvict(cacheNames = "users", key = "#userId")
public void delete(Integer userId) {
AssertUtil.isTrue(userId == null || userMapper.selectByUserId(userId) == null, "用户不存在");
AssertUtil.isTrue(userMapper.delete(userId) < 1, "删除失败");
}
这里仅仅作为示例,如果方法内只有一个SQL语句执行,加事务控制是没有意义的
Spring Boot全局异常处理
SpringMvc中对异常统一处理提供了相应处理方式,推荐大家使用的是实现接口HandlerExceptionResolver的方式,对代码侵入性较小。
在Spring Boot应用中同样提供了对异常的全局性处理,相关注解如下:
- @ControllerAdvice
该注解组合了@Component注解功能,最常用的就是作为全局异常处理的切面类,同时通过该注解可以指定包扫描的范围。@ControllerAdvice 约定了几种可行的返回值,如果是直接返回model类的话,需要使用@ResponseBody进行json转换。 - @ExceptionHandler
该注解在Spring 3.X版本引入,在处理异常时标注在方法级别,代表当前方法处理的异常类型有哪些具体应用以Restful接口为例,测试保存用户接口。
全局异常应用
异常抛出与全局异常捕获
-
UserController 查询接口
@ApiOperation(value = "根据用户名查找用户记录", notes = "用户名不能为空") @ApiImplicitParam(name = "userName", value = "用户名", required = true, dataType = "String", // 参数类型,默认String paramType = "path") // 表示参数从路径上获取 @GetMapping("name/{userName}") public User findUserByUserName(@PathVariable String userName) { return userService.findUserByUserName(userName); }
-
UserService抛出自定义参数异常
public User findUserByUserName(String userName) { AssertUtil.isTrue(StringUtils.isBlank(userName), "用户名不能为空"); return userMapper.selectByUserName(userName); }
-
全局异常处理类
package cn.hxj.springboottemplate.exceptions.hander; // 全局异常处理类 @ControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(value = Exception.class) @ResponseBody public ResultInfo exceptionResolver(Exception e) { ResultInfo resultInfo = new ResultInfo(); resultInfo.setCode(300); resultInfo.setMsg("系统异常"); /* 处理特定的异常信息 */ /* if(e instanceof ParamsException) { ParamsException exception = (ParamsException) e; resultInfo.setCode(exception.getCode()); resultInfo.setMsg(exception.getMsg()); } */ return resultInfo; } @ExceptionHandler(value = ParamsException.class) public ResultInfo paramsExceptionResolver(ParamsException e) { ResultInfo resultInfo = new ResultInfo(); resultInfo.setCode(e.getCode()); resultInfo.setMsg(e.getMsg()); return resultInfo; } }
数据校验-Validation
日常项目开发中,对于前端提交的表单,后台接口接收到表单数据后,为了程序的严谨性,通常后端会加入业务参数的合法校验操作来避免程序的非技术性bug,这里对于客户端提交的数据校验,SpringBoot 通过spring-boot-starter-validation模块包含了数据校验的工作。
这里主要介绍Spring Boot中对请求数据进行校验,相关概念如下
- JSR303 : JSR303是一项标准,只提供规范不提供实现,规定一些校验规范即校验注解,如
@Null,@NotNull, @Pattern
, 位于javax.validation.constraints包下。JSR-349 是其升级版本,添加了一-些新特性。 - Hibernate Validation : Hibernate Validation是对这个规范的实现,并增加了一些其他校验注解,如
@Email,@Length, @Range
等等。 - Spring Validation : Spring Validation对Hibernate Validation进行了二次封装,在Spring MVC模块中添加了自动校验,并将校验信息封装进了特定的类中。
环境配置
从springboot-2.3
开始,校验包被独立成了一个starter
组件,所以需要引入validation和web,而springboot-2.3
之前的版本只需要引入 web 依赖就可以了
<!-- 检验 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
校验相关注解
注解 | 功能 |
---|---|
@AssertFalse @AssertTrue @DecimalMax @DecimalMin @Digits @Future @Past @Max @Min @NotNull @Min @Pattern @Size @Length @NotBlank @NotEmpty @Range @URL | 可以为null,如果不为null的话必须为false 可以为nll,如果不为null的话必须为true 设置不能超过最大值 设置不能超过最小值I 设置必须是数字且数字整数的位数和小数的位数必须在指定范围内 日期必须在当前日期的未来 日期必须在当前日期的过去 最大不得超过此最大值 最大不得小于此最小值 不能为null,可以是空 最大不得小于此最小值 必须满足指定的正则表达式 集合、数组、map等的size()值必须在指定范围内 必须是email格式 长度必须在指定范围内 字符串不能为null,字符串trim()后也不能等于“” 不能为null,集合、数组、map等size()不能为0; 字符串trim()后可以等于” 值必须在指定范围内 必须是一个URL |
示例
实体类参数校验注解
@ApiModel(description = "用户实体类")
public class User implements Serializable {
@ApiModelProperty(name = "userId", value = "用户ID", notes = "唯一")
@NotBlank(message = "用户id不能为空")
Integer userId;
@ApiModelProperty(name = "userName", value = "用户名", notes = "唯一")
@NotBlank(message = "用户名不能为空")
@Size(min = 8, max = 32, message = "用户名最少8位,最多32位")
String userName;
@Pattern(regexp = "^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z\\W]{6,18}$",
message = "用户密码最少8位,最多32位,必须包含数据和字母,可以包含特殊字符")
String userPwd;
}
注解要校验字段
@ApiOperation(value = "添加用户", notes = "用户不能为空")
@ApiImplicitParam(name = "user", value = "用户实体类",
dataType = "User", // 参数类型,默认String
paramType = "form") // 表示参数从表单上获取
@PostMapping
public ResultInfo insert(@Valid User user) {
ResultInfo resultInfo = new ResultInfo();
try {
// userService.insert(user);
} catch (ParamsException e) {
e.printStackTrace();
resultInfo.setCode(e.getCode());
resultInfo.setMsg(e.getMsg());
} catch (Exception e) {
e.printStackTrace();
resultInfo.setCode(500);
resultInfo.setMsg("用户添加失败!");
}
return resultInfo;
}
绑定异常的处理
// 全局异常处理类
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(value = BindException.class)
@ResponseBody // 注意要响应json格式数据
public ResultInfo bindExceptionResolver(BindException e) {
ResultInfo resultInfo = new ResultInfo();
resultInfo.setCode(500);
resultInfo.setMsg("参数校验失败");
resultInfo.setResult(e.getBindingResult().getFieldError().getDefaultMessage());
return resultInfo;
}
}