自动加载缓存框架

【源码解读】Spring Boot中redis缓存如何在项目初次启动时加载 以若依开源框架为例,探究redis缓存中的数据,如何在项目启动时进行加载。 该缓存的加载涉及到两张表:sys_dict_data(字典数据) 和 sys_dict_type(字典类型) 加载字典类型 SysDictType @Service public class SysDictTypeServiceImpl implements ISysDictTypeService{ /** * 项目启动时,初始化字典到缓存 */ @PostConstruct 阅读详情

代码
现在使用的缓存技术很多,比如Redis、 Memcache 、 EhCache等,甚至还有使用ConcurrentHashMap 或 HashTable 来实现缓存。但在缓存的使用上,每个人都有自己的实现方式,大部分是直接与业务代码绑定,随着业务的变化,要更换缓存方案时,非常麻烦。接下来我们就使用AOP + Annotation 来解决这个问题,同时使用自动加载机制 来实现数据“常驻内存”。

Spring AOP这几年非常热门,使用也越来越多,但个人建议AOP只用于处理一些辅助的功能(比如:接下来我们要说的缓存),而不能把业务逻辑使用AOP中实现,尤其是在需要“事务”的环境中。

如下图所示:
Alt 缓存框架

AOP拦截到请求后:

“
  1. 根据请求参数生成Key,后面我们会对生成Key的规则,进一步说明;
  2. 如果是AutoLoad的,则请求相关参数,封装到AutoLoadTO中,并放到AutoLoadHandler中。
  3. 根据Key去缓存服务器中取数据,如果取到数据,则返回数据,如果没有取到数据,则执行DAO中的方法,获取数据,同时将数据放到缓存中。如果是AutoLoad的,则把最后加载时间,更新到AutoLoadTO中,最后返回数据;如是AutoLoad的请求,每次请求时,都会更新AutoLoadTO中的 最后请求时间。
  4. 为了减少并发,增加等待机制:如果多个用户同时取一个数据,那么先让第一个用户去DAO取数据,其它用户则等待其返回后,去缓存中获取,尝试一定次数后,如果还没获取到,再去DAO中取数据。
”

AutoLoadHandler(自动加载处理器)主要做的事情:当缓存即将过期时,去执行DAO的方法,获取数据,并将数据放到缓存中。为了防止自动加载队列过大,设置了容量限制;同时会将超过一定时间没有用户请求的也会从自动加载队列中移除,把服务器资源释放出来,给真正需要的请求。

使用自加载的目的:

“
  1. 避免在请求高峰时,因为缓存失效,而造成数据库压力无法承受;
  2. 把一些耗时业务得以实现。
  3. 把一些使用非常频繁的数据,使用自动加载,因为这样的数据缓存失效时,最容易造成服务器的压力过大。
”

分布式自动加载

如果将应用部署在多台服务器上,理论上可以认为自动加载队列是由这几台服务器共同完成自动加载任务。比如应用部署在A,B两台服务器上,A服务器自动加载了数据D,(因为两台服务器的自动加载队列是独立的,所以加载的顺序也是一样的),接着有用户从B服务器请求数据D,这时会把数据D的最后加载时间更新给B服务器,这样B服务器就不会重复加载数据D。

使用方法

1. 实现com.jarvis.cache.CacheGeterSeter

下面举个使用Redis做缓存服务器的例子:

package com.jarvis.example.cache;
import ... ...
/**
 * 缓存切面,用于拦截数据并调用Redis进行缓存操作
 */
@Aspect
public class CachePointCut implements CacheGeterSeter<Serializable> {

    private static final Logger logger=Logger.getLogger(CachePointCut.class);

    private AutoLoadHandler<Serializable> autoLoadHandler;

    private static List<RedisTemplate<String, Serializable>> redisTemplateList;

    public CachePointCut() {
        autoLoadHandler=new AutoLoadHandler<Serializable>(10, this, 20000);
    }

    @Pointcut(value="execution(public !void com.jarvis.example.dao..*.*(..)) && @annotation(cahce)", argNames="cahce")
    public void daoCachePointcut(Cache cahce) {
        logger.info("----------------------init daoCachePointcut()--------------------");
    }

    @Around(value="daoCachePointcut(cahce)", argNames="pjp, cahce")
    public Object controllerPointCut(ProceedingJoinPoint pjp, Cache cahce) throws Exception {
        return CacheUtil.proceed(pjp, cahce, autoLoadHandler, this);
    }

    public static RedisTemplate<String, Serializable> getRedisTemplate(String key) {
        if(null == redisTemplateList || redisTemplateList.isEmpty()) {
            return null;
        }
        int hash=Math.abs(key.hashCode());
        Integer clientKey=hash % redisTemplateList.size();
        RedisTemplate<String, Serializable> redisTemplate=redisTemplateList.get(clientKey);
        return redisTemplate;
    }

    @Override
    public void setCache(final String cacheKey, final CacheWrapper<Serializable> result, final int expire) {
        try {
            final RedisTemplate<String, Serializable> redisTemplate=getRedisTemplate(cacheKey);
            redisTemplate.execute(new RedisCallback<Object>() {

                @Override
                public Object doInRedis(RedisConnection connection) throws DataAccessException {
                    byte[] key=redisTemplate.getStringSerializer().serialize(cacheKey);
                    JdkSerializationRedisSerializer serializer=(JdkSerializationRedisSerializer)redisTemplate.getValueSerializer();
                    byte[] val=serializer.serialize(result);
                    connection.set(key, val);
                    connection.expire(key, expire);
                    return null;
                }
            });
        } catch(Exception ex) {
            logger.error(ex.getMessage(), ex);
        }
    }

    @Override
    public CacheWrapper<Serializable> get(final String cacheKey) {
        CacheWrapper<Serializable> res=null;
        try {
            final RedisTemplate<String, Serializable> redisTemplate=getRedisTemplate(cacheKey);
            res=redisTemplate.execute(new RedisCallback<CacheWrapper<Serializable>>() {

                @Override
                public CacheWrapper<Serializable> doInRedis(RedisConnection connection) throws DataAccessException {
                    byte[] key=redisTemplate.getStringSerializer().serialize(cacheKey);
                    byte[] value=connection.get(key);
                    if(null != value && value.length > 0) {
                        JdkSerializationRedisSerializer serializer=
                            (JdkSerializationRedisSerializer)redisTemplate.getValueSerializer();
                        @SuppressWarnings("unchecked")
                        CacheWrapper<Serializable> res=(CacheWrapper<Serializable>)serializer.deserialize(value);
                        return res;
                    }
                    return null;
                }
            });
        } catch(Exception ex) {
            logger.error(ex.getMessage(), ex);
        }
        return res;
    }

    /**
     * 删除缓存
     * @param cs Class
     * @param method
     * @param arguments
     * @param subKeySpEL
     * @param deleteByPrefixKey 是否批量删除
     */
    public static void delete(@SuppressWarnings("rawtypes") Class cs, String method, Object[] arguments, String subKeySpEL,
        boolean deleteByPrefixKey) {
        try {
            if(deleteByPrefixKey) {
                final String cacheKey=CacheUtil.getCacheKeyPrefix(cs.getName(), method, arguments, subKeySpEL) + "*";
                for(final RedisTemplate<String, Serializable> redisTemplate : redisTemplateList){
                    redisTemplate.execute(new RedisCallback<Object>() {
                        @Override
                        public Object doInRedis(RedisConnection connection) throws DataAccessException {
                            byte[] key=redisTemplate.getStringSerializer().serialize(cacheKey);
                            Set<byte[]> keys=connection.keys(key);
                            if(null != keys && keys.size() > 0) {
                                byte[][] keys2=new byte[keys.size()][];
                                keys.toArray(keys2);
                                connection.del(keys2);
                            }
                            return null;
                        }
                    });
                }

            } else {
                final String cacheKey=CacheUtil.getCahcaheKey(cs.getName(), method, arguments, subKeySpEL);
                final RedisTemplate<String, Serializable> redisTemplate=getRedisTemplate(cacheKey);
                redisTemplate.execute(new RedisCallback<Object>() {

                    @Override
                    public Object doInRedis(RedisConnection connection) throws DataAccessException {
                        byte[] key=redisTemplate.getStringSerializer().serialize(cacheKey);

                        connection.del(key);

                        return null;
                    }
                });
            }
        } catch(Exception ex) {
            logger.error(ex.getMessage(), ex);
        }
    }

    public AutoLoadHandler<Serializable> getAutoLoadHandler() {
        return autoLoadHandler;
    }

    public void destroy() {
        autoLoadHandler.shutdown();
        autoLoadHandler=null;
    }

    public List<RedisTemplate<String, Serializable>> getRedisTemplateList() {
        return redisTemplateList;
    }

    public void setRedisTemplateList(List<RedisTemplate<String, Serializable>> redisTemplateList) {
        CachePointCut.redisTemplateList=redisTemplateList;
    }

}

从上面的代码可以看出,对缓存的操作,还是由业务系统自己来实现的,我们只是对AOP拦截到的ProceedingJoinPoint,进行做一些处理。

java代码实现后,接下来要在spring中进行相关的配置:

<aop:aspectj-autoproxy proxy-target-class="true"/>
<bean id="cachePointCut" class="com.jarvis.example.cache.CachePointCut" destroy-method="destroy">
    <property name="redisTemplateList">
        <list>
            <ref bean="redisTemplate1"/>
            <ref bean="redisTemplate2"/>
        </list>
    </property>
</bean>

2. 将需要使用缓存的方法前增加@Cache注解

package com.jarvis.example.dao;
import ... ...
public class UserDAO {
    @Cache(expire=600, autoload=true, requestTimeout=72000)
    public List<UserTO> getUserList(... ...) {
        ... ...
    }
}

缓存Key的生成

生成缓存Key的生成方法:CacheUtil.getCahcaheKey(String className, String method, Object[] arguments, String subKeySpEL)

  • className 类名称
  • method 方法名称
  • arguments 参数
  • subKeySpEL SpringEL表达式

生成的Key格式为:{类名称}.{方法名称}{.SpringEL表达式运算结果}:{参数值的Hash字符串}

SpringEL表达式的作用

根据业务的需要,将缓存Key进行分组。举个例子,商品的评论列表:

package com.jarvis.example.dao;
import ... ...
public class GoodsCommentDAO{
    @Cache(expire=600, subKeySpEL="#args[0]", autoload=true, requestTimeout=18000)
    public List<CommentTO> getCommentListByGoodsId(Long goodsId, int pageNo, int pageSize) {
        ... ...
    }
}

如果商品Id为:100,那么生成缓存Key格式为:com.jarvis.example.dao.GoodsCommentDAO.getCommentListByGoodsId.100:xxxx
在Redis中,能精确删除商品Id为100的评论列表,执行命令即可:
del com.jarvis.example.dao.GoodsCommentDAO.getCommentListByGoodsId.100:*

SpringEL表达式使用起来确实非常方便,如果需要,@Cache中的expire,requestTimeout以及autoload参数都可以用SpringEL表达式来动态设置。但使用起来就变得复杂,所以我们没有这样做。

数据实时性

上面商品评论的例子中,如果用户发表了评论,要立即显示该如何来处理?

比较简单的方法就是,在发表评论成功后,立即把缓存中的数据也清除,这样就可以了。

package com.jarvis.example.dao;
import ... ...
public class GoodsCommentDAO{
    @Cache(expire=600, subKeySpEL="#args[0]", autoload=true, requestTimeout=18000)
    public List<CommentTO> getCommentListByGoodsId(Long goodsId, int pageNo, int pageSize) {
        ... ...
    }
    public void addComment(Long goodsId, String comment) {
        ... ...// 省略添加评论代码
        deleteCache(goodsId);
    }
    private void deleteCache(Long goodsId) {
        Object arguments[]=new Object[]{goodsId};
        CachePointCut.delete(this.getClass(), "getCommentListByGoodsId", arguments, "#args[0]", true);
    }
}

@Cache

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Cache {

    /**
     * 缓存的过期时间,单位:秒
     */
    int expire();

    /**
     * 是否启用自动加载缓存
     * @return
     */
    boolean autoload() default false;

    /**
     * 当autoload为true时,缓存数据在 requestTimeout 秒之内没有使用了,就不进行自动加载数据,如果requestTimeout为0时,会一直自动加载
     * @return
     */
    long requestTimeout() default 36000L;

    /**
     * 使用SpEL,将缓存key,根据业务需要进行二次分组
     * @return
     */
    String subKeySpEL() default "";
}

注意事项

1. 当@Cache中 autoload 设置为 ture 时,对应方法的参数必须都是Serializable的。

AutoLoadHandler中需要缓存通过深度复制后的参数。

2. 参数中只设置必要的属性值,在DAO中用不到的属性值尽量不要设置,这样能避免生成不同的缓存Key,降低缓存的使用率。

例如:

    public CollectionTO<AccountTO> getAccountByCriteria(AccountCriteriaTO criteria)  {
        List<AccountTO> list=null;
        PaginationTO paging=criteria.getPaging();
        if(null != paging && paging.getPageNo() > 0 && paging.getPageSize() > 0) {// 如果需要分页查询,先查询总数
            criteria.setPaging(null);// 减少缓存KEY的变化,在查询记录总数据时,不用设置分页相关的属性值
            Integer recordCnt=accountDAO.getAccountCntByCriteria(criteria);
            if(recordCnt > 0) {
                criteria.setPaging(paging);
                paging.setRecordCnt(recordCnt);
                list=accountDAO.getAccountByCriteria(criteria);
            }
            return new CollectionTO<AccountTO>(list, recordCnt, criteria.getPaging().getPageSize());
        } else {
            list=accountDAO.getAccountByCriteria(criteria);
            return new CollectionTO<AccountTO>(list, null != list ? list.size() : 0, 0);
        }
    }

3. 注意AOP失效的情况;

例如:

    TempDAO {

        public Object a() {
            return b().get(0);
        }

        @Cache(expire=600)
        public List<Object> b(){
            return ... ...;
        }
    }

通过 new TempDAO().a() 调用b方法时,AOP失效,也无法进行缓存相关操作。

4. 自动加载缓存时,不能在缓存方法内叠加查询参数值;

例如:

    @Cache(expire=600, autoload=true)
    public List<AccountTO> getDistinctAccountByPlayerGet(AccountCriteriaTO criteria) {
        List<AccountTO> list;
        int count=criteria.getPaging().getThreshold() ;
        // 查预设查询数量的10倍
        criteria.getPaging().setThreshold(count * 10);
        … …
    }

因为自动加载时,AutoLoadHandler 缓存了查询参数,执行自动加载时,每次执行时 threshold 都会乘以10,这样threshold的值就会越来越大。

5. 当方法返回值类型改变了怎么办?

在代码重构时,可能会出现改方法返回值类型的情况,而参数不变的情况,那上线部署时,可能会从缓存中取到旧数据类型的数据,可以通过以下方法处理:

  • 上线后,快速清理缓存中的数据;
  • 在CacheGeterSeter的实现类中统一加个version;
  • 在@Cache中加version(未实现)。

6. 对于一些比较耗时的方法尽量使用自动加载。

7. 对于查询条件变化比较剧烈的,不要使用自动加载机制。

比如,根据用户输入的关键字进行搜索数据的方法,不建议使用自动加载。

使用规范

  1. 将调接口或数据库中取数据,封装在DAO层,不能什么地方都有调接口的方法。
  2. 自动加载缓存时,不能在缓存方法内叠加(或减)查询条件值,但允许设置值。
  3. DAO层内部,没使用@Cache的方法,不能调用加了@Cache的方法,避免AOP失效。
  4. 因缓存Key是方法参数转为字符串获得的,为了避免生成的Key不同,尽量只设置必要的参数及属性,也便于反向定位。
  5. 对于比较大的系统,要进行模块化设计,这样可以将自动加载,均分到各个模块中。

为什么要使用自动加载机制?

首页我们想一下系统的瓶颈在哪里?

  1. 在高并发的情况下数据库性能极差,即使查询语句的性能很高;如果没有自动加载机制的话,在当缓存过期时,访问洪峰到来时,很容易就使数据压力大增。

  2. 往缓存写数据与从缓存读数据相比,效率也差很多,因为写缓存时需要分配内存等操作。使用自动加载,可以减少同时往缓存写数据的情况,同时也能提升缓存服务器的吞吐量。

如何减少DAO层并发

  1. 使用缓存;
  2. 使用自动加载机制;“写”数据往往比读数据性能要差,使用自动加载也能减少写并发。
  3. 从DAO层加载数据时,增加等待机制(拿来主义):如果有多个请求同时请求同一个数据,会先让其中一个请求去取数据,其它的请求则等待它的数据。

可扩展性及维护性

  1. 通过AOP实现缓存与业务逻辑的解耦;如果要实时显示数据,还是会有点耦合。
  2. 非常方便更换缓存服务器或缓存实现(比如:从Memcache换成Redis);
  3. 非常方便增减缓存服务器(如:增加Redis的节点数);
  4. 非常方便增加或去除缓存,方便测试期间排查问题;
使用spring框架在应用启动时加载数据库中的数据资源加入缓存 今天研究了一下应用在启动的时候,将一些固定的资源加载到缓存中,这样 阅读详情

相关推荐

高效的缓存管理解决方案AutoLoadCache.zip

现在使用的缓存技术很多,比如Redis、 Memcache 、 EhCache等,甚至还有使用ConcurrentHashMap 或 HashTable 来实现缓存。但在缓存的使用上,每个人都有自己的实现方式,大部分是直接与业务代码绑定,随着业务的变化,要更换缓存方案时,非常麻烦。接下来我们就使用AOP   Annotation 来解决这个问题,同时使用自动加载机制 来实现数据“常驻内存”。Spring AOP这几年非常热门,使用也越来越多,但个人建议AOP只用于处理一些辅助的功能(比如:接下来我们要说的缓存),而不能把业务逻辑使用AOP中实现,尤其是在需要“事务”的环境中。设计思想及原理使用方法注解(Annotation)说明表达式的应用缓存删除注意事项缓存管理页面与Spring Cache的区别源码阅读已经实现基于aspectj,代码在com.jarvis.cache.aop.aspectj.AspectjAopInterceptor。想通过阅读代码了解详细细节,可以以此为入口。

Extjs4 combobox autoLoad: true 时,加载两次

问题是这样的,combobox 远程加载数据时,当我们ba

cdmamata的专栏 3726

华大HC32F460 TCP Server实验

华大HC32F460 TCP Server实验

ZhuoWuKeJi的博客 1272

AutoLoadCache 使用以及规范

简介 AutoLoadCache 是基于AOP+Annotation等技术实现的高效的缓存管理解决方案,实现缓存与业务逻辑的解耦,并增加异步刷新及“拿来主义机制”,以适应高并发环境下的使用。 使用AOP + Annotation 来解决这个问题,同时使用自动加载机制 来实现数据“常驻内存” —> 跳转官方git 如何使用 本文的自动缓存使用的redis作为缓存技术, 请确保自己系统中的redis能够正常使用 引入jar包 <!-- autoload-cache 依赖 --> &lt

_WanG 1301

火星遥感数据的获取与处理工具安装详解

有关火星遥感影像数据的那些事...

FengYingLuo的博客 5576

微信小程序返回按钮的隐藏玩法:用onShow实现数据自动刷新(附完整代码)

本文深入探讨了微信小程序中用户点击返回按钮后,如何利用onShow生命周期函数实现页面数据的自动刷新。文章对比了onLoad与onShow的差异,提供了从基础的本地存储标记到进阶的EventChannel事件通信,再到状态管理库的多种解决方案,并附有完整的代码示例,帮助开发者构建流畅的用户体验。

agile9scrum的博客 925

SpringBoot(九) 缓存

内容概览:JSR-107、Spring缓存抽象、整合Redis 1、JSR-107简介 Java Caching定义五个核心接口:CachingProvider、CacheManager、Cache、Entry和Expiry; CachingProvider 定义了创建,配置,获取,管理和控制多个CacheManager,一个应用可以在运行期间访问多个CacheProvider; CacheMa...

zengdongwen的博客 334

高温环境下RS485通信故障分析与MOS管解决方案

RS485通信作为工业控制领域的基础总线技术,其可靠性直接影响系统稳定性。在高温等恶劣环境下,传统三极管开关电路会因温度特性导致通信故障。通过分析三极管Ube电压随温度变化的特性,揭示了电路失效的物理原理。相比之下,MOS管作为电压控制型器件,具有温度稳定性好、功耗低等优势,更适合工业环境应用。本文通过一个发动机舱内RS485通信故障的实际案例,详细对比了三极管与MOS管在高温环境下的性能差异,并给出了具体的电路改进方案。该案例对工业通信接口设计具有普遍参考价值,特别适用于需要应对高温、振动等挑战性环境的硬

weixin_42612405的博客 406

ExtJS6.0和后台交互数据渲染到表格中并实现分页,以及解决跨域提取数据(一)

官方文档api: https://docs.sencha.com/extjs/6.2.0/modern/Ext.data.Store.html 一、与后台交互并实现分页 我们可以从官方api看到数据源store的定义 我们要将数据展示出来,需要渲染到gridpanel中,gridpanel需要如下基本数据 这里最后做说明,解决跨域通过jsonp 需要 model(数据模型)、proxy(数据...

mianxing2357的博客 1280

深入解析EtherCAT通讯DS402协议下的伺服电机控制模式选择与配置

本文深入解析了EtherCAT通讯DS402协议下的伺服电机控制模式选择与配置,详细介绍了六种核心控制模式(如PPM、CSP、CSV、CST)的应用场景及实战技巧。通过EtherCAT和DS402协议的高效配合,实现工业自动化中伺服电机的精准控制,提升设备性能与稳定性。

h0i1j2k3l的博客 1175

62. 如何通过增强(Enhancement) 的方式给 SAP ABAP 标准程序增添新功能

本文通过一个实际的例子,介绍了如何不通过修改 SAP 标准 ABAP 程序源代码的方式,对其的功能进行增强,即采取 ABAP 增强开发的方式进行二次开发,这是一种在实际 SAP 项目实施过程中使用非常广泛的技术。

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 3311

android错误自动跳转缓存的活动中,android快速开发框架--附带MVP模式Demo,网络请求及缓存,快速实现 页面 加载中 加载失败 无数据等状态以及下拉刷新和自动加载...

RapidDevelop-Android快速开发框架框架持续更新中这个框架是从平时项目里用的比较多的框架里整合而来对本项目感兴趣的可以一起研究喜欢的朋友欢迎star同时也欢迎大家的宝贵意见issues如果大家对MVP模式的开发 网络爬虫以及缓存策略感兴趣的话可以看看我最新写的Freebook功能说明retrofit rxjava okhttp rxcache--------------------...

weixin_36383576的博客 214

aic8800出现insmod: can‘t insert ‘/lib/modules/4.9.191/aic8800_fdrv.ko‘: No such device

2、如果单独编译wifi驱动,则把CONFIG_PLATFORM_UBUNTU?= y都改为CONFIG_PLATFORM_ALLWINNER?1、如果把wifi驱动嵌入到内核编译,则在makefile中加入这一句。后面再修改对应平台的交叉编译链和内核吗路径等。

mantouyouyou的博客 511

thinkphp框架的类自动加载是怎么实现的,遵循什么规范?一共包含哪些部分?使用场景是什么?底层原理是什么?

【代码】thinkphp框架的类自动加载是怎么实现的,遵循什么规范?一共包含哪些部分?使用场景是什么?底层原理是什么?

长风破浪会有时的博客 1077

pscad IEEE标准系统搭建说明

pscad IEEE标准系统搭建说明,包含典型的IEEE 9节点、14节点、30节点、39节点和118节点系统。内含标准测试系统的原始数据和潮流结果。

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

最佳内存缓存框架Caffeine

最佳内存缓存框架Caffeine Caffeine是一种高性能的缓存库,是基于Java 8的最佳(最优)缓存框架。 Cache(缓存),基于Google Guava,Caffeine提供一个内存缓存,大大改善了设计Guava's cache 和 ConcurrentLinkedHashMap 的体验。 1 LoadingCache<Key, Graph> graphs = Ca...

zdy0_2004的专栏 714

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

PHP加layui信息流加载,使用ThinkPHP5和前端框架layui的信息流加载实现文章的自动加载...

本文主要是在基于ThinkPHP5和前端框架layui开发的内容管理系统中,利用thinkPHP5的api特性和layui的信息流加载实现文章的自动加载,即列表不分页,自动加载下一页信息。关于thinkPHP5和layui的更多内容,请移步阅读官方文档,也可在本站搜索一下,应该还要一些相关内容,更多内容正在补充中。本例中场景为:网站首页默认加载10篇文章,往下拖动滚动条,自动再加载10篇文章,直至...

weixin_33140809的博客 394

IDEA的使用指南

IDEA基于java的使用手册,可以最快速的上手,在使用中不断的丰富自己的使用技巧.

Unity AssetBundle 进阶框架使用说明

Unity AssetBundle 进阶框架使用说明 去发现同类优质开源项目:https://gitcode.com/ 简介 此资源文件提供了Unity中AssetBundle的高级实现框架。该框架支持自动加载AssetBundle及其依赖资源,自动为资源打AB标签,以及自动打包AssetBundle。 功能特点 自动标记AB包标签 自动打包标记的AB包资源 自动加载AB包及其依赖资源 支持资源...

gitblog_06752的博客 1016

Caffeine缓存的使用

jvm缓存之Caffeine的使用。

aaaPostcard的博客 5396

深入理解阿里巴巴JetCache缓存框架

JetCache是阿里巴巴开源的一款通用缓存访问框架,它为Java应用提供了强大而灵活的缓存解决方案。与Spring Cache相比,JetCache提供了更多高级特性,如多级缓存、自动刷新、缓存穿透保护等,能够满足更复杂的业务场景需求。 ## 核心特性解析 ### 1. 统一API与注解支持 JetCache提供了类似JSR-107风格的API,同时支持通过注解配置缓存。开发者可以通过简单...

gitblog_00820的博客 602

缓存框架Caffeine探究

缓存框架Caffeine探究Java高性能本地缓存框架Caffeine依赖缓存加载手动加载CleanUp方法自动加载手动异步加载自动异步加载过期策略基于大小基于时间基于引用Caffeine.weakKeys()Caffeine.weakValues()Caffeine.softValues()刷新机制缓存移除invalidate(Object key)方法invalidateAll(Iterable<?> keys)方法invalidateAll()方法移除监听器(RemovalListener

m0_53157173的博客 4110

自动加载函数流程之bootstrap autoload.php

当需要开启自动加载的。一般情况下,都是注册自动处理函数! 第一步:首先定义 开启时间 define('LARAVEL_START',microtime(true)); 第二步:包含系统 框架内核心 函数 ,我们来看看 核心函数 是怎么 实现自动加载的呢? require__DIR_...

cicongchu5275的博客 100
上一篇: 安装完 MySQL 后必须调整的 10 项配置
下一篇: web应用中Spring ApplicationContext的动态更新
杜引强
博客等级 码龄13年 806粉丝 68原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值