Springboot之---强大的Servlet(九)

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
简介: 如今回头看下Servlet不仅如此强大,还具有很强烈的参考意义,能在现如今流行的大部分框架中找到它的影子。下面文章不止与探索Servlet,可能在其中穿插其他的关联知识点,旨在能从此次的学习中获取更多的知识点参考资料总结,转化为自己的理解输出,在文中我尽量以截图+复制全限定类名的方式记录,以便感兴趣的再次查找。

接着来看缓存类CacheManager

从名字就能看出是管理缓存的类,CacheManager有两种,一种是Spring的,一种是javax的,就是上面所说的扩展类,但实现确实大体一致,
就接口实现入手,先从最简单的看起,从名字看就是SimpleCacheManager,提供最基本的set方法,load方法。
SimpleCacheManager在spring-context包下,5.1.4版本,rediscachemanager在spring-data-redis包下
流程

CachingProvider:创建、配置、获取、管理和控制多个CacheManager
CacheManager:创建、配置、获取、管理和控制多个唯一命名的Cache。(一个CacheManager仅被一个CachingProvider所拥有)
Cache:一个类似Map的数据结构。(一个Cache仅被一个CacheManager所拥有)
Entry:一个存储在Cache中的key-value对
Expiry:每一个存储在Cache中的条目有一个定义的有效期,过期后不可访问、更新、删除。缓存有效期可以通过ExpiryPolicy设置
https://cloud.tencent.com/developer/article/1497762
缓存么,除了快之外,还要满足有过期时间,但是除了在redis中并没有提供响应的方法,为什么呢?我觉得既然你启动或者加载就将bean放入cache管理了
就不可能伴随过期,应该会有响应的destroy方法在实例结束运行时清理,要不不可能实例还没运行完就进行清理吧。
@Override
    protected Collection<RedisCache> loadCaches() {

        List<RedisCache> caches = new LinkedList<>();

        for (Map.Entry<String, RedisCacheConfiguration> entry : initialCacheConfiguration.entrySet()) {
            caches.add(createRedisCache(entry.getKey(), entry.getValue()));
        }

        return caches;
    }

读取缓存的配置时间,一级缓存60s,二级缓存30s
在spring-autoconfigure-metadata.properties中的org.springframework.data.redis.cache.RedisCacheConfiguration配置此参数

org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration.AutoConfigureAfter=
org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,
org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration,
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration

启动时

c.y.c.b.redis.config.RedisConfiguration  : [BSF][Redis]已启动,addressList:
2022-02-18 15:04:45.674  INFO 2604 --- [           main] c.y.c.b.e.c.EurekaClientConfiguration    : [BSF][Eureka-Client]已启动!!! eureka.client.serviceUrl.defaultZone=http://10.:8080/eureka/
2022-02-18 15:04:47.846  WARN 2604 --- [           main] c.n.c.sources.URLConfigurationSource     : No URLs will be polled as dynamic configuration sources.
2022-02-18 15:04:47.847  INFO 2604 --- [           main] c.n.c.sources.URLConfigurationSource     : To enable URLs as dynamic configuration sources, define System property archaius.configurationSource.additionalUrls or make config.properties available on classpath.
2022-02-18 15:04:47.943  INFO 2604 --- [           main] c.netflix.config.DynamicPropertyFactory  : DynamicPropertyFactory is initialized with configuration sources: com.netflix.config.ConcurrentCompositeConfiguration@4bf9f44b
2022-02-18 15:04:54.662  INFO 2604 --- [           main] c.y.c.b.s.ShardingJdbcConfiguration      : [BSF][Sharding-jdbc]已启动!!!
2022-02-18 15:04:57.232  INFO 2604 --- [           main] com.alibaba.druid.pool.DruidDataSource   : {dataSource-1} inited
2022-02-18 15:04:58.661  INFO 2604 --- [           main] com.alibaba.druid.pool.DruidDataSource   : {dataSource-2} inited
2022-02-18 15:05:02.531 ERROR 2604 --- [           main] c.b.mybatisplus.MybatisConfiguration 

或者在redisconfig中配置

@Configuration
public class RedisCacheConfig {

    @Bean
    public KeyGenerator simpleKeyGenerator() {
        return (o, method, objects) -> {
            StringBuilder stringBuilder = new StringBuilder();
            stringBuilder.append(o.getClass().getSimpleName());
            stringBuilder.append(".");
            stringBuilder.append(method.getName());
            stringBuilder.append("[");
            for (Object obj : objects) {
                stringBuilder.append(obj.toString());
            }
            stringBuilder.append("]");

            return stringBuilder.toString();
        };
    }

    @Bean
    public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
        return new RedisCacheManager(
            RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory),
            this.getRedisCacheConfigurationWithTtl(600), // 默认策略,未配置的 key 会使用这个
            this.getRedisCacheConfigurationMap() // 指定 key 策略
        );
    }

    private Map<String, RedisCacheConfiguration> getRedisCacheConfigurationMap() {
        Map<String, RedisCacheConfiguration> redisCacheConfigurationMap = new HashMap<>();
        redisCacheConfigurationMap.put("UserInfoList", this.getRedisCacheConfigurationWithTtl(3000));
        redisCacheConfigurationMap.put("UserInfoListAnother", this.getRedisCacheConfigurationWithTtl(18000));

        return redisCacheConfigurationMap;
    }

    private RedisCacheConfiguration getRedisCacheConfigurationWithTtl(Integer seconds) {
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);

        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();
        redisCacheConfiguration = redisCacheConfiguration.serializeValuesWith(
            RedisSerializationContext
                .SerializationPair
                .fromSerializer(jackson2JsonRedisSerializer)
        ).entryTtl(Duration.ofSeconds(seconds));

        return redisCacheConfiguration;
    }
}
过期时间
    @Cacheable(value = "UserInfoList", keyGenerator = "simpleKeyGenerator") // 3000秒
    @Cacheable(value = "UserInfoListAnother", keyGenerator = "simpleKeyGenerator") // 18000秒
    @Cacheable(value = "DefaultKeyTest", keyGenerator = "simpleKeyGenerator") // 600秒,未指定的key,使用默认策略
    

在spring2.0前后差异
构造器差异
before

RedisCacheManager cacheManager = new RedisCacheManager(RedisTemplate redisTemplate);

after

RedisCacheManager cacheManager = new RedisCacheManager(RedisCacheWriter redisCacheWriter,RedisCacheConfiguration redisCacheConfiguration);

创建RedisCacheWriter分为有锁和无锁
流程

static RedisCacheWriter nonLockingRedisCacheWriter(RedisConnectionFactory connectionFactory) {

        Assert.notNull(connectionFactory, "ConnectionFactory must not be null!");

        return new DefaultRedisCacheWriter(connectionFactory);
    }
    JedisConnectionFactory
    /**
     * Create new {@link RedisCacheWriter} with locking behavior.
     *
     * @param connectionFactory must not be {@literal null}.
     * @return new instance of {@link DefaultRedisCacheWriter}.
     */
    static RedisCacheWriter lockingRedisCacheWriter(RedisConnectionFactory connectionFactory) {

        Assert.notNull(connectionFactory, "ConnectionFactory must not be null!");

        return new DefaultRedisCacheWriter(connectionFactory, Duration.ofMillis(50));
    }

即使是同一个缓存CacheManager管理的缓存实例,配置有可能不一样。
指定redis数据序列化
流程

.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(keySerializer()))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(valueSerializer()))

JAVA序列化方式
序列化方式一实现:Serializable接口
序列化方式二:Externalizable显式序列化
序列化方式三:实现Serializable接口+添加writeObject()和readObject()方法。(显+隐序列化)
对了,想要使用cache记得开启缓存注解,@EnableCaching
转过头来看下CacheOperation,这里面是缓存相关注解的父类,在SpringCacheAnnotationParser中管理了子类相关

static {
        CACHE_OPERATION_ANNOTATIONS.add(Cacheable.class);
        CACHE_OPERATION_ANNOTATIONS.add(CacheEvict.class);
        CACHE_OPERATION_ANNOTATIONS.add(CachePut.class);
        CACHE_OPERATION_ANNOTATIONS.add(Caching.class);
    }

解析注解的时候他们的入参,实现一模一样,只有返回值不一样,但是一模一样的代码写了三遍,为什么不判断类型动态返回呢~


@Nullable
    private Collection<CacheOperation> parseCacheAnnotations(
            DefaultCacheConfig cachingConfig, AnnotatedElement ae, boolean localOnly) {

        Collection<? extends Annotation> anns = (localOnly ?
                AnnotatedElementUtils.getAllMergedAnnotations(ae, CACHE_OPERATION_ANNOTATIONS) :
                AnnotatedElementUtils.findAllMergedAnnotations(ae, CACHE_OPERATION_ANNOTATIONS));
        if (anns.isEmpty()) {
            return null;
        }

        final Collection<CacheOperation> ops = new ArrayList<>(1);
        anns.stream().filter(ann -> ann instanceof Cacheable).forEach(
                ann -> ops.add(parseCacheableAnnotation(ae, cachingConfig, (Cacheable) ann)));
        anns.stream().filter(ann -> ann instanceof CacheEvict).forEach(
                ann -> ops.add(parseEvictAnnotation(ae, cachingConfig, (CacheEvict) ann)));
        anns.stream().filter(ann -> ann instanceof CachePut).forEach(
                ann -> ops.add(parsePutAnnotation(ae, cachingConfig, (CachePut) ann)));
        anns.stream().filter(ann -> ann instanceof Caching).forEach(
                ann -> parseCachingAnnotation(ae, cachingConfig, (Caching) ann, ops));
        return ops;
    }

    private CacheableOperation parseCacheableAnnotation(
            AnnotatedElement ae, DefaultCacheConfig defaultConfig, Cacheable cacheable) {

        CacheableOperation.Builder builder = new CacheableOperation.Builder();

        builder.setName(ae.toString());
        builder.setCacheNames(cacheable.cacheNames());
        builder.setCondition(cacheable.condition());
        builder.setUnless(cacheable.unless());
        builder.setKey(cacheable.key());
        builder.setKeyGenerator(cacheable.keyGenerator());
        builder.setCacheManager(cacheable.cacheManager());
        builder.setCacheResolver(cacheable.cacheResolver());
        builder.setSync(cacheable.sync());

        defaultConfig.applyDefault(builder);
        CacheableOperation op = builder.build();
        validateCacheOperation(ae, op);

        return op;
    }

    private CacheEvictOperation parseEvictAnnotation(
            AnnotatedElement ae, DefaultCacheConfig defaultConfig, CacheEvict cacheEvict) {

        CacheEvictOperation.Builder builder = new CacheEvictOperation.Builder();

        builder.setName(ae.toString());
        builder.setCacheNames(cacheEvict.cacheNames());
        builder.setCondition(cacheEvict.condition());
        builder.setKey(cacheEvict.key());
        builder.setKeyGenerator(cacheEvict.keyGenerator());
        builder.setCacheManager(cacheEvict.cacheManager());
        builder.setCacheResolver(cacheEvict.cacheResolver());
        builder.setCacheWide(cacheEvict.allEntries());
        builder.setBeforeInvocation(cacheEvict.beforeInvocation());

        defaultConfig.applyDefault(builder);
        CacheEvictOperation op = builder.build();
        validateCacheOperation(ae, op);

        return op;
    }

    private CacheOperation parsePutAnnotation(
            AnnotatedElement ae, DefaultCacheConfig defaultConfig, CachePut cachePut) {

        CachePutOperation.Builder builder = new CachePutOperation.Builder();

        builder.setName(ae.toString());
        builder.setCacheNames(cachePut.cacheNames());
        builder.setCondition(cachePut.condition());
        builder.setUnless(cachePut.unless());
        builder.setKey(cachePut.key());
        builder.setKeyGenerator(cachePut.keyGenerator());
        builder.setCacheManager(cachePut.cacheManager());
        builder.setCacheResolver(cachePut.cacheResolver());

        defaultConfig.applyDefault(builder);
        CachePutOperation op = builder.build();
        validateCacheOperation(ae, op);

        return op;
    }

    private void parseCachingAnnotation(
            AnnotatedElement ae, DefaultCacheConfig defaultConfig, Caching caching, Collection<CacheOperation> ops) {

        Cacheable[] cacheables = caching.cacheable();
        for (Cacheable cacheable : cacheables) {
            ops.add(parseCacheableAnnotation(ae, defaultConfig, cacheable));
        }
        CacheEvict[] cacheEvicts = caching.evict();
        for (CacheEvict cacheEvict : cacheEvicts) {
            ops.add(parseEvictAnnotation(ae, defaultConfig, cacheEvict));
        }
        CachePut[] cachePuts = caching.put();
        for (CachePut cachePut : cachePuts) {
            ops.add(parsePutAnnotation(ae, defaultConfig, cachePut));
        }
    }

JSR(JCP Java Community Process)文档查询地址,例如JSR107规范,servlet规范等等,

https://www.docs4dev.com/docs/zh/spring-framework/5.1.3.RELEASE/reference/web.html

以及各类中文版在线开发文档

https://www.docs4dev.com/docs
相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
1月前
|
缓存 Java Spring
servlet和SpringBoot两种方式分别获取Cookie和Session方式比较(带源码) —— 图文并茂 两种方式获取Header
文章比较了在Servlet和Spring Boot中获取Cookie、Session和Header的方法,并提供了相应的代码实例,展示了两种方式在实际应用中的异同。
149 3
servlet和SpringBoot两种方式分别获取Cookie和Session方式比较(带源码) —— 图文并茂 两种方式获取Header
|
4月前
|
XML Java 应用服务中间件
springboot定制嵌入式的servlet
SpringBoot允许定制嵌入式Servlet容器,如修改配置或更换默认的Tomcat。配置可通过`application.properties`设置`server.port`和`server.tomcat.*`属性。此外,可创建`EmbeddedServletContainerCustomizer` Bean来自定义容器,例如改变端口。要替换默认的Tomcat,需排除`spring-boot-starter-tomcat`依赖,并引入`spring-boot-starter-jetty`。
|
5月前
|
Java 应用服务中间件 容器
手写SpringBoot(二)之动态切换Servlet容器
我们在切换serlvet容器的时候,会将SpringBoot默认的tomcat jar包给排除掉,换上我们需要的jar包,比如jetty。
39 0
|
Java 应用服务中间件 容器
25 SpringBoot使用外置的Servlet容器
25 SpringBoot使用外置的Servlet容器
52 0
|
6月前
|
Java 应用服务中间件 Maven
框架的优点(SpringBoot VS Servlet)
框架的优点(SpringBoot VS Servlet)
|
6月前
|
前端开发 Java 容器
SpringBoot中注册Servlet、Filter和Listener(代码和注解两种方式)
SpringBoot中注册Servlet、Filter和Listener(代码和注解两种方式)
126 0
|
前端开发 Java 应用服务中间件
24 SpringBoot配置嵌入式Servlet容器
24 SpringBoot配置嵌入式Servlet容器
80 0
24 SpringBoot配置嵌入式Servlet容器
|
Java API Spring
在spring boot中添加servlet filter *Listener
在spring boot中添加servlet filter *Listener
在spring boot中添加servlet filter *Listener
|
消息中间件 负载均衡 Java
Springboot之---强大的Servlet(十 一)
如今回头看下Servlet不仅如此强大,还具有很强烈的参考意义,能在现如今流行的大部分框架中找到它的影子。下面文章不止与探索Servlet,可能在其中穿插其他的关联知识点,旨在能从此次的学习中获取更多的知识点参考资料总结,转化为自己的理解输出,在文中我尽量以截图+复制全限定类名的方式记录,以便感兴趣的再次查找。
114 0
|
消息中间件 SQL 缓存
Springboot之---强大的Servlet(十)
如今回头看下Servlet不仅如此强大,还具有很强烈的参考意义,能在现如今流行的大部分框架中找到它的影子。下面文章不止与探索Servlet,可能在其中穿插其他的关联知识点,旨在能从此次的学习中获取更多的知识点参考资料总结,转化为自己的理解输出,在文中我尽量以截图+复制全限定类名的方式记录,以便感兴趣的再次查找。
144 0