0
点赞
收藏
分享

微信扫一扫

springboot使用缓存的源码分析和demo代码上传(二)


demo的下载地址:​​https://github.com/pshdhx/springboot-redis-cache-mysql​​

说明:我的mysql的版本是8.xx

1、必要的准备,数据库中的两张表,很简单,根据代码中的实体类建立即可。

application.properities

spring.datasource.url=jdbc:mysql://localhost:3306/cache?serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=pshdhx
#开启驼峰写法识别
#spring.datasource.driver-class-name=com.mysql.jdbc.Driver com.mysql.cj.jdbc.Driver
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
mybatis.configuration.map-underscore-to-camel-case=true
logging.level.com.pshdhx.cache.mapper=debug

pom.xml:已上传github;

开始使用缓存:这里要注意的是,

@Cacheable(cacheNames = "emp"),里边的cacheNames必须要指定名字,否则cache无法被创建;

 

@Service
public class EmployeeService {
@Autowired
private Employeemapper employeemapper;

/**
* 将方法的结果进行缓存,以后要是在遇到相同的数据,直接从缓存中获取即可,不用调用查询数据库的方法
* @param id
* @return
*/
@Cacheable(cacheNames = "emp")
public Employee getEmp(Integer id){
System.out.println("查询"+id+"号员工");
Employee emp = employeemapper.getEmployee(id);
return emp;
}
}

@MapperScan("com.pshdhx.cache.mapper")
@SpringBootApplication
@EnableCaching
public class DemoApplication {

public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}

}

这里的主启动类一定要开启EnableCaching注解;

/**
* @Authtor pshdhx
* @Date 2020/12/1317:53
* @Version 1.0
*/
@RestController
public class EmployeeController {
@Autowired
private EmployeeService employeeService;


@GetMapping("/emp/{id}")
public Employee getEmp(@PathVariable("id") Integer id){
Employee emp = employeeService.getEmp(id);
return emp;
}
}

直接再浏览器中访问即可。可以看到第二次访问并不会调用查询数据库中的方法;

这就是@Cacheable注解的作用

下边对@Cacheable注解的源码分析;

它有很多的属性;

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Cacheable {
@AliasFor("cacheNames")
String[] value() default {};

@AliasFor("value")
String[] cacheNames() default {};

String key() default "";

String keyGenerator() default "";

String cacheManager() default "";

String cacheResolver() default "";

String condition() default "";

String unless() default "";

boolean sync() default false;
}

cacheName/value属性:用来指定缓存组件的名字;

key:缓存数据使用的key,它的默认是使用注解在方法中的参数,可以用SPEL表达式来指定其的值。 #id=参数id的值,#a0,@p0第一个参数的值,#root.args[0];

keyGenerator:key的生成器,可以自己自定义生成组件的id;【key和keyGenerator二选一使用】

cacheManager:指定缓存管理器,或者cacheResolver指定获取解析器;

condition:指定符合条件的情况下才缓存;

unless:否定缓存;当unless指定的条件为true,方法的返回值就不会被缓存;可以获取到结果进行判断 unless = "#result == null";

sync:是否使用异步模式

源码分析(二)

CacheAutoConfiguration.java

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(CacheManager.class)
@ConditionalOnBean(CacheAspectSupport.class)
@ConditionalOnMissingBean(value = CacheManager.class, name = "cacheResolver")
@EnableConfigurationProperties(CacheProperties.class)
@AutoConfigureAfter({ CouchbaseAutoConfiguration.class, HazelcastAutoConfiguration.class,
HibernateJpaAutoConfiguration.class, RedisAutoConfiguration.class })
@Import({ CacheConfigurationImportSelector.class, CacheManagerEntityManagerFactoryDependsOnPostProcessor.class })
public class CacheAutoConfiguration {

该类下需要引入

CacheConfigurationImportSelector

/**
* {@link ImportSelector} to add {@link CacheType} configuration classes.
*/
static class CacheConfigurationImportSelector implements ImportSelector {

@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
CacheType[] types = CacheType.values();
String[] imports = new String[types.length];
for (int i = 0; i < types.length; i++) {
imports[i] = CacheConfigurations.getConfigurationClass(types[i]);
}
return imports;
}

}

我们通过debug模式打开,可以看到这里边有11个配置项;

springboot使用缓存的源码分析和demo代码上传(二)_spring

这11个就是所有的缓存配置;-缓存配置类;

其中我们引入了pom文件中的

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>

我们在application.properities中加入了debug=true时,再次启动,显示;

springboot使用缓存的源码分析和demo代码上传(二)_缓存_02

所以,springboot默认的缓存配置是:SimpleCacheConfiguration.java

我们可以看到

@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(CacheManager.class)
@Conditional(CacheCondition.class)
class SimpleCacheConfiguration {

@Bean
ConcurrentMapCacheManager cacheManager(CacheProperties cacheProperties,
CacheManagerCustomizers cacheManagerCustomizers) {
ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager();
List<String> cacheNames = cacheProperties.getCacheNames();
if (!cacheNames.isEmpty()) {
cacheManager.setCacheNames(cacheNames);
}
return cacheManagerCustomizers.customize(cacheManager);
}

}

SimpleCacheConfiguration这个配置类,给容器中加了一个Bean

ConcurrentMapCacheManager,定制化配置ConcurrentMapCacheManager

我们可以看到ConcurrentMapCacheManager.java这个类

public class ConcurrentMapCacheManager implements CacheManager, BeanClassLoaderAware {

实现了CacheManager, BeanClassLoaderAware这两个接口;

该类重写了这两个方法;创建了cache,并且加入到了this.cacheMap中;

@Nullable
public Cache getCache(String name) {
Cache cache = (Cache)this.cacheMap.get(name);
if (cache == null && this.dynamic) {
synchronized(this.cacheMap) {
cache = (Cache)this.cacheMap.get(name);
if (cache == null) {
cache = this.createConcurrentMapCache(name);
this.cacheMap.put(name, cache);
}
}
}

return cache;
}

而this.cacheMap是该类中的

private final ConcurrentMap<String, Cache> cacheMap = new ConcurrentHashMap(16);

就是一个Map的容器,初始化大小是16个;

根据缓存名字和缓存对象来进行判断;如果为空,则进行创建操作:

protected Cache createConcurrentMapCache(String name) {
SerializationDelegate actualSerialization = this.isStoreByValue() ? this.serialization : null;
return new ConcurrentMapCache(name, new ConcurrentHashMap(256), this.isAllowNullValues(), actualSerialization);
}

这就给容器中注册了一个CacheManager:ConcurrentMapManager

可以获取和创建ConcurrentMapCache类型的缓存组件

这时,我们就可以往缓存中放入值和取值了;

public class ConcurrentMapCache extends AbstractValueAdaptingCache {
private final String name;
private final ConcurrentMap<Object, Object> store;

ConcurrentMapCache.java中有

将数据保存到了ConcurrentMap里边,取值的时候从这里边取值;

@Nullable
protected Object lookup(Object key) {
return this.store.get(key);
}


public void put(Object key, @Nullable Object value) {
this.store.put(key, this.toStoreValue(value));
}

整个cache的运行流程源码分析;

1、首先,页面先从缓存中取值,如果缓存中没有值,那么就调用查询数据库的方法,并且把返回的结果放入到缓存中去;

2、如果第二次取值的key相同,那么就直接从缓存中获取值;

3、如果要修改数据库中的值,只要在修改的方法中加入了注解@***,只要是key相同,那么缓存中的数据也会被修改。

@Override
public void put(Object key, @Nullable Object value) {
this.store.put(key, toStoreValue(value));
}

这是往缓存中保存数据;

@Override
@Nullable
protected Object lookup(Object key) {
return this.store.get(key);
}

这是从缓存中获取数据;

没有缓存,会创建一个emp的缓存,里边有个store,就是我们之前说的hashmap

使用一个key,默认就是方法的参数?

这个key就是生成出来的?策略就是方法的参数:

springboot使用缓存的源码分析和demo代码上传(二)_缓存_03

我们默认的就是使用的整个key生成策略;

public class SimpleKeyGenerator implements KeyGenerator {

@Override
public Object generate(Object target, Method method, Object... params) {
return generateKey(params);
}

/**
* Generate a key based on the specified parameters.
*/
public static Object generateKey(Object... params) {
if (params.length == 0) {
return SimpleKey.EMPTY;
}
if (params.length == 1) {
Object param = params[0];
if (param != null && !param.getClass().isArray()) {
return param;
}
}
return new SimpleKey(params);
}

}

spel表达式

springboot使用缓存的源码分析和demo代码上传(二)_spring_04

自定义生成key策略

 

package com.pshdhx.cache.config;

import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.lang.reflect.Method;
import java.util.Arrays;

/**
* @Authtor pshdhx
* @Date 2020/12/1320:51
* @Version 1.0
* 自定义key生成策略
*/
@Configuration
public class MyCacheConfig {
@Bean("myKeyGenerator")
public KeyGenerator keyGenerator(){
return new KeyGenerator() {
@Override
public Object generate(Object target, Method method, Object... params) {
return method.getName()+"["+ Arrays.asList(params).toString()+"]";
}
};
}
}

在service中使用我们自定义的key生成策略:

@Cacheable(cacheNames = "emp",keyGenerator = "myKeyGenerator")

dubug显示

springboot使用缓存的源码分析和demo代码上传(二)_spring_05

@Cacheable(cacheNames = "emp",keyGenerator = "myKeyGenerator",condition = "#a0>1 and #root.methodName eq 'aaa'",unless = "#a0==2")

condition是满足条件的缓存下来,unless是满足条件的不进行缓存;

sync:异步模式默认为false。

springboot使用缓存的源码分析和demo代码上传(二)_spring_06

@CachePut:既调用方法,又缓存数据;

修改数据库的某个数据,同时更新到缓存

此时,修改​​http://localhost:8080/emp?id=1&lastName=wangwu​​ 张三为王五,但是页面的缓存没有变,这是因为缓存的key不同;

查询的key是一个id=1,修改的key是一个employee对象,所有要指定key相同,缓存才能跟随数据库修改;

@Cacheable(cacheNames = "emp")
public Employee getEmp(Integer id){
System.out.println("查询"+id+"号员工");
Employee emp = employeemapper.getEmployee(id);
return emp;
}


@CachePut(value = "emp",key = "#result.id")
public Employee updateEmp(Employee employee){
employeemapper.updateEmployee(employee);
return employee;
}

 此时,页面的缓存跟着数据库在动了;

@CacheEvict清除缓存

@GetMapping("/delemp")
public String deleteEmp(Integer id){
employeeService.deleteEmp(id);
return "success";
}

 

/**
* key是清除指定缓存,
* allEntries是清除所有缓存,allEntries = true
* @param id
*/
@CacheEvict(value = "emp",key = "#id")
public void deleteEmp(Integer id){
System.out.println("deleteEmp:"+id);
}

果然,经测试后,缓存清除;

此外,还有一个属性:

beforeInvocation = false,这个属性是清除缓存是在方法执行之后清除的【默认】;如果方法执行过程中出现了错误,那么就不清空缓存;

beforeInvocation = true,这是清空缓存是方法执行之前清空,无论方法是否执行成功,都是要清除缓存的;

@Caching注解=组合

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Caching {

Cacheable[] cacheable() default {};

CachePut[] put() default {};

CacheEvict[] evict() default {};

}

 

@Caching(
cacheable={
@Cacheable(value = "emp",key = "#lastName")
},
put = {
@CachePut(value = "emp",key = "#result.id"),
@CachePut(value = "emp",key = "#result.email")
}
)//Key选择了lastname作key,但是put中使用了result.id作为key缓存,所有传递id也能从缓存中取到数据;
public Employee getEmployeeByLastName(String lastName){
return employeemapper.getEmployee(lastName);
}

 

package com.pshdhx.cache.mapper;

import com.pshdhx.cache.domain.Employee;
import org.apache.ibatis.annotations.*;

/**
* @Authtor pshdhx
* @Date 2020/12/1021:13
* @Version 1.0
*/
@Mapper
public interface Employeemapper {

@Select("select * from employee where id = #{id}")
public Employee getEmployee(Integer Id);

@Update("update employee set last_name = #{lastName},email=#{email},gender=#{gender},d_id=#{dId} where id = #{id}")
public void updateEmployee(Employee employee);

@Delete("delete from employee where id=#{id}")
public void deleteEmp(Integer id);

@Insert("insert into employee(last_name,email,gender,d_id) values(#{lastName},#{email},#{gender},#{dId})")
public void insertEmployee(Employee employee);

@Select("select * from employee where last_name=#{lastName}")
public Employee getEmployee(String lastName);
}

整理

给每个方法写value太麻烦了,我们能在类中写;

springboot使用缓存的源码分析和demo代码上传(二)_java_07

至此,cache源码和demo分析结束;

下一篇:springboot+cache+redis整合篇;

 

 

举报

相关推荐

0 条评论