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);

}

}

过滤器与拦截器的区别:

  1. 过滤器可以拦截任何请求,拦截器只能拦截Controller和在static目录下的资源
  2. 执行时机: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必须在指定的范围内

@Email

可以为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:测试

2. SpringBoot进阶篇_spring

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中配置缓存

2. SpringBoot进阶篇_User_02

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方法时,就会可以更加细粒度地控制缓存了:

2. SpringBoot进阶篇_User_03

在UserController中暴露相应的端点:

2. SpringBoot进阶篇_缓存_04

表中数据如下:

2. SpringBoot进阶篇_缓存_05

测试数据:

2. SpringBoot进阶篇_缓存_06

多次发起以上请求,发现控制台只发起一次sql请求,这证明缓存已经生效了:

2. SpringBoot进阶篇_spring_07

针对于search方法,缓存中的键是由请求参数构成的,所以在请求参数变化后,第一次查询仍然是会查询数据库的:

2. SpringBoot进阶篇_缓存_08

2. SpringBoot进阶篇_User_09

因为比较简单,所以其他更为详细的测试就由老师现场演示吧。为了测试删除,这里提供一些测试数据:

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的知识等工作中真的需要用到再深入学习也不迟。