mybatis缓存源码分析
mybatis缓存源码分析
背景
在java程序与数据库交互的过程中永远存在着性能瓶颈,所以需要一直进行优化.而我们大部分会直接将目标放到数据库优化,其实我们应该先从宏观上去解决问题进而再去解决微观上的问题.性能瓶颈体现在什么地方呢?第一网络通信开销,网络数据传输通信.一般jdbc的连接和创建一个个线程都需要网络开销.第二我们常用的比如mysql.Oracle这样的数据库数据都是存储在硬盘中的,硬盘中大量的数据不利于读写.第三关于java对象的复用.在jdbc中的Connection对象可以通过连接池进行复用,Statement对象的复用.
那么想要解决上述问题,基本都需要从数据库本身出发进行数据库优化,但是这些性能瓶颈我们是不好优化的.因为数据库是由厂商驱动来决定的. 所以缓存应运而生.
缓存的本质就是内存,缓存在我们现在应用的价值是程序与数据库之间搭建的一个桥梁,提高用户的查询效率,尽量避免数据库的硬盘查询.
换出策略
比如缓存中可以存1000个对象,每一个对象对应可以存对应数据库中的一页数据.那么要存1001页的时候,就会将多出来的对象采用序列化的方式存储在硬盘空间.而序列化必须要实现Serializable接口,还有json的方式也可以实现.但是传统的序列化是二进制的方式,而json是字符串在传输上面明显性能很差(字符串格式没有二进制占用空间小),一般不采用.
换出算法
LRU: 最不常用的对象会被换出到硬盘中(或者说最少使用的对象),通过给每个对象记录使用次数可以实现.
FIFO:先入先出,第一个进来的对象第一个将会被换出到硬盘中
缓存的分类
1.ORM框架集成缓存
Hibernate Mybatis JDO(Hive) 缓存
基于DAO层框架为业务提供缓存
将缓存存储在jvm的内存中,速度比较快不像第三方中间件会有网络开销2.第三方中间件充当缓存
Redis Memcache
直接为java程序提供缓存服务
mybatis中如何使用缓存
使用动态代理实现缓存操作
下面看下关于操作数据库的一段代码
UserDAOImpl{public List<User> queryAllUsers(){//对缓存的操作//--->Redis 获取 取到 直接返回//没取到 访问数据库 执行JDBCJDBC----> DB}public User queryUserById(int id){//对缓存的操作//--->Redis 获取 取到 直接返回//没取到 访问数据库 执行JDBCJDBC----> DB}}
上面是一个查询所有用户的操作,可以发现这里会先通过缓存去获取数据,没有取到才会向数据库获取数据.但是下面的通过id查询用户还会重复这种对缓存的操作.这样很明显虽然可以实现功能但是明显代码冗余,可以通过动态代理的方式对需要缓存的接口进行方法增强,mybatis这里是使用的AOP当然俩种方法都是没什么问题.下面通过代码来演示动态代理进行操作
创建ProductDAO接口
public interface ProductDAO {public void save();public Product queryProductById(int id);public List<Product> queryAllProducts();
}
创建ProductDAOImpl实现类
public class ProductDAOImpl implements ProductDAO {@Overridepublic void save() {System.out.println("jdbc 的方式操作 数据库 完成 插入的操作");}@Overridepublic Product queryProductById(int id) {System.out.println("jdbc 的方式基于ID 进行查询 " + id);return new Product();}@Overridepublic List<Product> queryAllProducts() {System.out.println("jdbc 的方式进行全表查询 ");return new ArrayList();}
}
创建代理类
public class TestMybaits2 {/*** 用于测试:创建DAO接口的代理*/@Testpublic void test() {ProductDAO productDAO = new ProductDAOImpl();ProductDAO productDAOProxy = (ProductDAO) Proxy.newProxyInstance(TestMybaits2.class.getClassLoader(), new Class[]{ProductDAO.class}, new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {//方法 只有以query开头,在进行缓存的处理 如果不是以query开头,就直接运行if (method.getName().startsWith("query")) {System.out.println("连接redis 查看数据是否 存在 如果存在 则直接返回 return data");return method.invoke(productDAO, args);}//非查询方法return method.invoke(productDAO, args);}});//进行测试productDAOProxy.save();System.out.println("---------------------------------");productDAOProxy.queryProductById(10);System.out.println("---------------------------------");productDAOProxy.queryAllProducts();}}
可以发现通过动态代理可以实现缓存,但是上面的代码只能将方法名中带有query的方法进行缓存,这样太过于死板不够灵活.下面采用注解的方式将其优化一下.
创建注解@Cache
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Cache {}
将需要使用缓存的查询方法都在方法上加@Cache注解
public interface ProductDAO {public void save();public Product queryProductById(int id);@Cachepublic List<Product> queryAllProducts();
}
将代理测试类的业务逻辑修改为判断方法上是否有@Cache注解
public class TestMybaits2 {/*** 用于测试:创建DAO接口的代理*/@Testpublic void test() {ProductDAO productDAO = new ProductDAOImpl();ProductDAO productDAOProxy = (ProductDAO) Proxy.newProxyInstance(TestMybaits2.class.getClassLoader(), new Class[]{ProductDAO.class}, new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {//方法 只有以query开头,在进行缓存的处理 如果不是以query开头,就直接运行
// if (method.getName().startsWith("query")) {
// System.out.println("连接redis 查看数据是否 存在 如果存在 则直接返回 return data");
// return method.invoke(productDAO, args);
// }Cache cache = method.getDeclaredAnnotation(Cache.class);if (cache != null) {System.out.println("连接redis 查看数据是否 存在 如果存在 则直接返回 return data");return method.invoke(productDAO, args);}//非查询方法return method.invoke(productDAO, args);}});productDAOProxy.save();System.out.println("---------------------------------");productDAOProxy.queryProductById(10);System.out.println("---------------------------------");productDAOProxy.queryAllProducts();}
mybatis封装的缓存
直接进入mybatis的cache接口源码,来看看定义了哪些方法
/*** Copyright 2009-2015 the original author or authors.** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at** http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/
package org.apache.ibatis.cache;import java.util.concurrent.locks.ReadWriteLock;/*** SPI for cache providers.* * One instance of cache will be created for each namespace.* * The cache implementation must have a constructor that receives the cache id as an String parameter.* * MyBatis will pass the namespace as id to the constructor.* * <pre>* public MyCache(final String id) {* if (id == null) {* throw new IllegalArgumentException("Cache instances require an ID");* }* this.id = id;* initialize();* }* </pre>** @author Clinton Begin*/
//定义为一个借口,看看下面定义了哪些方法
public interface Cache {/**官方注释上写的是这是一个缓存的标识符* @return The identifier of this cache*/String getId();/**向缓存中存储数据* @param key Can be any object but usually it is a {@link CacheKey}* @param value The result of a select.*/void putObject(Object key, Object value);/**从缓存中获取数据* @param key The key* @return The object stored in the cache.*/Object getObject(Object key);/*** As of 3.3.0 this method is only called during a rollback * for any previous value that was missing in the cache.* This lets any blocking cache to release the lock that * may have previously put on the key.* A blocking cache puts a lock when a value is null * and releases it when the value is back again.* This way other threads will wait for the value to be * available instead of hitting the database.** * @param key The key* @return Not used*/Object removeObject(Object key);/*** Clears this cache instance*/ void clear();/*** Optional. This method is not called by the core.* * @return The number of elements stored in the cache (not its capacity).*/int getSize();/** 已经废除* Optional. As of 3.2.6 this method is no longer called by the core.* * Any locking needed by the cache must be provided internally by the cache provider.* * @return A ReadWriteLock */ReadWriteLock getReadWriteLock();}
通过上述Cache接口源码中可以发现,这个缓存的存储结构很像Java语言中的Map集合.都有put方法根据key进行插入,和get方法通过key去获取数据.如果我们要使用Cache这一套缓存操作,那么就需要实现这个接口.
自定义实现缓存接口
定义MyMybatisCache实现Cache接口
/*** 1. 存数据 存多个数据* 2. key value*/
public class MyMybatisCache implements Cache {//定义一个map来进行缓存的存储private Map<Object,Object> internalCache = new HashMap();@Overridepublic String getId() {//获取类名来做为缓存标识符return getClass().getName() ;}@Overridepublic void putObject(Object key, Object value) {//这里以下方法直接使用map集合的常用方法实现internalCache.put(key,value);}@Overridepublic Object getObject(Object key) {return internalCache.get(key);}@Overridepublic Object removeObject(Object key) {return internalCache.remove(key);}@Overridepublic void clear() {internalCache.clear();}@Overridepublic int getSize() {return internalCache.size();}@Overridepublic ReadWriteLock getReadWriteLock() {//废弃方法一般不实现,直接返回null.这里我们为了实现接口的完整性new了一个ReadWriteLock的实现类return new ReentrantReadWriteLock();}
}
可以发现通过map的方式可以实现Cache接口,那么还有另外一种可以直接使用redis来进行存储,这里只需要引入redis的java客户端也就是jedis来进行key/value的存储就可以实现.这里就不过多演示了.
mybatis实现的缓存接口
上述图片为mybatis中对Cache接口的实现,可以发现只有PerpetualCache是属于impl包下,其他的实现类都是在decorators包下.因为Cache的实现使用了装饰器设计模式,decorators包下的实现类都是来对PerpetualCache进行功能增强所以说最终还是通过PerpetualCache为主.下面直接来看看PerpetualCache源码,看看跟我们自己实现的有什么区别.
public class PerpetualCache implements Cache {private final String id;//可以发现源码中也是通过hashMap来实现的private Map<Object, Object> cache = new HashMap<Object, Object>();//这里的id是通过构造方法也就是初始化的时候由用户传入的public PerpetualCache(String id) {this.id = id;}@Overridepublic String getId() {return id;}@Overridepublic int getSize() {return cache.size();}@Overridepublic void putObject(Object key, Object value) {cache.put(key, value);}@Overridepublic Object getObject(Object key) {return cache.get(key);}@Overridepublic Object removeObject(Object key) {return cache.remove(key);}@Overridepublic void clear() {cache.clear();}@Overridepublic ReadWriteLock getReadWriteLock() {//这里废弃的方法也是并没有去做实现return null;}@Overridepublic boolean equals(Object o) {if (getId() == null) {throw new CacheException("Cache instances require an ID.");}if (this == o) {return true;}if (!(o instanceof Cache)) {return false;}Cache otherCache = (Cache) o;return getId().equals(otherCache.getId());}@Overridepublic int hashCode() {if (getId() == null) {throw new CacheException("Cache instances require an ID.");}return getId().hashCode();}}
可以看出基本跟我们实现的方式一样,并没有什么区别.
装饰器的增强功能
FifoCache 增强换出功能使用先入先出的方式进行换出操作
LruCache最少使用的缓存将被换出 (默认的装饰器)
LoggingCacheCache增加日志功能
BlockingCache保证同一时间只有一个线程到缓存中查找对应key的数据
ScheduledCache设置一个时间间隔来清空缓存
SerializbledCache自动完成key/value的序列化和反序列化操作
TransactionalCache只有在事务操作成功时,把对应的数据防止在缓存中
装饰器的使用方法
/*** 用于测试:Cache 与 装饰器*/@Testpublic void test2() {//创建PerpetualCache对象传入参数idPerpetualCache perpetualCache = new PerpetualCache("sunshuai");//将PerpetualCache对象作为参数提供给LruCacheLruCache lruCache = new LruCache(perpetualCache);//也可以使用套娃将LRU换出算法作为参数提供给LoggingCache,使其拥有日志功能LoggingCache loggingCache = new LoggingCache(lruCache);}
关于设计模式区分
Mybatis大量增加装饰器设计模式作用:为目标拓展功能(本职工作 核心功能)装饰器 代理 类图 都一样本质的区别:装饰器:增加核心功能 和被装饰的对象做的是一件事代理:增加额外功能 和被代理的对象做的是不同的事无中生有接口 --- 动态代理(动态字节码技术)自动创建接口实现类
Cache在Mybaits运行过程中如何进行应用
1.Mybatis缓存二层的体系
1)一级缓存 (默认开启)
注意事项:一级缓存只对本SqlSession生效,换SqlSession,不能在利用原有SqlSession对应的一级缓存
查询操作也是有事务操作的,在一些特殊场景如:加悲观锁(需要通过加锁将查询的数据锁住让别人不能影响数据),二级缓存(只有加上事务myabtis的二级缓存才会有效果)
一级缓存相关源码分析
mybatis查询流程:
sqlSession.getMapper() ------->UserDAO接口的实现类(代理)
sqlSession.selectAll()
executor
|-statementHandler
|- …
.selectOne()
之前我们学过executor接口实现是通过BaseExecutor接口,然后有三个实现类,下面展示下executor的类图
executor|BaseExecutor| | |SimpleExecutor ReuseExecutor BatchExecutor这是一个标准的适配器设计模式,在实现接口的过程中,只想或者只能实现其部分方法可以考虑适配器设计模式.那么缓存操作肯定实在BaseExcutor中去体现的,因为不可能每个实现类都去实现一级缓存的操作.下面我们直接进入BaseExecutor源码
public abstract class BaseExecutor implements Executor {private static final Log log = LogFactory.getLog(BaseExecutor.class);protected Transaction transaction;protected Executor wrapper;protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads;//缓存的数据存储在hashmap中 (对应mybatis的基本操作)protected PerpetualCache localCache;//缓存存储过程protected PerpetualCache localOutputParameterCache;protected Configuration configuration;protected int queryStack;private boolean closed;protected BaseExecutor(Configuration configuration, Transaction transaction) {this.transaction = transaction;this.deferredLoads = new ConcurrentLinkedQueue<DeferredLoad>();this.localCache = new PerpetualCache("LocalCache");this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache");this.closed = false;this.configuration = configuration;this.wrapper = this;}
那么缓存肯定是在查询操作中去介入的,继续看源码中的查询方法.
//查询中抽象方法由适配器实现类实现,所以我们只需要关注query()方法
@Overridepublic <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {//获取sql语句BoundSql boundSql = ms.getBoundSql(parameter);//创建缓存的keyCacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);//重载进入下面的query()方法return query(ms, parameter, rowBounds, resultHandler, key, boundSql);}@SuppressWarnings("unchecked")@Overridepublic <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());if (closed) {throw new ExecutorException("Executor was closed.");}if (queryStack == 0 && ms.isFlushCacheRequired()) {clearLocalCache();}//查询返回结果listList<E> list;try {queryStack++;//缓存相关操作--如果从缓存中没有获取到数据执行else内容直接查询数据库,查询到直接返回listlist = resultHandler == null ? (List<E>) localCache.getObject(key) : null;if (list != null) {//这个操作是处理存储过程中的输出参数handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);} else {//正常查询数据库操作list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);}} finally {queryStack--;}if (queryStack == 0) {for (DeferredLoad deferredLoad : deferredLoads) {deferredLoad.load();}// issue #601deferredLoads.clear();if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {// issue #482clearLocalCache();}}return list;}
//抽象方法会交给适配器的实现类来进行实现
protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)throws SQLException;protected abstract <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql)throws SQLException;
继续进入queryFromDatabase()方法查看数据库操作如何执行
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {List<E> list;//向缓存中存储一个枚举类型的占位符localCache.putObject(key, EXECUTION_PLACEHOLDER);try {//执行数据库查询操作,这里我们就不继续往下看源码了因为是由simlpeExecutor去执行list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);} finally {localCache.removeObject(key);}//将查询到的数据存储在缓存中localCache.putObject(key, list);if (ms.getStatementType() == StatementType.CALLABLE) {localOutputParameterCache.putObject(key, parameter);}return list;}
2)二级缓存(全局缓存)
默认关闭激活二级缓存
1.mybatis-config.xml中加入setting的配置(现在版本已经不需要进行配置了,默认开启)
2.mapper文件中引入二级缓存Cache标签(必须要配置)
3.查询时需要加上 userCache=true属性(也是可以不用配置的)
4.事务存在
核心装饰器:目标增强核心功能
CachingExecutor ---- SimpleExecutor ReuseExecutor 增强缓存功能(通过套娃)
CachingExecutor ce = new CachingExecutor(simpleExecutor)
上述代码 在mybatis源码中的哪个位置体现呢
通过在idea直接搜索new CachingExecutor(),可以发现在Configuration类中有这一操作,下面见源码
可以发现通过上面的cacheEnabled属性会使用套娃给executor增强缓存的功能,而这个cacheEnabled属性正是对应的我们mybatis-config配置文件中setting标签的配置.默认为true,这就是我们为什么现在不需要对setting进行配置的原因.按理论上来说mybatis的全局缓存也就是二级缓存应该也是默认开启的,但是需要在mapper文件中进行配置才会生效.继续追溯 CachingExecutor()构造方法看看CachingExecutor是如何提供缓存操作的.
//可以发现CachingExecutor中有很多方法,那么缓存操作肯定是为查询来提供的所以直接看到CachingExecutor中的query()方法
public class CachingExecutor implements Executor {private final Executor delegate;private final TransactionalCacheManager tcm = new TransactionalCacheManager();public CachingExecutor(Executor delegate) {this.delegate = delegate;delegate.setExecutorWrapper(this);}@Overridepublic <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {BoundSql boundSql = ms.getBoundSql(parameterObject);//这里创建了缓存的keyCacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);//然后是一个方法重载进入到下面的query方法return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);}@Overridepublic <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException {flushCacheIfRequired(ms);return delegate.queryCursor(ms, parameter, rowBounds);}@Overridepublic <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)throws SQLException {//首先从mappedStatement中获取缓存Cache cache = ms.getCache();//如果缓存不为空,那么判断mappedStatement中是否添加了isUserCache=true的属性,然后从缓存中获取数据.如果数据为空,则认为是第一次查询那么就会直接查询数据库然后将查询到的数据put到缓存中,最后返回list数据.if (cache != null) {//这里是如果sql语句加上了清除缓存的属性才会进行清除,查询默认为false也就是默认查询不清除缓存,增删改默认为true,这个默认值是在MappedStatement创建时赋值的flushCacheIfRequired(ms);if (ms.isUseCache() && resultHandler == null) {ensureNoOutParams(ms, boundSql);@SuppressWarnings("unchecked")List<E> list = (List<E>) tcm.getObject(cache, key);if (list == null) {list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);tcm.putObject(cache, key, list); // issue #578 and #116}return list;}}//如果没有获取到缓存那么直接查询数据库return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);}...
}
以上俩种查询数据库的操作虽然代码是一样的,但是最初设计是完全不同,缓存不为空时,并且在查询语句上加了Cache标签时,才会查询.而下面的是没有在mapper文件中写上Cache标签的查询.然后继续看增删改的执行流程
@Overridepublic int update(MappedStatement ms, Object parameterObject) throws SQLException {//因为默认设置为true,所以会清除缓存主要是怕读到脏数据flushCacheIfRequired(ms);return delegate.update(ms, parameterObject);}
2.缓存类的创建
直接搜索useNewCache()方法,进入他的源码
可以查看这个方法被哪里调用过
一个是xml的方式,另外一个是使用注解的方式.分别对应了俩种方式去开启缓存操作,通过快捷键Ctrl +Alt+H对着useNewCache()方法可以查看方法的调用以及重载调用结构图
因为我们还是主要分析XML文件的方式,所以直接看看configurationElement()这个方法
private void configurationElement(XNode context) {try {String namespace = context.getStringAttribute("namespace");if (namespace == null || namespace.equals("")) {throw new BuilderException("Mapper's namespace cannot be empty");}builderAssistant.setCurrentNamespace(namespace);cacheRefElement(context.evalNode("cache-ref"));//当解析到配置文件的cache标签后,便会为我们在ms中创建cache也就是会调用useNewCache()方法cacheElement(context.evalNode("cache"));parameterMapElement(context.evalNodes("/mapper/parameterMap"));resultMapElements(context.evalNodes("/mapper/resultMap"));sqlElement(context.evalNodes("/mapper/sql"));buildStatementFromContext(context.evalNodes("select|insert|update|delete"));} catch (Exception e) {throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);}}
缓存的创建流程
Mybatis解析XML配置文件
---|Mybatis-config.xml---|Mapper 读取mapper.xml文件---|<cache>---|useNewCache()Mybatis创建二级缓存时使用构建者Builder设计模式创建对象对比构建者与工厂创建对象工厂 创建对象一般为XXXFactory只注重最终的结果构建者 创建对象一般为 new XXXBuilder().build()注重零件组装成最后的产品
下面来看看build()方法如何来构建Cache的,首先继续看到useNewCache()这个方法
public Cache useNewCache(Class<? extends Cache> typeClass,Class<? extends Cache> evictionClass,Long flushInterval,Integer size,boolean readWrite,boolean blocking,Properties props) {//可以看到这里使用构建者设计模式将零件组装创建对象Cache cache = new CacheBuilder(currentNamespace).implementation(valueOrDefault(typeClass, PerpetualCache.class)).addDecorator(valueOrDefault(evictionClass, LruCache.class)).clearInterval(flushInterval).size(size).readWrite(readWrite).blocking(blocking).properties(props).build();configuration.addCache(cache);currentCache = cache;return cache;}
这里直接进入build()方法看看具体做了什么操作
public Cache build() {//设置默认的实现setDefaultImplementations();Cache cache = newBaseCacheInstance(implementation, id);setCacheProperties(cache);if (PerpetualCache.class.equals(cache.getClass())) {for (Class<? extends Cache> decorator : decorators) {cache = newCacheDecoratorInstance(decorator, cache);setCacheProperties(cache);}cache = setStandardDecorators(cache);} else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {cache = new LoggingCache(cache);}return cache;}
继续看看这个方法 setDefaultImplementations()
private void setDefaultImplementations() {if (implementation == null) {//默认实现为PerpetualCache和下面的LruCacheimplementation = PerpetualCache.class;if (decorators.isEmpty()) {//LruCache为PerpetualCache的装饰器decorators.add(LruCache.class);}}}
重新回到build()方法看到下面这一行
Cache cache = newBaseCacheInstance(implementation, id);
进入newBaseCacheInstance()中查看源码
//可以发现传入的implementation参数是一个Cache的实现类
private Cache newBaseCacheInstance(Class<? extends Cache> cacheClass, String id) {Constructor<? extends Cache> cacheConstructor = getBaseCacheConstructor(cacheClass);try {//通过反射创建Cache对象设置一个idreturn cacheConstructor.newInstance(id);} catch (Exception e) {throw new CacheException("Could not instantiate cache implementation (" + cacheClass + "). Cause: " + e, e);}}
继续回到build()中,这行代码主要是为二级缓存设置一些参数例如cache标签中的一些Properties
setCacheProperties(cache);
继续往下看这个if判断
public Cache build() {setDefaultImplementations();Cache cache = newBaseCacheInstance(implementation, id);setCacheProperties(cache);//如果没有对缓存进行拓展也就是拓展其他的装饰器那么执行以下代码if (PerpetualCache.class.equals(cache.getClass())) {for (Class<? extends Cache> decorator : decorators) {//可以进行自定义装饰器cache = newCacheDecoratorInstance(decorator, cache);setCacheProperties(cache);}//直接使用默认的装饰器cache = setStandardDecorators(cache);//如果拓展了日志功能那么将日志功能套进cache} else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {cache = new LoggingCache(cache);}return cache;}
让我们来看看setStandardDecorators()这个方法具体是什么作用,进入源码
private Cache setStandardDecorators(Cache cache) {try {//获取相关参数(根据cache标签中是否增加对应的属性来判断添加对应的装饰器)MetaObject metaCache = SystemMetaObject.forObject(cache);if (size != null && metaCache.hasSetter("size")) {metaCache.setValue("size", size);}if (clearInterval != null) {cache = new ScheduledCache(cache);((ScheduledCache) cache).setClearInterval(clearInterval);}if (readWrite) {cache = new SerializedCache(cache);}cache = new LoggingCache(cache);cache = new SynchronizedCache(cache);if (blocking) {cache = new BlockingCache(cache);}return cache;} catch (Exception e) {throw new CacheException("Error building standard cache decorators. Cause: " + e, e);}}
通过以上源码得知useNewCache创建缓存的具体流程应该是
useNewCache()
1.创建默认缓存---> PerpetualCache和 LruCache
2.创建新的实现---> Cache cache = newBaseCacheInstance(implementation, id);
3.读取整合cache的property标签增加额外的参数(内置缓存不用,主要用于自定义缓存 如redis ,OsCache,EhCache)
4.增加装饰器----->cache标签上的属性如size,clearInterval,readWrite,blocking等
相关文章:

mybatis缓存源码分析
mybatis缓存源码分析 背景 在java程序与数据库交互的过程中永远存在着性能瓶颈,所以需要一直进行优化.而我们大部分会直接将目标放到数据库优化,其实我们应该先从宏观上去解决问题进而再去解决微观上的问题.性能瓶颈体现在什么地方呢?第一网络通信开销,网络数据传输通信.…...

机房小探索
现在连不了NJU-WLAN,怀疑是没有插网线,可以考虑买个USB转网卡的接口,但是我的电脑只有两个USB插口,还不知道版本是什么,之后还想连鼠标跟键盘外设呢。只能连NJU_SWI_WLAN,合理怀疑是Software Internet的缩写…...

PHP8的类与对象的基本操作之成员变量-PHP8知识详解
成员变量是指在类中定义的变量。在类中可以声明多个变量,所以对象中可以存在多个成员变量,每个变量将存储不同的对象属性信息。 例如以下定义: public class Goods { 关键字 $name; //类的成员变量 }成员属性必须使用关键词进行修饰…...

phpstudy2016 RCE漏洞验证
文章目录 漏洞描述漏洞验证 漏洞描述 PHPStudyRCE(Remote Code Execution),也称为phpstudy_backdoor漏洞,是指PHPStudy软件中存在的一个远程代码执行漏洞。 漏洞验证 打开phpstudy2016,用bp自带的浏览器访问www目录下…...

【QT】QT事件Event大全
很高兴在雪易的CSDN遇见你 ,给你糖糖 欢迎大家加入雪易社区-CSDN社区云 前言 本文分享QT中的事件Event技术,主要从QT事件流程和常用QT事件方法等方面展开,希望对各位小伙伴有所帮助! 感谢各位小伙伴的点赞关注,小易…...

华为云云耀云服务器L实例评测|华为云上安装etcd
文章目录 华为云云耀云服务器L实例评测|华为云上安装etcd一、什么是etcd官方硬件建议 二、华为云主机准备三、etcd安装1. 安装预构建的二进制文件2. 从源代码构建 四、etcd服务注册与发现1. 配置etcd2. 使用systemctl 管理启动etcd服务3. 注册服务4. 发现服务 五、其…...

RDLC动态设置整个表格是否显示
最近有个新的需求:使用RDLC打印,当数据库中能查出数据时,显示表格。没有数据时,不显示整个表格。 1.首先在RDLC中选中表格的任意一列,右键Tablix属性 2.Tablix属性中选中可见性》选中基于表达式显示或隐藏(E)并点开右…...

xp 系统 安装 python 2.7 ide pip
1 下载python http://www.python.org/ftp/python/ python-2.7.2.msi 安装完需要设置环境变量 2 下载 setuptools setuptools-0.6c11.win32-py2.7.exe https://pypi.tuna.tsinghua.edu.cn/simple/setuptools/ 3 下载 pip ,python 2.7 最高支持 pip 20.3.4 https:…...

RabbitMQ生产故障问题分析
1. 问题引发 由某个服务BI-collector-xx队列出现阻塞,影响很整个rabbitMQ集群服务不可用,多个应用MQ生产者服务出现假死状态,系统影响面较广,业务影响很大。当时为了应急处理,恢复系统可用,运维相对粗暴的把…...

12大常用自动化测试工具,请记得转发收藏!
常用自动化测试工具 1、Appium AppUI自动化测试 Appium 是一个移动端自动化测试开源工具,支持iOS 和Android 平台,支持Python、Java 等语言,即同一套Java 或Python 脚本可以同时运行在iOS 和Android平台,Appium 是一个C/S 架构&…...

Android Studio 的aapt2.exe在哪个目录下
一般在:C:\Users\admin\AppData\Local\Android\Sdk\build-tools\30.0.2(不一定是30.0.2,这个得看你的版本) 怎么找: 1.打开Android studio...

【pytest】conftest.py使用
1. 创建test_project 目录 test_project/sub/test_sub.py def test_baidu(test_url):print(fsub {test_url}) test_project/conftest.py 设置钩子函数 只对当前目录 和子目录起作用 import pytest #设置测试钩子函数 pytest.fixture() def test_url():return "http…...

SpringBoot集成Prometheus实现监控
SpringBoot配置Prometheus pom.xml 引入监控以及prometheus依赖 <dependency><groupId>io.micrometer</groupId><artifactId>micrometer-registry-prometheus</artifactId></dependency><dependency><groupId>org.springfram…...

【操作系统笔记十】缓存一致性
CPU 核心之间数据如何传播 高速缓存中的值被修改了,那么怎么同步到内存中呢? ① 写直达(Write-Through)② 写回(Write-Back) 写直达(Write-Through) 简单,但是很慢&am…...

lS1028 + 六网口TSN 硬交换+QNX/Linux实时系统解决方案在轨道交通系统的应用
lS1028 六网口TSN 硬交换QNX/Linux实时系统解决方案在轨道交通系统的应用 以下是在轨道交通应用的实物: CPUNXP LS1028A架构双核Cortex-A72主频1.5GHzRAM2GB DDR4ROM8GB eMMCOSUbuntu20.04供电DC 12V工作温度-40℃~ 80℃ 功能数量参数Display Port≤1路支持DP1.3…...

实现字符串反转函数
实现字符串反转 #include <stdio.h> #include <string.h>void reverse(char *str) {int len = strlen(str);...

抽检监测实施
声明 本文是学习GB-T 42893-2023 电子商务交易产品质量监测实施指南. 而整理的学习笔记,分享出来希望更多人受益,如果存在侵权请及时联系我们 1 范围 本文件提供了开展电子商务交易的有形产品质量监测的总则,监测准备、监测实施、监测效果评价 与反馈等过程指导…...

C++中的静态库与动态库
文章目录 静态库构建静态库 动态库构建动态库 它们的不同参考文章 单独提这个 库,我想我们在coding过程中,可能也会知道一两个词,如 标准库、xx库等。库作为一组已编写好、组织好的、可复用的资源接口,可以被用于其他程序。很不…...

UGUI 绘制线段
描述 点击鼠标左键在屏幕上绘制线段 准备 VertexHelper 网格绘制工具类向量、叉乘RectTransformUtility.ScreenPointToLocalPointInRectangleSetVerticesDirtyOnPopulateMesh 思路 鼠标按下,记录线段起点;鼠标持续按下,记录鼠标当前帧的…...

详细学习Mybatis(2)
详细学习Mybatis(2) 一、Mybatis核心配置文件详细解释1.1 environment(环境)1.2 事务管理器(transactionManager)1.3、dataSource(数据源)1.4、properties1.5、mapper 一、Mybatis核…...

LinkedList与链表
目录 一、Arraylist的缺陷 二、链表 2.1 链表的概念和结构 2.2 链表的实现 三、链表面试题 3.1 删除链表中所有值为val的节点 3.2 反转一个单链表 3.3 链表的中间节点 3.4 将有序链表合并 3.5 输出倒数第k个节点 3.6 链表分割 3.7 链表的回文结构 3.8 找两个链表的公共节…...

纳米软件芯片自动化测试系统测试电源芯片稳压反馈的方法
在一些电源芯片或稳压芯片中,通常内部都会有稳压反馈电路,这些电路可以将输入电压通过内部调整后输出一个稳定的输出电压,以满足电路中的稳定电源需求。也就是说芯片的稳压反馈就是内部稳压反馈电路中的电压。 芯片稳压反馈原理介绍 稳压反馈…...

微信小程序之项目基本结构、页面的基础及宿主环境
文章目录 前言一、基本组成结构基本组成小程序页面的组成部分JSON配置文件作用 二、页面基础pagesWXML和HTML的区别WXSS和CSS的区别小程序中js文件分类 三、小程序宿主环境总结 前言 微信小程序的项目基本结构、页面的基础及宿主环境 一、基本组成结构 基本组成 新建一个微信…...

C/C++鸡尾酒疗法 2023年5月电子学会青少年软件编程(C/C++)等级考试一级真题答案解析
目录 C/C鸡尾酒疗法 一、题目要求 1、编程实现 2、输入输出 二、解题思路 1、案例分析 三、程序代码 四、程序说明 五、运行结果 六、考点分析 C/C鸡尾酒疗法 2020年6月 C/C编程等级考试一级编程题 一、题目要求 1、编程实现 鸡尾酒疗法,原指“高效抗…...

人工智能及大模型简介
一、人工智能介绍 人工智能(Artificial Intelligence),英文缩写为AI。它试图赋予机器智能的能力,使它们能够像人类一样思考、学习和做出决策。它的核心要素是数据、模型和算力。 数据是人工智能的基础,数据的质量和…...

基于springboot消防员招录系统
博主主页:猫头鹰源码 博主简介:Java领域优质创作者、CSDN博客专家、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战 主要内容:毕业设计(Javaweb项目|小程序等)、简历模板、学习资料、面试题库、技术咨询 文末联系获取 项目介绍…...

手把手教你制作登录、注册界面 SpringBoot+Vue.js(cookie的灵活运用,验证码功能)
一、用户登录界面 实现思路:用户在界面输入用户名和密码传入变量。用post方法传输到后端,后端接收整个实体对象。将用户名提取出。在dao层方法中通过select注解查询,返回数据库对应的数据对象。如果返回为空则return false。不为空则通过比对…...

C++ Qt零基础入门进阶与企业级项目实战教程与学习方法分享
Qt是一个卓越的客户端跨平台开发框架,可以在Windows、Linux、macOS进行客户端开发,无缝切换,一统三端;当然除了桌面端,在移动端的早期,Qt也展现了其多才多艺,在Android和ios也可以使用Qt编写app…...

TypeScript学习记录
一、TS开发环境的搭建 1、下载并安装node.js 2、使用npm全局安装typeScript 进入命令行输入:npm i -g typescript 3、创建一个ts文件 4、使用tsc对ts文件进行编译 进入命令行进入ts文件所在目录执行命令:tsc 文件名.ts 二、TS基本变量 1、类型声…...

vue内置组件Transition的详解
1. Transition定义 Vue 提供了两个内置组件,可以帮助你制作基于状态变化的过渡和动画: <Transition>会在一个元素或组件进入和离开 DOM 时应用动画。 <TransitionGroup> 会在一个 v-for 列表中的元素或组件被插入,移动࿰…...