文档结构  
翻译进度:已翻译     翻译赏金:0 元 (?)    ¥ 我要打赏

缓存是大多数应用中的一个主要组件,而且只要我们想要避免磁盘读取,那它就会显得很重要。

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"
}
第 1 段(可获 1.11 积分)

因为同样的项目可能会用到不同的缓存策略提供商,所以我们也会使用不同的Spring配置。这篇教程的Spring里使用的是名为simple-cache的配置,因为我们将要使用基于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"}
]
第 2 段(可获 0.85 积分)

我还需要指定一个简单的模型来方便数据检索。

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;
    }
}
第 3 段(可获 0.16 积分)

然后我们添加一个能够读取信息的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));
        }
    }
}
第 4 段(可获 0.14 积分)

显然,为了能够获取信息,我们将会使用包含有所有用户信息的实例化的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);
}
第 5 段(可获 0.45 积分)

现在让我们深入其实现类去看看,它包含了所需要的缓存注解。

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;
        }
    }
}
第 6 段(可获 0.18 积分)

带有@Cacheable注解的方法会触发缓存储存操作,而相反的,带有@CacheEvict注解的方法将会触发缓存回收操作。

通过使用@Cacheable注解而不是通过指定一个存放值的map来实现缓存,我们就可以基于方法名称或者方法的参数名称来指定缓存的键值。这样我就实现了方法缓存。

例如,firstUser方法使用使用方法名作为键值,而userByFirstNameAndLastName方法则使用方法参数作为键值。

两个使用@CacheEvict注解的方法会将指定的缓存清空。

第 7 段(可获 1.15 积分)

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() {
    }
}
第 8 段(可获 0.13 积分)

由于我们使用了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();
    }
}
第 9 段(可获 0.45 积分)

接下来,我们会使用一个controller来调用指定的那些方法。

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);
    }
}
第 10 段(可获 0.16 积分)

最后但并非最不重要的,我们的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上找到源码。

第 11 段(可获 0.45 积分)

文章评论