SpringBoot + Caffeine实现本地缓存(内存缓存)

1. Caffeine简介#

  Caffeine是一个基于Java8开发的提供了近乎最佳命中率的高性能的缓存库。借鉴Google GuavaConcurrentLinkedHashMap的经验,实现内存缓存。
  缓存和ConcurrentMap有点相似,但还是有所区别。最根本的区别是ConcurrentMap将会持有所有加入到缓存当中的元素,直到它们被从缓存当中手动移除。但是,Caffeine的缓存Cache通常会被配置成自动驱逐缓存中元素,以限制其内存占用。在某些场景下,自动加载缓存LoadingCache和异步自动加载缓存AsyncLoadingCache是非常实用的。
  Caffeine提供了灵活的构造器去创建一个拥有以下列特性的缓存:
基于过期时间的淘汰策略

  • 异步刷新策略
  • key值自动包装成弱引用
  • 元素值自动包装成弱引用或软引用
  • 通知淘汰元素策略
  • 向外部存储资源写入元素
  • 统计缓存访问信息

2. 相关博客#

  SpringBoot集成阿里缓存框架Jetcache代替Spring Cache

3. 示例代码#

  • 创建项目
  • 修改pom.xml
Copy
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.c3stones</groupId> <artifactId>spring-boot-caffeine-demo</artifactId> <version>0.0.1-SNAPSHOT</version> <name>spring-boot-caffeine-demo</name> <description>Spring Boot Caffeine Demo</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.1</version> <relativePath /> </parent> <dependencies> <dependency> <groupId>com.github.ben-manes.caffeine</groupId> <artifactId>caffeine</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <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> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
  • 创建本地缓存配置类
Copy
/** * 本地缓存配置类 * * @author CL */ @Configuration public class NativeCacheConfig { private static final Logger log = LoggerFactory.getLogger(NativeCacheConfig.class); /** * 本地全局缓存 * * @return */ @Bean public Cache<String, Object> globalCache() { return Caffeine.newBuilder() // 最后一次写入后经过固定时间过期 .expireAfterWrite(5, TimeUnit.MINUTES) // 初始的缓存空间大小 .initialCapacity(10) // 缓存的最大条数 .maximumSize(100) // 移除缓存监听 .removalListener((k, v, cause) -> log.warn("NativeCache RemovalListener key:{}, value:{}, cause:{}", k, v, cause)) .build(); } /** * 注入本地缓存管理器 * * @return */ @Bean public NativeCacheMagger nativeCacheManager() { return new NativeCacheMagger(); } /** * 构建缓存 * * @param cacheName 缓存名称 * @param expireTime 过期时间 * @param initCapacity 初始化大小 * @param maxCapacity 最大大小 * @return */ public static org.springframework.cache.Cache buildCache(String cacheName, Duration expireTime, int initCapacity, int maxCapacity) { notNull(cacheName, "缓存名称不能为空"); notNull(expireTime, "过期时间不能为空"); isTrue(initCapacity >= 0, "初始化大小为正整数"); isTrue(maxCapacity >= 0, "最大大小为正整数"); return new CaffeineCache(cacheName, Caffeine.newBuilder() // 最后一次写入后经过固定时间过期 .expireAfterWrite(expireTime) // 初始的缓存空间大小 .initialCapacity(initCapacity) // 缓存的最大条数 .maximumSize(maxCapacity) // 设置值软引用 .softValues() // 移除缓存监听 .removalListener((k, v, cause) -> log.warn("NativeCache RemovalListener key:{}, value:{}, cause:{}", k, v, cause)) .build()); } /** * 本地缓存管理器 */ public static class NativeCacheMagger extends AbstractCacheManager { /** * 缓存队列 */ private Collection<org.springframework.cache.Cache> caches = new CopyOnWriteArraySet<>(); /** * 添加缓存到缓存管理器 * * @param cache * @return */ public NativeCacheMagger putCache(org.springframework.cache.Cache cache) { this.caches.add(cache); return this; } /** * 将缓存加载到缓存管理器 */ @Override public Collection<org.springframework.cache.Cache> loadCaches() { return this.caches; } } }
  • 创建缓存客户端
Copy
/** * 缓存客户端 * * @author CL */ @Component public class CacheClient { private static final ObjectMapper MAPPER = new ObjectMapper(); @Autowired private NativeCacheMagger nativeCacheMagger; @Autowired private com.github.benmanes.caffeine.cache.Cache<String, Object> globalCache; /** * 缓存数据 * * @param key 缓存键 * @param value 数据 */ public void putOfNative(String key, Object value) { if (!StringUtils.hasLength(key) || Objects.isNull(value)) { return; } globalCache.put(key, value); } /** * 移除缓存 * * @param key 缓存键 */ public void removeOfNative(String key) { if (!StringUtils.hasLength(key)) { return; } globalCache.invalidate(key); } /** * 获取缓存 * * @param key 缓存键 * @return */ public Object getByNative(String key) { if (!StringUtils.hasLength(key)) { return null; } return globalCache.getIfPresent(key); } /** * 获取缓存 * * @param key 缓存键 * @param clazz 数据类型 * @param <T> * @return */ public <T> T getByNative(String key, Class<T> clazz) { if (Objects.nonNull(clazz)) { Object obj = getByNative(key); try { if (Objects.nonNull(obj) && obj.getClass().isAssignableFrom(clazz)) { return MAPPER.convertValue(obj, clazz); } } catch (Exception e) { } } return null; } /** * 获取全部缓存 * * @return */ public Map<String, Object> getAllByNative() { return globalCache.asMap(); } /** * 缓存数据 * * @param cacheName 缓存名称 * @param key 缓存键 * @param value 缓存值 */ public void putOfNative(String cacheName, String key, Object value) { if (Objects.isNull(value)) { return; } putOfNative(Optional.ofNullable(nativeCacheMagger.getCache(cacheName)) .orElse(buildCache(cacheName, Duration.ofHours(1), 20, 100)), key, value); } /** * 缓存数据 * * @param cache 缓存 * @param key 缓存键 * @param value 数据 */ public void putOfNative(org.springframework.cache.Cache cache, String key, Object value) { if (Objects.isNull(value)) { return; } cache.put(key, value); nativeCacheMagger.putCache(cache).initializeCaches(); } /** * 移除缓存 * * @param cacheName 缓存名称 * @param key 缓存键 */ public void removeOfNative(String cacheName, String key) { if (!StringUtils.hasLength(cacheName)) { return; } removeOfNative(nativeCacheMagger.getCache(cacheName), key); } /** * 移除缓存 * * @param cache 缓存 * @param key 缓存键 */ public void removeOfNative(Cache cache, String key) { if (Objects.isNull(cache) || !StringUtils.hasLength(key)) { return; } cache.evict(key); } /** * 移除全部 * * @param cache * @return */ public boolean removeAllOfNative(Cache cache) { if (Objects.isNull(cache)) { return true; } return cache.invalidate(); } /** * 移除全部 * * @param cacheName * @return */ public boolean removeAllOfNative(String cacheName) { if (!StringUtils.hasLength(cacheName)) { return true; } return removeAllOfNative(nativeCacheMagger.getCache(cacheName)); } /** * 加载所有缓存 * * @return */ public Collection<Cache> loadCaches() { return nativeCacheMagger.loadCaches(); } /** * 获取缓存 * * @param cacheName 缓存名称 * @param key 缓存键 * @return */ public Object getByNative(String cacheName, String key) { if (!StringUtils.hasLength(cacheName)) { return null; } return getByNative(nativeCacheMagger.getCache(cacheName), key); } /** * 获取缓存 * * @param cache 缓存 * @param key 缓存键 * @return */ public Object getByNative(Cache cache, String key) { if (Objects.isNull(cache) || !StringUtils.hasLength(key)) { return null; } return cache.get(key); } /** * 获取缓存 * * @param cacheName 缓存名称 * @param key 缓存键 * @param clazz 数据类型 * @param <T> * @return */ public <T> T getByNative(String cacheName, String key, Class<T> clazz) { if (!StringUtils.hasLength(cacheName)) { return null; } return getByNative(nativeCacheMagger.getCache(cacheName), key, clazz); } /** * 获取缓存 * * @param cache 缓存 * @param key 缓存键 * @param clazz 数据类型 * @param <T> * @return */ public <T> T getByNative(Cache cache, String key, Class<T> clazz) { if (Objects.isNull(cache) || Objects.isNull(key) || Objects.isNull(clazz)) { return null; } return cache.get(key, clazz); } /** * 获取全部缓存 * * @param cache 缓存 * @return */ public List<Object> getAllByNative(Cache cache) { if (Objects.nonNull(cache) && cache instanceof CaffeineCache) { return ((CaffeineCache) cache).getNativeCache().asMap().values().stream().collect(Collectors.toList()); } return Collections.emptyList(); } /** * 获取全部缓存 * * @param cache 缓存 * @param clazz 数据类型 * @param <T> * @return */ public <T> List<T> getAllByNative(Cache cache, Class<T> clazz) { return getAllByNative(cache).stream().map(obj -> { if (obj.getClass().isAssignableFrom(clazz)) { return MAPPER.convertValue(obj, clazz); } return null; }).filter(Objects::nonNull).collect(Collectors.toList()); } /** * 获取全部缓存 * * @param cacheName 缓存名称 * @return */ public List<Object> getAllByNative(String cacheName) { return getAllByNative(nativeCacheMagger.getCache(cacheName)); } /** * 获取全部缓存 * * @param cacheName 缓存名称 * @param clazz 数据类型 * @return */ public <T> List<T> getAllByNative(String cacheName, Class<T> clazz) { return getAllByNative(nativeCacheMagger.getCache(cacheName), clazz); } }
  • 创建实体类
Copy
/** * 用户信息 * * @author CL */ @Data @NoArgsConstructor @AllArgsConstructor @Accessors(chain = true) public class User { private String id; private String username; private Integer age; }
Copy
/** * 班级信息 * * @author CL */ @Data @NoArgsConstructor @AllArgsConstructor @Accessors(chain = true) public class Classes { private String code; private String name; }
Copy
/** * 学生信息 * * @author CL */ @Data @NoArgsConstructor @AllArgsConstructor @Accessors(chain = true) public class Student { private String stuNo; private String name; private Classes classes; }
  • 创建响应体
Copy
/** * 响应实体 * * @author CL */ @Data @AllArgsConstructor @NoArgsConstructor public class Response<T> { /** * 成功响应码 */ public static final int SUCCESS_CODE = 200; /** * 异常响应码 */ public static final int ERROR_CODE = 500; /** * 响应码 */ private int code; /** * 响应消息体 */ private String msg; /** * 响应数据 */ private T data; /** * 失败响应 * * @param msg 响应消息体 * @return */ public static <T> Response<T> error(String msg) { return new Response<T>(ERROR_CODE, msg, null); } /** * 成功响应 * * @return */ public static <T> Response<T> success() { return new Response<T>(SUCCESS_CODE, null, null); } /** * 成功响应 * * @param data 响应数据 * @return */ public static <T> Response<T> success(T data) { return new Response<T>(SUCCESS_CODE, null, data); } /** * 成功响应 * * @param msg 响应消息体 * @return */ public static <T> Response<T> success(String msg) { return new Response<T>(SUCCESS_CODE, msg, null); } /** * 成功响应 * * @param msg 响应消息体 * @param data 响应数据 * @return */ public static <T> Response<T> success(String msg, T data) { return new Response<T>(SUCCESS_CODE, msg, data); } }
  • 创建工具类
Copy
/** * Bean 工具类 * * @author CL */ public class BeanUtil extends BeanUtils { public static void copyPropertiesIgnoreNull(Object source, Object target) { BeanUtils.copyProperties(source, target, getNullProperties(source)); } /** * 获取为Null的属性 * * @param obj * @return */ public static String[] getNullProperties(Object obj) { final BeanWrapper src = new BeanWrapperImpl(obj); return Arrays.stream(src.getPropertyDescriptors()) .filter(pd -> Objects.isNull(src.getPropertyValue(pd.getName()))) .map(PropertyDescriptor::getName) .distinct() .toArray(String[]::new); } }
  • 创建Controller
Copy
/** * 用户Controller * * @author CL */ @RestController @RequestMapping("/user") public class UserController { @Autowired private CacheClient cacheClient; /** * 新增用户 * * @param user 用户 * @return */ @RequestMapping("/save") public Response save(User user) { cacheClient.putOfNative(user.getId(), user); return Response.success(); } /** * 更新用户 * * @param user 用户 * @return */ @RequestMapping("/update") public Response update(User user) { User oldUser = cacheClient.getByNative(user.getId(), User.class); if (Objects.nonNull(oldUser)) { BeanUtil.copyPropertiesIgnoreNull(user, oldUser); cacheClient.putOfNative(user.getId(), oldUser); } return Response.success(); } /** * 删除用户 * * @param id 用户ID * @return */ @RequestMapping("/delete") public Response delete(String id) { cacheClient.removeOfNative(id); return Response.success(); } /** * 查询用户 * * @param id 用户ID * @return */ @RequestMapping("/get") public Response get(String id) { User user = cacheClient.getByNative(id, User.class); return Response.success(user); } }
Copy
/** * 学生Controller * * @author CL */ @RestController @RequestMapping("/student") public class StudentController { @Autowired private CacheClient cacheClient; /** * 新增学生 * * @param student * @return */ @RequestMapping("/save") public Response save(Student student) { cacheClient.putOfNative(student.getClasses().getCode(), student.getStuNo(), student); return Response.success(); } /** * 更新学生 * * @param student 学生 * @return */ @RequestMapping("/update") public Response update(Student student) { Student oldStudent = cacheClient.getByNative(student.getClasses().getCode(), student.getStuNo(), Student.class); if (Objects.nonNull(oldStudent)) { BeanUtil.copyPropertiesIgnoreNull(student, oldStudent); cacheClient.putOfNative(student.getClasses().getCode(), student.getStuNo(), oldStudent); } return Response.success(); } /** * 删除学生 * * @param student 学生 * @return */ @RequestMapping("/delete") public Response delete(Student student) { cacheClient.removeOfNative(student.getClasses().getCode(), student.getStuNo()); return Response.success(); } /** * 删除所有学生 * * @return */ @RequestMapping("/deleteAll") public Response delete() { cacheClient.loadCaches().forEach(cache -> cacheClient.removeAllOfNative(cache)); return Response.success(); } /** * 查询学生 * * @param classesCode 班号 * @param stuNo 学号 * @return */ @RequestMapping("/get") public Response get(String classesCode, String stuNo) { AtomicReference<Student> student = new AtomicReference<>(); if (Objects.isNull(classesCode)) { cacheClient.loadCaches().forEach(cache -> { Student temp = cacheClient.getByNative(cache, stuNo, Student.class); if (Objects.nonNull(temp)) { student.set(temp); return; } }); } else { student.set(cacheClient.getByNative(classesCode, stuNo, Student.class)); } return Response.success(student.get()); } /** * 查询班级学生 * * @param classesCode * @return */ @RequestMapping("list") public Response list(String classesCode) { List<Student> list = cacheClient.getAllByNative(classesCode, Student.class); return Response.success(list); } /** * 查询所有学生 * * @return */ @RequestMapping("/all") public Response all() { List<Student> list = new ArrayList<>(); cacheClient.loadCaches().forEach(cache -> { list.addAll(cacheClient.getAllByNative(cache, Student.class)); }); return Response.success(list); } }
  • 创建启动类
Copy
/** * 启动类 * * @author CL * */ @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }

4. 单元测试#

  • 使用Eclipse进行单元测试时,如果出现No tests found with test runner Junit5,原因是配置的JUnit版本和使用的JUnit不一致。右键Debug As -> Debug Configurations... -> Test runner,选择JUnit4即可。


  • UserController接口测试
Copy
/** * UserController 单元测试 * * @author CL */ @Slf4j @RunWith(SpringRunner.class) @SpringBootTest public class UserControllerTest { @Autowired private UserController userController; private MockMvc mockMvc; @Before public void setup() { mockMvc = MockMvcBuilders.standaloneSetup(userController).build(); } /** * 接口测试 */ @Test public void userTest() throws Exception { User user = new User() .setId("001") .setUsername("张三") .setAge(24); log.info("新增用户 ==>"); save(user); log.info("查询用户 ==>"); select( user.getId(), jsonPath("$.data.id").value(user.getId()) ); log.info("修改用户信息 ==>"); User newUser = new User() .setId(user.getId()) .setAge(26); update(newUser); log.info("查询用户 ==>"); select( user.getId(), jsonPath("$.data.id").value(newUser.getId()), jsonPath("$.data.age").value(newUser.getAge()) ); log.info("删除用户 ==>"); delete(user.getId()); log.info("查询用户 ==>"); select( user.getId(), jsonPath("$.data").isEmpty() ); log.info("测试超时 ==>"); save(user); log.info("查询用户 ==>"); select( user.getId(), jsonPath("$.data").isNotEmpty() ); log.info("等待五分钟(本地全局缓存默认过期时间) ==>"); Thread.sleep(5 * 60 * 1000 + 10); log.info("查询用户 ==>"); select( user.getId(), jsonPath("$.data").isEmpty() ); } /** * 新增用户 * * @param user */ private void save(User user) throws Exception { mockMvc.perform(MockMvcRequestBuilders.post("/user/save") .param("id", user.getId()) .param("username", user.getUsername()) .param("age", user.getAge().toString())) .andExpect(status().isOk()) // 增加断言 .andExpect(jsonPath("$.code").value(SUCCESS_CODE)) // 增加断言 .andDo(print()) // 打印结果 .andReturn(); } /** * 查询用户 * * @param id * @param matcher */ private void select(String id, ResultMatcher... matcher) throws Exception { mockMvc.perform(MockMvcRequestBuilders.get("/user/get") .param("id", id)) .andExpect(status().isOk()) // 增加断言 .andExpect(jsonPath("$.code").value(SUCCESS_CODE)) // 增加断言 .andExpectAll(matcher) // 增加断言 .andDo(print()) // 打印结果 .andReturn(); } /** * 更新用户 * * @param newUser */ private void update(User newUser) throws Exception { mockMvc.perform(MockMvcRequestBuilders.get("/user/update") .param("id", newUser.getId()) .param("username", newUser.getUsername()) .param("age", newUser.getAge().toString())) .andExpect(status().isOk()) // 增加断言 .andExpect(jsonPath("$.code").value(SUCCESS_CODE)) // 增加断言 .andDo(print()) // 打印结果 .andReturn(); } /** * 删除用户 * * @param id */ private void delete(String id) throws Exception { mockMvc.perform(MockMvcRequestBuilders.get("/user/delete") .param("id", id)) .andExpect(status().isOk()) // 增加断言 .andExpect(jsonPath("$.code").value(SUCCESS_CODE)) // 增加断言 .andDo(print()) // 打印结果 .andReturn(); } }

  测试结果:

Copy
2022-09-07 22:25:03.755 INFO 23060 --- [ main] c.c.controller.UserControllerTest : 新增用户 ==> MockHttpServletRequest: HTTP Method = POST Request URI = /user/save Parameters = {id=[001], username=[张三], age=[24]} Headers = [] Body = <no character encoding set> Session Attrs = {} Handler: Type = com.c3stones.controller.UserController Method = com.c3stones.controller.UserController#save(User) Async: Async started = false Async result = null Resolved Exception: Type = null ModelAndView: View name = null View = null Model = null FlashMap: Attributes = null MockHttpServletResponse: Status = 200 Error message = null Headers = [Content-Type:"application/json"] Content type = application/json Body = {"code":200,"msg":null,"data":null} Forwarded URL = null Redirected URL = null Cookies = [] 2022-09-07 22:25:04.178 INFO 23060 --- [ main] c.c.controller.UserControllerTest : 查询用户 ==> MockHttpServletRequest: HTTP Method = GET Request URI = /user/get Parameters = {id=[001]} Headers = [] Body = <no character encoding set> Session Attrs = {} Handler: Type = com.c3stones.controller.UserController Method = com.c3stones.controller.UserController#get(String) Async: Async started = false Async result = null Resolved Exception: Type = null ModelAndView: View name = null View = null Model = null FlashMap: Attributes = null MockHttpServletResponse: Status = 200 Error message = null Headers = [Content-Type:"application/json"] Content type = application/json Body = {"code":200,"msg":null,"data":{"id":"001","username":"张三","age":24}} Forwarded URL = null Redirected URL = null Cookies = [] 2022-09-07 22:25:04.245 INFO 23060 --- [ main] c.c.controller.UserControllerTest : 修改用户信息 ==> MockHttpServletRequest: HTTP Method = GET Request URI = /user/update Parameters = {id=[001], username=[null], age=[26]} Headers = [] Body = <no character encoding set> Session Attrs = {} Handler: Type = com.c3stones.controller.UserController Method = com.c3stones.controller.UserController#update(User) Async: Async started = false Async result = null Resolved Exception: Type = null ModelAndView: View name = null View = null Model = null FlashMap: 2022-09-07 22:25:04.255 WARN 23060 --- [onPool-worker-1] com.c3stones.config.NativeCacheConfig : NativeCache RemovalListener key:001, value:User(id=001, username=张三, age=24), cause:REPLACED Attributes = null MockHttpServletResponse: Status = 200 Error message = null Headers = [Content-Type:"application/json"] Content type = application/json Body = {"code":200,"msg":null,"data":null} Forwarded URL = null Redirected URL = null Cookies = [] 2022-09-07 22:25:04.259 INFO 23060 --- [ main] c.c.controller.UserControllerTest : 查询用户 ==> MockHttpServletRequest: HTTP Method = GET Request URI = /user/get Parameters = {id=[001]} Headers = [] Body = <no character encoding set> Session Attrs = {} Handler: Type = com.c3stones.controller.UserController Method = com.c3stones.controller.UserController#get(String) Async: Async started = false Async result = null Resolved Exception: Type = null ModelAndView: View name = null View = null Model = null FlashMap: Attributes = null MockHttpServletResponse: Status = 200 Error message = null Headers = [Content-Type:"application/json"] Content type = application/json Body = {"code":200,"msg":null,"data":{"id":"001","username":"张三","age":26}} Forwarded URL = null Redirected URL = null Cookies = [] 2022-09-07 22:25:04.263 INFO 23060 --- [ main] c.c.controller.UserControllerTest : 删除用户 ==> 2022-09-07 22:25:04.266 WARN 23060 --- [onPool-worker-1] com.c3stones.config.NativeCacheConfig : NativeCache RemovalListener key:001, value:User(id=001, username=张三, age=26), cause:EXPLICIT MockHttpServletRequest: HTTP Method = GET Request URI = /user/delete Parameters = {id=[001]} Headers = [] Body = <no character encoding set> Session Attrs = {} Handler: Type = com.c3stones.controller.UserController Method = com.c3stones.controller.UserController#delete(String) Async: Async started = false Async result = null Resolved Exception: Type = null ModelAndView: View name = null View = null Model = null FlashMap: Attributes = null MockHttpServletResponse: Status = 200 Error message = null Headers = [Content-Type:"application/json"] Content type = application/json Body = {"code":200,"msg":null,"data":null} Forwarded URL = null Redirected URL = null Cookies = [] 2022-09-07 22:25:04.270 INFO 23060 --- [ main] c.c.controller.UserControllerTest : 查询用户 ==> MockHttpServletRequest: HTTP Method = GET Request URI = /user/get Parameters = {id=[001]} Headers = [] Body = <no character encoding set> Session Attrs = {} Handler: Type = com.c3stones.controller.UserController Method = com.c3stones.controller.UserController#get(String) Async: Async started = false Async result = null Resolved Exception: Type = null ModelAndView: View name = null View = null Model = null FlashMap: Attributes = null MockHttpServletResponse: Status = 200 Error message = null Headers = [Content-Type:"application/json"] Content type = application/json Body = {"code":200,"msg":null,"data":null} Forwarded URL = null Redirected URL = null Cookies = [] 2022-09-07 22:25:04.275 INFO 23060 --- [ main] c.c.controller.UserControllerTest : 测试超时 ==> MockHttpServletRequest: HTTP Method = POST Request URI = /user/save Parameters = {id=[001], username=[张三], age=[24]} Headers = [] Body = <no character encoding set> Session Attrs = {} Handler: Type = com.c3stones.controller.UserController Method = com.c3stones.controller.UserController#save(User) Async: Async started = false Async result = null Resolved Exception: Type = null ModelAndView: View name = null View = null Model = null FlashMap: Attributes = null MockHttpServletResponse: Status = 200 Error message = null Headers = [Content-Type:"application/json"] Content type = application/json Body = {"code":200,"msg":null,"data":null} Forwarded URL = null Redirected URL = null Cookies = [] 2022-09-07 22:25:04.280 INFO 23060 --- [ main] c.c.controller.UserControllerTest : 查询用户 ==> MockHttpServletRequest: HTTP Method = GET Request URI = /user/get Parameters = {id=[001]} Headers = [] Body = <no character encoding set> Session Attrs = {} Handler: Type = com.c3stones.controller.UserController Method = com.c3stones.controller.UserController#get(String) Async: Async started = false Async result = null Resolved Exception: Type = null ModelAndView: View name = null View = null Model = null FlashMap: Attributes = null MockHttpServletResponse: Status = 200 Error message = null Headers = [Content-Type:"application/json"] Content type = application/json Body = {"code":200,"msg":null,"data":{"id":"001","username":"张三","age":24}} Forwarded URL = null Redirected URL = null Cookies = [] 2022-09-07 22:25:04.285 INFO 23060 --- [ main] c.c.controller.UserControllerTest : 等待五分钟(本地全局缓存默认过期时间) ==> 2022-09-07 22:30:04.294 INFO 23060 --- [ main] c.c.controller.UserControllerTest : 查询用户 ==> MockHttpServletRequest: HTTP Method = GET Request URI = /user/get Parameters = {id=[001]} Headers = [] Body = <no character encoding set> Session Attrs = {} Handler: Type = com.c3stones.controller.UserController Method = com.c3stones.controller.UserController#get(String) Async: Async started = false Async result = null Resolved Exception: Type = null ModelAndView: View name = null View = null Model = null FlashMap: Attributes = null MockHttpServletResponse: Status = 200 Error message = null Headers = [Content-Type:"application/json"] Content type = application/json Body = {"code":200,"msg":null,"data":null} Forwarded URL = null Redirected URL = null Cookies = [] 2022-09-07 22:30:04.298 WARN 23060 --- [onPool-worker-4] com.c3stones.config.NativeCacheConfig : NativeCache RemovalListener key:001, value:User(id=001, username=张三, age=24), cause:EXPIRED
  • StudentController接口测试
Copy
/** * StudentController 单元测试 * * @author CL */ @Slf4j @RunWith(SpringRunner.class) @SpringBootTest public class StudentControllerTest { @Autowired private StudentController studentController; private MockMvc mockMvc; @Before public void setup() { mockMvc = MockMvcBuilders.standaloneSetup(studentController).build(); } /** * 接口测试 */ @Test public void studentTest() throws Exception { Classes classes = new Classes() .setCode("A") .setName("一班"); Student student1 = new Student() .setStuNo("A01") .setName("小明") .setClasses(classes); log.info("新增学生1 ==>"); save(student1); Student student2 = new Student() .setStuNo("A02") .setName("小李") .setClasses(classes); log.info("新增学生2 ==>"); save(student2); log.info("查询学生 ==>"); select(classes.getCode(), student1.getStuNo(), jsonPath("$.data.stuNo").value(student1.getStuNo()), jsonPath("$.data.classes.code").value(student1.getClasses().getCode())); select(classes.getCode(), student2.getStuNo(), jsonPath("$.data.stuNo").value(student2.getStuNo()), jsonPath("$.data.classes.code").value(student2.getClasses().getCode())); log.info("查询班级 ==>"); selectList(classes.getCode(), jsonPath("$.data[0].classes.code").value(classes.getCode())); log.info("查询所有学生 ==>"); selectAll(jsonPath("$.data", hasSize(2))); log.info("更新学生 ==>"); Student newStudent1 = new Student() .setStuNo("A01") .setName("小鹏") .setClasses(classes); update(newStudent1); log.info("查询学生 ==>"); select(null, newStudent1.getStuNo(), jsonPath("$.data.stuNo").value(newStudent1.getStuNo()), jsonPath("$.data.classes.code").value(newStudent1.getClasses().getCode()), jsonPath("$.data.name").value(newStudent1.getName())); log.info("删除学生 ==>"); delete(student2.getClasses().getCode(), student2.getStuNo()); log.info("查询所有学生 ==>"); selectAll(jsonPath("$.data", hasSize(1))); log.info("删除所有学生 ==>"); deleteAll(); log.info("查询所有学生 ==>"); selectAll(jsonPath("$.data", hasSize(0))); } /** * 新增学生 * * @param student */ private void save(Student student) throws Exception { mockMvc.perform(MockMvcRequestBuilders.post("/student/save") .param("stuNo", student.getStuNo()) .param("name", student.getName()) .param("classes.code", student.getClasses().getCode()) .param("classes.name", student.getClasses().getName())) .andExpect(status().isOk()) // 增加断言 .andExpect(jsonPath("$.code").value(SUCCESS_CODE)) // 增加断言 .andDo(print()) // 打印结果 .andReturn(); } /** * 查询学生 * * @param classesCode * @param stuNo * @param matcher */ private void select(String classesCode, String stuNo, ResultMatcher... matcher) throws Exception { mockMvc.perform(MockMvcRequestBuilders.get("/student/get") .param("classesCode", classesCode) .param("stuNo", stuNo)) .andExpect(status().isOk()) // 增加断言 .andExpect(jsonPath("$.code").value(SUCCESS_CODE)) // 增加断言 .andExpectAll(matcher) // 增加断言 .andDo(print()) // 打印结果 .andReturn(); } /** * 查询班级学生 * * @param classesCode * @param matcher */ private void selectList(String classesCode, ResultMatcher... matcher) throws Exception { mockMvc.perform(MockMvcRequestBuilders.get("/student/list") .param("classesCode", classesCode)) .andExpect(status().isOk()) // 增加断言 .andExpect(jsonPath("$.code").value(SUCCESS_CODE)) // 增加断言 .andExpectAll(matcher) // 增加断言 .andDo(print()) // 打印结果 .andReturn(); } /** * 查询所有学生 * * @param matcher */ private void selectAll(ResultMatcher... matcher) throws Exception { mockMvc.perform(MockMvcRequestBuilders.get("/student/all")) .andExpect(status().isOk()) // 增加断言 .andExpect(jsonPath("$.code").value(SUCCESS_CODE)) // 增加断言 .andExpectAll(matcher) // 增加断言 .andDo(print()) // 打印结果 .andReturn(); } /** * 更新学生 * * @param newStudent */ private void update(Student newStudent) throws Exception { mockMvc.perform(MockMvcRequestBuilders.get("/student/update") .param("stuNo", newStudent.getStuNo()) .param("name", newStudent.getName()) .param("classes.code", newStudent.getClasses().getCode()) .param("classes.name", newStudent.getClasses().getName())) .andExpect(status().isOk()) // 增加断言 .andExpect(jsonPath("$.code").value(SUCCESS_CODE)) // 增加断言 .andDo(print()) // 打印结果 .andReturn(); } /** * 删除学生 * * @param classesCode * @param stuNo */ private void delete(String classesCode, String stuNo) throws Exception { mockMvc.perform(MockMvcRequestBuilders.get("/student/delete") .param("classes.code", classesCode) .param("stuNo", stuNo)) .andExpect(status().isOk()) // 增加断言 .andExpect(jsonPath("$.code").value(SUCCESS_CODE)) // 增加断言 .andDo(print()) // 打印结果 .andReturn(); } /** * 删除所有学生 */ private void deleteAll() throws Exception { mockMvc.perform(MockMvcRequestBuilders.get("/student/deleteAll")) .andExpect(status().isOk()) // 增加断言 .andExpect(jsonPath("$.code").value(SUCCESS_CODE)) // 增加断言 .andDo(print()) // 打印结果 .andReturn(); } }

  测试结果:

Copy
2022-09-07 20:31:25.896 INFO 22204 --- [ main] c.c.controller.StudentControllerTest : 新增学生1 ==> MockHttpServletRequest: HTTP Method = POST Request URI = /student/save Parameters = {stuNo=[A01], name=[小明], classes.code=[A], classes.name=[一班]} Headers = [] Body = <no character encoding set> Session Attrs = {} Handler: Type = com.c3stones.controller.StudentController Method = com.c3stones.controller.StudentController#save(Student) Async: Async started = false Async result = null Resolved Exception: Type = null ModelAndView: View name = null View = null Model = null FlashMap: Attributes = null MockHttpServletResponse: Status = 200 Error message = null Headers = [Content-Type:"application/json"] Content type = application/json Body = {"code":200,"msg":null,"data":null} Forwarded URL = null Redirected URL = null Cookies = [] 2022-09-07 20:31:26.447 INFO 22204 --- [ main] c.c.controller.StudentControllerTest : 新增学生2 ==> MockHttpServletRequest: HTTP Method = POST Request URI = /student/save Parameters = {stuNo=[A02], name=[小李], classes.code=[A], classes.name=[一班]} Headers = [] Body = <no character encoding set> Session Attrs = {} Handler: Type = com.c3stones.controller.StudentController Method = com.c3stones.controller.StudentController#save(Student) Async: Async started = false Async result = null Resolved Exception: Type = null ModelAndView: View name = null View = null Model = null FlashMap: Attributes = null MockHttpServletResponse: Status = 200 Error message = null Headers = [Content-Type:"application/json"] Content type = application/json Body = {"code":200,"msg":null,"data":null} Forwarded URL = null Redirected URL = null Cookies = [] 2022-09-07 20:31:26.454 INFO 22204 --- [ main] c.c.controller.StudentControllerTest : 查询学生 ==> MockHttpServletRequest: HTTP Method = GET Request URI = /student/get Parameters = {classesCode=[A], stuNo=[A01]} Headers = [] Body = <no character encoding set> Session Attrs = {} Handler: Type = com.c3stones.controller.StudentController Method = com.c3stones.controller.StudentController#get(String, String) Async: Async started = false Async result = null Resolved Exception: Type = null ModelAndView: View name = null View = null Model = null FlashMap: Attributes = null MockHttpServletResponse: Status = 200 Error message = null Headers = [Content-Type:"application/json"] Content type = application/json Body = {"code":200,"msg":null,"data":{"stuNo":"A01","name":"小明","classes":{"code":"A","name":"一班"}}} Forwarded URL = null Redirected URL = null Cookies = [] MockHttpServletRequest: HTTP Method = GET Request URI = /student/get Parameters = {classesCode=[A], stuNo=[A02]} Headers = [] Body = <no character encoding set> Session Attrs = {} Handler: Type = com.c3stones.controller.StudentController Method = com.c3stones.controller.StudentController#get(String, String) Async: Async started = false Async result = null Resolved Exception: Type = null ModelAndView: View name = null View = null Model = null FlashMap: Attributes = null MockHttpServletResponse: Status = 200 Error message = null Headers = [Content-Type:"application/json"] Content type = application/json Body = {"code":200,"msg":null,"data":{"stuNo":"A02","name":"小李","classes":{"code":"A","name":"一班"}}} Forwarded URL = null Redirected URL = null Cookies = [] 2022-09-07 20:31:26.479 INFO 22204 --- [ main] c.c.controller.StudentControllerTest : 查询班级 ==> MockHttpServletRequest: HTTP Method = GET Request URI = /student/list Parameters = {classesCode=[A]} Headers = [] Body = <no character encoding set> Session Attrs = {} Handler: Type = com.c3stones.controller.StudentController Method = com.c3stones.controller.StudentController#list(String) Async: Async started = false Async result = null Resolved Exception: Type = null ModelAndView: View name = null View = null Model = null FlashMap: Attributes = null MockHttpServletResponse: Status = 200 Error message = null Headers = [Content-Type:"application/json"] Content type = application/json Body = {"code":200,"msg":null,"data":[{"stuNo":"A01","name":"小明","classes":{"code":"A","name":"一班"}},{"stuNo":"A02","name":"小李","classes":{"code":"A","name":"一班"}}]} Forwarded URL = null Redirected URL = null Cookies = [] 2022-09-07 20:31:26.560 INFO 22204 --- [ main] c.c.controller.StudentControllerTest : 查询所有学生 ==> MockHttpServletRequest: HTTP Method = GET Request URI = /student/all Parameters = {} Headers = [] Body = <no character encoding set> Session Attrs = {} Handler: Type = com.c3stones.controller.StudentController Method = com.c3stones.controller.StudentController#all() Async: Async started = false Async result = null Resolved Exception: Type = null ModelAndView: View name = null View = null Model = null FlashMap: Attributes = null MockHttpServletResponse: Status = 200 Error message = null Headers = [Content-Type:"application/json"] Content type = application/json Body = {"code":200,"msg":null,"data":[{"stuNo":"A01","name":"小明","classes":{"code":"A","name":"一班"}},{"stuNo":"A02","name":"小李","classes":{"code":"A","name":"一班"}}]} Forwarded URL = null Redirected URL = null Cookies = [] 2022-09-07 20:31:26.580 INFO 22204 --- [ main] c.c.controller.StudentControllerTest : 更新学生 ==> MockHttpServletRequest: HTTP Method = GET Request URI = /student/update Parameters = {stuNo=[A01], name=[小鹏], classes.code=[A], classes.name=[一班]} Headers = [] Body = <no character encoding set> Session Attrs = {} Handler: Type = com.c3stones.controller.StudentController Method = com.c3stones.controller.StudentController#update(Student) Async: Async started = false Async result = null Resolved Exception: Type = null ModelAndView: View name = null View = null Model = null FlashMap: Attributes = null MockHttpServletResponse: Status = 200 Error message = null Headers = [Content-Type:"application/json"] Content type = application/json Body = {"code":200,"msg":null,"data":null} Forwarded URL = null Redirected URL = null Cookies = [] 2022-09-07 20:31:26.595 INFO 22204 --- [ main] c.c.controller.StudentControllerTest : 查询学生 ==> MockHttpServletRequest: HTTP Method = GET Request URI = /student/get Parameters = {classesCode=[null], stuNo=[A01]} Headers = [] Body = <no character encoding set> Session Attrs = {} Handler: Type = com.c3stones.controller.StudentController Method = com.c3stones.controller.StudentController#get(String, String) Async: Async started = false Async result = null Resolved Exception: Type = null ModelAndView: View name = null View = null Model = null FlashMap: Attributes = null MockHttpServletResponse: Status = 200 Error message = null Headers = [Content-Type:"application/json"] Content type = application/json Body = {"code":200,"msg":null,"data":{"stuNo":"A01","name":"小鹏","classes":{"code":"A","name":"一班"}}} Forwarded URL = null Redirected URL = null Cookies = [] 2022-09-07 20:31:26.602 INFO 22204 --- [ main] c.c.controller.StudentControllerTest : 删除学生 ==> MockHttpServletRequest: HTTP Method = GET Request URI = /student/delete Parameters = {classes.code=[A], stuNo=[A02]} Headers = [] Body = <no character encoding set> Session Attrs = {} Handler: Type = com.c3stones.controller.StudentController Method = com.c3stones.controller.StudentController#delete(Student) Async: Async started = false Async result = null 2022-09-07 20:31:26.611 WARN 22204 --- [onPool-worker-1] com.c3stones.config.NativeCacheConfig : NativeCache RemovalListener key:A02, value:Student(stuNo=A02, name=小李, classes=Classes(code=A, name=一班)), cause:EXPLICIT Resolved Exception: Type = null ModelAndView: View name = null View = null Model = null FlashMap: Attributes = null MockHttpServletResponse: Status = 200 Error message = null Headers = [Content-Type:"application/json"] Content type = application/json Body = {"code":200,"msg":null,"data":null} Forwarded URL = null Redirected URL = null Cookies = [] 2022-09-07 20:31:26.616 INFO 22204 --- [ main] c.c.controller.StudentControllerTest : 查询所有学生 ==> MockHttpServletRequest: HTTP Method = GET Request URI = /student/all Parameters = {} Headers = [] Body = <no character encoding set> Session Attrs = {} Handler: Type = com.c3stones.controller.StudentController Method = com.c3stones.controller.StudentController#all() Async: Async started = false Async result = null Resolved Exception: Type = null ModelAndView: View name = null View = null Model = null FlashMap: Attributes = null MockHttpServletResponse: Status = 200 Error message = null Headers = [Content-Type:"application/json"] Content type = application/json Body = {"code":200,"msg":null,"data":[{"stuNo":"A01","name":"小鹏","classes":{"code":"A","name":"一班"}}]} Forwarded URL = null Redirected URL = null Cookies = [] 2022-09-07 20:31:26.623 INFO 22204 --- [ main] c.c.controller.StudentControllerTest : 删除所有学生 ==> 2022-09-07 20:31:26.632 WARN 22204 --- [onPool-worker-1] com.c3stones.config.NativeCacheConfig : NativeCache RemovalListener key:A01, value:Student(stuNo=A01, name=小鹏, classes=Classes(code=A, name=一班)), cause:EXPLICIT MockHttpServletRequest: HTTP Method = GET Request URI = /student/deleteAll Parameters = {} Headers = [] Body = <no character encoding set> Session Attrs = {} Handler: Type = com.c3stones.controller.StudentController Method = com.c3stones.controller.StudentController#delete() Async: Async started = false Async result = null Resolved Exception: Type = null ModelAndView: View name = null View = null Model = null FlashMap: Attributes = null MockHttpServletResponse: Status = 200 Error message = null Headers = [Content-Type:"application/json"] Content type = application/json Body = {"code":200,"msg":null,"data":null} Forwarded URL = null Redirected URL = null Cookies = [] 2022-09-07 20:31:26.638 INFO 22204 --- [ main] c.c.controller.StudentControllerTest : 查询所有学生 ==> MockHttpServletRequest: HTTP Method = GET Request URI = /student/all Parameters = {} Headers = [] Body = <no character encoding set> Session Attrs = {} Handler: Type = com.c3stones.controller.StudentController Method = com.c3stones.controller.StudentController#all() Async: Async started = false Async result = null Resolved Exception: Type = null ModelAndView: View name = null View = null Model = null FlashMap: Attributes = null MockHttpServletResponse: Status = 200 Error message = null Headers = [Content-Type:"application/json"] Content type = application/json Body = {"code":200,"msg":null,"data":[]} Forwarded URL = null Redirected URL = null Cookies = []

5. 项目地址#

  spring-boot-caffeine-demo

posted @   C3Stones  阅读(1559)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
点击右上角即可分享
微信分享提示
CONTENTS