spring boot 源码解析33-spring boot集成spring cache(基于ConcurrentMapCache)

 2019-10-17 22:06  阅读(1392)
文章分类:Spring boot

前言

接下来的几篇我们来讲解一下spring boot 中如何集成spring cache. spring cache 中支持如下cache:

  1. ConcurrentMap Cache
  2. Caffeine Cache
  3. EhCache
  4. GuavaCache(1.5版本废弃)
  5. Hazelcast Cache
  6. Infinispan Cache
  7. JCache Cache

我们只讲解ConcurrentMapCache和EhCache,其他的cache感兴趣的可以查阅相关资料.本文首先来看下ConcurrentMapCache.

ConcurrentMapCache集成

  1. 在pom文件中加入如下依赖:

    org.springframework.boot
            spring-boot-starter-cache
    
  2. 在启动类加上@EnableCaching注解开始spring cache的支持.

  3. 由于加入cache,为了模拟的方便,我们加入mybatis,pom文件加入如下配置:

    org.mybatis.spring.boot
            mybatis-spring-boot-starter
            1.3.0
    

    在application.properties中加入如下配置:

    mybatis.mapper-locations=classpath:com/example/demo/mapper/*.xml
    

    在启动类中加入如下注解即可:

    @MapperScan("com.example.demo.mapper")
    
  4. UserMapper如下:

    package com.example.demo.mapper;
    
        import com.example.demo.model.User;
    
        public interface UserMapper {
            int deleteByPrimaryKey(Long id);
    
            int insert(User record);
    
            int insertSelective(User record);
    
            User selectByPrimaryKey(Long id);
    
            int updateByPrimaryKeySelective(User record);
    
            int updateByPrimaryKey(User record);
        }
    
  5. User,代码如下:

    package com.example.demo.model;
        import java.util.Date;
        public class User {
            private Long id;
    
            private String nickName;
    
            private String emailNew;
    
            private Date registerTime;
    
            public Long getId() {
                return id;
            }
    
            public void setId(Long id) {
                this.id = id;
            }
    
            public String getNickName() {
                return nickName;
            }
    
            public void setNickName(String nickName) {
                this.nickName = nickName == null ? null : nickName.trim();
            }
    
            public String getEmailNew() {
                return emailNew;
            }
    
            public void setEmailNew(String emailNew) {
                this.emailNew = emailNew == null ? null : emailNew.trim();
            }
    
            public Date getRegisterTime() {
                return registerTime;
            }
    
            public void setRegisterTime(Date registerTime) {
                this.registerTime = registerTime;
            }
        }
    
  6. UserMapper.xml 如下:

    id, nick_name, email_new, register_time
    
    
        select 
    
        from user
        where id = #{id,jdbcType=BIGINT}
    
    
        delete from user
        where id = #{id,jdbcType=BIGINT}
    
    
        insert into user (id, nick_name, email_new, 
          register_time)
        values (#{id,jdbcType=BIGINT}, #{nickName,jdbcType=VARCHAR}, #{emailNew,jdbcType=VARCHAR}, 
          #{registerTime,jdbcType=TIMESTAMP})
    
    
        insert into user
    
    
            id,
    
    
            nick_name,
    
    
            email_new,
    
    
            register_time,
    
    
    
    
            #{id,jdbcType=BIGINT},
    
    
            #{nickName,jdbcType=VARCHAR},
    
    
            #{emailNew,jdbcType=VARCHAR},
    
    
            #{registerTime,jdbcType=TIMESTAMP},
    
    
    
    
        update user
    
    
            nick_name = #{nickName,jdbcType=VARCHAR},
    
    
            email_new = #{emailNew,jdbcType=VARCHAR},
    
    
            register_time = #{registerTime,jdbcType=TIMESTAMP},
    
    
        where id = #{id,jdbcType=BIGINT}
    
    
        update user
        set nick_name = #{nickName,jdbcType=VARCHAR},
          email_new = #{emailNew,jdbcType=VARCHAR},
          register_time = #{registerTime,jdbcType=TIMESTAMP}
        where id = #{id,jdbcType=BIGINT}
    
  7. UserService如下:

    package com.example.demo.service;
    
        import com.example.demo.model.User;
    
        public interface UserService {
    
            int insertSelective(User record);
    
            User selectByPrimaryKey(Long id);
    
            User updateByPrimaryKeySelective(User record);
    
            int deleteByPrimaryKey(Long id);
        }
    
  8. UserServiceImpl 如下:

    package com.example.demo.service;
        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.CachePut;
        import org.springframework.cache.annotation.Cacheable;
        import org.springframework.stereotype.Service;
        import com.example.demo.mapper.UserMapper;
        import com.example.demo.model.User;
        @Service
        public class UserServiceImpl implements UserService{
    
            private static Logger logger = LoggerFactory.getLogger(UserServiceImpl.class);
    
            @Autowired
            private UserMapper userMapper;
    
            @Override
            public int insertSelective(User record) {
                return userMapper.insertSelective(record);
            }
    
            @Override
            @Cacheable(cacheNames="users",key="#id")
            public User selectByPrimaryKey(Long id) {
                logger.info("查询数据库");
                return userMapper.selectByPrimaryKey(id);
            }
    
            @Override
            @CachePut(cacheNames="users",key="#record.id")
            public User updateByPrimaryKeySelective(User record) {
                userMapper.updateByPrimaryKeySelective(record);
                return userMapper.selectByPrimaryKey(record.getId());
            }
    
            @Override
            @CacheEvict(cacheNames="users",key="#id")
            public int deleteByPrimaryKey(Long id) {
                return userMapper.deleteByPrimaryKey(id);
            }
        }
    

    这里有必要说明一下spring cache相关的注解:

    • @CacheConfig:主要用于配置该类中会用到的一些共用的缓存配置

    • @Cacheable:配置了findByName函数的返回值将被加入缓存。同时在查询时,会先从缓存中获取,若不存在才再发起对数据库的访问。该注解主要有下面几个参数:

      • value、cacheNames:两个等同的参数(cacheNames为Spring 4新增,作为value的别名),用于指定缓存存储的集合名。由于Spring 4中新增了@CacheConfig,因此在Spring 3中原本必须有的value属性,也成为非必需项了
      • key:缓存对象存储在Map集合中的key值,非必需,缺省按照函数的所有参数组合作为key值,若自己配置需使用SpEL表达式,比如:@Cacheable(key = “#p0”):使用函数第一个参数作为缓存的key值
      • condition:缓存对象的条件,非必需,也需使用SpEL表达式,只有满足表达式条件的内容才会被缓存,比如:@Cacheable(key = “#p0”, condition = “#p0.length() < 3”),表示只有当第一个参数的长度小于3的时候才会被缓存
      • unless:另外一个缓存条件参数,非必需,需使用SpEL表达式。它不同于condition参数的地方在于它的判断时机,该条件是在函数被调用之后才做判断的,所以它可以通过对result进行判断。
      • keyGenerator:用于指定key生成器,非必需。若需要指定一个自定义的key生成器,我们需要去实现org.springframework.cache.interceptor.KeyGenerator接口,并使用该参数来指定。需要注意的是:该参数与key是互斥的
      • cacheManager:用于指定使用哪个缓存管理器,非必需。只有当有多个时才需要使用
      • cacheResolver:用于指定使用那个缓存解析器,非必需。需通过org.springframework.cache.interceptor.CacheResolver接口来实现自己的缓存解析器,并用该参数指定。
    • @CachePut:配置于函数上,能够根据参数定义条件来进行缓存,它与@Cacheable不同的是,它每次都会真是调用函数,所以主要用于数据新增和修改操作上。它的参数与@Cacheable类似,具体功能可参考上面对@Cacheable参数的解析

    • @CacheEvict:配置于函数上,通常用在删除方法上,用来从缓存中移除相应数据。除了同@Cacheable一样的参数之外,它还有下面两个参数:

      • allEntries:非必需,默认为false。当为true时,会移除所有数据
      • beforeInvocation:非必需,默认为false,会在调用方法之后移除数据。当为true时,会在调用方法之前移除数据。

    参考链接如下:

    Spring Boot中的缓存支持(一)注解配置与EhCache使用

    Spring4.1新特性——Spring缓存框架增强

  9. UserController,代码如下:

    package com.example.demo;
        import org.springframework.beans.factory.annotation.Autowired;
        import org.springframework.stereotype.Controller;
        import org.springframework.web.bind.annotation.RequestMapping;
        import org.springframework.web.bind.annotation.RequestMethod;
        import org.springframework.web.bind.annotation.ResponseBody;
        import com.example.demo.model.User;
        import com.example.demo.service.UserService;
        @Controller
        public class UserController {
    
            @Autowired
            private UserService userService;
    
            @RequestMapping("/add-user")
            @ResponseBody
            public String insertSelective(User record) {
                userService.insertSelective(record);
                return "OK";
            }
    
            @RequestMapping("/get-by-id")
            @ResponseBody
            public User selectByPrimaryKey(Long id) {
                return userService.selectByPrimaryKey(id);
            }
    
            @RequestMapping("/update-by-id")
            @ResponseBody
            public String update(User record) {
                userService.updateByPrimaryKeySelective(record);
                return "ok";
            }
    
            @RequestMapping("/delete-by-id")
            @ResponseBody
            public Integer delete(Long id) {
                return userService.deleteByPrimaryKey(id);
            }  
        }
    
  10. 测试:

    1. 首先我们访问如下链接:

      http://127.0.0.1:8080/add-user?nickName=harry&emailNew=xxx@xxx.com

      http://127.0.0.1:8080/add-user?nickName=harry2&emailNew=xxx@xxx.com

      加入2条数据

    2. 然后我们访问如下链接:

      http://127.0.0.1:8080/get-by-id?id=1

      发现有打印日志,如下:

      2018-01-23 14:52:41.658  INFO 60766 --- [nio-8881-exec-6] c.example.demo.service.UserServiceImpl   : 查询数据库
      

      返回值如下:

      { id: 1, nickName: "harry", emailNew: "xxx@xxx.com", registerTime: 1484488802000 }
      

      此时我们再次访问,发现没有再次打印日志,而是直接从缓存中返回的.

    3. 访问如下链接就行修改:

      http://127.0.0.1:8080/update-by-id?id=1&nickName=22

      将昵称改为22.

      此时我们继续访问 http://127.0.0.1:8080/get-by-id?id=1 ,发现没有打印日志,且返回的结果中昵称也改为了22,如下:

      {
          id: 1,
          nickName: "22",
          emailNew: "xxx@xxx.com",
          registerTime: 1484488802000
          }
      

ConcurrentMapCache解析

ConcurrentMapCache的自动装配声明在SimpleCacheConfiguration中.

  1. SimpleCacheConfiguration 声明了如下注解:

    @Configuration
        @ConditionalOnMissingBean(CacheManager.class)
        @Conditional(CacheCondition.class)
    
    • @Configuration –> 配置类

    • @ConditionalOnMissingBean(CacheManager.class)–> 当BeanFactory中缺少CacheManager类型的bean时生效

    • @Conditional(CacheCondition.class) –> 通过CacheCondition来进行判断,代码如下:

      public ConditionOutcome getMatchOutcome(ConditionContext context,
              AnnotatedTypeMetadata metadata) {
              // 1. 获得被注解的类名
              String sourceClass = "";
              if (metadata instanceof ClassMetadata) {
                  sourceClass = ((ClassMetadata) metadata).getClassName();
              }
              ConditionMessage.Builder message = ConditionMessage.forCondition("Cache",
                      sourceClass);
              // 2. 实例化RelaxedPropertyResolver,读取spring.cache.开头的属性
              RelaxedPropertyResolver resolver = new RelaxedPropertyResolver(
                      context.getEnvironment(), "spring.cache.");
              // 3. 如果没有配置spring.cache.type,则返回匹配--> 进行自动选择
              if (!resolver.containsProperty("type")) {
                  return ConditionOutcome.match(message.because("automatic cache type"));
              }
              // 4. 根据被注解的类名获得对应的CacheType
              CacheType cacheType = CacheConfigurations
                      .getType(((AnnotationMetadata) metadata).getClassName());
              // 5. 将spring.cache.type 配置的值 中的-变为_,然后变成大写,如果和CacheType的name相等的化,则返回匹配,否则,返回不匹配
              String value = resolver.getProperty("type").replace('-', '_').toUpperCase();
              if (value.equals(cacheType.name())) {
                  return ConditionOutcome.match(message.because(value + " cache type"));
              }
              return ConditionOutcome.noMatch(message.because(value + " cache type"));
          }
      
      1. 获得被注解的类名,对于当前是org.springframework.boot.autoconfigure.cache.SimpleCacheConfiguration
      2. 实例化RelaxedPropertyResolver,读取spring.cache.开头的属性
      3. 如果没有配置spring.cache.type,则返回匹配–> 进行自动选择. 由于默认情况下是没有配置spring.cache.type的,因此,在这里返回.
      4. 根据被注解的类名获得对应的CacheType
      5. 将spring.cache.type 配置的值 中的-变为_,然后变成大写,如果和CacheType的name相等的化,则返回匹配,否则,返回不匹配
  2. bean方法,代码如下:

    @Bean
        public ConcurrentMapCacheManager cacheManager() {
            ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager();
            List cacheNames = this.cacheProperties.getCacheNames();
            if (!cacheNames.isEmpty()) {
                cacheManager.setCacheNames(cacheNames);
            }
            return this.customizerInvoker.customize(cacheManager);
        }
    
    • @Bean –> 注册1个id为cacheManager,类型为ConcurrentMapCacheManager的bean

    该方法的处理逻辑如下:

    1. 实例化ConcurrentMapCacheManager
    2. 如果配置有spring.cache.cache-names=xx,xx,则进行配置cacheNames,默认是没有配置的
    3. 调用CacheManagerCustomizers#customize 进行个性化设置,在该方法中是遍历其持有的List

来源:[]()

点赞(0)
版权归原创作者所有,任何形式转载请联系作者; Java 技术驿站 >> spring boot 源码解析33-spring boot集成spring cache(基于ConcurrentMapCache)

相关推荐