1. SpringBoot Web配置
作为springboot web环境的配置类,必须要实现WebMvcConfigurer接口,且必须将@Configuration注解添加到类配置类上
1.1 配置拦截器
创建拦截器
@Component
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("MyInterceptor.preHandle");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("MyInterceptor.postHandle");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("MyInterceptor.afterCompletion");
}
}
配置拦截器
@Configuration
public class MyWebMvcConfigurer implements WebMvcConfigurer {
@Autowired
private MyInterceptor myInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(myInterceptor)
.addPathPatterns("/**");
}
}
1.2 配置过滤器和监听器
创建过滤器
@WebFilter(urlPatterns = {"/*"})
public class MyFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
过滤器开始");
filterChain.doFilter(servletRequest, servletResponse);
过滤器结束");
}
}
创建监听器
在启动类上添加@ServletComponentScan注解
@SpringBootApplication
@ServletComponentScan
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
过滤器与拦截器的区别:
- 过滤器可以拦截任何请求,拦截器只能拦截Controller和在static目录下的资源
- 执行时机:Filter、Servlet、拦截器
1.4 配置Converter
创建字符串到日期的类型转换器
@Component
public class StringToDateConverter implements Converter<String, Date> {
@Override
public Date convert(String s) {
原始数据:" + s);
return new Date();
}
}
配置类型转换器
@Configuration
public class MyWebMvcConfigurer implements WebMvcConfigurer {
@Autowired
private StringToDateConverter stringToDateConverter;
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(stringToDateConverter);
}
}
1.5 配置HttpMessageConverter
只要自己配置了MappingJackson2HttpMessageConverter,则SpringBoot就不会再创建并配置MappingJackson2HttpMessageConverter,此时我们可以自己指定客户端发来的json中的日期数据的日期格式
@Bean
public HttpMessageConverter httpMessageConverter() {
MappingJackson2HttpMessageConverter mc = new MappingJackson2HttpMessageConverter();
mc.getObjectMapper().setDateFormat(new SimpleDateFormat("yyyy/MM/dd"));
return mc;
}
1.6 日志
在开发企业项目时,日志的输出对于定位系统bug是一种很有效的方式。在项目进入生产环境后,日志的输出也是快速发现错误,并解决错误的一种有效手段。
1.6.1 日志依赖
SpringBoot默认使用LogBack日志系统,如果不需要更改为其他日志系统(如Log4j2)等,则无需多余的配置,在默认情况下LogBack默认将日志打印到控制台上。如果要使用LogBack,原则上是需要添加以下依赖的
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
因为新建SpringBoot项目一般都会引入spring-boot-starter或者spring-boot-starter-web,而这两个依赖中都已经包含了对于 spring-boot-starter-logging 的依赖,所以无需额外添加日志依赖。
1.6.2 日志输出
@SpringBootApplication
public class App {
private static Logger logger = LoggerFactory.getLogger(App.class);
public static void main(String[] args) {
应用开始启动...");
欢迎{}访问", "admin");
SpringApplication.run(App.class, args);
}
}
SpringBoot日志有4个级别(由高到底排列):
☐ error
☐ no(关闭日志)
☐ warn
☐ infoss
☐ debug
如果SpringBoot的日志级别设置为debug,则SpringBoot会打印debug级别的日志,以及debug级别以上的日志。
SpringBoot日志级别默认为info级别,所以默认情况下只能打印info warn error级别的日志
1.6.3 配置日志格式
在application.yml中添加以下配置:
logging:
pattern:
console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger- %msg%n"
level:
com.gao.dao: debug
2. 全局异常处理
SpringBoot应用可以通过@ControllerAdvice对异常统一处理。
@ControllerAdvice解封装了@Component,所以被该注解标注的类也会被容器管理的。该注解标注的类是一个切面类,默认拦截所有Controller的所有方法。该注解经常配合@ExceptionHandler以实现全局异常处理。
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ArithmeticException.class)
@ResponseBody
public ResultVO f1(ArithmeticException e) {
// for debug
e.printStackTrace();
return ResultVO.failure(e.toString());
}
@ExceptionHandler(NullPointerException.class)
@ResponseBody
public ResultVO f2(NullPointerException e) {
// for debug
e.printStackTrace();
return ResultVO.failure(e.toString());
}
}
3. SpringBoot数据校验
SpringBoot通过 spring-boot-starter-validation 模型进行校验工作。这里介绍SpringBoot对请求数据进行校验。相关概念如下:
☐ JSR303:JSR303是一项标准,只提供规范不提供实现。如@Null,@NotNull、@Pattern等。
☐ Hibernate Validation:Hibernate Validation是对这个规范的实现,并增加了一些其他校验注解,如@Email、@Length、@Range等等。
☐ Spring Validation:Spring Validation对Hibernate Validation进行了二次封装。
3.1 环境配置
为了进行数据校验,需要引入 spring-boot-starter-validation 依赖。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
3.2 校验相关注解
注解 | 功能 |
@AssertFalse | 可以为null,如果不为null则必须为false |
@AssertTrue | 可以为null,如果不为null则必须为true |
@DecimalMax | 最大值,可用于String形式的数字 |
@DecimalMin | 最小值,可用于String形式的数字 |
@Digits | 必须是数字,且整数位数和小数位数必须在执行的范围内 |
@Future | 日期必须是当前日期的未来 |
@Past | 日期必须是当前日期的过去 |
@Max | 最大值,不可用于String形式的数字 |
@Min | 最小值,不可用于String形式的数字 |
@NotNull | 不能为null,可以为空串 |
@NotBlank | 字符串不能为null,且必须至少包含1个字符 |
@Pattern | 通过正则自定义规则 |
@Size | 字符串的列数或集合的size必须在指定的范围内 |
可以为null,如果不为null则必须是正确的邮箱格式 | |
@Length | 只适用于字符串,限制字符串的长度必须在指定范围内 |
@Valid | 级联校验 |
3.3 校验注解使用
步骤1:在User实体类上添加校验规则
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User implements Serializable {
private Integer id;
用户名必须在2到4列之间")
private String name;
private Date birthday;
@Max(10000)
private BigDecimal balance;
}
步骤2:在UserController方法的参数前加上@Validated注解
@RestController
@RequestMapping("user")
public class UserController {
@Autowired
private IUserService service;
@PostMapping("save")
public ResultVO save(@Validated @RequestBody User user) {
System.out.println("user = " + user);
service.save(user);
return ResultVO.success("save ok");
}
}
步E3:配置全局异常处理器
数据校验失败时,会抛出MethodArgumentNotValidException异常,所以在全局异常处理器中,专门捕获该异常:
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseBody
public ResultVO handleBindException(MethodArgumentNotValidException e) {
List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors();
Map<String, String> map = new HashMap<>();
for (FieldError fieldError : fieldErrors) {
map.put(fieldError.getField(), fieldError.getDefaultMessage());
}
return ResultVO.failure(map);
}
}
步骤4:测试
4. SpringBoot缓存
SpringBoot使用SpringCache来实现缓存机制。SpringCache是一个对缓存的抽象,这里我们将使用Ehcache作为具体的实现。
4.1 Spring Cache相关注解说明
4.1.1 @CacheConfig
@CacheConfig用于在类上标注,可以存放该类中所有缓存的共有属性,比如设置缓存的名字。
@CacheConfig(cacheNames = "users")
public interface IUserService {
void save(User user);
}
配置了IUserService将使用名为“users”的缓存对象。我们也可以不使用该注解,直接用@Cacheable默认的缓存名字。
4.1.2 @Cacheable
应用到查询方法上。先从缓存中读取,如果缓存中没有则再调用相应的方法获取数据,然后把数据存入缓存中。
@Cacheable(value = "users", key = "#id")
User find(int id) {
...
}
4.1.3 @CachePut
应用到新增/修改方法上。修改数据库的同时,也同修缓存中的数据。
@CachePut(value = "users", key = "#user.id")
User update(User user) {
...
}
4.1.4 @CacheEvict
应用到移除数据的方法上,如删除方法,调用方法时会从缓存中移除相应的数据
@CacheEvict(value = "users", key = "#id")
User delete(int id) {
...
}
@CacheEvict还有下面两个参数
☐ allEntries:非必需,默认为false,当为true时会移除当前缓存下的所有数据。
☐ beforeInvocation:非必须,用于控制删除缓存数据的动作是否发生在目标方法调用之前。默认为false,会在调用方法之后移除数据。当为true时,会在调用方法之前移除数据。
4.2 配置环境
4.2.1 pom.xml 依赖添加
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
</dependency>
4.2.2 ehcache.xml配置文件
在resources下添加ehcache.xml
<ehcache>
<diskStore path="java.io.tmpdir"/>
默认缓存 -->
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSecnotallow="120"
timeToLiveSecnotallow="120"
overflowToDisk="true"
maxElementsOnDisk="10000000"
diskExpiryThreadIntervalSecnotallow="120"
memoryStoreEvictinotallow="LRU"
/>
针对User,普通CRUD的缓存 -->
<cache
name="users"
maxElementsInMemory="10000"
eternal="false"
timeToIdleSecnotallow="120"
timeToLiveSecnotallow="120"
overflowToDisk="true"
maxElementsOnDisk="10000000"
diskExpiryThreadIntervalSecnotallow="120"
memoryStoreEvictinotallow="LRU"
/>
针对User,搜索 + 分页的缓存 -->
<cache
name="usersSearch"
maxElementsInMemory="10000"
eternal="false"
timeToIdleSecnotallow="120"
timeToLiveSecnotallow="120"
overflowToDisk="true"
maxElementsOnDisk="10000000"
diskExpiryThreadIntervalSecnotallow="120"
memoryStoreEvictinotallow="LRU"
/>
</ehcache>
4.2.3 配置缓存
在application.yml中配置缓存
4.2.4 启动缓存
在启动类上添加@EnableCaching注解
@SpringBootApplication
@EnableCaching
@MapperScan("com.gao.mapper")
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
4.2.5 使用分页
为了测试缓存是否能对分页数据进行缓存,我们引入分页插件
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.4.6</version>
</dependency>
4.2.6 使用缓存
在IUserService中定义查询一个用户、查询所有用户、修改、删除、查询,以及search方法(search方法支持条件查询和分页功能)
/**
* @author gao
* @time 2022/04/30 11:55:19
*/
public interface IUserService {
User save(User user);
/**
返回影响的行数
*/
int delete(int id);
User update(User user);
User find(int id);
List<User> find();
/**
搜索条件,支持的条件如下
大于minId
小于maxId
分页数据
搜索+分页的结果
*/
List<User> search(Map<String, String> conditionMap, int... pages);
}
在UserServiceImpl中实现在IUserService中新增的抽象方法,并在UserServiceImpl的方法上使用缓存,注意find(int)、find()、save()、delete()使用的是ehcache.xml中配置的“users”缓存,search方法使用的是ehcache.xml中配置的“usersSearch”缓存,而在update方法上使用的是@Caching注解,这样在执行update方法时,就会可以更加细粒度地控制缓存了:
在UserController中暴露相应的端点:
表中数据如下:
测试数据:
多次发起以上请求,发现控制台只发起一次sql请求,这证明缓存已经生效了:
针对于search方法,缓存中的键是由请求参数构成的,所以在请求参数变化后,第一次查询仍然是会查询数据库的:
因为比较简单,所以其他更为详细的测试就由老师现场演示吧。为了测试删除,这里提供一些测试数据:
INSERT INTO USER VALUES(100, 'foo', NOW(), 1000);
INSERT INTO USER VALUES(200, 'bar', NOW(), 1000);
INSERT INTO USER VALUES(300, 'baz', NOW(), 1000);
INSERT INTO USER VALUES(400, 'qux', NOW(), 1000);
INSERT INTO USER VALUES(500, 'quz', NOW(), 1000);
5. SpringBoot单元测试
SpringBoot框架对单元测试提供了良好的支持
5.1 添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
5.2 编写测试代码
在src/test/java下创建App_UserServiceTest类:
/**
* @author gao
* @time 2022/05/01 11:33:04
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = {App.class})
public class App_UserServiceTest {
private Logger log = LoggerFactory.getLogger(App_UserServiceTest.class);
@Autowired
private IUserService userService;
@Before
public void before() {
单元测试开始,这里可以添加测试数据");
}
@Test
public void test() {
用户记录:" + userService.find(1));
}
@After
public void after() {
单元测试结束,这里可以清空测试数据");
}
}
5.3 控制层接口方法测试
控制器使用MockMvc进行测试,这里以UserController为例进行测试。注意例子中使用到了静态引入。
package com.gao.test;
import com.gao.App;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
import static
org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static
org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* @author gao
* @time 2022/05/01 12:05:39
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = App.class)
@AutoConfigureMockMvc
public class App_UserControllerTest {
private Logger log = LoggerFactory.getLogger(App_UserControllerTest.class);
@Autowired
private MockMvc mockMvc;
@Test
public void testFindAll() throws Exception {
创建Mock请求
MockHttpServletRequestBuilder req = post("/user/findAll")
.accept(MediaType.APPLICATION_JSON);
发送Mock请求
MvcResult mvcResult = mockMvc.perform(req)
期望响应码结果是200,如果不是则会报错
.andExpect(status().isOk())
返回结果
.andReturn();
从返回结果中获取响应
MockHttpServletResponse response = mvcResult.getResponse();
响应状态: {}", response.getStatus());
响应内容: {}", response.getContentAsString());
}
}
MockMvc了解一下就好了,更多关于MockMvc的知识等工作中真的需要用到再深入学习也不迟。