缓存是大多数应用程序的主要组成部分,只要我们尽量避免磁盘访问,它就会保持强大。 Spring 很好地支持 具有多种配置的缓存。您可以从最简单的开始,然后逐步发展到更加可定制的东西。
这将是 spring 提供的最简单缓存形式的示例。 Spring 默认带有一个非常容易设置的内存缓存。
让我们从我们的 gradle 文件开始。
group 'com.gkatzioura' version '1.0-SNAPSHOT' buildscript { repositories { mavenCentral() } dependencies { classpath("org.springframework.boot:spring-boot-gradle-plugin:1.4.2.RELEASE") } } apply plugin: 'java' apply plugin: 'idea' apply plugin: 'org.springframework.boot' repositories { mavenCentral() } sourceCompatibility = 1.8 targetCompatibility = 1.8 dependencies { compile("org.springframework.boot:spring-boot-starter-web") compile("org.springframework.boot:spring-boot-starter-cache") compile("org.springframework.boot:spring-boot-starter") testCompile("junit:junit") } bootRun { systemProperty "spring.profiles.active", "simple-cache" }
由于同一个项目将用于不同的缓存提供程序,因此会有多个 spring 配置文件。本教程的 spring 配置文件将是简单缓存,因为我们将使用基于 ConcurrentMap 的缓存,而这恰好是默认缓存。
我们将实现一个应用程序,该应用程序将从我们的本地文件系统中获取用户信息。该信息应驻留在 users.json 文件中
[ {"userName":"user1","firstName":"User1","lastName":"First"}, {"userName":"user2","firstName":"User2","lastName":"Second"}, {"userName":"user3","firstName":"User3","lastName":"Third"}, {"userName":"user4","firstName":"User4","lastName":"Fourth"} ]
我们还将为要检索的数据指定一个简单的模型。
package com.gkatzioura.caching.model; /** * Created by gkatzioura on 1/5/17. */ public class UserPayload { private String userName; private String firstName; private String lastName; public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } }
然后我们将添加一个将读取信息的 bean。
package com.gkatzioura.caching.config; import com.fasterxml.jackson.databind.ObjectMapper; import com.gkatzioura.caching.model.UserPayload; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; import org.springframework.core.io.Resource; import java.io.IOException; import java.io.InputStream; import java.util.Arrays; import java.util.Collections; import java.util.List; /** * Created by gkatzioura on 1/5/17. */ @Configuration @Profile("simple-cache") public class SimpleDataConfig { @Autowired private ObjectMapper objectMapper; @Value("classpath:/users.json") private Resource usersJsonResource; @Bean public List<UserPayload> payloadUsers() throws IOException { try(InputStream inputStream = usersJsonResource.getInputStream()) { UserPayload[] payloadUsers = objectMapper.readValue(inputStream,UserPayload[].class); return Collections.unmodifiableList(Arrays.asList(payloadUsers)); } } }
显然,为了访问信息,我们将使用实例化的包含所有用户信息的 bean。
下一步将是创建一个存储库接口来指定将要使用的方法。
package com.gkatzioura.caching.repository; import com.gkatzioura.caching.model.UserPayload; import java.util.List; /** * Created by gkatzioura on 1/6/17. */ public interface UserRepository { List<UserPayload> fetchAllUsers(); UserPayload firstUser(); UserPayload userByFirstNameAndLastName(String firstName,String lastName); }
现在让我们深入研究将包含所需缓存注释的实现。
package com.gkatzioura.caching.repository; import com.gkatzioura.caching.model.UserPayload; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.Cacheable; import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Repository; import java.util.List; import java.util.Optional; /** * Created by gkatzioura on 12/30/16. */ @Repository @Profile("simple-cache") public class UserRepositoryLocal implements UserRepository { @Autowired private List<UserPayload> payloadUsers; private static final Logger LOGGER = LoggerFactory.getLogger(UserRepositoryLocal.class); @Override @Cacheable("alluserscache") public List<UserPayload> fetchAllUsers() { LOGGER.info("Fetching all users"); return payloadUsers; } @Override @Cacheable(cacheNames = "usercache",key = "#root.methodName") public UserPayload firstUser() { LOGGER.info("fetching firstUser"); return payloadUsers.get(0); } @Override @Cacheable(cacheNames = "usercache",key = "{#firstName,#lastName}") public UserPayload userByFirstNameAndLastName(String firstName,String lastName) { LOGGER.info("fetching user by firstname and lastname"); Optional<UserPayload> user = payloadUsers.stream().filter( p-> p.getFirstName().equals(firstName) &&p.getLastName().equals(lastName)) .findFirst(); if(user.isPresent()) { return user.get(); } else { return null; } } }
包含 @Cacheable 的方法将触发缓存填充,而包含 @CacheEvict 的方法会触发缓存逐出。通过使用@Cacheable 而不是仅仅指定我们的值将被存储的缓存映射,我们可以继续指定基于方法名称或方法参数的键。
这样我们就实现了方法缓存。例如,方法 firstUser 使用方法名称作为键,而方法 userByFirstNameAndLastName 使用方法参数来创建键。
带有@CacheEvict 注释的两个方法将清空指定的缓存。
LocalCacheEvict 将是处理驱逐的组件。
package com.gkatzioura.caching.repository; import org.springframework.cache.annotation.CacheEvict; import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Component; /** * Created by gkatzioura on 1/7/17. */ @Component @Profile("simple-cache") public class LocalCacheEvict { @CacheEvict(cacheNames = "alluserscache",allEntries = true) public void evictAllUsersCache() { } @CacheEvict(cacheNames = "usercache",allEntries = true) public void evictUserCache() { } }
由于我们使用一种非常简单的缓存形式,因此不支持 ttl 逐出。因此,我们将仅为这种特定情况添加一个调度程序,它将在一段时间后驱逐缓存。
package com.gkatzioura.caching.scheduler; import com.gkatzioura.caching.repository.LocalCacheEvict; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Profile; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; /** * Created by gkatzioura on 1/7/17. */ @Component @Profile("simple-cache") public class EvictScheduler { @Autowired private LocalCacheEvict localCacheEvict; private static final Logger LOGGER = LoggerFactory.getLogger(EvictScheduler.class); @Scheduled(fixedDelay=10000) public void clearCaches() { LOGGER.info("Invalidating caches"); localCacheEvict.evictUserCache(); localCacheEvict.evictAllUsersCache(); } }
最后,我们将使用一个控制器来调用指定的方法
package com.gkatzioura.caching.controller; import com.gkatzioura.caching.model.UserPayload; import com.gkatzioura.caching.repository.UserRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import java.util.List; /** * Created by gkatzioura on 12/30/16. */ @RestController public class UsersController { @Autowired private UserRepository userRepository; @RequestMapping(path = "/users/all",method = RequestMethod.GET) public List<UserPayload> fetchUsers() { return userRepository.fetchAllUsers(); } @RequestMapping(path = "/users/first",method = RequestMethod.GET) public UserPayload fetchFirst() { return userRepository.firstUser(); } @RequestMapping(path = "/users/",method = RequestMethod.GET) public UserPayload findByFirstNameLastName(String firstName,String lastName ) { return userRepository.userByFirstNameAndLastName(firstName,lastName); } }
最后但同样重要的是,我们的 Application 类应该包含两个额外的注释。需要@EnableScheduling 才能启用调度程序,需要@EnableCaching 才能启用缓存
package com.gkatzioura.caching; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cache.annotation.EnableCaching; import org.springframework.scheduling.annotation.EnableScheduling; /** * Created by gkatzioura on 12/30/16. */ @SpringBootApplication @EnableScheduling @EnableCaching public class Application { public static void main(String[] args) { SpringApplication.run(Application.class,args); } }
您可以在 github 上找到源代码。
标签2: Java教程地址:https://www.cundage.com/article/jcg-spring-boot-cache-abstraction.html