SpringBoot + Caffeine实现本地缓存(内存缓存)
1. Caffeine简介
Caffeine
是一个基于Java8开发的提供了近乎最佳命中率的高性能的缓存库。借鉴Google Guava
和ConcurrentLinkedHashMap
的经验,实现内存缓存。
缓存和ConcurrentMap
有点相似,但还是有所区别。最根本的区别是ConcurrentMap
将会持有所有加入到缓存当中的元素,直到它们被从缓存当中手动移除。但是,Caffeine
的缓存Cache
通常会被配置成自动驱逐缓存中元素,以限制其内存占用。在某些场景下,自动加载缓存LoadingCache
和异步自动加载缓存AsyncLoadingCache
是非常实用的。
Caffeine
提供了灵活的构造器去创建一个拥有以下列特性的缓存:
基于过期时间的淘汰策略
- 异步刷新策略
- key值自动包装成弱引用
- 元素值自动包装成弱引用或软引用
- 通知淘汰元素策略
- 向外部存储资源写入元素
- 统计缓存访问信息
2. 相关博客
SpringBoot集成阿里缓存框架Jetcache代替Spring Cache
3. 示例代码
- 创建项目
- 修改pom.xml
<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>
- 创建本地缓存配置类
/**
* 本地缓存配置类
*
* @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;
}
}
}
- 创建缓存客户端
/**
* 缓存客户端
*
* @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);
}
}
- 创建实体类
/**
* 用户信息
*
* @author CL
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
public class User {
private String id;
private String username;
private Integer age;
}
/**
* 班级信息
*
* @author CL
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
public class Classes {
private String code;
private String name;
}
/**
* 学生信息
*
* @author CL
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
public class Student {
private String stuNo;
private String name;
private Classes classes;
}
- 创建响应体
/**
* 响应实体
*
* @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);
}
}
- 创建工具类
/**
* 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
/**
* 用户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);
}
}
/**
* 学生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);
}
}
- 创建启动类
/**
* 启动类
*
* @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
接口测试
/**
* 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();
}
}
测试结果:
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
接口测试
/**
* 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();
}
}
测试结果:
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 = []