├── .gitignore ├── README.md ├── demo └── layering-cache-start-demo │ ├── .gitignore │ ├── .mvn │ └── wrapper │ │ ├── maven-wrapper.jar │ │ └── maven-wrapper.properties │ ├── mvnw │ ├── mvnw.cmd │ ├── pom.xml │ └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── github │ │ │ └── xiaolyuh │ │ │ └── demo │ │ │ ├── LayeringCacheStartDemoApplication.java │ │ │ ├── controller │ │ │ └── CacheController.java │ │ │ ├── entity │ │ │ ├── Person.java │ │ │ └── User.java │ │ │ ├── service │ │ │ ├── PersonService.java │ │ │ └── impl │ │ │ │ └── PersonServiceImpl.java │ │ │ └── utils │ │ │ └── OkHttpClientUtil.java │ └── resources │ │ └── application.properties │ └── test │ └── java │ └── com │ └── github │ └── xiaolyuh │ └── demo │ ├── LayeringCacheStartDemoTests.java │ ├── controller │ └── CacheControllerTest.java │ └── service │ └── PersonServiceTest.java ├── git-flow-plus.config ├── images ├── layering-cache.drawio ├── offset原理.png ├── pay.jpg ├── pubsub重连.png ├── web-1.png ├── web-2.png ├── web-3.png ├── wechat.jpeg ├── wechat.png ├── wx.jpg ├── 总体架构.png ├── 拉模式数据同步流程.png ├── 推模式数据同步流程.png ├── 数据一致性架构.png └── 数据读取流程.png ├── layering-cache-aspectj ├── pom.xml └── src │ ├── main │ └── java │ │ └── com │ │ └── github │ │ └── xiaolyuh │ │ ├── annotation │ │ ├── BatchCacheable.java │ │ ├── CacheEvict.java │ │ ├── CachePut.java │ │ ├── Cacheable.java │ │ ├── Caching.java │ │ ├── FirstCache.java │ │ └── SecondaryCache.java │ │ ├── aspect │ │ └── LayeringAspect.java │ │ ├── expression │ │ ├── CacheEvaluationContext.java │ │ ├── CacheExpressionRootObject.java │ │ ├── CacheOperationExpressionEvaluator.java │ │ └── VariableNotAvailableException.java │ │ └── support │ │ ├── KeyGenerator.java │ │ ├── SimpleKey.java │ │ └── SimpleKeyGenerator.java │ └── test │ ├── java │ └── com │ │ └── github │ │ └── xiaolyuh │ │ ├── config │ │ ├── CacheClusterConfig.java │ │ ├── CacheSentinelConfig.java │ │ ├── CacheSingleConfig.java │ │ ├── RedisClusterConfig.java │ │ ├── RedisSentinelConfig.java │ │ └── RedisSingleConfig.java │ │ ├── domain │ │ └── User.java │ │ └── test │ │ ├── BatchTestService.java │ │ ├── CacheClusterAspectTest.java │ │ ├── CacheSentinelAspectTest.java │ │ ├── CacheSingleAspectTest.java │ │ └── TestService.java │ └── resources │ ├── application.properties │ └── log4j.properties ├── layering-cache-core ├── pom.xml └── src │ ├── main │ └── java │ │ └── com │ │ └── github │ │ └── xiaolyuh │ │ ├── cache │ │ ├── AbstractValueAdaptingCache.java │ │ ├── Cache.java │ │ ├── LayeringCache.java │ │ ├── caffeine │ │ │ └── CaffeineCache.java │ │ └── redis │ │ │ ├── RedisCache.java │ │ │ └── RedisCacheKey.java │ │ ├── listener │ │ ├── RedisMessageListener.java │ │ ├── RedisMessagePullTask.java │ │ ├── RedisMessageService.java │ │ ├── RedisPubSubMessage.java │ │ ├── RedisPubSubMessageType.java │ │ └── RedisPublisher.java │ │ ├── manager │ │ ├── AbstractCacheManager.java │ │ ├── CacheManager.java │ │ └── LayeringCacheManager.java │ │ ├── redis │ │ ├── clinet │ │ │ ├── ClusterRedisClient.java │ │ │ ├── RedisClient.java │ │ │ ├── RedisClientException.java │ │ │ ├── RedisProperties.java │ │ │ ├── SentinelRedisClient.java │ │ │ └── SingleRedisClient.java │ │ ├── command │ │ │ └── TencentScan.java │ │ └── serializer │ │ │ ├── AbstractRedisSerializer.java │ │ │ ├── FastJsonRedisSerializer.java │ │ │ ├── JacksonRedisSerializer.java │ │ │ ├── JdkRedisSerializer.java │ │ │ ├── KryoRedisSerializer.java │ │ │ ├── ProtostuffRedisSerializer.java │ │ │ ├── RedisSerializer.java │ │ │ ├── SerializationException.java │ │ │ ├── SerializationUtils.java │ │ │ └── StringRedisSerializer.java │ │ ├── setting │ │ ├── FirstCacheSetting.java │ │ ├── LayeringCacheSetting.java │ │ └── SecondaryCacheSetting.java │ │ ├── stats │ │ ├── CacheStats.java │ │ ├── CacheStatsInfo.java │ │ ├── StatsService.java │ │ └── extend │ │ │ ├── CacheStatsReportService.java │ │ │ └── DefaultCacheStatsReportServiceImpl.java │ │ ├── support │ │ ├── AwaitThreadContainer.java │ │ ├── CacheMode.java │ │ ├── ExpireMode.java │ │ ├── LayeringCacheRedisLock.java │ │ ├── MdcThreadPoolTaskExecutor.java │ │ ├── NestedRuntimeException.java │ │ ├── NullValue.java │ │ └── Type.java │ │ └── util │ │ ├── BeanFactory.java │ │ ├── GlobalConfig.java │ │ ├── NamedThreadFactory.java │ │ ├── RandomUtils.java │ │ ├── StringUtils.java │ │ ├── ThreadTaskUtils.java │ │ └── ToStringUtils.java │ └── test │ ├── java │ └── com │ │ └── github │ │ └── xiaolyuh │ │ └── cache │ │ ├── MainTest.java │ │ ├── config │ │ ├── CacheClusterConfig.java │ │ ├── CacheSentinelConfig.java │ │ ├── CacheSingleConfig.java │ │ ├── RedisClusterConfig.java │ │ ├── RedisSentinelConfig.java │ │ └── RedisSingleConfig.java │ │ └── test │ │ ├── CacheClusterCoreTest.java │ │ ├── CacheSentinelCoreTest.java │ │ └── CacheSingleCoreTest.java │ └── resources │ ├── application.properties │ └── log4j.properties ├── layering-cache-starter ├── pom.xml └── src │ └── main │ ├── java │ └── com │ │ └── github │ │ └── xiaolyuh │ │ └── cache │ │ ├── config │ │ ├── EnableLayeringCache.java │ │ └── LayeringCacheAutoConfig.java │ │ └── properties │ │ ├── LayeringCacheProperties.java │ │ └── LayeringCacheRedisProperties.java │ └── resources │ └── META-INF │ └── spring.factories ├── layering-cache-web ├── .gitignore ├── mvnw ├── mvnw.cmd ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── github │ │ │ └── xiaolyuh │ │ │ └── web │ │ │ ├── LayeringCacheWebApplication.java │ │ │ ├── config │ │ │ └── WebMvcConfig.java │ │ │ ├── constant │ │ │ └── URLConstant.java │ │ │ ├── controller │ │ │ ├── CacheController.java │ │ │ ├── RedisController.java │ │ │ └── UserController.java │ │ │ ├── interceptor │ │ │ └── LoginInterceptor.java │ │ │ ├── service │ │ │ ├── CacheService.java │ │ │ ├── UserService.java │ │ │ └── WebStatsService.java │ │ │ ├── utils │ │ │ ├── OkHttpClientUtil.java │ │ │ └── Result.java │ │ │ └── vo │ │ │ └── CacheStatsInfoVo.java │ └── resources │ │ ├── application.properties │ │ ├── html │ │ ├── cache.html │ │ ├── help.html │ │ ├── index.html │ │ ├── login.html │ │ └── nopermit.html │ │ └── static │ │ ├── css │ │ ├── admin.css │ │ ├── amazeui.min.css │ │ └── app.css │ │ ├── fonts │ │ ├── FontAwesome.otf │ │ ├── fontawesome-webfont.eot │ │ ├── fontawesome-webfont.ttf │ │ ├── fontawesome-webfont.woff │ │ └── fontawesome-webfont.woff2 │ │ ├── i │ │ ├── app-icon72x72@2x.png │ │ ├── favicon.png │ │ └── startup-640x1096.png │ │ └── js │ │ ├── lib │ │ ├── amazeui.min.js │ │ ├── app.js │ │ ├── jquery.min.js │ │ ├── knockout-3.3.0.js │ │ └── knockout.mapping.js │ │ └── views │ │ ├── index.js │ │ ├── login.js │ │ └── redis.js │ └── test │ └── java │ └── com │ └── github │ └── xiaolyuh │ └── demo │ └── LayeringCacheWebApplicationTests.java ├── license.txt └── pom.xml /.gitignore: -------------------------------------------------------------------------------- 1 | *.sw? 2 | .#* 3 | *# 4 | *~ 5 | /build 6 | /code 7 | .classpath 8 | .project 9 | .settings 10 | .metadata 11 | .factorypath 12 | .recommenders 13 | bin 14 | build 15 | target 16 | .factorypath 17 | .springBeans 18 | interpolated*.xml 19 | dependency-reduced-pom.xml 20 | build.log 21 | _site/ 22 | .*.md.html 23 | manifest.yml 24 | MANIFEST.MF 25 | settings.xml 26 | activemq-data 27 | overridedb.* 28 | *.iml 29 | *.ipr 30 | *.iws 31 | .idea 32 | *.jar 33 | .DS_Store 34 | .factorypath 35 | dump.rdb 36 | transaction-logs 37 | /target/ 38 | *.log 39 | *.log.* 40 | logs/ 41 | lottery-web/logs/ 42 | *.stackdump 43 | rebel.xml 44 | *.bak 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # layering-cache 2 | [![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.github.xiaolyuh/layering-cache/badge.svg)](https://search.maven.org/artifact/com.github.xiaolyuh/layering-cache/) 3 | [![License](https://img.shields.io/badge/license-Apache%202-4EB1BA.svg)](https://www.apache.org/licenses/LICENSE-2.0.html) 4 | 5 | # 简介 6 | layering-cache是一个支持分布式环境的多级缓存框架,主要解决在高并发下数据快速读取的问题。整体采用了分层架构设计的思路,来保证整个框架的扩展性;采用了面向切面的设计模式,来解决了缓存和业务代码耦合性。 7 | 8 | 它使用Caffeine作为一级本地缓存,redis作为二级集中式缓存。 9 | 10 | 一级缓存和二级缓存的数据一致性是通过推和拉两种模式相结合的方式来保证。 11 | - 推主要是基于redis的pub/sub机制 12 | - 拉主要是基于消息队列和记录消费消息的偏移量来实现的。 13 | 14 | # 支持 15 | - 支持缓存命中率的监控统计,统计数据上报支持自定义扩展 16 | - 内置dashboard,支持对缓存的管理和缓存命中率的查看 17 | - 支持缓存过期时间在注解上直接配置 18 | - 支持缓存的自动刷新(当缓存命中并发现二级缓存将要过期时,会开启一个异步线程刷新缓存) 19 | - 缓存Key支持SpEL表达式 20 | - Redis支持Kryo、FastJson、Jackson、Jdk和Protostuff序列化,默认使用Protostuff序列化,并支持自定义的序列化 21 | - 支持同一个缓存名称设置不同的过期时间 22 | - 支持只使用一级缓存或者只使用二级缓存 23 | - Redis支持单机、集群、Sentinel三种客户端 24 | 25 | # 优势 26 | 1. 提供缓存命中率的监控统计,统计数据上报支持自定义扩展 27 | 2. 支持本地缓存和集中式两级缓存 28 | 3. 接入成本和使用成本都非常低 29 | 4. 无缝集成Spring、Spring boot 30 | 5. 内置dashboard使得缓存具备可运维性 31 | 6. 通过缓存空值来解决缓存穿透问题、通过异步加载缓存的方式来解决缓存击穿和雪崩问题 32 | 33 | 34 | # 文档 35 | 36 | [中文文档](https://github.com/xiaolyuh/layering-cache/wiki/%E6%96%87%E6%A1%A3) 37 | 38 | # Redis序列化方式对比 39 | [Redis序列化同一个User对象对比](https://github.com/xiaolyuh/layering-cache/wiki/Redis%E5%BA%8F%E5%88%97%E5%8C%96%E6%96%B9%E5%BC%8F%E5%AF%B9%E6%AF%94) 40 | 41 | ||size|serialize(get 10W次)|deserialize(set 10W次)|serialize(cpu)|deserialize(cpu)| 42 | ---|---|---|---|---|--- 43 | Kryo|273 b|82919 ms|90917 ms|8%|12% 44 | FastJson|329 b|15405 ms|18886 ms|12%|13% 45 | Jackson|473 b|16066 ms|16140 ms|15%|14% 46 | Jdk|1036 b|17344 ms|24917 ms|14%|13% 47 | Protostuff|282 b|14295 ms|14355 ms|15%|13% 48 | 49 | # 打开监控统计功能 50 | 51 | [打开监控统计功能](https://github.com/xiaolyuh/layering-cache/wiki/%E7%9B%91%E6%8E%A7%E7%BB%9F%E8%AE%A1%E5%8A%9F%E8%83%BD) 52 | 53 | # 重要提示 54 | - layering-cache支持同一个缓存名称设置不同的过期时间,但是一定要保证key唯一,否则会出现缓存过期时间错乱的情况 55 | - 删除缓存的时候会将同一个缓存名称的不同的过期时间的缓存都删掉 56 | - 在集成layering-cache之前还需要添加以下的依赖,主要是为了减少jar包冲突([依赖jar列表](https://github.com/xiaolyuh/layering-cache/wiki/%E4%BE%9D%E8%B5%96jar%E5%88%97%E8%A1%A8))。 57 | - redis的key序列化方式必须StringRedisSerializer 58 | 59 | # 更新日志 60 | 61 | [更新日志](https://github.com/xiaolyuh/layering-cache/wiki/%E6%9B%B4%E6%96%B0%E6%97%A5%E5%BF%97) 62 | 63 | # 实现原理 64 | [实现原理](https://github.com/xiaolyuh/layering-cache/wiki/%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86) 65 | 66 | # 作者信息 67 | 68 | 作者博客:https://xiaolyuh.blog.csdn.net/ 69 | 70 | 作者邮箱: xiaolyuh@163.com 71 | 72 | github 地址:https://github.com/xiaolyuh/layering-cache 73 | 74 | 75 | # 捐赠 76 | 项目的发展离不开你的支持,请作者喝杯咖啡吧! 77 | 78 | ![微信-支付宝](https://github.com/xiaolyuh/layering-cache/blob/master/images/pay.jpg) 79 | 80 | # 技术支持 81 | 添加微信记得备注 ```layering-cache```。 82 | 83 | ![微信](https://github.com/xiaolyuh/layering-cache/blob/master/images/wx.jpg) 84 | 85 | # 特别感谢 86 | 感谢何一睿同学贡献的```@BatchCacheable```批量缓存注解 87 | 88 | 89 | -------------------------------------------------------------------------------- /demo/layering-cache-start-demo/.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | 4 | ### STS ### 5 | .apt_generated 6 | .classpath 7 | .factorypath 8 | .project 9 | .settings 10 | .springBeans 11 | .sts4-cache 12 | 13 | ### IntelliJ IDEA ### 14 | .idea 15 | *.iws 16 | *.iml 17 | *.ipr 18 | 19 | ### NetBeans ### 20 | /nbproject/private/ 21 | /build/ 22 | /nbbuild/ 23 | /dist/ 24 | /nbdist/ 25 | /.nb-gradle/ -------------------------------------------------------------------------------- /demo/layering-cache-start-demo/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaolyuh/layering-cache/282d3c10fc935bbfd981397a0997bb27ffd9aeae/demo/layering-cache-start-demo/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /demo/layering-cache-start-demo/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.4/apache-maven-3.5.4-bin.zip 2 | -------------------------------------------------------------------------------- /demo/layering-cache-start-demo/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | com.github.xiaolyuh 7 | layering-cache-start-demo 8 | 0.0.1-SNAPSHOT 9 | jar 10 | 11 | layering-cache-start-demo 12 | layering-cache start demo 13 | 14 | 15 | org.springframework.boot 16 | spring-boot-starter-parent 17 | 2.5.6 18 | 19 | 20 | 21 | UTF-8 22 | UTF-8 23 | 1.8 24 | 25 | 26 | 27 | 28 | org.springframework.boot 29 | spring-boot-starter-data-redis 30 | 31 | 32 | 33 | org.springframework.boot 34 | spring-boot-starter-web 35 | 36 | 37 | 38 | org.springframework.boot 39 | spring-boot-starter-test 40 | test 41 | 42 | 43 | 44 | com.alibaba 45 | fastjson 46 | 1.2.58 47 | 48 | 49 | 50 | org.aspectj 51 | aspectjweaver 52 | 53 | 54 | 55 | io.lettuce 56 | lettuce-core 57 | 58 | 59 | 60 | com.squareup.okhttp3 61 | okhttp 62 | 3.6.0 63 | 64 | 65 | 66 | 67 | com.esotericsoftware 68 | kryo-shaded 69 | 3.0.3 70 | 71 | 72 | 73 | junit 74 | junit 75 | 4.13.2 76 | test 77 | 78 | 79 | 80 | com.github.xiaolyuh 81 | layering-cache-starter 82 | 3.5.0 83 | 84 | 85 | 86 | 87 | 88 | 89 | org.springframework.boot 90 | spring-boot-maven-plugin 91 | 92 | 93 | org.apache.maven.plugins 94 | maven-compiler-plugin 95 | 96 | 8 97 | 8 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /demo/layering-cache-start-demo/src/main/java/com/github/xiaolyuh/demo/LayeringCacheStartDemoApplication.java: -------------------------------------------------------------------------------- 1 | package com.github.xiaolyuh.demo; 2 | 3 | import com.github.xiaolyuh.cache.config.EnableLayeringCache; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | 7 | @SpringBootApplication 8 | @EnableLayeringCache 9 | public class LayeringCacheStartDemoApplication { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(LayeringCacheStartDemoApplication.class, args); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /demo/layering-cache-start-demo/src/main/java/com/github/xiaolyuh/demo/controller/CacheController.java: -------------------------------------------------------------------------------- 1 | package com.github.xiaolyuh.demo.controller; 2 | 3 | import com.github.xiaolyuh.demo.entity.Person; 4 | import com.github.xiaolyuh.demo.service.PersonService; 5 | import java.util.List; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.web.bind.annotation.RequestBody; 8 | import org.springframework.web.bind.annotation.RequestMapping; 9 | import org.springframework.web.bind.annotation.RestController; 10 | 11 | @RestController 12 | public class CacheController { 13 | 14 | @Autowired 15 | PersonService personService; 16 | 17 | @RequestMapping("/put") 18 | public long put(@RequestBody Person person) { 19 | Person p = personService.save(person); 20 | return 1L; 21 | } 22 | 23 | @RequestMapping("/able") 24 | public Person cacheable(@RequestBody Person person) { 25 | 26 | return personService.findOne(person); 27 | } 28 | 29 | @RequestMapping("/batch/able") 30 | public List batchCacheable(@RequestBody List personList) { 31 | return personService.batch(personList); 32 | } 33 | 34 | @RequestMapping("/evit") 35 | public String evit(@RequestBody Person person) { 36 | 37 | personService.remove(person.getId()); 38 | return "ok"; 39 | } 40 | 41 | } -------------------------------------------------------------------------------- /demo/layering-cache-start-demo/src/main/java/com/github/xiaolyuh/demo/entity/Person.java: -------------------------------------------------------------------------------- 1 | package com.github.xiaolyuh.demo.entity; 2 | 3 | import java.io.Serializable; 4 | 5 | public class Person implements Serializable { 6 | private long id; 7 | 8 | private String name; 9 | 10 | private Integer age; 11 | 12 | private String address; 13 | 14 | public Person() { 15 | super(); 16 | } 17 | 18 | public Person(long id) { 19 | this.id = id; 20 | } 21 | 22 | public Person(long id, String name, Integer age, String address) { 23 | this.id = id; 24 | this.name = name; 25 | this.age = age; 26 | this.address = address; 27 | } 28 | 29 | public Person(String name, Integer age) { 30 | this.name = name; 31 | this.age = age; 32 | } 33 | 34 | public long getId() { 35 | return id; 36 | } 37 | 38 | public void setId(long id) { 39 | this.id = id; 40 | } 41 | 42 | public String getName() { 43 | return name; 44 | } 45 | 46 | public void setName(String name) { 47 | this.name = name; 48 | } 49 | 50 | public Integer getAge() { 51 | return age; 52 | } 53 | 54 | public void setAge(Integer age) { 55 | this.age = age; 56 | } 57 | 58 | public String getAddress() { 59 | return address; 60 | } 61 | 62 | public void setAddress(String address) { 63 | this.address = address; 64 | } 65 | 66 | @Override 67 | public String toString() { 68 | return "Person [id=" + id + ", name=" + name + ", age=" + age + ", address=" + address + "]"; 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /demo/layering-cache-start-demo/src/main/java/com/github/xiaolyuh/demo/entity/User.java: -------------------------------------------------------------------------------- 1 | package com.github.xiaolyuh.demo.entity; 2 | 3 | import java.io.Serializable; 4 | import java.math.BigDecimal; 5 | import java.util.ArrayList; 6 | import java.util.Date; 7 | import java.util.HashSet; 8 | import java.util.List; 9 | import java.util.Set; 10 | 11 | public class User implements Serializable { 12 | 13 | public User() { 14 | this.userId = 11L; 15 | this.name = "name"; 16 | this.address = new Address(); 17 | this.lastName = new String[]{"w", "四川", "~!@#%……&*()——+{}:“?》:''\">?《~!@#$%^&*()_+{}\\"}; 18 | List lastNameList = new ArrayList<>(); 19 | lastNameList.add("W"); 20 | lastNameList.add("成都"); 21 | this.lastNameList = lastNameList; 22 | this.lastNameSet = new HashSet<>(lastNameList); 23 | this.lastName = new String[]{"w", "四川", "~!@#%……&*()——+{}:“?》:''\">?《~!@#$%^&*()_+{}\\"}; 24 | this.age = 122; 25 | this.height = 18.2; 26 | this.income = new BigDecimal(22.22); 27 | this.birthday = new Date(); 28 | } 29 | 30 | private long userId; 31 | 32 | private String name; 33 | 34 | private Address address; 35 | 36 | private String[] lastName; 37 | 38 | private List lastNameList; 39 | 40 | private Set lastNameSet; 41 | 42 | private int age; 43 | 44 | private double height; 45 | 46 | private BigDecimal income; 47 | 48 | private Date birthday; 49 | 50 | public long getUserId() { 51 | return userId; 52 | } 53 | 54 | public void setUserId(long userId) { 55 | this.userId = userId; 56 | } 57 | 58 | public String getName() { 59 | return name; 60 | } 61 | 62 | public void setName(String name) { 63 | this.name = name; 64 | } 65 | 66 | public String[] getLastName() { 67 | return lastName; 68 | } 69 | 70 | public void setLastName(String[] lastName) { 71 | this.lastName = lastName; 72 | } 73 | 74 | public int getAge() { 75 | return age; 76 | } 77 | 78 | public void setAge(int age) { 79 | this.age = age; 80 | } 81 | 82 | public Address getAddress() { 83 | return address; 84 | } 85 | 86 | public void setAddress(Address address) { 87 | this.address = address; 88 | } 89 | 90 | public List getLastNameList() { 91 | return lastNameList; 92 | } 93 | 94 | public void setLastNameList(List lastNameList) { 95 | this.lastNameList = lastNameList; 96 | } 97 | 98 | public Set getLastNameSet() { 99 | return lastNameSet; 100 | } 101 | 102 | public void setLastNameSet(Set lastNameSet) { 103 | this.lastNameSet = lastNameSet; 104 | } 105 | 106 | public BigDecimal getIncome() { 107 | return income.setScale(2, BigDecimal.ROUND_UP); 108 | } 109 | 110 | public void setIncome(BigDecimal income) { 111 | this.income = income; 112 | } 113 | 114 | public double getHeight() { 115 | return height; 116 | } 117 | 118 | public void setHeight(double height) { 119 | this.height = height; 120 | } 121 | 122 | public static class Address implements Serializable { 123 | private String addredd; 124 | 125 | public Address() { 126 | this.addredd = "成都"; 127 | } 128 | 129 | public String getAddredd() { 130 | return addredd; 131 | } 132 | 133 | public void setAddredd(String addredd) { 134 | this.addredd = addredd; 135 | } 136 | } 137 | 138 | public Date getBirthday() { 139 | return birthday; 140 | } 141 | 142 | public void setBirthday(Date birthday) { 143 | this.birthday = birthday; 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /demo/layering-cache-start-demo/src/main/java/com/github/xiaolyuh/demo/service/PersonService.java: -------------------------------------------------------------------------------- 1 | package com.github.xiaolyuh.demo.service; 2 | 3 | 4 | import com.github.xiaolyuh.demo.entity.Person; 5 | import java.util.List; 6 | 7 | public interface PersonService { 8 | Person save(Person person); 9 | 10 | void remove(Long id); 11 | 12 | void removeAll(); 13 | 14 | Person findOne(Person person); 15 | 16 | List batch(List personList); 17 | } 18 | -------------------------------------------------------------------------------- /demo/layering-cache-start-demo/src/main/java/com/github/xiaolyuh/demo/utils/OkHttpClientUtil.java: -------------------------------------------------------------------------------- 1 | package com.github.xiaolyuh.demo.utils; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import okhttp3.FormBody; 5 | import okhttp3.MediaType; 6 | import okhttp3.OkHttpClient; 7 | import okhttp3.Request; 8 | import okhttp3.RequestBody; 9 | import okhttp3.Response; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | import org.springframework.remoting.RemoteAccessException; 13 | 14 | import java.io.IOException; 15 | import java.util.Map; 16 | import java.util.concurrent.TimeUnit; 17 | 18 | /** 19 | * OkHttpClient工具 20 | * 21 | * @author yuhao.wang3 22 | */ 23 | public abstract class OkHttpClientUtil { 24 | private static final Logger logger = LoggerFactory.getLogger(OkHttpClientUtil.class); 25 | 26 | private static OkHttpClient okHttpClient = new OkHttpClient.Builder() 27 | .connectTimeout(10, TimeUnit.SECONDS) 28 | .writeTimeout(10, TimeUnit.SECONDS) 29 | .readTimeout(20, TimeUnit.SECONDS) 30 | .build(); 31 | 32 | 33 | /** 34 | * 发起post请求,不做任何签名 35 | * 36 | * @param url 发送请求的URL 37 | * @param requestBody 请求体 38 | * @throws IOException 39 | */ 40 | public static String post(String url, RequestBody requestBody) throws IOException { 41 | Request request = new Request.Builder() 42 | //请求的url 43 | .url(url) 44 | .post(requestBody) 45 | .build(); 46 | 47 | //创建/Call 48 | Response response = okHttpClient.newCall(request).execute(); 49 | if (!response.isSuccessful()) { 50 | logger.error("访问外部系统异常 {}: {}", url, JSON.toJSONString(response)); 51 | throw new RemoteAccessException("访问外部系统异常 " + url); 52 | } 53 | return response.body().string(); 54 | } 55 | 56 | /** 57 | * 发起post请求,不做任何签名 宽松的参数构造 58 | * 59 | * @param url 发送请求的URL 60 | * @param builder 请求体 61 | * @throws IOException 62 | */ 63 | public static String post(String url, Request.Builder builder) throws IOException { 64 | 65 | Request request = builder.url(url).build(); 66 | //创建/Call 67 | Response response = okHttpClient.newCall(request).execute(); 68 | if (!response.isSuccessful()) { 69 | logger.error("访问外部系统异常 {}: {}", url, JSON.toJSONString(response)); 70 | throw new RemoteAccessException("访问外部系统异常 " + url); 71 | } 72 | return response.body().string(); 73 | } 74 | 75 | 76 | public static String post(String url, Map param, Map header) throws Exception { 77 | // 生成requestBody 78 | RequestBody requestBody = FormBody.create(MediaType.parse("application/json; charset=utf-8") 79 | , JSON.toJSONString(param)); 80 | 81 | Request.Builder builder = new Request.Builder() 82 | //请求的url 83 | .url(url) 84 | .post(requestBody); 85 | 86 | for (String key : header.keySet()) { 87 | builder.header(key, header.get(key)); 88 | } 89 | 90 | Request request = builder.build(); 91 | 92 | //创建/Call 93 | Response response = okHttpClient.newCall(request).execute(); 94 | if (!response.isSuccessful()) { 95 | logger.error("访问外部系统异常 {}: {}", url, JSON.toJSONString(response)); 96 | throw new RemoteAccessException("访问外部系统异常 " + url); 97 | } 98 | return response.body().string(); 99 | } 100 | 101 | public static String get(String url) throws IOException { 102 | Request request = new Request.Builder() 103 | //请求的url 104 | .url(url) 105 | .get() 106 | .build(); 107 | 108 | Response response = okHttpClient.newCall(request).execute(); 109 | if (!response.isSuccessful()) { 110 | logger.error("访问外部系统异常 {}: {}", url, JSON.toJSONString(response)); 111 | throw new RemoteAccessException("访问外部系统异常 " + url); 112 | } 113 | return response.body().string(); 114 | } 115 | 116 | } 117 | -------------------------------------------------------------------------------- /demo/layering-cache-start-demo/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | server.port=8082 2 | 3 | #layering-cache 配置 4 | layering-cache.stats=true 5 | # 缓存命名空间,如果不配置取 "spring.application.name" 6 | layering-cache.namespace=layering-cache-web 7 | 8 | layering-cache.redis.database=0 9 | layering-cache.redis.timeout=60 10 | layering-cache.redis.password= 11 | # redis单机 12 | #layering-cache.redis.host=127.0.0.1 13 | #layering-cache.redis.port=6378 14 | # redis集群 15 | #layering-cache.redis.cluster=127.0.0.1:6379,127.0.0.1:6378 16 | # redis sentinel 17 | layering-cache.redis.sentinel-nodes=127.0.0.1:26379,127.0.0.1:26380,127.0.0.1:26381 18 | layering-cache.redis.sentinel-master=mymaster 19 | 20 | # 设置redis值的序列化方式,默认是kryo 21 | #com.github.xiaolyuh.redis.serializer.KryoRedisSerializer 22 | #com.github.xiaolyuh.redis.serializer.FastJsonRedisSerializer 23 | #com.github.xiaolyuh.redis.serializer.JacksonRedisSerializer 24 | #com.github.xiaolyuh.redis.serializer.JdkRedisSerializer 25 | #com.github.xiaolyuh.redis.serializer.ProtostuffRedisSerializer 26 | layering-cache.redis.serializer=com.github.xiaolyuh.redis.serializer.KryoRedisSerializer 27 | 28 | -------------------------------------------------------------------------------- /demo/layering-cache-start-demo/src/test/java/com/github/xiaolyuh/demo/LayeringCacheStartDemoTests.java: -------------------------------------------------------------------------------- 1 | package com.github.xiaolyuh.demo; 2 | 3 | import org.junit.runner.RunWith; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | import org.springframework.test.context.junit4.SpringRunner; 6 | 7 | @RunWith(SpringRunner.class) 8 | @SpringBootTest 9 | public class LayeringCacheStartDemoTests { 10 | 11 | } 12 | -------------------------------------------------------------------------------- /demo/layering-cache-start-demo/src/test/java/com/github/xiaolyuh/demo/controller/CacheControllerTest.java: -------------------------------------------------------------------------------- 1 | package com.github.xiaolyuh.demo.controller; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.github.xiaolyuh.demo.entity.Person; 5 | import com.github.xiaolyuh.demo.utils.OkHttpClientUtil; 6 | import okhttp3.FormBody; 7 | import okhttp3.MediaType; 8 | import okhttp3.RequestBody; 9 | import org.junit.Ignore; 10 | import org.junit.Test; 11 | import org.junit.runner.RunWith; 12 | import org.springframework.boot.test.context.SpringBootTest; 13 | import org.springframework.test.context.junit4.SpringRunner; 14 | 15 | import java.io.IOException; 16 | 17 | @RunWith(SpringRunner.class) 18 | @SpringBootTest 19 | public class CacheControllerTest { 20 | 21 | String host = "http://localhost:8081/"; 22 | 23 | @Test 24 | @Ignore 25 | public void testPut() throws IOException { 26 | 27 | Person person = new Person(1, "name1", 12, "address1"); 28 | RequestBody requestBody = FormBody.create(MediaType.parse("application/json; charset=utf-8") 29 | , JSON.toJSONString(person)); 30 | String post = OkHttpClientUtil.post(host + "put", requestBody); 31 | System.out.println("返回结果:" + post); 32 | } 33 | 34 | @Test 35 | // @Ignore 36 | public void cacheable() throws IOException { 37 | 38 | Person person = new Person(1, "name1", 12, "address1"); 39 | RequestBody requestBody = FormBody.create(MediaType.parse("application/json; charset=utf-8") 40 | , JSON.toJSONString(person)); 41 | String post = OkHttpClientUtil.post(host + "able", requestBody); 42 | System.out.println("返回结果:" + post); 43 | } 44 | 45 | @Test 46 | @Ignore 47 | public void testEvit() throws IOException { 48 | Person person = new Person(1, "name1", 12, "address1"); 49 | RequestBody requestBody = FormBody.create(MediaType.parse("application/json; charset=utf-8") 50 | , JSON.toJSONString(person)); 51 | String post = OkHttpClientUtil.post(host + "evit", requestBody); 52 | System.out.println("返回结果:" + post); 53 | } 54 | 55 | } -------------------------------------------------------------------------------- /demo/layering-cache-start-demo/src/test/java/com/github/xiaolyuh/demo/service/PersonServiceTest.java: -------------------------------------------------------------------------------- 1 | package com.github.xiaolyuh.demo.service; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.github.xiaolyuh.demo.entity.Person; 5 | import com.github.xiaolyuh.demo.utils.OkHttpClientUtil; 6 | import java.io.IOException; 7 | import java.util.Arrays; 8 | import java.util.List; 9 | import okhttp3.FormBody; 10 | import okhttp3.MediaType; 11 | import okhttp3.RequestBody; 12 | import org.junit.Assert; 13 | import org.junit.Test; 14 | import org.junit.runner.RunWith; 15 | import org.springframework.beans.factory.annotation.Autowired; 16 | import org.springframework.boot.test.context.SpringBootTest; 17 | import org.springframework.test.context.junit4.SpringRunner; 18 | 19 | @RunWith(SpringRunner.class) 20 | @SpringBootTest 21 | public class PersonServiceTest { 22 | 23 | @Autowired 24 | private PersonService personService; 25 | 26 | @Test 27 | public void testSave() { 28 | Person p = new Person(1, "name1", 12, "address1"); 29 | personService.save(p); 30 | 31 | Person person = personService.findOne(p); 32 | Assert.assertEquals(person.getId(), 1); 33 | } 34 | 35 | @Test 36 | public void testRemove() { 37 | Person p = new Person(5, "name1", 12, "address1"); 38 | personService.save(p); 39 | 40 | personService.remove(5L); 41 | Person person = personService.findOne(p); 42 | Assert.assertEquals(person.getId(), 2); 43 | } 44 | 45 | @Test 46 | public void testRemoveAll() throws InterruptedException { 47 | Person p = new Person(6, "name1", 12, "address1"); 48 | personService.save(p); 49 | 50 | Person person = personService.findOne(p); 51 | Assert.assertEquals(person.getId(), 6); 52 | personService.removeAll(); 53 | 54 | Thread.sleep(1000); 55 | person = personService.findOne(p); 56 | Assert.assertEquals(person.getId(), 2); 57 | } 58 | 59 | @Test 60 | public void testFindOne() { 61 | Person p = new Person(2); 62 | personService.findOne(p); 63 | Person person = personService.findOne(p); 64 | Assert.assertEquals(person.getName(), "name2"); 65 | } 66 | 67 | 68 | @Test 69 | // @Ignore 70 | public void batch() throws IOException { 71 | 72 | Person person0 = new Person(100, "name1", 12, "address1"); 73 | Person person1 = new Person(1, "name1", 12, "address1"); 74 | Person person2 = new Person(1000, "name1", 12, "address1"); 75 | Person person3 = new Person(2, "name1", 12, "address1"); 76 | Person person4 = new Person(1001, "name1", 12, "address1"); 77 | 78 | List personList = Arrays.asList(person0, person1, person2, person3, person4); 79 | 80 | List batch = personService.batch(personList); 81 | Assert.assertEquals(batch.size(), 3); 82 | } 83 | 84 | 85 | } 86 | -------------------------------------------------------------------------------- /git-flow-plus.config: -------------------------------------------------------------------------------- 1 | {"dingtalkToken":"","featurePrefix":"feature/","helloToken":"","hotfixPrefix":"hotfix/","masterBranch":"master","pattern":"(?:fix|chore|docs|feat|refactor|style|test)(?:\\(.*\\))?\\s+(?:XM\\d{7}-\\d+)(?:\\s*)(?:\\S+)(?:[\\n\\r]{2,})(?:[\\s\\S]*)","patternExplain":"规范说明:https://blog.csdn.net/xiaolyuh123/article/details/129042182\n正确示例:\n\nfeat(web)XM2102301-30用户目标地图\n\n背景:\nhttp://jira.xxx.com/browse/XM2607301-30\n修改:\n1.使用idea自带http工具来替换postman\n2.新增查询我的目标接口\n影响:\n1.影响PC端个人目标地图显示","releaseBranch":"release","tagPrefix":"","testBranch":"test"} 2 | -------------------------------------------------------------------------------- /images/offset原理.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaolyuh/layering-cache/282d3c10fc935bbfd981397a0997bb27ffd9aeae/images/offset原理.png -------------------------------------------------------------------------------- /images/pay.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaolyuh/layering-cache/282d3c10fc935bbfd981397a0997bb27ffd9aeae/images/pay.jpg -------------------------------------------------------------------------------- /images/pubsub重连.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaolyuh/layering-cache/282d3c10fc935bbfd981397a0997bb27ffd9aeae/images/pubsub重连.png -------------------------------------------------------------------------------- /images/web-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaolyuh/layering-cache/282d3c10fc935bbfd981397a0997bb27ffd9aeae/images/web-1.png -------------------------------------------------------------------------------- /images/web-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaolyuh/layering-cache/282d3c10fc935bbfd981397a0997bb27ffd9aeae/images/web-2.png -------------------------------------------------------------------------------- /images/web-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaolyuh/layering-cache/282d3c10fc935bbfd981397a0997bb27ffd9aeae/images/web-3.png -------------------------------------------------------------------------------- /images/wechat.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaolyuh/layering-cache/282d3c10fc935bbfd981397a0997bb27ffd9aeae/images/wechat.jpeg -------------------------------------------------------------------------------- /images/wechat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaolyuh/layering-cache/282d3c10fc935bbfd981397a0997bb27ffd9aeae/images/wechat.png -------------------------------------------------------------------------------- /images/wx.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaolyuh/layering-cache/282d3c10fc935bbfd981397a0997bb27ffd9aeae/images/wx.jpg -------------------------------------------------------------------------------- /images/总体架构.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaolyuh/layering-cache/282d3c10fc935bbfd981397a0997bb27ffd9aeae/images/总体架构.png -------------------------------------------------------------------------------- /images/拉模式数据同步流程.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaolyuh/layering-cache/282d3c10fc935bbfd981397a0997bb27ffd9aeae/images/拉模式数据同步流程.png -------------------------------------------------------------------------------- /images/推模式数据同步流程.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaolyuh/layering-cache/282d3c10fc935bbfd981397a0997bb27ffd9aeae/images/推模式数据同步流程.png -------------------------------------------------------------------------------- /images/数据一致性架构.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaolyuh/layering-cache/282d3c10fc935bbfd981397a0997bb27ffd9aeae/images/数据一致性架构.png -------------------------------------------------------------------------------- /images/数据读取流程.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaolyuh/layering-cache/282d3c10fc935bbfd981397a0997bb27ffd9aeae/images/数据读取流程.png -------------------------------------------------------------------------------- /layering-cache-aspectj/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | layering-cache 7 | com.github.xiaolyuh 8 | 3.5.0 9 | 10 | 4.0.0 11 | 12 | layering-cache-aspectj 13 | layering-cache-aspectj 14 | 多级缓存注解模块 15 | jar 16 | 17 | 18 | 19 | com.github.xiaolyuh 20 | layering-cache-core 21 | 3.5.0 22 | 23 | 24 | 25 | org.slf4j 26 | slf4j-api 27 | true 28 | 29 | 30 | 31 | com.github.ben-manes.caffeine 32 | caffeine 33 | 34 | 35 | 36 | org.springframework 37 | spring-core 38 | true 39 | 40 | 41 | 42 | org.springframework 43 | spring-aop 44 | true 45 | 46 | 47 | 48 | org.springframework 49 | spring-context 50 | true 51 | 52 | 53 | 54 | org.aspectj 55 | aspectjweaver 56 | true 57 | 58 | 59 | 60 | org.springframework.boot 61 | spring-boot-starter-test 62 | test 63 | 64 | 65 | 66 | org.slf4j 67 | slf4j-log4j12 68 | test 69 | 70 | 71 | 72 | com.alibaba 73 | fastjson 74 | test 75 | 76 | 77 | 78 | com.esotericsoftware 79 | kryo-shaded 80 | test 81 | 82 | 83 | 84 | io.protostuff 85 | protostuff-core 86 | test 87 | 88 | 89 | 90 | io.protostuff 91 | protostuff-runtime 92 | test 93 | 94 | 95 | 96 | io.lettuce 97 | lettuce-core 98 | test 99 | 100 | 101 | 102 | com.fasterxml.jackson.core 103 | jackson-databind 104 | test 105 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /layering-cache-aspectj/src/main/java/com/github/xiaolyuh/annotation/BatchCacheable.java: -------------------------------------------------------------------------------- 1 | package com.github.xiaolyuh.annotation; 2 | 3 | import com.github.xiaolyuh.support.CacheMode; 4 | import java.lang.annotation.Documented; 5 | import java.lang.annotation.ElementType; 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.RetentionPolicy; 8 | import java.lang.annotation.Target; 9 | import org.springframework.core.annotation.AliasFor; 10 | 11 | /** 12 | * 表示调用的方法的结果是可以被缓存的。 13 | * 通过缓存key队列批量获取缓存数据。 14 | * 当该方法被调用时先检查缓存是否命中,如果没有命中再调用被缓存的方法,并将其返回值放到缓存中。 15 | * 这里的value和key都支持SpEL 表达式 16 | * 17 | * @author rayon177 18 | */ 19 | @Target({ElementType.METHOD}) 20 | @Retention(RetentionPolicy.RUNTIME) 21 | @Documented 22 | public @interface BatchCacheable { 23 | 24 | /** 25 | * 别名是 {@link #cacheNames}. 26 | * 27 | * @return String[] 28 | */ 29 | @AliasFor("cacheNames") 30 | String[] value() default {}; 31 | 32 | /** 33 | * 缓存名称,支持SpEL表达式 34 | * 35 | * @return String[] 36 | */ 37 | @AliasFor("value") 38 | String[] cacheNames() default {}; 39 | 40 | /** 41 | * 描述 42 | * 43 | * @return String 44 | */ 45 | String depict() default ""; 46 | 47 | /** 48 | * 缓存keys,需要是SpEL表达式。这个表达式会用于从参数和返回值中获取缓存keys 49 | * 该表达式需要返回一个List对象 50 | * 51 | * @return String 52 | */ 53 | String keys(); 54 | 55 | /** 56 | * 生成缓存的条件,默认空全部缓存。支持SpEL表达式 57 | * 58 | * @return 59 | */ 60 | String condition() default ""; 61 | 62 | /** 63 | * 缓存模式(只使用一级缓存或者二级缓存) 64 | * 65 | * @return boolean 66 | */ 67 | CacheMode cacheMode() default CacheMode.ALL; 68 | 69 | /** 70 | * 一级缓存配置 71 | * 72 | * @return FirstCache 73 | */ 74 | FirstCache firstCache() default @FirstCache(); 75 | 76 | /** 77 | * 二级缓存配置 78 | * 79 | * @return SecondaryCache 80 | */ 81 | SecondaryCache secondaryCache() default @SecondaryCache(); 82 | } 83 | -------------------------------------------------------------------------------- /layering-cache-aspectj/src/main/java/com/github/xiaolyuh/annotation/CacheEvict.java: -------------------------------------------------------------------------------- 1 | package com.github.xiaolyuh.annotation; 2 | 3 | import com.github.xiaolyuh.support.CacheMode; 4 | import java.lang.annotation.Documented; 5 | import java.lang.annotation.ElementType; 6 | import java.lang.annotation.Inherited; 7 | import java.lang.annotation.Retention; 8 | import java.lang.annotation.RetentionPolicy; 9 | import java.lang.annotation.Target; 10 | import org.springframework.core.annotation.AliasFor; 11 | 12 | /** 13 | * 删除缓存 14 | * 15 | * @author olafwang 16 | */ 17 | @Target({ElementType.METHOD, ElementType.TYPE}) 18 | @Retention(RetentionPolicy.RUNTIME) 19 | @Inherited 20 | @Documented 21 | public @interface CacheEvict { 22 | 23 | /** 24 | * 别名 {@link #cacheNames}. 25 | * 26 | * @return String[] 27 | */ 28 | @AliasFor("cacheNames") 29 | String[] value() default {}; 30 | 31 | /** 32 | * 缓存名称 33 | * 34 | * @return String[] 35 | */ 36 | @AliasFor("value") 37 | String[] cacheNames() default {}; 38 | 39 | /** 40 | * 缓存key,支持SpEL表达式 41 | *
    42 | *
  • {@code #root.method}, {@code #root.target}, and {@code #root.caches} for 43 | * references to the {@link java.lang.reflect.Method method}, target object, and 44 | * affected cache(s) respectively.
  • 45 | *
  • Shortcuts for the method name ({@code #root.methodName}) and target class 46 | * ({@code #root.targetClass}) are also available. 47 | *
  • Method arguments can be accessed by index. For instance the second argument 48 | * can be accessed via {@code #root.args[1]}, {@code #p1} or {@code #a1}. Arguments 49 | * can also be accessed by name if that information is available.
  • 50 | *
51 | * 52 | * @return String 53 | */ 54 | String key() default ""; 55 | 56 | /** 57 | * 缓存模式(只使用一级缓存或者二级缓存) 58 | * 59 | * @return boolean 60 | */ 61 | CacheMode cacheMode() default CacheMode.ALL; 62 | 63 | /** 64 | * 是否删除缓存中所有数据 65 | *

默认情况下是只删除关联key的缓存数据 66 | *

注意:当该参数设置成 {@code true} 时 {@link #key} 参数将无效 67 | * 68 | * @return boolean 69 | */ 70 | boolean allEntries() default false; 71 | 72 | /** 73 | * 是否异步删除缓存中所有数据 74 | * 75 | * @return boolean 76 | */ 77 | boolean async() default false; 78 | } 79 | -------------------------------------------------------------------------------- /layering-cache-aspectj/src/main/java/com/github/xiaolyuh/annotation/CachePut.java: -------------------------------------------------------------------------------- 1 | package com.github.xiaolyuh.annotation; 2 | 3 | import com.github.xiaolyuh.support.CacheMode; 4 | import java.lang.annotation.Documented; 5 | import java.lang.annotation.ElementType; 6 | import java.lang.annotation.Inherited; 7 | import java.lang.annotation.Retention; 8 | import java.lang.annotation.RetentionPolicy; 9 | import java.lang.annotation.Target; 10 | import org.springframework.core.annotation.AliasFor; 11 | 12 | /** 13 | * 将对应数据放到缓存中 14 | * 15 | * @author olafwang 16 | */ 17 | @Target({ElementType.METHOD, ElementType.TYPE}) 18 | @Retention(RetentionPolicy.RUNTIME) 19 | @Inherited 20 | @Documented 21 | public @interface CachePut { 22 | 23 | /** 24 | * 别名 {@link #cacheNames}. 25 | * 26 | * @return String[] 27 | */ 28 | @AliasFor("cacheNames") 29 | String[] value() default {}; 30 | 31 | /** 32 | * 缓存名称 33 | * 34 | * @return String[] 35 | */ 36 | @AliasFor("value") 37 | String[] cacheNames() default {}; 38 | 39 | /** 40 | * 描述 41 | * 42 | * @return String 43 | */ 44 | String depict() default ""; 45 | 46 | /** 47 | * 缓存key,支持SpEL表达式 48 | *

The SpEL expression evaluates against a dedicated context that provides the 49 | * following meta-data: 50 | *

    51 | *
  • {@code #result} for a reference to the result of the method invocation. For 52 | * supported wrappers such as {@code Optional}, {@code #result} refers to the actual 53 | * object, not the wrapper
  • 54 | *
  • {@code #root.method}, {@code #root.target}, and {@code #root.caches} for 55 | * references to the {@link java.lang.reflect.Method method}, target object, and 56 | * affected cache(s) respectively.
  • 57 | *
  • Shortcuts for the method name ({@code #root.methodName}) and target class 58 | * ({@code #root.targetClass}) are also available. 59 | *
  • Method arguments can be accessed by index. For instance the second argument 60 | * can be accessed via {@code #root.args[1]}, {@code #p1} or {@code #a1}. Arguments 61 | * can also be accessed by name if that information is available.
  • 62 | *
63 | * 64 | * @return String 65 | */ 66 | String key() default ""; 67 | 68 | /** 69 | * 缓存模式(只使用一级缓存或者二级缓存) 70 | * 71 | * @return boolean 72 | */ 73 | CacheMode cacheMode() default CacheMode.ALL; 74 | 75 | /** 76 | * 一级缓存配置 77 | * 78 | * @return FirstCache 79 | */ 80 | FirstCache firstCache() default @FirstCache(); 81 | 82 | /** 83 | * 二级缓存配置 84 | * 85 | * @return SecondaryCache 86 | */ 87 | SecondaryCache secondaryCache() default @SecondaryCache(); 88 | } 89 | -------------------------------------------------------------------------------- /layering-cache-aspectj/src/main/java/com/github/xiaolyuh/annotation/Cacheable.java: -------------------------------------------------------------------------------- 1 | package com.github.xiaolyuh.annotation; 2 | 3 | import com.github.xiaolyuh.support.CacheMode; 4 | import java.lang.annotation.Documented; 5 | import java.lang.annotation.ElementType; 6 | import java.lang.annotation.Inherited; 7 | import java.lang.annotation.Retention; 8 | import java.lang.annotation.RetentionPolicy; 9 | import java.lang.annotation.Target; 10 | import org.springframework.core.annotation.AliasFor; 11 | 12 | /** 13 | * 表示调用的方法(或类中的所有方法)的结果是可以被缓存的。 14 | * 当该方法被调用时先检查缓存是否命中,如果没有命中再调用被缓存的方法,并将其返回值放到缓存中。 15 | * 这里的value和key都支持SpEL 表达式 16 | * 17 | * @author olafwang 18 | */ 19 | @Target({ElementType.METHOD, ElementType.TYPE}) 20 | @Retention(RetentionPolicy.RUNTIME) 21 | @Inherited 22 | @Documented 23 | public @interface Cacheable { 24 | 25 | /** 26 | * 别名是 {@link #cacheNames}. 27 | * 28 | * @return String[] 29 | */ 30 | @AliasFor("cacheNames") 31 | String[] value() default {}; 32 | 33 | /** 34 | * 缓存名称,支持SpEL表达式 35 | * 36 | * @return String[] 37 | */ 38 | @AliasFor("value") 39 | String[] cacheNames() default {}; 40 | 41 | /** 42 | * 描述 43 | * 44 | * @return String 45 | */ 46 | String depict() default ""; 47 | 48 | /** 49 | * 缓存key,支持SpEL表达式 50 | *

The SpEL expression evaluates against a dedicated context that provides the 51 | * following meta-data: 52 | *

    53 | *
  • {@code #root.method}, {@code #root.target}, and {@code #root.caches} for 54 | * references to the {@link java.lang.reflect.Method method}, target object, and 55 | * affected cache(s) respectively.
  • 56 | *
  • Shortcuts for the method name ({@code #root.methodName}) and target class 57 | * ({@code #root.targetClass}) are also available. 58 | *
  • Method arguments can be accessed by index. For instance the second argument 59 | * can be accessed via {@code #root.args[1]}, {@code #p1} or {@code #a1}. Arguments 60 | * can also be accessed by name if that information is available.
  • 61 | *
62 | * 63 | * @return String 64 | */ 65 | String key() default ""; 66 | 67 | /** 68 | * 生成缓存的条件,默认空全部缓存。支持SpEL表达式 69 | * 70 | * @return 条件表达式 71 | */ 72 | String condition() default ""; 73 | 74 | /** 75 | * 缓存模式(只使用一级缓存或者二级缓存) 76 | * 77 | * @return boolean 78 | */ 79 | CacheMode cacheMode() default CacheMode.ALL; 80 | 81 | /** 82 | * 一级缓存配置 83 | * 84 | * @return FirstCache 85 | */ 86 | FirstCache firstCache() default @FirstCache(); 87 | 88 | /** 89 | * 二级缓存配置 90 | * 91 | * @return SecondaryCache 92 | */ 93 | SecondaryCache secondaryCache() default @SecondaryCache(); 94 | } 95 | -------------------------------------------------------------------------------- /layering-cache-aspectj/src/main/java/com/github/xiaolyuh/annotation/Caching.java: -------------------------------------------------------------------------------- 1 | package com.github.xiaolyuh.annotation; 2 | 3 | import java.lang.annotation.Documented; 4 | import java.lang.annotation.ElementType; 5 | import java.lang.annotation.Inherited; 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.RetentionPolicy; 8 | import java.lang.annotation.Target; 9 | 10 | /** 11 | * 同时使用多个缓存注解 12 | * 13 | * @author olafwang 14 | */ 15 | @Target({ElementType.METHOD, ElementType.TYPE}) 16 | @Retention(RetentionPolicy.RUNTIME) 17 | @Inherited 18 | @Documented 19 | public @interface Caching { 20 | Cacheable[] cacheable() default {}; 21 | 22 | CachePut[] put() default {}; 23 | 24 | CacheEvict[] evict() default {}; 25 | } 26 | -------------------------------------------------------------------------------- /layering-cache-aspectj/src/main/java/com/github/xiaolyuh/annotation/FirstCache.java: -------------------------------------------------------------------------------- 1 | package com.github.xiaolyuh.annotation; 2 | 3 | import com.github.xiaolyuh.support.ExpireMode; 4 | import java.lang.annotation.Documented; 5 | import java.lang.annotation.ElementType; 6 | import java.lang.annotation.Inherited; 7 | import java.lang.annotation.Retention; 8 | import java.lang.annotation.RetentionPolicy; 9 | import java.lang.annotation.Target; 10 | import java.util.concurrent.TimeUnit; 11 | 12 | /** 13 | * 一级缓存配置项 14 | * 15 | * @author yuhao.wang 16 | */ 17 | @Target({ElementType.METHOD, ElementType.TYPE}) 18 | @Retention(RetentionPolicy.RUNTIME) 19 | @Inherited 20 | @Documented 21 | public @interface FirstCache { 22 | /** 23 | * 缓存初始Size 24 | * 25 | * @return int 26 | */ 27 | int initialCapacity() default 10; 28 | 29 | /** 30 | * 缓存最大Size 31 | * 32 | * @return int 33 | */ 34 | int maximumSize() default 5000; 35 | 36 | /** 37 | * 缓存有效时间 38 | * 39 | * @return int 40 | */ 41 | int expireTime() default 9; 42 | 43 | /** 44 | * 缓存时间单位 45 | * 46 | * @return TimeUnit 47 | */ 48 | TimeUnit timeUnit() default TimeUnit.MINUTES; 49 | 50 | /** 51 | * 缓存失效模式 52 | * 53 | * @return ExpireMode 54 | * @see ExpireMode 55 | */ 56 | ExpireMode expireMode() default ExpireMode.WRITE; 57 | } 58 | -------------------------------------------------------------------------------- /layering-cache-aspectj/src/main/java/com/github/xiaolyuh/annotation/SecondaryCache.java: -------------------------------------------------------------------------------- 1 | package com.github.xiaolyuh.annotation; 2 | 3 | import java.lang.annotation.Documented; 4 | import java.lang.annotation.ElementType; 5 | import java.lang.annotation.Inherited; 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.RetentionPolicy; 8 | import java.lang.annotation.Target; 9 | import java.util.concurrent.TimeUnit; 10 | 11 | /** 12 | * 二级缓存配置项 13 | * 14 | * @author yuhao.wang 15 | */ 16 | @Target({ElementType.METHOD, ElementType.TYPE}) 17 | @Retention(RetentionPolicy.RUNTIME) 18 | @Inherited 19 | @Documented 20 | public @interface SecondaryCache { 21 | /** 22 | * 缓存有效时间 23 | * 24 | * @return long 25 | */ 26 | long expireTime() default 5; 27 | 28 | /** 29 | * 缓存主动在失效前强制刷新缓存的时间 30 | * 建议是: preloadTime = expireTime * 0.2 31 | * 32 | * @return long 33 | */ 34 | long preloadTime() default 1; 35 | 36 | /** 37 | * 时间单位 {@link TimeUnit} 38 | * 39 | * @return TimeUnit 40 | */ 41 | TimeUnit timeUnit() default TimeUnit.HOURS; 42 | 43 | /** 44 | * 是否强制刷新(直接执行被缓存方法),默认是false 45 | * 不建议在批量缓存式开启强制刷新 46 | * 47 | * @return boolean 48 | */ 49 | boolean forceRefresh() default false; 50 | 51 | /** 52 | * 非空值和null值之间的时间倍率,默认是1。isAllowNullValue=true才有效 53 | *

54 | * 如配置缓存的有效时间是200秒,倍率这设置成10, 55 | * 那么当缓存value为null时,缓存的有效时间将是20秒,非空时为200秒 56 | *

57 | * 58 | * @return int 59 | */ 60 | int magnification() default 1; 61 | } 62 | -------------------------------------------------------------------------------- /layering-cache-aspectj/src/main/java/com/github/xiaolyuh/expression/CacheEvaluationContext.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2002-2016 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.xiaolyuh.expression; 18 | 19 | import java.lang.reflect.Method; 20 | import java.util.HashSet; 21 | import java.util.Set; 22 | import org.springframework.context.expression.MethodBasedEvaluationContext; 23 | import org.springframework.core.ParameterNameDiscoverer; 24 | 25 | /** 26 | * Cache specific evaluation context that adds a method parameters as SpEL 27 | * variables, in a lazy manner. The lazy nature eliminates unneeded 28 | * parsing of classes byte code for parameter discovery. 29 | * 30 | *

Also define a set of "unavailable variables" (i.e. variables that should 31 | * lead to an exception right the way when they are accessed). This can be useful 32 | * to verify a condition does not match even when not all potential variables 33 | * are present. 34 | * 35 | *

To limit the creation of objects, an ugly constructor is used 36 | * (rather then a dedicated 'closure'-like class for deferred execution). 37 | * 38 | * @author Costin Leau 39 | * @author Stephane Nicoll 40 | * @author Juergen Hoeller 41 | * @since 3.1 42 | */ 43 | class CacheEvaluationContext extends MethodBasedEvaluationContext { 44 | 45 | private final Set unavailableVariables = new HashSet(1); 46 | 47 | 48 | CacheEvaluationContext(Object rootObject, Method method, Object[] arguments, 49 | ParameterNameDiscoverer parameterNameDiscoverer) { 50 | 51 | super(rootObject, method, arguments, parameterNameDiscoverer); 52 | } 53 | 54 | 55 | /** 56 | * Add the specified variable name as unavailable for that context. 57 | * Any expression trying to access this variable should lead to an exception. 58 | *

This permits the validation of expressions that could potentially a 59 | * variable even when such variable isn't available yet. Any expression 60 | * trying to use that variable should therefore fail to evaluate. 61 | */ 62 | public void addUnavailableVariable(String name) { 63 | this.unavailableVariables.add(name); 64 | } 65 | 66 | 67 | /** 68 | * Load the param information only when needed. 69 | */ 70 | @Override 71 | public Object lookupVariable(String name) { 72 | if (this.unavailableVariables.contains(name)) { 73 | throw new VariableNotAvailableException(name); 74 | } 75 | return super.lookupVariable(name); 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /layering-cache-aspectj/src/main/java/com/github/xiaolyuh/expression/CacheExpressionRootObject.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2002-2013 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.xiaolyuh.expression; 18 | 19 | import java.lang.reflect.Method; 20 | import org.springframework.util.Assert; 21 | 22 | /** 23 | * Class describing the root object used during the expression evaluation. 24 | * 25 | * @author Costin Leau 26 | * @author Sam Brannen 27 | * @since 3.1 28 | */ 29 | class CacheExpressionRootObject { 30 | 31 | private final Method method; 32 | 33 | private final Object[] args; 34 | 35 | private final Object target; 36 | 37 | private final Class targetClass; 38 | 39 | 40 | public CacheExpressionRootObject(Method method, Object[] args, Object target, Class targetClass) { 41 | 42 | Assert.notNull(method, "Method is required"); 43 | Assert.notNull(targetClass, "targetClass is required"); 44 | this.method = method; 45 | this.target = target; 46 | this.targetClass = targetClass; 47 | this.args = args; 48 | } 49 | 50 | public Method getMethod() { 51 | return this.method; 52 | } 53 | 54 | public String getMethodName() { 55 | return this.method.getName(); 56 | } 57 | 58 | public Object[] getArgs() { 59 | return this.args; 60 | } 61 | 62 | public Object getTarget() { 63 | return this.target; 64 | } 65 | 66 | public Class getTargetClass() { 67 | return this.targetClass; 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /layering-cache-aspectj/src/main/java/com/github/xiaolyuh/expression/VariableNotAvailableException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2002-2016 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.xiaolyuh.expression; 18 | 19 | import org.springframework.expression.EvaluationException; 20 | 21 | /** 22 | * A specific {@link EvaluationException} to mention that a given variable 23 | * used in the expression is not available in the context. 24 | * 25 | * @author Stephane Nicoll 26 | * @since 4.0.6 27 | */ 28 | @SuppressWarnings("serial") 29 | class VariableNotAvailableException extends EvaluationException { 30 | 31 | private final String name; 32 | 33 | public VariableNotAvailableException(String name) { 34 | super("Variable '" + name + "' is not available"); 35 | this.name = name; 36 | } 37 | 38 | 39 | public String getName() { 40 | return this.name; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /layering-cache-aspectj/src/main/java/com/github/xiaolyuh/support/KeyGenerator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2002-2013 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.xiaolyuh.support; 18 | 19 | import java.lang.reflect.Method; 20 | 21 | /** 22 | * Cache key generator. Used for creating a key based on the given method 23 | * (used as context) and its parameters. 24 | * 25 | * @author Costin Leau 26 | * @author Chris Beams 27 | * @author Phillip Webb 28 | * @since 3.1 29 | */ 30 | public interface KeyGenerator { 31 | 32 | /** 33 | * Generate a key for the given method and its parameters. 34 | * 35 | * @param target the target instance 36 | * @param method the method being called 37 | * @param params the method parameters (with any var-args expanded) 38 | * @return a generated key 39 | */ 40 | Object generate(Object target, Method method, Object... params); 41 | 42 | } 43 | -------------------------------------------------------------------------------- /layering-cache-aspectj/src/main/java/com/github/xiaolyuh/support/SimpleKey.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2002-2016 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.xiaolyuh.support; 18 | 19 | import java.io.Serializable; 20 | import java.util.Arrays; 21 | import org.springframework.util.Assert; 22 | import org.springframework.util.StringUtils; 23 | 24 | /** 25 | * A simple key as returned from the {@link SimpleKeyGenerator}. 26 | * 27 | * @author Phillip Webb 28 | * @see SimpleKeyGenerator 29 | * @since 4.0 30 | */ 31 | public class SimpleKey implements Serializable { 32 | 33 | public static final SimpleKey EMPTY = new SimpleKey(); 34 | 35 | private final Object[] params; 36 | private final int hashCode; 37 | 38 | 39 | /** 40 | * Create a new {@link SimpleKey} instance. 41 | * 42 | * @param elements the elements of the key 43 | */ 44 | public SimpleKey(Object... elements) { 45 | Assert.notNull(elements, "Elements must not be null"); 46 | this.params = new Object[elements.length]; 47 | System.arraycopy(elements, 0, this.params, 0, elements.length); 48 | this.hashCode = Arrays.deepHashCode(this.params); 49 | } 50 | 51 | @Override 52 | public boolean equals(Object obj) { 53 | return (this == obj || (obj instanceof SimpleKey 54 | && Arrays.deepEquals(this.params, ((SimpleKey) obj).params))); 55 | } 56 | 57 | @Override 58 | public final int hashCode() { 59 | return this.hashCode; 60 | } 61 | 62 | @Override 63 | public String toString() { 64 | return getClass().getSimpleName() + " [" + StringUtils.arrayToCommaDelimitedString(this.params) + "]"; 65 | } 66 | 67 | public Object[] getParams() { 68 | return params; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /layering-cache-aspectj/src/main/java/com/github/xiaolyuh/support/SimpleKeyGenerator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2002-2014 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.xiaolyuh.support; 18 | 19 | import java.lang.reflect.Method; 20 | 21 | /** 22 | * Simple key generator. Returns the parameter itself if a single non-null value 23 | * is given, otherwise returns a {@link SimpleKey} of the parameters. 24 | *

Unlike DefaultKeyGenerator, no collisions will occur with the keys 25 | * generated by this class. The returned {@link SimpleKey} object can be safely 26 | * used with a {@link org.springframework.cache.concurrent.ConcurrentMapCache}, 27 | * however, might not be suitable for all {@link org.springframework.cache.Cache} 28 | * implementations. 29 | * 30 | * @author Phillip Webb 31 | * @author Juergen Hoeller 32 | * @see SimpleKey 33 | * @see org.springframework.cache.annotation.CachingConfigurer 34 | * @since 4.0 35 | */ 36 | public class SimpleKeyGenerator implements KeyGenerator { 37 | 38 | @Override 39 | public Object generate(Object target, Method method, Object... params) { 40 | return generateKey(params); 41 | } 42 | 43 | /** 44 | * Generate a key based on the specified parameters. 45 | * 46 | * @param params params 47 | * @return Object 48 | */ 49 | public static Object generateKey(Object... params) { 50 | if (params.length == 0) { 51 | return SimpleKey.EMPTY; 52 | } 53 | if (params.length == 1) { 54 | Object param = params[0]; 55 | if (param != null && !param.getClass().isArray()) { 56 | return param; 57 | } 58 | } 59 | return new SimpleKey(params); 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /layering-cache-aspectj/src/test/java/com/github/xiaolyuh/config/CacheClusterConfig.java: -------------------------------------------------------------------------------- 1 | package com.github.xiaolyuh.config; 2 | 3 | import com.github.xiaolyuh.aspect.LayeringAspect; 4 | import com.github.xiaolyuh.manager.CacheManager; 5 | import com.github.xiaolyuh.manager.LayeringCacheManager; 6 | import com.github.xiaolyuh.redis.clinet.RedisClient; 7 | import com.github.xiaolyuh.test.BatchTestService; 8 | import com.github.xiaolyuh.test.TestService; 9 | import org.springframework.context.annotation.Bean; 10 | import org.springframework.context.annotation.Configuration; 11 | import org.springframework.context.annotation.EnableAspectJAutoProxy; 12 | import org.springframework.context.annotation.Import; 13 | 14 | @Configuration 15 | @Import({RedisClusterConfig.class}) 16 | @EnableAspectJAutoProxy 17 | public class CacheClusterConfig { 18 | 19 | @Bean 20 | public CacheManager layeringCacheManager(RedisClient layeringCacheRedisClient) { 21 | LayeringCacheManager layeringCacheManager = new LayeringCacheManager(layeringCacheRedisClient); 22 | // 开启统计功能 23 | layeringCacheManager.setStats(true); 24 | return layeringCacheManager; 25 | } 26 | 27 | @Bean 28 | public LayeringAspect layeringAspect() { 29 | return new LayeringAspect(); 30 | } 31 | 32 | @Bean 33 | public TestService testService() { 34 | return new TestService(); 35 | } 36 | 37 | @Bean 38 | public BatchTestService testBatchTestService() { 39 | return new BatchTestService(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /layering-cache-aspectj/src/test/java/com/github/xiaolyuh/config/CacheSentinelConfig.java: -------------------------------------------------------------------------------- 1 | package com.github.xiaolyuh.config; 2 | 3 | import com.github.xiaolyuh.aspect.LayeringAspect; 4 | import com.github.xiaolyuh.manager.CacheManager; 5 | import com.github.xiaolyuh.manager.LayeringCacheManager; 6 | import com.github.xiaolyuh.redis.clinet.RedisClient; 7 | import com.github.xiaolyuh.test.BatchTestService; 8 | import com.github.xiaolyuh.test.TestService; 9 | import org.springframework.context.annotation.Bean; 10 | import org.springframework.context.annotation.Configuration; 11 | import org.springframework.context.annotation.EnableAspectJAutoProxy; 12 | import org.springframework.context.annotation.Import; 13 | 14 | @Configuration 15 | @Import({RedisSentinelConfig.class}) 16 | @EnableAspectJAutoProxy 17 | public class CacheSentinelConfig { 18 | 19 | @Bean 20 | public CacheManager layeringCacheManager(RedisClient layeringCacheRedisClient) { 21 | LayeringCacheManager layeringCacheManager = new LayeringCacheManager(layeringCacheRedisClient); 22 | // 开启统计功能 23 | layeringCacheManager.setStats(true); 24 | return layeringCacheManager; 25 | } 26 | 27 | @Bean 28 | public LayeringAspect layeringAspect() { 29 | return new LayeringAspect(); 30 | } 31 | 32 | @Bean 33 | public TestService testService() { 34 | return new TestService(); 35 | } 36 | 37 | @Bean 38 | public BatchTestService testBatchTestService() { 39 | return new BatchTestService(); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /layering-cache-aspectj/src/test/java/com/github/xiaolyuh/config/CacheSingleConfig.java: -------------------------------------------------------------------------------- 1 | package com.github.xiaolyuh.config; 2 | 3 | import com.github.xiaolyuh.aspect.LayeringAspect; 4 | import com.github.xiaolyuh.manager.CacheManager; 5 | import com.github.xiaolyuh.manager.LayeringCacheManager; 6 | import com.github.xiaolyuh.redis.clinet.RedisClient; 7 | import com.github.xiaolyuh.test.BatchTestService; 8 | import com.github.xiaolyuh.test.TestService; 9 | import org.springframework.context.annotation.Bean; 10 | import org.springframework.context.annotation.Configuration; 11 | import org.springframework.context.annotation.EnableAspectJAutoProxy; 12 | import org.springframework.context.annotation.Import; 13 | 14 | @Configuration 15 | @Import({RedisSingleConfig.class}) 16 | @EnableAspectJAutoProxy 17 | public class CacheSingleConfig { 18 | 19 | @Bean 20 | public CacheManager layeringCacheManager(RedisClient layeringCacheRedisClient) { 21 | LayeringCacheManager layeringCacheManager = new LayeringCacheManager(layeringCacheRedisClient); 22 | // 开启统计功能 23 | layeringCacheManager.setStats(true); 24 | return layeringCacheManager; 25 | } 26 | 27 | @Bean 28 | public LayeringAspect layeringAspect() { 29 | return new LayeringAspect(); 30 | } 31 | 32 | @Bean 33 | public TestService testService() { 34 | return new TestService(); 35 | } 36 | 37 | @Bean 38 | public BatchTestService testBatchTestService() { 39 | return new BatchTestService(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /layering-cache-aspectj/src/test/java/com/github/xiaolyuh/config/RedisClusterConfig.java: -------------------------------------------------------------------------------- 1 | package com.github.xiaolyuh.config; 2 | 3 | import com.github.xiaolyuh.redis.clinet.ClusterRedisClient; 4 | import com.github.xiaolyuh.redis.clinet.RedisClient; 5 | import com.github.xiaolyuh.redis.clinet.RedisProperties; 6 | import com.github.xiaolyuh.redis.serializer.FastJsonRedisSerializer; 7 | import com.github.xiaolyuh.redis.serializer.JacksonRedisSerializer; 8 | import com.github.xiaolyuh.redis.serializer.JdkRedisSerializer; 9 | import com.github.xiaolyuh.redis.serializer.KryoRedisSerializer; 10 | import com.github.xiaolyuh.redis.serializer.ProtostuffRedisSerializer; 11 | import com.github.xiaolyuh.redis.serializer.StringRedisSerializer; 12 | import com.github.xiaolyuh.util.StringUtils; 13 | import org.springframework.beans.factory.annotation.Value; 14 | import org.springframework.context.annotation.Bean; 15 | import org.springframework.context.annotation.Configuration; 16 | import org.springframework.context.annotation.PropertySource; 17 | 18 | @Configuration 19 | @PropertySource({"classpath:application.properties"}) 20 | public class RedisClusterConfig { 21 | 22 | @Value("${spring.redis.cluster:127.0.0.1:9000,127.0.0.1:9001,127.0.0.1:9002,127.0.0.1:9003,127.0.0.1:9004,127.0.0.1:9005}") 23 | private String cluster; 24 | 25 | @Value("${spring.redis.password:}") 26 | private String password; 27 | 28 | @Bean 29 | public RedisClient layeringCacheRedisClient() { 30 | RedisProperties redisProperties = new RedisProperties(); 31 | redisProperties.setPassword(StringUtils.isBlank(password) ? null : password); 32 | redisProperties.setCluster(cluster); 33 | 34 | KryoRedisSerializer kryoRedisSerializer = new KryoRedisSerializer(); 35 | FastJsonRedisSerializer fastJsonRedisSerializer = new FastJsonRedisSerializer(); 36 | JacksonRedisSerializer jacksonRedisSerializer = new JacksonRedisSerializer(); 37 | JdkRedisSerializer jdkRedisSerializer = new JdkRedisSerializer(); 38 | ProtostuffRedisSerializer protostuffRedisSerializer = new ProtostuffRedisSerializer(); 39 | 40 | StringRedisSerializer keyRedisSerializer = new StringRedisSerializer(); 41 | ClusterRedisClient redisClient = new ClusterRedisClient(redisProperties); 42 | redisClient.setKeySerializer(keyRedisSerializer); 43 | redisClient.setValueSerializer(kryoRedisSerializer); 44 | return redisClient; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /layering-cache-aspectj/src/test/java/com/github/xiaolyuh/config/RedisSentinelConfig.java: -------------------------------------------------------------------------------- 1 | package com.github.xiaolyuh.config; 2 | 3 | import com.github.xiaolyuh.redis.clinet.RedisClient; 4 | import com.github.xiaolyuh.redis.clinet.RedisProperties; 5 | import com.github.xiaolyuh.redis.clinet.SentinelRedisClient; 6 | import com.github.xiaolyuh.redis.serializer.FastJsonRedisSerializer; 7 | import com.github.xiaolyuh.redis.serializer.JacksonRedisSerializer; 8 | import com.github.xiaolyuh.redis.serializer.JdkRedisSerializer; 9 | import com.github.xiaolyuh.redis.serializer.KryoRedisSerializer; 10 | import com.github.xiaolyuh.redis.serializer.ProtostuffRedisSerializer; 11 | import com.github.xiaolyuh.redis.serializer.StringRedisSerializer; 12 | import com.github.xiaolyuh.util.StringUtils; 13 | import org.springframework.beans.factory.annotation.Value; 14 | import org.springframework.context.annotation.Bean; 15 | import org.springframework.context.annotation.Configuration; 16 | import org.springframework.context.annotation.PropertySource; 17 | 18 | @Configuration 19 | @PropertySource({"classpath:application.properties"}) 20 | public class RedisSentinelConfig { 21 | 22 | @Value("${layering-cache.redis.nodes:127.0.0.1:26379,127.0.0.1:26380,127.0.0.1:26381}") 23 | private String nodes; 24 | 25 | @Value("${spring.redis.password:123}") 26 | private String password; 27 | 28 | @Bean 29 | public RedisClient layeringCacheRedisClient() { 30 | RedisProperties redisProperties = new RedisProperties(); 31 | redisProperties.setSentinelNodes(nodes); 32 | redisProperties.setPassword(StringUtils.isBlank(password) ? null : password); 33 | 34 | KryoRedisSerializer kryoRedisSerializer = new KryoRedisSerializer(); 35 | FastJsonRedisSerializer fastJsonRedisSerializer = new FastJsonRedisSerializer(); 36 | JacksonRedisSerializer jacksonRedisSerializer = new JacksonRedisSerializer(); 37 | JdkRedisSerializer jdkRedisSerializer = new JdkRedisSerializer(); 38 | ProtostuffRedisSerializer protostuffRedisSerializer = new ProtostuffRedisSerializer(); 39 | 40 | StringRedisSerializer keyRedisSerializer = new StringRedisSerializer(); 41 | 42 | SentinelRedisClient redisClient = new SentinelRedisClient(redisProperties); 43 | 44 | redisClient.setKeySerializer(keyRedisSerializer); 45 | redisClient.setValueSerializer(protostuffRedisSerializer); 46 | return redisClient; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /layering-cache-aspectj/src/test/java/com/github/xiaolyuh/config/RedisSingleConfig.java: -------------------------------------------------------------------------------- 1 | package com.github.xiaolyuh.config; 2 | 3 | import com.github.xiaolyuh.redis.clinet.RedisClient; 4 | import com.github.xiaolyuh.redis.clinet.RedisProperties; 5 | import com.github.xiaolyuh.redis.clinet.SingleRedisClient; 6 | import com.github.xiaolyuh.redis.serializer.FastJsonRedisSerializer; 7 | import com.github.xiaolyuh.redis.serializer.JacksonRedisSerializer; 8 | import com.github.xiaolyuh.redis.serializer.JdkRedisSerializer; 9 | import com.github.xiaolyuh.redis.serializer.KryoRedisSerializer; 10 | import com.github.xiaolyuh.redis.serializer.ProtostuffRedisSerializer; 11 | import com.github.xiaolyuh.redis.serializer.StringRedisSerializer; 12 | import com.github.xiaolyuh.util.StringUtils; 13 | import org.springframework.beans.factory.annotation.Value; 14 | import org.springframework.context.annotation.Bean; 15 | import org.springframework.context.annotation.Configuration; 16 | import org.springframework.context.annotation.PropertySource; 17 | 18 | @Configuration 19 | @PropertySource({"classpath:application.properties"}) 20 | public class RedisSingleConfig { 21 | 22 | @Value("${spring.redis.database:0}") 23 | private int database; 24 | 25 | @Value("${spring.redis.host:127.0.0.1}") 26 | private String host; 27 | 28 | @Value("${spring.redis.password:}") 29 | private String password; 30 | 31 | @Value("${spring.redis.port:6379}") 32 | private int port; 33 | 34 | @Bean 35 | public RedisClient layeringCacheRedisClient() { 36 | RedisProperties redisProperties = new RedisProperties(); 37 | redisProperties.setDatabase(database); 38 | redisProperties.setHost(host); 39 | redisProperties.setPassword(StringUtils.isBlank(password) ? null : password); 40 | redisProperties.setPort(port); 41 | 42 | KryoRedisSerializer kryoRedisSerializer = new KryoRedisSerializer(); 43 | FastJsonRedisSerializer fastJsonRedisSerializer = new FastJsonRedisSerializer(); 44 | JacksonRedisSerializer jacksonRedisSerializer = new JacksonRedisSerializer(); 45 | JdkRedisSerializer jdkRedisSerializer = new JdkRedisSerializer(); 46 | ProtostuffRedisSerializer protostuffRedisSerializer = new ProtostuffRedisSerializer(); 47 | 48 | StringRedisSerializer keyRedisSerializer = new StringRedisSerializer(); 49 | 50 | SingleRedisClient redisClient = new SingleRedisClient(redisProperties); 51 | 52 | redisClient.setKeySerializer(keyRedisSerializer); 53 | redisClient.setValueSerializer(protostuffRedisSerializer); 54 | return redisClient; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /layering-cache-aspectj/src/test/java/com/github/xiaolyuh/domain/User.java: -------------------------------------------------------------------------------- 1 | package com.github.xiaolyuh.domain; 2 | 3 | import java.io.Serializable; 4 | import java.math.BigDecimal; 5 | import java.util.ArrayList; 6 | import java.util.Date; 7 | import java.util.HashSet; 8 | import java.util.List; 9 | import java.util.Set; 10 | 11 | public class User implements Serializable { 12 | 13 | public User() { 14 | this.userId = 11L; 15 | this.name = "name"; 16 | this.address = new Address(); 17 | this.lastName = new String[]{"w", "四川", "~!@#%……&*()——+{}:“?》:''\">?《~!@#$%^&*()_+{}\\"}; 18 | List lastNameList = new ArrayList<>(); 19 | lastNameList.add("W"); 20 | lastNameList.add("成都"); 21 | this.lastNameList = lastNameList; 22 | this.lastNameSet = new HashSet<>(lastNameList); 23 | this.lastName = new String[]{"w", "四川", "~!@#%……&*()——+{}:“?》:''\">?《~!@#$%^&*()_+{}\\"}; 24 | this.age = 122; 25 | this.height = 18.2; 26 | this.income = new BigDecimal(22.22); 27 | this.birthday = new Date(); 28 | } 29 | 30 | public User(long userId,int age) { 31 | this(); 32 | this.userId = userId; 33 | this.age = age; 34 | } 35 | 36 | public User(long userId ) { 37 | this(); 38 | this.userId = userId; 39 | } 40 | 41 | private long userId; 42 | 43 | private String name; 44 | 45 | private Address address; 46 | 47 | private String[] lastName; 48 | 49 | private List lastNameList; 50 | 51 | private Set lastNameSet; 52 | 53 | private int age; 54 | 55 | private double height; 56 | 57 | private BigDecimal income; 58 | 59 | private Date birthday; 60 | 61 | public long getUserId() { 62 | return userId; 63 | } 64 | 65 | public void setUserId(long userId) { 66 | this.userId = userId; 67 | } 68 | 69 | public String getName() { 70 | return name; 71 | } 72 | 73 | public void setName(String name) { 74 | this.name = name; 75 | } 76 | 77 | public String[] getLastName() { 78 | return lastName; 79 | } 80 | 81 | public void setLastName(String[] lastName) { 82 | this.lastName = lastName; 83 | } 84 | 85 | public int getAge() { 86 | return age; 87 | } 88 | 89 | public void setAge(int age) { 90 | this.age = age; 91 | } 92 | 93 | public Address getAddress() { 94 | return address; 95 | } 96 | 97 | public void setAddress(Address address) { 98 | this.address = address; 99 | } 100 | 101 | public List getLastNameList() { 102 | return lastNameList; 103 | } 104 | 105 | public void setLastNameList(List lastNameList) { 106 | this.lastNameList = lastNameList; 107 | } 108 | 109 | public Set getLastNameSet() { 110 | return lastNameSet; 111 | } 112 | 113 | public void setLastNameSet(Set lastNameSet) { 114 | this.lastNameSet = lastNameSet; 115 | } 116 | 117 | public BigDecimal getIncome() { 118 | return income.setScale(2, BigDecimal.ROUND_UP); 119 | } 120 | 121 | public void setIncome(BigDecimal income) { 122 | this.income = income; 123 | } 124 | 125 | public double getHeight() { 126 | return height; 127 | } 128 | 129 | public void setHeight(double height) { 130 | this.height = height; 131 | } 132 | 133 | public static class Address implements Serializable { 134 | private String addredd; 135 | 136 | public Address() { 137 | this.addredd = "成都"; 138 | } 139 | 140 | public String getAddredd() { 141 | return addredd; 142 | } 143 | 144 | public void setAddredd(String addredd) { 145 | this.addredd = addredd; 146 | } 147 | } 148 | 149 | public Date getBirthday() { 150 | return birthday; 151 | } 152 | 153 | public void setBirthday(Date birthday) { 154 | this.birthday = birthday; 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /layering-cache-aspectj/src/test/java/com/github/xiaolyuh/test/BatchTestService.java: -------------------------------------------------------------------------------- 1 | package com.github.xiaolyuh.test; 2 | 3 | import com.github.xiaolyuh.annotation.BatchCacheable; 4 | import com.github.xiaolyuh.annotation.FirstCache; 5 | import com.github.xiaolyuh.annotation.SecondaryCache; 6 | import com.github.xiaolyuh.domain.User; 7 | import com.github.xiaolyuh.support.CacheMode; 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | import java.util.concurrent.TimeUnit; 11 | import org.slf4j.Logger; 12 | import org.slf4j.LoggerFactory; 13 | 14 | /** 15 | * BatchTestService 16 | * 17 | * @author heyirui 18 | * @date 2024/7/11 19 | */ 20 | public class BatchTestService { 21 | Logger logger = LoggerFactory.getLogger(getClass()); 22 | 23 | @BatchCacheable(value = "user:info", keys = "#users.![userId]", depict = "用户信息缓存", 24 | firstCache = @FirstCache(expireTime = 10, timeUnit = TimeUnit.SECONDS), 25 | secondaryCache = @SecondaryCache(expireTime = 10, preloadTime = 3, timeUnit = TimeUnit.SECONDS)) 26 | public List getUserByIds(List users) { 27 | logger.debug("测试正常配置的批量缓存方法"); 28 | 29 | ArrayList res = new ArrayList<>(); 30 | for (int i = 0; i < Math.min(users.size(), 2); i++) { 31 | User user = new User(); 32 | user.setUserId(users.get(i).getUserId()); 33 | user.setAge(i); 34 | user.setLastName(new String[]{"w", "y", Integer.toString(i)}); 35 | res.add(user); 36 | } 37 | return res; 38 | } 39 | 40 | @BatchCacheable(value = "user:info", keys = "#users.![userId]", depict = "用户信息缓存", 41 | firstCache = @FirstCache(expireTime = 10, timeUnit = TimeUnit.SECONDS), 42 | secondaryCache = @SecondaryCache(expireTime = 10, preloadTime = 3, timeUnit = TimeUnit.SECONDS)) 43 | public List getUserByIdsReturnNone(List users) { 44 | logger.debug("测试批量缓存方法返回空数组"); 45 | return new ArrayList<>(); 46 | } 47 | 48 | @BatchCacheable(value = "user:info:01", keys = "#users.![userId]", cacheMode = CacheMode.FIRST, 49 | firstCache = @FirstCache(expireTime = 10, timeUnit = TimeUnit.SECONDS), 50 | secondaryCache = @SecondaryCache(expireTime = 10, preloadTime = 7, forceRefresh = true, timeUnit = TimeUnit.SECONDS)) 51 | public List getUserByIdDisableFirstCache(List users) { 52 | logger.debug("user:info:01 测试禁用二级缓存"); 53 | ArrayList res = new ArrayList<>(); 54 | for (int i = 0; i < Math.min(users.size(), 2); i++) { 55 | User user = new User(); 56 | user.setUserId(users.get(i).getUserId()); 57 | user.setAge(i); 58 | user.setLastName(new String[]{"w", "y", Integer.toString(i)}); 59 | res.add(user); 60 | } 61 | return res; 62 | } 63 | 64 | @BatchCacheable(value = "user:info:02", keys = "#users.![userId]", cacheMode = CacheMode.SECOND, 65 | firstCache = @FirstCache(expireTime = 10, timeUnit = TimeUnit.SECONDS), 66 | secondaryCache = @SecondaryCache(expireTime = 10, preloadTime = 3, forceRefresh = true, timeUnit = TimeUnit.SECONDS)) 67 | public List getUserByIdDisableSecondCache(List users) { 68 | logger.debug("user:info:01 测试禁用二级缓存"); 69 | ArrayList res = new ArrayList<>(); 70 | for (int i = 0; i < Math.min(users.size(), 2); i++) { 71 | User user = new User(); 72 | user.setUserId(users.get(i).getUserId()); 73 | user.setAge(i); 74 | user.setLastName(new String[]{"w", "y", Integer.toString(i)}); 75 | res.add(user); 76 | } 77 | return res; 78 | } 79 | 80 | 81 | @BatchCacheable(value = "user:info:all", keys = "#users.![userId]", 82 | firstCache = @FirstCache(expireTime = 4, timeUnit = TimeUnit.SECONDS), 83 | secondaryCache = @SecondaryCache(expireTime = 10, preloadTime = 9, forceRefresh = true, timeUnit = TimeUnit.SECONDS)) 84 | public List batchRefreshSecondCacheSyncFistCache(List users) { 85 | logger.debug("测试刷新二级缓存,同步更新一级缓存"); 86 | return users; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /layering-cache-aspectj/src/test/resources/application.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaolyuh/layering-cache/282d3c10fc935bbfd981397a0997bb27ffd9aeae/layering-cache-aspectj/src/test/resources/application.properties -------------------------------------------------------------------------------- /layering-cache-aspectj/src/test/resources/log4j.properties: -------------------------------------------------------------------------------- 1 | log4j.rootLogger=debug, stdout 2 | log4j.appender.stdout=org.apache.log4j.ConsoleAppender 3 | log4j.appender.stdout.layout=org.apache.log4j.PatternLayout 4 | log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n -------------------------------------------------------------------------------- /layering-cache-core/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | layering-cache 7 | com.github.xiaolyuh 8 | 3.5.0 9 | 10 | 4.0.0 11 | 12 | layering-cache-core 13 | layering-cache-core 14 | 多级缓存核心模块 15 | jar 16 | 17 | 18 | 19 | com.github.ben-manes.caffeine 20 | caffeine 21 | true 22 | 23 | 24 | 25 | io.lettuce 26 | lettuce-core 27 | true 28 | 29 | 30 | 31 | org.springframework 32 | spring-core 33 | true 34 | 35 | 36 | 37 | org.springframework 38 | spring-beans 39 | true 40 | 41 | 42 | 43 | org.springframework 44 | spring-context 45 | true 46 | 47 | 48 | 49 | org.slf4j 50 | slf4j-api 51 | true 52 | 53 | 54 | 55 | com.alibaba 56 | fastjson 57 | true 58 | 59 | 60 | 61 | com.esotericsoftware 62 | kryo-shaded 63 | true 64 | 65 | 66 | 67 | io.protostuff 68 | protostuff-core 69 | true 70 | 71 | 72 | 73 | com.fasterxml.jackson.core 74 | jackson-databind 75 | true 76 | 77 | 78 | 79 | io.protostuff 80 | protostuff-runtime 81 | true 82 | 83 | 84 | 85 | org.springframework.boot 86 | spring-boot-starter-test 87 | true 88 | test 89 | 90 | 91 | 92 | org.slf4j 93 | slf4j-log4j12 94 | true 95 | test 96 | 97 | 98 | 99 | org.projectlombok 100 | lombok 101 | true 102 | provided 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /layering-cache-core/src/main/java/com/github/xiaolyuh/cache/Cache.java: -------------------------------------------------------------------------------- 1 | package com.github.xiaolyuh.cache; 2 | 3 | import com.github.xiaolyuh.manager.CacheManager; 4 | import com.github.xiaolyuh.stats.CacheStats; 5 | import java.util.List; 6 | import java.util.Map; 7 | import java.util.concurrent.Callable; 8 | import java.util.function.Function; 9 | 10 | /** 11 | * 缓存的顶级接口 12 | * 13 | * @author yuhao.wang 14 | */ 15 | public interface Cache { 16 | 17 | /** 18 | * 返回缓存名称 19 | * 20 | * @return String 21 | */ 22 | String getName(); 23 | 24 | /** 25 | * 返回真实Cache对象 26 | * 27 | * @return Object 28 | */ 29 | Object getNativeCache(); 30 | 31 | /** 32 | * 根据KEY返回缓存中对应的值,并将其返回类型转换成对应类型,如果对应key不存在返回NULL 33 | * 34 | * @param key 缓存key 35 | * @param resultType 返回值类型 36 | * @param Object 37 | * @return 缓存key对应的值 38 | */ 39 | T get(String key, Class resultType); 40 | 41 | /** 42 | * 根据KEY返回缓存中对应的值,并将其返回类型转换成对应类型,如果对应key不存在则调用valueLoader加载数据 43 | * 44 | * @param key 缓存key 45 | * @param resultType 返回值类型 46 | * @param valueLoader 加载缓存的回调方法 47 | * @param Object 48 | * @return 缓存key对应的值 49 | */ 50 | T get(String key, Class resultType, Callable valueLoader); 51 | 52 | /** 53 | * 根据KEY返回缓存中对应的值,并将其返回类型转换成对应类型。只返回缓存中存在的KEY-VALUE对 54 | * 55 | * @param keys 缓存keys 56 | * @param resultType 返回值类型 57 | * @param 键的类型 58 | * @param 值的类型 59 | * @return 缓存key对应的值 60 | */ 61 | Map getAll(List keys, Class resultType); 62 | 63 | /** 64 | * 根据KEY返回缓存中对应的值,并将其返回类型转换成对应类型,如果对应key不存在则调用valueLoader加载数据 65 | * 66 | * @param keys 缓存keys 67 | * @param resultType 返回值类型 68 | * @param valueLoader 加载缓存的回调方法 69 | * @param 键的类型 70 | * @param 值的类型 71 | * @return 缓存key对应的值 72 | */ 73 | Map getAll(List keys, Class resultType, Function valueLoader); 74 | 75 | /** 76 | * 将对应key-value放到缓存,如果key原来有值就直接覆盖 77 | * 78 | * @param key 缓存key 79 | * @param value 缓存的值 80 | */ 81 | void put(String key, Object value); 82 | 83 | /** 84 | * 如果缓存key没有对应的值就将值put到缓存,如果有就直接返回原有的值 85 | *

就相当于: 86 | *


 87 |      * Object existingValue = cache.get(key);
 88 |      * if (existingValue == null) {
 89 |      *     cache.put(key, value);
 90 |      *     return null;
 91 |      * } else {
 92 |      *     return existingValue;
 93 |      * }
 94 |      * 
95 | * except that the action is performed atomically. While all out-of-the-box 96 | * {@link CacheManager} implementations are able to perform the put atomically, 97 | * the operation may also be implemented in two steps, e.g. with a check for 98 | * presence and a subsequent put, in a non-atomic way. Check the documentation 99 | * of the native cache implementation that you are using for more details. 100 | * 101 | * @param key 缓存key 102 | * @param value 缓存key对应的值 103 | * @param resultType 返回值类型 104 | * @param T 105 | * @return 因为值本身可能为NULL,或者缓存key本来就没有对应值的时候也为NULL, 106 | * 所以如果返回NULL就表示已经将key-value键值对放到了缓存中 107 | * @since 4.1 108 | */ 109 | T putIfAbsent(String key, Object value, Class resultType); 110 | 111 | /** 112 | * 在缓存中删除对应的key 113 | * 114 | * @param key 缓存key 115 | */ 116 | void evict(String key); 117 | 118 | /** 119 | * 在缓存中删除对应的keys 120 | * 121 | * @param keys 缓存keys 122 | */ 123 | void evictAll(List keys); 124 | 125 | /** 126 | * 清楚缓存 127 | */ 128 | void clear(); 129 | 130 | /** 131 | * 获取统计信息 132 | * 133 | * @return {@link CacheStats} 134 | */ 135 | CacheStats getCacheStats(); 136 | 137 | /** 138 | * 缓存预估size 139 | * 140 | * @return 预估大小 141 | */ 142 | default long estimatedSize() { 143 | return 0; 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /layering-cache-core/src/main/java/com/github/xiaolyuh/cache/redis/RedisCacheKey.java: -------------------------------------------------------------------------------- 1 | package com.github.xiaolyuh.cache.redis; 2 | 3 | import com.github.xiaolyuh.redis.serializer.RedisSerializer; 4 | import com.github.xiaolyuh.redis.serializer.StringRedisSerializer; 5 | import java.util.Arrays; 6 | import org.springframework.util.Assert; 7 | 8 | /** 9 | * redis key 生成 10 | * 11 | * @author yuhao.wang3 12 | */ 13 | public class RedisCacheKey { 14 | 15 | /** 16 | * 前缀序列化器 17 | */ 18 | private final RedisSerializer prefixSerializer1 = new StringRedisSerializer(); 19 | 20 | /** 21 | * 缓存key 22 | */ 23 | private final Object keyElement; 24 | 25 | /** 26 | * 缓存名称 27 | */ 28 | private String cacheName; 29 | 30 | /** 31 | * 是否使用缓存前缀 32 | */ 33 | private boolean usePrefix = true; 34 | 35 | /** 36 | * RedisTemplate 的key序列化器 37 | */ 38 | private final RedisSerializer serializer; 39 | 40 | /** 41 | * 缓存预加载时间 42 | */ 43 | private long preloadTime; 44 | 45 | /** 46 | * @param keyElement 缓存key 47 | * @param serializer RedisSerializer 48 | */ 49 | public RedisCacheKey(Object keyElement, RedisSerializer serializer) { 50 | 51 | Assert.notNull(keyElement, "缓存key不能为NULL"); 52 | Assert.notNull(serializer, "key的序列化器不能为NULL"); 53 | this.keyElement = keyElement; 54 | this.serializer = serializer; 55 | } 56 | 57 | /** 58 | * 获取缓存key 59 | * 60 | * @return String 61 | */ 62 | public String getKey() { 63 | 64 | return new String(getKeyBytes()); 65 | } 66 | 67 | /** 68 | * 获取key的byte数组 69 | * 70 | * @return byte[] 71 | */ 72 | public byte[] getKeyBytes() { 73 | 74 | byte[] rawKey = serializeKeyElement(); 75 | if (!usePrefix) { 76 | return rawKey; 77 | } 78 | byte[] prefix = getPrefix(); 79 | byte[] prefixedKey = Arrays.copyOf(prefix, prefix.length + rawKey.length); 80 | System.arraycopy(rawKey, 0, prefixedKey, prefix.length, rawKey.length); 81 | 82 | return prefixedKey; 83 | } 84 | 85 | private byte[] serializeKeyElement() { 86 | 87 | if (serializer == null && keyElement instanceof byte[]) { 88 | return (byte[]) keyElement; 89 | } 90 | 91 | return serializer.serialize(keyElement); 92 | } 93 | 94 | /** 95 | * 获取缓存前缀,默认缓存前缀是":" 96 | * 97 | * @return byte[] 98 | */ 99 | public byte[] getPrefix() { 100 | return prefixSerializer1.serialize((cacheName.concat(":"))); 101 | } 102 | 103 | /** 104 | * 设置缓存名称 105 | * 106 | * @param cacheName cacheName 107 | * @return RedisCacheKey 108 | */ 109 | public RedisCacheKey cacheName(String cacheName) { 110 | this.cacheName = cacheName; 111 | return this; 112 | } 113 | 114 | /** 115 | * 设置预加载时间 116 | * 117 | * @param preloadTime preloadTime 118 | * @return RedisCacheKey 119 | */ 120 | public RedisCacheKey preloadTime(long preloadTime) { 121 | this.preloadTime = preloadTime; 122 | return this; 123 | } 124 | 125 | /** 126 | * 设置是否使用缓存前缀,默认使用 127 | * 128 | * @param usePrefix usePrefix 129 | * @return RedisCacheKey 130 | */ 131 | public RedisCacheKey usePrefix(boolean usePrefix) { 132 | this.usePrefix = usePrefix; 133 | return this; 134 | } 135 | 136 | public Object getKeyElement() { 137 | return keyElement; 138 | } 139 | 140 | public long getPreloadTime() { 141 | return preloadTime; 142 | } 143 | } -------------------------------------------------------------------------------- /layering-cache-core/src/main/java/com/github/xiaolyuh/listener/RedisMessageListener.java: -------------------------------------------------------------------------------- 1 | package com.github.xiaolyuh.listener; 2 | 3 | import com.github.xiaolyuh.manager.AbstractCacheManager; 4 | import com.github.xiaolyuh.util.BeanFactory; 5 | import io.lettuce.core.pubsub.RedisPubSubListener; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | /** 10 | * redis消息的订阅者 11 | * 12 | * @author yuhao.wang 13 | */ 14 | public class RedisMessageListener implements RedisPubSubListener { 15 | private static final Logger log = LoggerFactory.getLogger(RedisMessageListener.class); 16 | 17 | public static final String CHANNEL = "layering-cache-channel"; 18 | 19 | /** 20 | * redis消息处理器 21 | */ 22 | private RedisMessageService redisMessageService; 23 | 24 | public void init(AbstractCacheManager cacheManager) { 25 | this.redisMessageService = BeanFactory.getBean(RedisMessageService.class).init(cacheManager); 26 | // 创建监听 27 | cacheManager.getRedisClient().subscribe(this, RedisMessageListener.CHANNEL); 28 | } 29 | 30 | @Override 31 | public void message(String channel, String message) { 32 | try { 33 | if (log.isDebugEnabled()) { 34 | log.debug("redis消息订阅者接收到频道【{}】发布的消息。消息内容:{}", channel, message); 35 | } 36 | 37 | // 更新最后一次处理拉消息的时间 38 | RedisMessageService.updateLastPushTime(); 39 | 40 | redisMessageService.pullMessage(); 41 | } catch (Exception e) { 42 | log.error("layering-cache 清楚一级缓存异常:{}", e.getMessage(), e); 43 | } 44 | } 45 | 46 | @Override 47 | public void message(String pattern, String channel, String message) { 48 | 49 | } 50 | 51 | @Override 52 | public void subscribed(String channel, long count) { 53 | 54 | } 55 | 56 | @Override 57 | public void psubscribed(String pattern, long count) { 58 | 59 | } 60 | 61 | @Override 62 | public void unsubscribed(String channel, long count) { 63 | 64 | } 65 | 66 | @Override 67 | public void punsubscribed(String pattern, long count) { 68 | 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /layering-cache-core/src/main/java/com/github/xiaolyuh/listener/RedisMessagePullTask.java: -------------------------------------------------------------------------------- 1 | package com.github.xiaolyuh.listener; 2 | 3 | import com.github.xiaolyuh.manager.AbstractCacheManager; 4 | import com.github.xiaolyuh.util.BeanFactory; 5 | import com.github.xiaolyuh.util.NamedThreadFactory; 6 | import java.util.Calendar; 7 | import java.util.Random; 8 | import java.util.concurrent.ScheduledThreadPoolExecutor; 9 | import java.util.concurrent.TimeUnit; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | 13 | /** 14 | * redis消息拉模式 15 | * 16 | * @author yuhao.wang 17 | */ 18 | public class RedisMessagePullTask { 19 | private static final Logger log = LoggerFactory.getLogger(RedisMessagePullTask.class); 20 | 21 | /** 22 | * 定时任务线程池 23 | */ 24 | private static final ScheduledThreadPoolExecutor EXECUTOR = new ScheduledThreadPoolExecutor(3, new NamedThreadFactory("layering-cache-pull-message")); 25 | 26 | /** 27 | * redis消息处理器 28 | */ 29 | private RedisMessageService redisMessageService; 30 | 31 | public void init(AbstractCacheManager cacheManager) { 32 | Random random = new Random(); 33 | int initialDelay = Math.abs(random.nextInt()) % 23 + 1; 34 | int delay = Math.abs(random.nextInt()) % 7 + 1; 35 | log.info("一级缓存拉模式同步消息每隔{}秒,执行一次", delay); 36 | redisMessageService = BeanFactory.getBean(RedisMessageService.class).init(cacheManager); 37 | // 1. 服务启动同步最新的偏移量 38 | BeanFactory.getBean(RedisMessageService.class).syncOffset(); 39 | // 2. 启动PULL TASK 40 | startPullTask(initialDelay, delay); 41 | // 3. 启动重置本地偏消息移量任务 42 | clearMessageQueueTask(); 43 | // 4. 重连检测 44 | reconnectionTask(initialDelay, delay); 45 | } 46 | 47 | /** 48 | * 启动PULL TASK 49 | */ 50 | private void startPullTask(int initialDelay, int delay) { 51 | EXECUTOR.scheduleWithFixedDelay(() -> { 52 | try { 53 | redisMessageService.pullMessage(); 54 | } catch (Exception e) { 55 | log.error("layering-cache PULL 方式清楚一级缓存异常:{}", e.getMessage(), e); 56 | } 57 | // 初始时间间隔是7秒 58 | }, initialDelay, delay, TimeUnit.SECONDS); 59 | } 60 | 61 | /** 62 | * 启动清空消息队列的任务 63 | */ 64 | private void clearMessageQueueTask() { 65 | 66 | Calendar cal = Calendar.getInstance(); 67 | cal.set(Calendar.HOUR_OF_DAY, 3); 68 | cal.set(Calendar.MINUTE, 0); 69 | cal.set(Calendar.SECOND, 0); 70 | long initialDelay = System.currentTimeMillis() - cal.getTimeInMillis(); 71 | initialDelay = initialDelay > 0 ? initialDelay : 1; 72 | // 每天晚上凌晨3:00执行任务 73 | EXECUTOR.scheduleWithFixedDelay(() -> { 74 | try { 75 | redisMessageService.clearMessageQueue(); 76 | } catch (Exception e) { 77 | e.printStackTrace(); 78 | log.error("layering-cache 重置本地消息偏移量异常:{}", e.getMessage(), e); 79 | } 80 | }, initialDelay, TimeUnit.DAYS.toMillis(1), TimeUnit.MILLISECONDS); 81 | 82 | } 83 | 84 | /** 85 | * 启动重连pub/sub检查 86 | */ 87 | private void reconnectionTask(int initialDelay, int delay) { 88 | EXECUTOR.scheduleWithFixedDelay(() -> redisMessageService.reconnection(), 89 | initialDelay, delay, TimeUnit.SECONDS); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /layering-cache-core/src/main/java/com/github/xiaolyuh/listener/RedisPubSubMessage.java: -------------------------------------------------------------------------------- 1 | package com.github.xiaolyuh.listener; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * redis pub/sub 消息 7 | * 8 | * @author yuhao.wang3 9 | */ 10 | public class RedisPubSubMessage implements Serializable { 11 | public static final String SOURCE = "web-manage"; 12 | 13 | /** 14 | * 缓存名称 15 | */ 16 | private String cacheName; 17 | 18 | /** 19 | * 缓存key 20 | */ 21 | private String key; 22 | 23 | /** 24 | * 消息类型 25 | */ 26 | private RedisPubSubMessageType messageType; 27 | 28 | /** 29 | * 消息来源 30 | */ 31 | private String source; 32 | 33 | public String getCacheName() { 34 | return cacheName; 35 | } 36 | 37 | public void setCacheName(String cacheName) { 38 | this.cacheName = cacheName; 39 | } 40 | 41 | public String getKey() { 42 | return key; 43 | } 44 | 45 | public void setKey(String key) { 46 | this.key = key; 47 | } 48 | 49 | public RedisPubSubMessageType getMessageType() { 50 | return messageType; 51 | } 52 | 53 | public void setMessageType(RedisPubSubMessageType messageType) { 54 | this.messageType = messageType; 55 | } 56 | 57 | public String getSource() { 58 | return source; 59 | } 60 | 61 | public void setSource(String source) { 62 | this.source = source; 63 | } 64 | } -------------------------------------------------------------------------------- /layering-cache-core/src/main/java/com/github/xiaolyuh/listener/RedisPubSubMessageType.java: -------------------------------------------------------------------------------- 1 | package com.github.xiaolyuh.listener; 2 | 3 | /** 4 | * 消息类型 5 | * 6 | * @author yuhao.wang3 7 | */ 8 | public enum RedisPubSubMessageType { 9 | /** 10 | * 删除缓存 11 | */ 12 | EVICT("删除缓存"), 13 | 14 | /** 15 | * 清空缓存 16 | */ 17 | CLEAR("清空缓存"); 18 | 19 | private String label; 20 | 21 | RedisPubSubMessageType(String label) { 22 | this.label = label; 23 | } 24 | 25 | public String getLabel() { 26 | return label; 27 | } 28 | } -------------------------------------------------------------------------------- /layering-cache-core/src/main/java/com/github/xiaolyuh/listener/RedisPublisher.java: -------------------------------------------------------------------------------- 1 | package com.github.xiaolyuh.listener; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.github.xiaolyuh.redis.clinet.RedisClient; 5 | import com.github.xiaolyuh.util.GlobalConfig; 6 | import java.util.concurrent.TimeUnit; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | /** 11 | * redis消息的发布者 12 | * 13 | * @author yuhao.wang 14 | */ 15 | public class RedisPublisher { 16 | private static final Logger logger = LoggerFactory.getLogger(RedisPublisher.class); 17 | 18 | private RedisPublisher() { 19 | } 20 | 21 | /** 22 | * 发布消息到频道(Channel) 23 | * 24 | * @param redisClient redis客户端 25 | * @param message 消息内容 26 | */ 27 | public static void publisher(RedisClient redisClient, RedisPubSubMessage message) { 28 | publisher(redisClient, message, GlobalConfig.NAMESPACE); 29 | } 30 | 31 | /** 32 | * 发布消息到频道(Channel) 33 | * 34 | * @param redisClient redis客户端 35 | * @param message 消息内容 36 | * @param nameSpace 命名空间 37 | */ 38 | public static void publisher(RedisClient redisClient, RedisPubSubMessage message, String nameSpace) { 39 | String messageJson = JSON.toJSONString(message); 40 | // pull 拉模式消息 41 | redisClient.lpush(GlobalConfig.getMessageRedisKey(nameSpace), GlobalConfig.GLOBAL_REDIS_SERIALIZER, messageJson); 42 | redisClient.expire(GlobalConfig.getMessageRedisKey(nameSpace), 25, TimeUnit.HOURS); 43 | // pub/sub 推模式消息 44 | redisClient.publish(RedisMessageListener.CHANNEL, "m"); 45 | if (logger.isDebugEnabled()) { 46 | logger.debug("redis消息发布者向频道【{}】发布了【{}】消息", RedisMessageListener.CHANNEL, messageJson); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /layering-cache-core/src/main/java/com/github/xiaolyuh/manager/CacheManager.java: -------------------------------------------------------------------------------- 1 | package com.github.xiaolyuh.manager; 2 | 3 | import com.github.xiaolyuh.cache.Cache; 4 | import com.github.xiaolyuh.setting.LayeringCacheSetting; 5 | import java.util.Collection; 6 | 7 | /** 8 | * 缓存管理器 9 | * 允许通过缓存名称来获的对应的 {@link Cache}. 10 | * 11 | * @author yuhao.wang3 12 | */ 13 | public interface CacheManager { 14 | 15 | /** 16 | * 根据缓存名称返回对应的{@link Collection}. 17 | * 18 | * @param name 缓存的名称 (不能为 {@code null}) 19 | * @return 返回对应名称的Cache, 如果没找到返回 {@code null} 20 | */ 21 | Collection getCache(String name); 22 | 23 | /** 24 | * 根据缓存名称返回对应的{@link Cache},如果没有找到就新建一个并放到容器 25 | * 26 | * @param name 缓存名称 27 | * @param layeringCacheSetting 多级缓存配置 28 | * @return {@link Cache} 29 | */ 30 | Cache getCache(String name, LayeringCacheSetting layeringCacheSetting); 31 | 32 | /** 33 | * 获取所有缓存名称的集合 34 | * 35 | * @return 所有缓存名称的集合 36 | */ 37 | Collection getCacheNames(); 38 | } 39 | -------------------------------------------------------------------------------- /layering-cache-core/src/main/java/com/github/xiaolyuh/manager/LayeringCacheManager.java: -------------------------------------------------------------------------------- 1 | package com.github.xiaolyuh.manager; 2 | 3 | import com.github.xiaolyuh.cache.Cache; 4 | import com.github.xiaolyuh.cache.LayeringCache; 5 | import com.github.xiaolyuh.cache.caffeine.CaffeineCache; 6 | import com.github.xiaolyuh.cache.redis.RedisCache; 7 | import com.github.xiaolyuh.redis.clinet.RedisClient; 8 | import com.github.xiaolyuh.setting.LayeringCacheSetting; 9 | 10 | /** 11 | * @author yuhao.wang 12 | */ 13 | public class LayeringCacheManager extends AbstractCacheManager { 14 | public LayeringCacheManager(RedisClient redisClient) { 15 | this.redisClient = redisClient; 16 | cacheManagers.add(this); 17 | } 18 | 19 | @Override 20 | protected Cache getMissingCache(String name, LayeringCacheSetting layeringCacheSetting) { 21 | // 创建一级缓存 22 | CaffeineCache caffeineCache = new CaffeineCache(name, layeringCacheSetting.getFirstCacheSetting(), getStats(), layeringCacheSetting.getCacheMode()); 23 | // 创建二级缓存 24 | RedisCache redisCache = new RedisCache(name, redisClient, layeringCacheSetting.getSecondaryCacheSetting(), getStats(), layeringCacheSetting.getCacheMode()); 25 | return new LayeringCache(redisClient, caffeineCache, redisCache, super.getStats(), layeringCacheSetting); 26 | } 27 | 28 | @Override 29 | public boolean equals(Object o) { 30 | return super.equals(o); 31 | } 32 | 33 | @Override 34 | public int hashCode() { 35 | return super.hashCode(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /layering-cache-core/src/main/java/com/github/xiaolyuh/redis/clinet/RedisClientException.java: -------------------------------------------------------------------------------- 1 | package com.github.xiaolyuh.redis.clinet; 2 | 3 | /** 4 | * redis客户端异常 5 | */ 6 | public class RedisClientException extends RuntimeException { 7 | public RedisClientException() { 8 | super(); 9 | } 10 | 11 | public RedisClientException(String message) { 12 | super(message); 13 | } 14 | 15 | public RedisClientException(String message, Throwable cause) { 16 | super(message, cause); 17 | } 18 | 19 | public RedisClientException(Throwable cause) { 20 | super(cause); 21 | } 22 | 23 | protected RedisClientException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { 24 | super(message, cause, enableSuppression, writableStackTrace); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /layering-cache-core/src/main/java/com/github/xiaolyuh/redis/clinet/RedisProperties.java: -------------------------------------------------------------------------------- 1 | package com.github.xiaolyuh.redis.clinet; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class RedisProperties { 7 | //********************单机配置项**************************/ 8 | /** 9 | * 单机配置项 10 | */ 11 | String host = "localhost"; 12 | Integer port = 6379; 13 | 14 | //********************集群配置**************************/ 15 | /** 16 | * 不为空表示集群版,示例 17 | * localhost:7379,localhost2:7379 18 | */ 19 | String cluster = ""; 20 | 21 | //********************哨兵配置**************************/ 22 | /** 23 | * 哨兵master名称,示例:mymaster 24 | */ 25 | String sentinelMaster = "mymaster"; 26 | 27 | /** 28 | * 哨兵节点,示例:localhost:26397,localhost2:26397 29 | */ 30 | String sentinelNodes = ""; 31 | 32 | //********************通用配置**************************/ 33 | /** 34 | * 使用数据库 35 | */ 36 | Integer database = 0; 37 | 38 | /** 39 | * 密码 40 | */ 41 | String password = null; 42 | 43 | /** 44 | * 超时时间 单位秒 默认一个小时 45 | */ 46 | Integer timeout = 3600; 47 | 48 | /** 49 | * 序列化方式: 50 | * com.github.xiaolyuh.redis.serializer.KryoRedisSerializer 51 | * com.github.xiaolyuh.redis.serializer.FastJsonRedisSerializer 52 | * com.github.xiaolyuh.redis.serializer.JacksonRedisSerializer 53 | * com.github.xiaolyuh.redis.serializer.JdkRedisSerializer 54 | * com.github.xiaolyuh.redis.serializer.ProtostuffRedisSerializer 55 | */ 56 | String serializer = "com.github.xiaolyuh.redis.serializer.ProtostuffRedisSerializer"; 57 | } -------------------------------------------------------------------------------- /layering-cache-core/src/main/java/com/github/xiaolyuh/redis/command/TencentScan.java: -------------------------------------------------------------------------------- 1 | package com.github.xiaolyuh.redis.command; 2 | 3 | import io.lettuce.core.dynamic.Commands; 4 | import io.lettuce.core.dynamic.annotation.Command; 5 | import java.util.List; 6 | 7 | /** 8 | * 腾讯云redis scan命令 9 | * 10 | * @author olafwang 11 | */ 12 | public interface TencentScan extends Commands { 13 | 14 | @Command("scan ?0 match ?1 count ?2 ?3") 15 | List scan(long cursor, String pattern, int count, String nodeId); 16 | } -------------------------------------------------------------------------------- /layering-cache-core/src/main/java/com/github/xiaolyuh/redis/serializer/AbstractRedisSerializer.java: -------------------------------------------------------------------------------- 1 | package com.github.xiaolyuh.redis.serializer; 2 | 3 | import com.github.xiaolyuh.support.NullValue; 4 | import java.util.Objects; 5 | 6 | /** 7 | * 序列化方式的抽象实现 8 | * 9 | * @author yuhao.wang 10 | */ 11 | public abstract class AbstractRedisSerializer implements RedisSerializer { 12 | private byte[] nullValueBytes; 13 | 14 | /** 15 | * 获取空值的序列化值 16 | * 17 | * @return byte[] 18 | */ 19 | public byte[] getNullValueBytes() { 20 | if (Objects.isNull(nullValueBytes)) { 21 | synchronized (this) { 22 | nullValueBytes = serialize(NullValue.INSTANCE); 23 | } 24 | } 25 | return nullValueBytes; 26 | } 27 | } -------------------------------------------------------------------------------- /layering-cache-core/src/main/java/com/github/xiaolyuh/redis/serializer/FastJsonRedisSerializer.java: -------------------------------------------------------------------------------- 1 | package com.github.xiaolyuh.redis.serializer; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.alibaba.fastjson.parser.Feature; 5 | import com.alibaba.fastjson.parser.ParserConfig; 6 | import com.alibaba.fastjson.serializer.SerializerFeature; 7 | import com.alibaba.fastjson.util.IOUtils; 8 | import java.util.Arrays; 9 | 10 | /** 11 | * FastJson 序列化方式 12 | * 13 | * @author yuhao.wang 14 | */ 15 | public class FastJsonRedisSerializer extends AbstractRedisSerializer { 16 | private static final ParserConfig DEFAULT_REDIS_CONFIG = new ParserConfig(); 17 | 18 | static { 19 | DEFAULT_REDIS_CONFIG.setAutoTypeSupport(true); 20 | } 21 | 22 | @Override 23 | public byte[] serialize(T value) throws SerializationException { 24 | if (value == null) { 25 | return SerializationUtils.EMPTY_ARRAY; 26 | } 27 | 28 | try { 29 | return JSON.toJSONBytes(value, SerializerFeature.WriteClassName); 30 | } catch (Exception e) { 31 | throw new SerializationException(String.format("FastJsonRedisSerializer 序列化异常: %s, 【%s】", e.getMessage(), JSON.toJSONString(value)), e); 32 | } 33 | } 34 | 35 | @Override 36 | public T deserialize(byte[] bytes, Class resultType) throws SerializationException { 37 | if (SerializationUtils.isEmpty(bytes)) { 38 | return null; 39 | } 40 | 41 | if (Arrays.equals(getNullValueBytes(), bytes)) { 42 | return null; 43 | } 44 | 45 | try { 46 | return JSON.parseObject(new String(bytes, IOUtils.UTF8), resultType, DEFAULT_REDIS_CONFIG, new Feature[0]); 47 | } catch (Exception e) { 48 | throw new SerializationException(String.format("FastJsonRedisSerializer 反序列化异常: %s, 【%s】", e.getMessage(), JSON.toJSONString(bytes)), e); 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /layering-cache-core/src/main/java/com/github/xiaolyuh/redis/serializer/JacksonRedisSerializer.java: -------------------------------------------------------------------------------- 1 | package com.github.xiaolyuh.redis.serializer; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.fasterxml.jackson.annotation.JsonAutoDetect; 5 | import com.fasterxml.jackson.annotation.JsonTypeInfo; 6 | import com.fasterxml.jackson.annotation.PropertyAccessor; 7 | import com.fasterxml.jackson.databind.ObjectMapper; 8 | import com.fasterxml.jackson.databind.SerializationFeature; 9 | import java.util.Arrays; 10 | 11 | /** 12 | * JackJson 序列化方式 13 | * 14 | * @author yuhao.wang 15 | */ 16 | public class JacksonRedisSerializer extends AbstractRedisSerializer { 17 | private final ObjectMapper objectMapper = new ObjectMapper(); 18 | 19 | { 20 | objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); 21 | objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY); 22 | objectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS); 23 | } 24 | 25 | @Override 26 | public byte[] serialize(T value) throws SerializationException { 27 | if (value == null) { 28 | return SerializationUtils.EMPTY_ARRAY; 29 | } 30 | try { 31 | return this.objectMapper.writeValueAsBytes(value); 32 | } catch (Exception e) { 33 | throw new SerializationException(String.format("JacksonRedisSerializer 序列化异常: %s, 【%s】", e.getMessage(), JSON.toJSONString(value)), e); 34 | } 35 | } 36 | 37 | @Override 38 | public T deserialize(byte[] bytes, Class resultType) throws SerializationException { 39 | if (SerializationUtils.isEmpty(bytes)) { 40 | return null; 41 | } 42 | 43 | if (Arrays.equals(getNullValueBytes(), bytes)) { 44 | return null; 45 | } 46 | 47 | try { 48 | return this.objectMapper.readValue(bytes, 0, bytes.length, resultType); 49 | } catch (Exception e) { 50 | throw new SerializationException(String.format("JacksonRedisSerializer 反序列化异常: %s, 【%s】", e.getMessage(), JSON.toJSONString(bytes)), e); 51 | } 52 | 53 | } 54 | } -------------------------------------------------------------------------------- /layering-cache-core/src/main/java/com/github/xiaolyuh/redis/serializer/JdkRedisSerializer.java: -------------------------------------------------------------------------------- 1 | package com.github.xiaolyuh.redis.serializer; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import java.io.ByteArrayInputStream; 5 | import java.io.ByteArrayOutputStream; 6 | import java.io.ObjectInputStream; 7 | import java.io.ObjectOutputStream; 8 | import java.io.Serializable; 9 | import java.util.Arrays; 10 | 11 | /** 12 | * JDK 序列化方式 13 | * 14 | * @author yuhao.wang 15 | */ 16 | public class JdkRedisSerializer extends AbstractRedisSerializer { 17 | @Override 18 | public byte[] serialize(T value) throws SerializationException { 19 | if (value == null) { 20 | return SerializationUtils.EMPTY_ARRAY; 21 | } 22 | 23 | if (!(value instanceof Serializable)) { 24 | throw new IllegalArgumentException(this.getClass().getSimpleName() + " requires a Serializable payload but received an object of type [" + value.getClass().getName() + "]"); 25 | } 26 | 27 | try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream(1024); 28 | ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream)) { 29 | 30 | objectOutputStream.writeObject(value); 31 | objectOutputStream.flush(); 32 | return outputStream.toByteArray(); 33 | } catch (Exception e) { 34 | throw new SerializationException(String.format("JdkRedisSerializer 序列化异常: %s, 【%s】", e.getMessage(), JSON.toJSONString(value)), e); 35 | } 36 | } 37 | 38 | @Override 39 | public T deserialize(byte[] bytes, Class resultType) throws SerializationException { 40 | if (SerializationUtils.isEmpty(bytes)) { 41 | return null; 42 | } 43 | 44 | if (Arrays.equals(getNullValueBytes(), bytes)) { 45 | return null; 46 | } 47 | 48 | try (ByteArrayInputStream byteStream = new ByteArrayInputStream(bytes); 49 | ObjectInputStream stream = new ObjectInputStream(byteStream)) { 50 | return (T) stream.readObject(); 51 | } catch (Exception e) { 52 | throw new SerializationException(String.format("JdkRedisSerializer 反序列化异常: %s, 【%s】", e.getMessage(), JSON.toJSONString(bytes)), e); 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /layering-cache-core/src/main/java/com/github/xiaolyuh/redis/serializer/KryoRedisSerializer.java: -------------------------------------------------------------------------------- 1 | package com.github.xiaolyuh.redis.serializer; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.esotericsoftware.kryo.Kryo; 5 | import com.esotericsoftware.kryo.io.Input; 6 | import com.esotericsoftware.kryo.io.Output; 7 | import java.io.ByteArrayOutputStream; 8 | import java.util.Arrays; 9 | 10 | /** 11 | * kryo 序列化方式 12 | * 13 | * @author yuhao.wang 14 | */ 15 | public class KryoRedisSerializer extends AbstractRedisSerializer { 16 | private static final ThreadLocal KRYO = ThreadLocal.withInitial(Kryo::new); 17 | 18 | @Override 19 | public byte[] serialize(T t) throws SerializationException { 20 | if (t == null) { 21 | return SerializationUtils.EMPTY_ARRAY; 22 | } 23 | 24 | Kryo kryo = KRYO.get(); 25 | // 设置成false 序列化速度更快,但是遇到循环应用序列化器会报栈内存溢出 26 | kryo.setReferences(false); 27 | kryo.register(t.getClass()); 28 | 29 | try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); 30 | Output output = new Output(baos)) { 31 | kryo.writeClassAndObject(output, t); 32 | output.flush(); 33 | return baos.toByteArray(); 34 | } catch (Exception e) { 35 | throw new SerializationException(String.format("KryoRedisSerializer 序列化异常: %s, 【%s】", e.getMessage(), JSON.toJSONString(t)), e); 36 | } finally { 37 | KRYO.remove(); 38 | } 39 | } 40 | 41 | @Override 42 | public T deserialize(byte[] bytes, Class resultType) throws SerializationException { 43 | if (SerializationUtils.isEmpty(bytes)) { 44 | return null; 45 | } 46 | 47 | if (Arrays.equals(getNullValueBytes(), bytes)) { 48 | return null; 49 | } 50 | 51 | Kryo kryo = KRYO.get(); 52 | // 设置成false 序列化速度更快,但是遇到循环应用序列化器会报栈内存溢出 53 | kryo.setReferences(false); 54 | kryo.register(resultType); 55 | 56 | try (Input input = new Input(bytes)) { 57 | Object result = kryo.readClassAndObject(input); 58 | return (T) result; 59 | } catch (Exception e) { 60 | throw new SerializationException(String.format("KryoRedisSerializer 反序列化异常: %s, 【%s】", e.getMessage(), JSON.toJSONString(bytes)), e); 61 | } finally { 62 | KRYO.remove(); 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /layering-cache-core/src/main/java/com/github/xiaolyuh/redis/serializer/ProtostuffRedisSerializer.java: -------------------------------------------------------------------------------- 1 | package com.github.xiaolyuh.redis.serializer; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import io.protostuff.LinkedBuffer; 5 | import io.protostuff.ProtostuffIOUtil; 6 | import io.protostuff.runtime.DefaultIdStrategy; 7 | import io.protostuff.runtime.IdStrategy; 8 | import io.protostuff.runtime.RuntimeSchema; 9 | import java.util.Arrays; 10 | 11 | /** 12 | * Protostuff 序列化方式 13 | * 14 | * @author yuhao.wang 15 | */ 16 | public class ProtostuffRedisSerializer extends AbstractRedisSerializer { 17 | static { 18 | System.getProperties().setProperty("protostuff.runtime.always_use_sun_reflection_factory", "true"); 19 | System.getProperties().setProperty("protostuff.runtime.preserve_null_elements", "true"); 20 | System.getProperties().setProperty("protostuff.runtime.morph_collection_interfaces", "true"); 21 | System.getProperties().setProperty("protostuff.runtime.morph_map_interfaces", "true"); 22 | System.getProperties().setProperty("protostuff.runtime.morph_non_final_pojos", "true"); 23 | } 24 | 25 | IdStrategy strategy = new DefaultIdStrategy(IdStrategy.DEFAULT_FLAGS | IdStrategy.COLLECTION_SCHEMA_ON_REPEATED_FIELDS, null, 0); 26 | RuntimeSchema schema = RuntimeSchema.createFrom(Wrapper.class, strategy); 27 | 28 | @Override 29 | public byte[] serialize(T value) throws SerializationException { 30 | if (value == null) { 31 | return SerializationUtils.EMPTY_ARRAY; 32 | } 33 | 34 | LinkedBuffer buffer = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE); 35 | try { 36 | return ProtostuffIOUtil.toByteArray(new Wrapper<>(value), schema, buffer); 37 | } catch (Exception e) { 38 | throw new SerializationException(String.format("ProtostuffRedisSerializer 序列化异常: %s, 【%s】", e.getMessage(), JSON.toJSONString(value)), e); 39 | } finally { 40 | buffer.clear(); 41 | } 42 | } 43 | 44 | @Override 45 | public T deserialize(byte[] bytes, Class resultType) throws SerializationException { 46 | if (SerializationUtils.isEmpty(bytes)) { 47 | return null; 48 | } 49 | 50 | if (Arrays.equals(getNullValueBytes(), bytes)) { 51 | return null; 52 | } 53 | 54 | 55 | try { 56 | Wrapper wrapper = new Wrapper<>(null); 57 | ProtostuffIOUtil.mergeFrom(bytes, wrapper, schema); 58 | return wrapper.getData(); 59 | } catch (Exception e) { 60 | throw new SerializationException(String.format("ProtostuffRedisSerializer 反序列化异常: %s, 【%s】", e.getMessage(), JSON.toJSONString(bytes)), e); 61 | } 62 | } 63 | 64 | /** 65 | * protobuff只能序列化pojo类,不能直接序列化List 或者Map,如果要序列化list或者map,需要用一个wrapper类包装一下 66 | * 67 | * @param T 68 | */ 69 | static class Wrapper { 70 | T data; 71 | 72 | public Wrapper(T data) { 73 | this.data = data; 74 | } 75 | 76 | public T getData() { 77 | return data; 78 | } 79 | } 80 | 81 | } -------------------------------------------------------------------------------- /layering-cache-core/src/main/java/com/github/xiaolyuh/redis/serializer/RedisSerializer.java: -------------------------------------------------------------------------------- 1 | package com.github.xiaolyuh.redis.serializer; 2 | 3 | import org.springframework.lang.Nullable; 4 | 5 | /** 6 | * redis序列化 7 | * 8 | * @author olafwang 9 | * @since 2020/6/29 3:25 下午 10 | */ 11 | public interface RedisSerializer { 12 | /** 13 | * 将给定对象序列化为二进制数据。 14 | * 15 | * @param value 需要序列化的对象.允许为 {@literal null}. 16 | * @param T 17 | * @return 返回对象的二进制数据. 允许为 {@literal null}. 18 | * @throws SerializationException 序列化异常 19 | */ 20 | @Nullable 21 | byte[] serialize(T value) throws SerializationException; 22 | 23 | /** 24 | * 将给定的二进制数据中反序列化对象。 25 | * 26 | * @param bytes 给定的二进制数据. 允许为 {@literal null}. 27 | * @param resultType 返回值类型 28 | * @param T 29 | * @return 反序列化后的对象.允许为 {@literal null}. 30 | * @throws SerializationException 序列化异常 31 | */ 32 | @Nullable 33 | T deserialize(@Nullable byte[] bytes, Class resultType) throws SerializationException; 34 | } 35 | -------------------------------------------------------------------------------- /layering-cache-core/src/main/java/com/github/xiaolyuh/redis/serializer/SerializationException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011-2013 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.github.xiaolyuh.redis.serializer; 17 | 18 | 19 | import com.github.xiaolyuh.support.NestedRuntimeException; 20 | 21 | /** 22 | * Generic exception indicating a serialization/deserialization error. 23 | * 24 | * @author Costin Leau 25 | */ 26 | public class SerializationException extends NestedRuntimeException { 27 | 28 | /** 29 | * Constructs a new SerializationException instance. 30 | * 31 | * @param msg msg 32 | * @param cause 原因 33 | */ 34 | public SerializationException(String msg, Throwable cause) { 35 | super(msg, cause); 36 | } 37 | 38 | /** 39 | * Constructs a new SerializationException instance. 40 | * 41 | * @param msg msg 42 | */ 43 | public SerializationException(String msg) { 44 | super(msg); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /layering-cache-core/src/main/java/com/github/xiaolyuh/redis/serializer/SerializationUtils.java: -------------------------------------------------------------------------------- 1 | package com.github.xiaolyuh.redis.serializer; 2 | 3 | /** 4 | * 序列化工具类 5 | * 6 | * @author yuhao.wang3 7 | */ 8 | public abstract class SerializationUtils { 9 | 10 | static final byte[] EMPTY_ARRAY = new byte[0]; 11 | 12 | static boolean isEmpty(byte[] data) { 13 | return (data == null || data.length == 0); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /layering-cache-core/src/main/java/com/github/xiaolyuh/redis/serializer/StringRedisSerializer.java: -------------------------------------------------------------------------------- 1 | package com.github.xiaolyuh.redis.serializer; 2 | 3 | import java.nio.charset.Charset; 4 | import java.nio.charset.StandardCharsets; 5 | import org.springframework.util.Assert; 6 | 7 | /** 8 | * 必须重写序列化器,否则@Cacheable注解的key会报类型转换错误 9 | * 10 | * @author yuhao.wang 11 | */ 12 | public class StringRedisSerializer implements RedisSerializer { 13 | 14 | private final Charset charset; 15 | 16 | public StringRedisSerializer() { 17 | this(StandardCharsets.UTF_8); 18 | } 19 | 20 | public StringRedisSerializer(Charset charset) { 21 | Assert.notNull(charset, "Charset must not be null!"); 22 | this.charset = charset; 23 | } 24 | 25 | @Override 26 | public byte[] serialize(T value) throws SerializationException { 27 | if (value == null) { 28 | return null; 29 | } 30 | 31 | if (value instanceof String) { 32 | return ((String) value).getBytes(charset); 33 | } 34 | throw new UnsupportedOperationException("String序列化方式不支持其他数据类型的序列化"); 35 | } 36 | 37 | @Override 38 | public String deserialize(byte[] bytes, Class resultType) throws SerializationException { 39 | 40 | return (bytes == null ? null : new String(bytes, charset)); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /layering-cache-core/src/main/java/com/github/xiaolyuh/setting/FirstCacheSetting.java: -------------------------------------------------------------------------------- 1 | package com.github.xiaolyuh.setting; 2 | 3 | import com.github.xiaolyuh.support.ExpireMode; 4 | import java.io.Serializable; 5 | import java.util.concurrent.TimeUnit; 6 | 7 | /** 8 | * 一级缓存配置项 9 | * 10 | * @author yuhao.wang 11 | */ 12 | public class FirstCacheSetting implements Serializable { 13 | 14 | /** 15 | * 缓存初始Size 16 | */ 17 | private int initialCapacity = 10; 18 | 19 | /** 20 | * 缓存最大Size 21 | */ 22 | private int maximumSize = 500; 23 | 24 | /** 25 | * 缓存有效时间 26 | */ 27 | private int expireTime = 0; 28 | 29 | /** 30 | * 缓存时间单位 31 | */ 32 | private TimeUnit timeUnit = TimeUnit.MILLISECONDS; 33 | 34 | /** 35 | * 缓存失效模式{@link ExpireMode} 36 | */ 37 | private ExpireMode expireMode = ExpireMode.WRITE; 38 | 39 | public FirstCacheSetting() { 40 | } 41 | 42 | /** 43 | * @param initialCapacity 缓存初始Size 44 | * @param maximumSize 缓存最大Size 45 | * @param expireTime 缓存有效时间 46 | * @param timeUnit 缓存时间单位 {@link TimeUnit} 47 | * @param expireMode 缓存失效模式{@link ExpireMode} 48 | */ 49 | public FirstCacheSetting(int initialCapacity, int maximumSize, int expireTime, TimeUnit timeUnit, ExpireMode expireMode) { 50 | this.initialCapacity = initialCapacity; 51 | this.maximumSize = maximumSize; 52 | this.expireTime = expireTime; 53 | this.timeUnit = timeUnit; 54 | this.expireMode = expireMode; 55 | } 56 | 57 | public int getInitialCapacity() { 58 | return initialCapacity; 59 | } 60 | 61 | public void setInitialCapacity(int initialCapacity) { 62 | this.initialCapacity = initialCapacity; 63 | } 64 | 65 | public int getMaximumSize() { 66 | return maximumSize; 67 | } 68 | 69 | public void setMaximumSize(int maximumSize) { 70 | this.maximumSize = maximumSize; 71 | } 72 | 73 | public int getExpireTime() { 74 | return expireTime; 75 | } 76 | 77 | public void setExpireTime(int expireTime) { 78 | this.expireTime = expireTime; 79 | } 80 | 81 | public TimeUnit getTimeUnit() { 82 | return timeUnit; 83 | } 84 | 85 | public void setTimeUnit(TimeUnit timeUnit) { 86 | this.timeUnit = timeUnit; 87 | } 88 | 89 | public ExpireMode getExpireMode() { 90 | return expireMode; 91 | } 92 | 93 | public void setExpireMode(ExpireMode expireMode) { 94 | this.expireMode = expireMode; 95 | } 96 | 97 | public boolean isAllowNullValues() { 98 | return false; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /layering-cache-core/src/main/java/com/github/xiaolyuh/setting/LayeringCacheSetting.java: -------------------------------------------------------------------------------- 1 | package com.github.xiaolyuh.setting; 2 | 3 | import com.alibaba.fastjson.annotation.JSONField; 4 | import com.github.xiaolyuh.support.CacheMode; 5 | import java.io.Serializable; 6 | 7 | /** 8 | * 多级缓存配置项 9 | * 10 | * @author yuhao.wang 11 | */ 12 | public class LayeringCacheSetting implements Serializable { 13 | private static final String SPLIT = "-"; 14 | /** 15 | * 内部缓存名,由[一级缓存有效时间-二级缓存有效时间]组成 16 | */ 17 | private String internalKey; 18 | 19 | /** 20 | * 描述,数据监控页面使用 21 | */ 22 | private String depict; 23 | 24 | /** 25 | * 是否使用一级缓存 26 | */ 27 | CacheMode cacheMode = CacheMode.ALL; 28 | 29 | /** 30 | * 一级缓存配置 31 | */ 32 | private FirstCacheSetting firstCacheSetting; 33 | 34 | /** 35 | * 二级缓存配置 36 | */ 37 | private SecondaryCacheSetting secondaryCacheSetting; 38 | 39 | public LayeringCacheSetting() { 40 | } 41 | 42 | public LayeringCacheSetting(FirstCacheSetting firstCacheSetting, SecondaryCacheSetting secondaryCacheSetting, 43 | String depict, CacheMode cacheMode) { 44 | this.firstCacheSetting = firstCacheSetting; 45 | this.secondaryCacheSetting = secondaryCacheSetting; 46 | this.depict = depict; 47 | this.cacheMode = cacheMode; 48 | internalKey(); 49 | } 50 | 51 | @JSONField(serialize = false, deserialize = false) 52 | private void internalKey() { 53 | // 一级缓存有效时间-二级缓存有效时间 54 | StringBuilder sb = new StringBuilder(); 55 | if (firstCacheSetting != null) { 56 | sb.append(firstCacheSetting.getTimeUnit().toMillis(firstCacheSetting.getExpireTime())); 57 | } 58 | if (secondaryCacheSetting != null) { 59 | sb.append(SPLIT); 60 | sb.append(secondaryCacheSetting.getTimeUnit().toMillis(secondaryCacheSetting.getExpiration())); 61 | } 62 | internalKey = sb.toString(); 63 | } 64 | 65 | public FirstCacheSetting getFirstCacheSetting() { 66 | return firstCacheSetting; 67 | } 68 | 69 | public SecondaryCacheSetting getSecondaryCacheSetting() { 70 | return secondaryCacheSetting; 71 | } 72 | 73 | public String getInternalKey() { 74 | return internalKey; 75 | } 76 | 77 | public CacheMode getCacheMode() { 78 | return cacheMode; 79 | } 80 | 81 | public void internalKey(String internalKey) { 82 | this.internalKey = internalKey; 83 | } 84 | 85 | public void setFirstCacheSetting(FirstCacheSetting firstCacheSetting) { 86 | this.firstCacheSetting = firstCacheSetting; 87 | } 88 | 89 | public void setSecondaryCacheSetting(SecondaryCacheSetting secondaryCacheSetting) { 90 | this.secondaryCacheSetting = secondaryCacheSetting; 91 | } 92 | 93 | public void setInternalKey(String internalKey) { 94 | this.internalKey = internalKey; 95 | } 96 | 97 | public String getDepict() { 98 | return depict; 99 | } 100 | 101 | public void setDepict(String depict) { 102 | this.depict = depict; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /layering-cache-core/src/main/java/com/github/xiaolyuh/setting/SecondaryCacheSetting.java: -------------------------------------------------------------------------------- 1 | package com.github.xiaolyuh.setting; 2 | 3 | import java.io.Serializable; 4 | import java.util.concurrent.TimeUnit; 5 | 6 | /** 7 | * 二级缓存配置项 8 | * 9 | * @author yuhao.wang 10 | */ 11 | public class SecondaryCacheSetting implements Serializable { 12 | /** 13 | * 缓存有效时间 14 | */ 15 | private long expiration = 0; 16 | 17 | /** 18 | * 缓存主动在失效前强制刷新缓存的时间 19 | */ 20 | private long preloadTime = 0; 21 | 22 | /** 23 | * 时间单位 {@link TimeUnit} 24 | */ 25 | private TimeUnit timeUnit = TimeUnit.MICROSECONDS; 26 | 27 | /** 28 | * 是否强制刷新(走数据库),默认是false 29 | */ 30 | private boolean forceRefresh = false; 31 | 32 | /** 33 | * 是否使用缓存名称作为 redis key 前缀 34 | */ 35 | private boolean usePrefix = true; 36 | 37 | /** 38 | * 是否允许存NULL值 39 | */ 40 | boolean allowNullValue = false; 41 | 42 | /** 43 | * 非空值和null值之间的时间倍率,默认是1。allowNullValue=true才有效 44 | *

45 | * 如配置缓存的有效时间是200秒,倍率这设置成10, 46 | * 那么当缓存value为null时,缓存的有效时间将是20秒,非空时为200秒 47 | *

48 | */ 49 | int magnification = 1; 50 | 51 | public SecondaryCacheSetting() { 52 | } 53 | 54 | /** 55 | * @param expiration 缓存有效时间 56 | * @param preloadTime 缓存刷新时间 57 | * @param timeUnit 时间单位 {@link TimeUnit} 58 | * @param forceRefresh 是否强制刷新 59 | * @param allowNullValues 是否允许存NULL值,模式允许 60 | * @param magnification 非空值和null值之间的时间倍率 61 | */ 62 | public SecondaryCacheSetting(long expiration, long preloadTime, TimeUnit timeUnit, boolean forceRefresh, 63 | boolean allowNullValues, int magnification) { 64 | this.expiration = expiration; 65 | this.preloadTime = preloadTime; 66 | this.timeUnit = timeUnit; 67 | this.forceRefresh = forceRefresh; 68 | this.allowNullValue = allowNullValues; 69 | this.magnification = magnification; 70 | this.usePrefix = true; 71 | } 72 | 73 | public long getExpiration() { 74 | return expiration; 75 | } 76 | 77 | public void setExpiration(long expiration) { 78 | this.expiration = expiration; 79 | } 80 | 81 | public long getPreloadTime() { 82 | return preloadTime; 83 | } 84 | 85 | public void setPreloadTime(long preloadTime) { 86 | this.preloadTime = preloadTime; 87 | } 88 | 89 | public TimeUnit getTimeUnit() { 90 | return timeUnit; 91 | } 92 | 93 | public void setTimeUnit(TimeUnit timeUnit) { 94 | this.timeUnit = timeUnit; 95 | } 96 | 97 | public boolean isForceRefresh() { 98 | return forceRefresh; 99 | } 100 | 101 | public void setForceRefresh(boolean forceRefresh) { 102 | this.forceRefresh = forceRefresh; 103 | } 104 | 105 | public boolean isUsePrefix() { 106 | return usePrefix; 107 | } 108 | 109 | public void setUsePrefix(boolean usePrefix) { 110 | this.usePrefix = usePrefix; 111 | } 112 | 113 | public boolean isAllowNullValue() { 114 | return allowNullValue; 115 | } 116 | 117 | public void setAllowNullValue(boolean allowNullValue) { 118 | this.allowNullValue = allowNullValue; 119 | } 120 | 121 | public int getMagnification() { 122 | return magnification; 123 | } 124 | 125 | public void setMagnification(int magnification) { 126 | this.magnification = magnification; 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /layering-cache-core/src/main/java/com/github/xiaolyuh/stats/CacheStats.java: -------------------------------------------------------------------------------- 1 | package com.github.xiaolyuh.stats; 2 | 3 | import java.io.Serializable; 4 | import java.util.concurrent.atomic.LongAdder; 5 | 6 | /** 7 | * 缓存统计信息实体类 8 | * 9 | * @author yuhao.wang3 10 | */ 11 | public final class CacheStats implements Serializable { 12 | /** 13 | * 请求缓存总数 14 | */ 15 | private LongAdder cacheRequestCount; 16 | 17 | /** 18 | * 请求被缓存方法总数 19 | */ 20 | private LongAdder cachedMethodRequestCount; 21 | 22 | /** 23 | * 请求被缓存方法总耗时(毫秒) 24 | */ 25 | private LongAdder cachedMethodRequestTime; 26 | 27 | public CacheStats() { 28 | this.cacheRequestCount = new LongAdder(); 29 | this.cachedMethodRequestCount = new LongAdder(); 30 | this.cachedMethodRequestTime = new LongAdder(); 31 | } 32 | 33 | /** 34 | * 自增请求缓存总数 35 | * 36 | * @param add 自增数量 37 | */ 38 | public void addCacheRequestCount(long add) { 39 | cacheRequestCount.add(add); 40 | } 41 | 42 | /** 43 | * 自增请求被缓存方法总数 44 | * 45 | * @param add 自增数量 46 | */ 47 | public void addCachedMethodRequestCount(long add) { 48 | cachedMethodRequestCount.add(add); 49 | } 50 | 51 | /** 52 | * 自增请求被缓存方法总耗时(毫秒) 53 | * 54 | * @param time 自增数量 55 | */ 56 | public void addCachedMethodRequestTime(long time) { 57 | cachedMethodRequestTime.add(time); 58 | } 59 | 60 | public LongAdder getCacheRequestCount() { 61 | return cacheRequestCount; 62 | } 63 | 64 | public void setCacheRequestCount(LongAdder cacheRequestCount) { 65 | this.cacheRequestCount = cacheRequestCount; 66 | } 67 | 68 | public LongAdder getCachedMethodRequestCount() { 69 | return cachedMethodRequestCount; 70 | } 71 | 72 | public void setCachedMethodRequestCount(LongAdder cachedMethodRequestCount) { 73 | this.cachedMethodRequestCount = cachedMethodRequestCount; 74 | } 75 | 76 | public LongAdder getCachedMethodRequestTime() { 77 | return cachedMethodRequestTime; 78 | } 79 | 80 | public void setCachedMethodRequestTime(LongAdder cachedMethodRequestTime) { 81 | this.cachedMethodRequestTime = cachedMethodRequestTime; 82 | } 83 | 84 | 85 | public long getAndResetCacheRequestCount() { 86 | long lodValue = cacheRequestCount.longValue(); 87 | cacheRequestCount.reset(); 88 | return lodValue; 89 | } 90 | 91 | public long getAndResetCachedMethodRequestCount() { 92 | long lodValue = cachedMethodRequestCount.longValue(); 93 | cachedMethodRequestCount.reset(); 94 | return lodValue; 95 | } 96 | 97 | public long getAndResetCachedMethodRequestTime() { 98 | long lodValue = cachedMethodRequestTime.longValue(); 99 | cachedMethodRequestTime.reset(); 100 | return lodValue; 101 | } 102 | 103 | } -------------------------------------------------------------------------------- /layering-cache-core/src/main/java/com/github/xiaolyuh/stats/extend/CacheStatsReportService.java: -------------------------------------------------------------------------------- 1 | package com.github.xiaolyuh.stats.extend; 2 | 3 | import com.github.xiaolyuh.stats.CacheStatsInfo; 4 | import java.util.List; 5 | 6 | /** 7 | * 缓存统计信息上报扩展类 8 | * 9 | * @author olafwang 10 | */ 11 | public interface CacheStatsReportService { 12 | 13 | /** 14 | * 上报缓存统计信息 15 | * 16 | * @param cacheStatsInfos {@link CacheStatsInfo} 17 | */ 18 | void reportCacheStats(List cacheStatsInfos); 19 | } -------------------------------------------------------------------------------- /layering-cache-core/src/main/java/com/github/xiaolyuh/stats/extend/DefaultCacheStatsReportServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.github.xiaolyuh.stats.extend; 2 | 3 | import com.github.xiaolyuh.stats.CacheStatsInfo; 4 | import java.util.List; 5 | 6 | /** 7 | * 缓存统计信息上报扩展类 8 | * 9 | * @author olafwang 10 | */ 11 | public class DefaultCacheStatsReportServiceImpl implements CacheStatsReportService { 12 | 13 | @Override 14 | public void reportCacheStats(List cacheStatsInfos) { 15 | 16 | } 17 | } -------------------------------------------------------------------------------- /layering-cache-core/src/main/java/com/github/xiaolyuh/support/AwaitThreadContainer.java: -------------------------------------------------------------------------------- 1 | package com.github.xiaolyuh.support; 2 | 3 | import java.util.Comparator; 4 | import java.util.Map; 5 | import java.util.Set; 6 | import java.util.concurrent.ConcurrentHashMap; 7 | import java.util.concurrent.ConcurrentSkipListSet; 8 | import java.util.concurrent.TimeUnit; 9 | import java.util.concurrent.locks.LockSupport; 10 | import org.springframework.util.CollectionUtils; 11 | 12 | /** 13 | * 等待线程容器 14 | * 15 | * @author yuhao.wang 16 | */ 17 | public class AwaitThreadContainer { 18 | private final Map> threadMap = new ConcurrentHashMap<>(); 19 | 20 | /** 21 | * 线程等待,最大等待100毫秒 22 | * 23 | * @param key 缓存Key 24 | * @param milliseconds 等待时间 25 | * @throws InterruptedException {@link InterruptedException} 26 | */ 27 | public final void await(String key, long milliseconds) throws InterruptedException { 28 | // 测试当前线程是否已经被中断 29 | if (Thread.interrupted()) { 30 | throw new InterruptedException(); 31 | } 32 | Set threadSet = threadMap.get(key); 33 | // 判断线程容器是否是null,如果是就新创建一个 34 | if (threadSet == null) { 35 | threadSet = new ConcurrentSkipListSet<>(Comparator.comparing(Thread::toString)); 36 | threadMap.put(key, threadSet); 37 | } 38 | // 将线程放到容器 39 | threadSet.add(Thread.currentThread()); 40 | // 阻塞一定的时间 41 | LockSupport.parkNanos(this, TimeUnit.MILLISECONDS.toNanos(milliseconds)); 42 | } 43 | 44 | /** 45 | * 线程唤醒 46 | * 47 | * @param key key 48 | */ 49 | public final void signalAll(String key) { 50 | Set threadSet = threadMap.get(key); 51 | // 判断key所对应的等待线程容器是否是null 52 | if (!CollectionUtils.isEmpty(threadSet)) { 53 | synchronized (threadSet) { 54 | if (!CollectionUtils.isEmpty(threadSet)) { 55 | for (Thread thread : threadSet) { 56 | LockSupport.unpark(thread); 57 | } 58 | // 清空等待线程容器 59 | threadSet.clear(); 60 | } 61 | } 62 | } 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /layering-cache-core/src/main/java/com/github/xiaolyuh/support/CacheMode.java: -------------------------------------------------------------------------------- 1 | package com.github.xiaolyuh.support; 2 | 3 | /** 4 | * 缓存模式 5 | * 6 | * @author yuhao.wang3 7 | */ 8 | public enum CacheMode { 9 | /** 10 | * 只开启一级缓存 11 | */ 12 | FIRST("只是用一级缓存"), 13 | 14 | /** 15 | * 只开启二级缓存 16 | */ 17 | SECOND("只是使用二级缓存"), 18 | 19 | /** 20 | * 同时开启一级缓存和二级缓存 21 | */ 22 | ALL("同时开启一级缓存和二级缓存"); 23 | 24 | private String label; 25 | 26 | CacheMode(String label) { 27 | this.label = label; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /layering-cache-core/src/main/java/com/github/xiaolyuh/support/ExpireMode.java: -------------------------------------------------------------------------------- 1 | package com.github.xiaolyuh.support; 2 | 3 | /** 4 | * 缓存失效模式 5 | * 6 | * @author yuhao.wang3 7 | */ 8 | public enum ExpireMode { 9 | /** 10 | * 每写入一次重新计算一次缓存的有效时间 11 | */ 12 | WRITE("最后一次写入后到期失效"), 13 | 14 | /** 15 | * 每访问一次重新计算一次缓存的有效时间 16 | */ 17 | ACCESS("最后一次访问后到期失效"); 18 | 19 | private String label; 20 | 21 | ExpireMode(String label) { 22 | this.label = label; 23 | } 24 | } -------------------------------------------------------------------------------- /layering-cache-core/src/main/java/com/github/xiaolyuh/support/MdcThreadPoolTaskExecutor.java: -------------------------------------------------------------------------------- 1 | package com.github.xiaolyuh.support; 2 | 3 | import java.util.Map; 4 | import org.slf4j.MDC; 5 | import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; 6 | 7 | /** 8 | * 这是{@link ThreadPoolTaskExecutor}的一个简单替换,可以在每个任务之前设置子线程的MDC数据。 9 | *

10 | * 在记录日志的时候,一般情况下我们会使用MDC来存储每个线程的特有参数,如身份信息等,以便更好的查询日志。 11 | * 但是Logback在最新的版本中因为性能问题,不会自动的将MDC的内存传给子线程。所以Logback建议在执行异步线程前 12 | * 先通过MDC.getCopyOfContextMap()方法将MDC内存获取出来,再传给线程。 13 | * 并在子线程的执行的最开始调用MDC.setContextMap(context)方法将父线程的MDC内容传给子线程。 14 | *

15 | * https://logback.qos.ch/manual/mdc.html 16 | * 17 | * @author yuhao.wang3 18 | */ 19 | public class MdcThreadPoolTaskExecutor extends ThreadPoolTaskExecutor { 20 | 21 | /** 22 | * 所有线程都会委托给这个execute方法,在这个方法中我们把父线程的MDC内容赋值给子线程 23 | * https://logback.qos.ch/manual/mdc.html#managedThreads 24 | * 25 | * @param runnable runnable 26 | */ 27 | @Override 28 | public void execute(Runnable runnable) { 29 | // 获取父线程MDC中的内容,必须在run方法之前,否则等异步线程执行的时候有可能MDC里面的值已经被清空了,这个时候就会返回null 30 | Map context = MDC.getCopyOfContextMap(); 31 | super.execute(() -> run(runnable, context)); 32 | } 33 | 34 | /** 35 | * 子线程委托的执行方法 36 | * 37 | * @param runnable {@link Runnable} 38 | * @param context 父线程MDC内容 39 | */ 40 | private void run(Runnable runnable, Map context) { 41 | // 将父线程的MDC内容传给子线程 42 | if (context != null) { 43 | try { 44 | MDC.setContextMap(context); 45 | } catch (Exception e) { 46 | logger.error(e.getMessage(), e); 47 | } 48 | } 49 | try { 50 | // 执行异步操作 51 | runnable.run(); 52 | } finally { 53 | // 清空MDC内容 54 | MDC.clear(); 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /layering-cache-core/src/main/java/com/github/xiaolyuh/support/NullValue.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2002-2015 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.github.xiaolyuh.support; 18 | 19 | import com.github.xiaolyuh.cache.AbstractValueAdaptingCache; 20 | import java.io.Serializable; 21 | 22 | /** 23 | * 空值标识 24 | * 25 | * @author olafwang 26 | * @see AbstractValueAdaptingCache 27 | */ 28 | public final class NullValue implements Serializable { 29 | 30 | public static final Object INSTANCE = new NullValue(); 31 | 32 | private static final long serialVersionUID = 1L; 33 | 34 | } 35 | -------------------------------------------------------------------------------- /layering-cache-core/src/main/java/com/github/xiaolyuh/support/Type.java: -------------------------------------------------------------------------------- 1 | package com.github.xiaolyuh.support; 2 | 3 | /** 4 | * 对象类型 5 | * 6 | * @author yuhao.wang3 7 | */ 8 | public enum Type { 9 | /** 10 | * null 11 | */ 12 | NULL("null"), 13 | 14 | /** 15 | * string 16 | */ 17 | STRING("string"), 18 | 19 | /** 20 | * object 21 | */ 22 | OBJECT("Object 对象"), 23 | 24 | /** 25 | * List集合 26 | */ 27 | LIST("List集合"), 28 | 29 | /** 30 | * Set集合 31 | */ 32 | SET("Set集合"), 33 | 34 | /** 35 | * 数组 36 | */ 37 | ARRAY("数组"), 38 | 39 | /** 40 | * 枚举 41 | */ 42 | ENUM("枚举"), 43 | 44 | /** 45 | * 其他类型 46 | */ 47 | OTHER("其他类型"); 48 | 49 | private String label; 50 | 51 | Type(String label) { 52 | this.label = label; 53 | } 54 | 55 | public static Type parse(String name) { 56 | for (Type type : Type.values()) { 57 | if (type.name().equals(name)) { 58 | return type; 59 | } 60 | } 61 | return OTHER; 62 | } 63 | } -------------------------------------------------------------------------------- /layering-cache-core/src/main/java/com/github/xiaolyuh/util/BeanFactory.java: -------------------------------------------------------------------------------- 1 | package com.github.xiaolyuh.util; 2 | 3 | import java.util.concurrent.ConcurrentHashMap; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | 7 | /** 8 | * Bean 工厂类 9 | * 10 | * @author yuhao.wang3 11 | */ 12 | public class BeanFactory { 13 | private static Logger logger = LoggerFactory.getLogger(BeanFactory.class); 14 | 15 | /** 16 | * bean 容器 17 | */ 18 | private static ConcurrentHashMap beanContainer = new ConcurrentHashMap<>(); 19 | 20 | public static T getBean(Class aClass) { 21 | return (T) beanContainer.computeIfAbsent(aClass, aClass1 -> { 22 | try { 23 | return aClass1.newInstance(); 24 | } catch (Exception e) { 25 | logger.error(e.getMessage(), e); 26 | } 27 | return null; 28 | }); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /layering-cache-core/src/main/java/com/github/xiaolyuh/util/GlobalConfig.java: -------------------------------------------------------------------------------- 1 | package com.github.xiaolyuh.util; 2 | 3 | import com.github.xiaolyuh.redis.serializer.JdkRedisSerializer; 4 | import com.github.xiaolyuh.redis.serializer.RedisSerializer; 5 | 6 | /** 7 | * 全局配置 8 | * 9 | * @author yuhao.wang3 10 | */ 11 | public class GlobalConfig { 12 | public static final String MESSAGE_KEY = "layering-cache:message-key:%s"; 13 | 14 | public static String NAMESPACE = ""; 15 | 16 | public static void setNamespace(String namespace) { 17 | GlobalConfig.NAMESPACE = namespace; 18 | } 19 | 20 | public static String getMessageRedisKey() { 21 | return String.format(MESSAGE_KEY, GlobalConfig.NAMESPACE); 22 | } 23 | 24 | public static String getMessageRedisKey(String nameSpace) { 25 | return String.format(MESSAGE_KEY, nameSpace); 26 | } 27 | 28 | /** 29 | * 缓存统计和消息推送序列化器 30 | */ 31 | public static final RedisSerializer GLOBAL_REDIS_SERIALIZER = new JdkRedisSerializer(); 32 | } 33 | -------------------------------------------------------------------------------- /layering-cache-core/src/main/java/com/github/xiaolyuh/util/NamedThreadFactory.java: -------------------------------------------------------------------------------- 1 | package com.github.xiaolyuh.util; 2 | 3 | import java.util.concurrent.ThreadFactory; 4 | import java.util.concurrent.atomic.AtomicInteger; 5 | 6 | /** 7 | * 设置线程名称的ThreadFactory 8 | * 9 | * @author olafwang 10 | */ 11 | public class NamedThreadFactory implements ThreadFactory { 12 | 13 | private final AtomicInteger poolNumber = new AtomicInteger(1); 14 | 15 | private final ThreadGroup threadGroup; 16 | 17 | private final AtomicInteger threadNumber = new AtomicInteger(1); 18 | 19 | public final String namePrefix; 20 | 21 | public NamedThreadFactory(String name) { 22 | SecurityManager s = System.getSecurityManager(); 23 | threadGroup = (s != null) ? s.getThreadGroup() : 24 | Thread.currentThread().getThreadGroup(); 25 | if (null == name || "".equals(name.trim())) { 26 | name = "pool"; 27 | } 28 | namePrefix = name + "-" + poolNumber.getAndIncrement() + "-thread-"; 29 | } 30 | 31 | @Override 32 | public Thread newThread(Runnable runnable) { 33 | Thread thread = new Thread(threadGroup, runnable, namePrefix + threadNumber.getAndIncrement(), 0); 34 | if (thread.isDaemon()) { 35 | thread.setDaemon(false); 36 | } 37 | if (thread.getPriority() != Thread.NORM_PRIORITY) { 38 | thread.setPriority(Thread.NORM_PRIORITY); 39 | } 40 | return thread; 41 | } 42 | } -------------------------------------------------------------------------------- /layering-cache-core/src/main/java/com/github/xiaolyuh/util/RandomUtils.java: -------------------------------------------------------------------------------- 1 | package com.github.xiaolyuh.util; 2 | 3 | import java.util.concurrent.ThreadLocalRandom; 4 | 5 | /** 6 | * RandomUtils 7 | * 8 | * @author heyirui 9 | */ 10 | public class RandomUtils { 11 | 12 | 13 | /** 14 | * 生成带有随机浮动的预加载时间 15 | * 16 | * @param basePreloadTime basePreloadTime 17 | * @return 带有随机浮动的预加载时间 18 | */ 19 | public static long getRandomPreloadTime(long basePreloadTime) { 20 | double randomFactor = ThreadLocalRandom.current().nextDouble(0.9, 1.1); 21 | return (long) (basePreloadTime * randomFactor); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /layering-cache-core/src/main/java/com/github/xiaolyuh/util/ThreadTaskUtils.java: -------------------------------------------------------------------------------- 1 | package com.github.xiaolyuh.util; 2 | 3 | import com.github.xiaolyuh.support.MdcThreadPoolTaskExecutor; 4 | import java.util.concurrent.ThreadPoolExecutor; 5 | 6 | /** 7 | * 线程池 8 | * 9 | * @author yuhao.wang3 10 | */ 11 | public class ThreadTaskUtils { 12 | private static MdcThreadPoolTaskExecutor refreshTaskExecutor = null; 13 | private static MdcThreadPoolTaskExecutor deleteTaskExecutor = null; 14 | 15 | static { 16 | refreshTaskExecutor = new MdcThreadPoolTaskExecutor(); 17 | // 核心线程数 18 | refreshTaskExecutor.setCorePoolSize(8); 19 | // 最大线程数 20 | refreshTaskExecutor.setMaxPoolSize(64); 21 | // 队列最大长度 22 | refreshTaskExecutor.setQueueCapacity(1000); 23 | // 线程池维护线程所允许的空闲时间(单位秒) 24 | refreshTaskExecutor.setKeepAliveSeconds(120); 25 | refreshTaskExecutor.setThreadNamePrefix("layering-cache-refresh-thread"); 26 | /* 27 | * 线程池对拒绝任务(无限程可用)的处理策略 28 | * ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。 29 | * ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。 30 | * ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程) 31 | * ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务,如果执行器已关闭,则丢弃. 32 | */ 33 | refreshTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy()); 34 | refreshTaskExecutor.initialize(); 35 | 36 | 37 | deleteTaskExecutor = new MdcThreadPoolTaskExecutor(); 38 | // 核心线程数 39 | deleteTaskExecutor.setCorePoolSize(8); 40 | // 最大线程数 41 | deleteTaskExecutor.setMaxPoolSize(64); 42 | // 队列最大长度 43 | deleteTaskExecutor.setQueueCapacity(1000); 44 | // 线程池维护线程所允许的空闲时间(单位秒) 45 | deleteTaskExecutor.setKeepAliveSeconds(120); 46 | deleteTaskExecutor.setThreadNamePrefix("layering-cache-delete-thread"); 47 | deleteTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); 48 | deleteTaskExecutor.initialize(); 49 | } 50 | 51 | public static void refreshCacheRun(Runnable runnable) { 52 | refreshTaskExecutor.execute(runnable); 53 | } 54 | 55 | public static void deleteCacheRun(Runnable runnable) { 56 | deleteTaskExecutor.execute(runnable); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /layering-cache-core/src/main/java/com/github/xiaolyuh/util/ToStringUtils.java: -------------------------------------------------------------------------------- 1 | package com.github.xiaolyuh.util; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import java.util.Objects; 5 | 6 | /** 7 | * 将一个Object转换成String 8 | * 9 | * @author yuhao.wang3 10 | */ 11 | public class ToStringUtils { 12 | 13 | /** 14 | * 将一个Object转换成String 15 | * 16 | * @param object Object 17 | * @return String 18 | */ 19 | public static String toString(final Object object) { 20 | if (Objects.isNull(object)) { 21 | return null; 22 | } 23 | if (object instanceof String) { 24 | return (String) object; 25 | } 26 | String string = JSON.toJSONString(object); 27 | return string.replace("\"", "").replace("{", "").replace("}", ""); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /layering-cache-core/src/test/java/com/github/xiaolyuh/cache/MainTest.java: -------------------------------------------------------------------------------- 1 | package com.github.xiaolyuh.cache; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.github.xiaolyuh.redis.serializer.FastJsonRedisSerializer; 5 | import com.github.xiaolyuh.redis.serializer.JacksonRedisSerializer; 6 | import com.github.xiaolyuh.redis.serializer.JdkRedisSerializer; 7 | import com.github.xiaolyuh.redis.serializer.KryoRedisSerializer; 8 | import com.github.xiaolyuh.redis.serializer.ProtostuffRedisSerializer; 9 | import com.github.xiaolyuh.support.NullValue; 10 | 11 | import java.util.Arrays; 12 | 13 | /** 14 | * @author olafwang 15 | * @since 2020/9/25 4:10 下午 16 | */ 17 | public class MainTest { 18 | public static void main(String[] args) { 19 | double b = 0.0f / 0.0; 20 | System.out.println(Double.isNaN(b)); 21 | System.out.println(Double.isInfinite(b)); 22 | System.out.println(Double.isInfinite(1.0)); 23 | System.out.println(JSON.toJSONString(null)); 24 | System.out.println(JSON.toJSONString(new NullValue())); 25 | 26 | KryoRedisSerializer kryoRedisSerializer = new KryoRedisSerializer(); 27 | System.out.println("KryoRedisSerializer:" + Arrays.toString(kryoRedisSerializer.getNullValueBytes())); 28 | 29 | FastJsonRedisSerializer fastJsonRedisSerializer = new FastJsonRedisSerializer(); 30 | System.out.println("FastJsonRedisSerializer:" + Arrays.toString(fastJsonRedisSerializer.getNullValueBytes())); 31 | 32 | JacksonRedisSerializer jacksonRedisSerializer = new JacksonRedisSerializer(); 33 | System.out.println("JacksonRedisSerializer:" + Arrays.toString(jacksonRedisSerializer.getNullValueBytes())); 34 | 35 | JdkRedisSerializer jdkRedisSerializer = new JdkRedisSerializer(); 36 | System.out.println("JdkRedisSerializer:" + Arrays.toString(jdkRedisSerializer.getNullValueBytes())); 37 | 38 | ProtostuffRedisSerializer protostuffRedisSerializer = new ProtostuffRedisSerializer(); 39 | System.out.println("ProtostuffRedisSerializer:" + Arrays.toString(protostuffRedisSerializer.getNullValueBytes())); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /layering-cache-core/src/test/java/com/github/xiaolyuh/cache/config/CacheClusterConfig.java: -------------------------------------------------------------------------------- 1 | package com.github.xiaolyuh.cache.config; 2 | 3 | import com.github.xiaolyuh.manager.CacheManager; 4 | import com.github.xiaolyuh.manager.LayeringCacheManager; 5 | import com.github.xiaolyuh.redis.clinet.RedisClient; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | import org.springframework.context.annotation.Import; 9 | 10 | @Configuration 11 | @Import({RedisClusterConfig.class}) 12 | public class CacheClusterConfig { 13 | 14 | @Bean 15 | public CacheManager layeringCacheManager(RedisClient layeringCacheRedisClient) { 16 | LayeringCacheManager layeringCacheManager = new LayeringCacheManager(layeringCacheRedisClient); 17 | // 开启统计功能 18 | layeringCacheManager.setStats(true); 19 | return layeringCacheManager; 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /layering-cache-core/src/test/java/com/github/xiaolyuh/cache/config/CacheSentinelConfig.java: -------------------------------------------------------------------------------- 1 | package com.github.xiaolyuh.cache.config; 2 | 3 | import com.github.xiaolyuh.manager.CacheManager; 4 | import com.github.xiaolyuh.manager.LayeringCacheManager; 5 | import com.github.xiaolyuh.redis.clinet.RedisClient; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | import org.springframework.context.annotation.Import; 9 | 10 | @Configuration 11 | @Import({RedisSentinelConfig.class}) 12 | public class CacheSentinelConfig { 13 | 14 | @Bean 15 | public CacheManager layeringCacheManager(RedisClient layeringCacheRedisClient) { 16 | LayeringCacheManager layeringCacheManager = new LayeringCacheManager(layeringCacheRedisClient); 17 | // 开启统计功能 18 | layeringCacheManager.setStats(true); 19 | return layeringCacheManager; 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /layering-cache-core/src/test/java/com/github/xiaolyuh/cache/config/CacheSingleConfig.java: -------------------------------------------------------------------------------- 1 | package com.github.xiaolyuh.cache.config; 2 | 3 | import com.github.xiaolyuh.manager.CacheManager; 4 | import com.github.xiaolyuh.manager.LayeringCacheManager; 5 | import com.github.xiaolyuh.redis.clinet.RedisClient; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | import org.springframework.context.annotation.Import; 9 | 10 | @Configuration 11 | @Import({RedisSingleConfig.class}) 12 | public class CacheSingleConfig { 13 | 14 | @Bean 15 | public CacheManager layeringCacheManager(RedisClient layeringCacheRedisClient) { 16 | LayeringCacheManager layeringCacheManager = new LayeringCacheManager(layeringCacheRedisClient); 17 | // 开启统计功能 18 | layeringCacheManager.setStats(true); 19 | return layeringCacheManager; 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /layering-cache-core/src/test/java/com/github/xiaolyuh/cache/config/RedisClusterConfig.java: -------------------------------------------------------------------------------- 1 | package com.github.xiaolyuh.cache.config; 2 | 3 | import com.github.xiaolyuh.redis.clinet.ClusterRedisClient; 4 | import com.github.xiaolyuh.redis.clinet.RedisClient; 5 | import com.github.xiaolyuh.redis.clinet.RedisProperties; 6 | import com.github.xiaolyuh.redis.serializer.FastJsonRedisSerializer; 7 | import com.github.xiaolyuh.redis.serializer.JacksonRedisSerializer; 8 | import com.github.xiaolyuh.redis.serializer.JdkRedisSerializer; 9 | import com.github.xiaolyuh.redis.serializer.KryoRedisSerializer; 10 | import com.github.xiaolyuh.redis.serializer.ProtostuffRedisSerializer; 11 | import com.github.xiaolyuh.redis.serializer.StringRedisSerializer; 12 | import com.github.xiaolyuh.util.StringUtils; 13 | import org.springframework.beans.factory.annotation.Value; 14 | import org.springframework.context.annotation.Bean; 15 | import org.springframework.context.annotation.Configuration; 16 | import org.springframework.context.annotation.PropertySource; 17 | 18 | @Configuration 19 | @PropertySource({"classpath:application.properties"}) 20 | public class RedisClusterConfig { 21 | 22 | @Value("${spring.redis.cluster:127.0.0.1:9000,127.0.0.1:9001,127.0.0.1:9002,127.0.0.1:9003,127.0.0.1:9004,127.0.0.1:9005}") 23 | private String cluster; 24 | 25 | @Value("${spring.redis.password:}") 26 | private String password; 27 | 28 | @Bean 29 | public RedisClient layeringCacheRedisClient() { 30 | RedisProperties redisProperties = new RedisProperties(); 31 | redisProperties.setCluster(cluster); 32 | redisProperties.setPassword(StringUtils.isBlank(password) ? null : password); 33 | 34 | KryoRedisSerializer kryoRedisSerializer = new KryoRedisSerializer(); 35 | FastJsonRedisSerializer fastJsonRedisSerializer = new FastJsonRedisSerializer(); 36 | JacksonRedisSerializer jacksonRedisSerializer = new JacksonRedisSerializer(); 37 | JdkRedisSerializer jdkRedisSerializer = new JdkRedisSerializer(); 38 | ProtostuffRedisSerializer protostuffRedisSerializer = new ProtostuffRedisSerializer(); 39 | 40 | StringRedisSerializer keyRedisSerializer = new StringRedisSerializer(); 41 | RedisClient redisClient = new ClusterRedisClient(redisProperties); 42 | 43 | redisClient.setKeySerializer(keyRedisSerializer); 44 | redisClient.setValueSerializer(kryoRedisSerializer); 45 | return redisClient; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /layering-cache-core/src/test/java/com/github/xiaolyuh/cache/config/RedisSentinelConfig.java: -------------------------------------------------------------------------------- 1 | package com.github.xiaolyuh.cache.config; 2 | 3 | import com.github.xiaolyuh.redis.clinet.RedisClient; 4 | import com.github.xiaolyuh.redis.clinet.RedisProperties; 5 | import com.github.xiaolyuh.redis.clinet.SentinelRedisClient; 6 | import com.github.xiaolyuh.redis.serializer.FastJsonRedisSerializer; 7 | import com.github.xiaolyuh.redis.serializer.JacksonRedisSerializer; 8 | import com.github.xiaolyuh.redis.serializer.JdkRedisSerializer; 9 | import com.github.xiaolyuh.redis.serializer.KryoRedisSerializer; 10 | import com.github.xiaolyuh.redis.serializer.ProtostuffRedisSerializer; 11 | import com.github.xiaolyuh.redis.serializer.StringRedisSerializer; 12 | import com.github.xiaolyuh.util.StringUtils; 13 | import org.springframework.beans.factory.annotation.Value; 14 | import org.springframework.context.annotation.Bean; 15 | import org.springframework.context.annotation.Configuration; 16 | import org.springframework.context.annotation.PropertySource; 17 | 18 | @Configuration 19 | @PropertySource({"classpath:application.properties"}) 20 | public class RedisSentinelConfig { 21 | 22 | @Value("${layering-cache.redis.nodes:127.0.0.1:26379,127.0.0.1:26380,127.0.0.1:26381}") 23 | private String nodes; 24 | 25 | @Value("${spring.redis.password:123}") 26 | private String password; 27 | 28 | @Bean 29 | public RedisClient layeringCacheRedisClient() { 30 | RedisProperties redisProperties = new RedisProperties(); 31 | redisProperties.setSentinelNodes(nodes); 32 | redisProperties.setPassword(StringUtils.isBlank(password) ? null : password); 33 | 34 | KryoRedisSerializer kryoRedisSerializer = new KryoRedisSerializer(); 35 | FastJsonRedisSerializer fastJsonRedisSerializer = new FastJsonRedisSerializer(); 36 | JacksonRedisSerializer jacksonRedisSerializer = new JacksonRedisSerializer(); 37 | JdkRedisSerializer jdkRedisSerializer = new JdkRedisSerializer(); 38 | ProtostuffRedisSerializer protostuffRedisSerializer = new ProtostuffRedisSerializer(); 39 | 40 | StringRedisSerializer keyRedisSerializer = new StringRedisSerializer(); 41 | RedisClient redisClient = new SentinelRedisClient(redisProperties); 42 | 43 | redisClient.setKeySerializer(keyRedisSerializer); 44 | redisClient.setValueSerializer(kryoRedisSerializer); 45 | return redisClient; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /layering-cache-core/src/test/java/com/github/xiaolyuh/cache/config/RedisSingleConfig.java: -------------------------------------------------------------------------------- 1 | package com.github.xiaolyuh.cache.config; 2 | 3 | import com.github.xiaolyuh.redis.clinet.RedisClient; 4 | import com.github.xiaolyuh.redis.clinet.RedisProperties; 5 | import com.github.xiaolyuh.redis.clinet.SingleRedisClient; 6 | import com.github.xiaolyuh.redis.serializer.FastJsonRedisSerializer; 7 | import com.github.xiaolyuh.redis.serializer.JacksonRedisSerializer; 8 | import com.github.xiaolyuh.redis.serializer.JdkRedisSerializer; 9 | import com.github.xiaolyuh.redis.serializer.KryoRedisSerializer; 10 | import com.github.xiaolyuh.redis.serializer.ProtostuffRedisSerializer; 11 | import com.github.xiaolyuh.redis.serializer.StringRedisSerializer; 12 | import com.github.xiaolyuh.util.StringUtils; 13 | import org.springframework.beans.factory.annotation.Value; 14 | import org.springframework.context.annotation.Bean; 15 | import org.springframework.context.annotation.Configuration; 16 | import org.springframework.context.annotation.PropertySource; 17 | 18 | @Configuration 19 | @PropertySource({"classpath:application.properties"}) 20 | public class RedisSingleConfig { 21 | 22 | @Value("${layering-cache.redis.database:0}") 23 | private int database; 24 | 25 | @Value("${layering-cache.redis.host:127.0.0.1}") 26 | private String host; 27 | 28 | @Value("${layering-cache.redis.password:}") 29 | private String password; 30 | 31 | @Value("${layering-cache.redis.port:6379}") 32 | private int port; 33 | 34 | @Bean 35 | public RedisClient layeringCacheRedisClient() { 36 | RedisProperties redisProperties = new RedisProperties(); 37 | redisProperties.setDatabase(database); 38 | redisProperties.setHost(host); 39 | redisProperties.setPassword(StringUtils.isBlank(password) ? null : password); 40 | redisProperties.setPort(port); 41 | 42 | KryoRedisSerializer kryoRedisSerializer = new KryoRedisSerializer(); 43 | FastJsonRedisSerializer fastJsonRedisSerializer = new FastJsonRedisSerializer(); 44 | JacksonRedisSerializer jacksonRedisSerializer = new JacksonRedisSerializer(); 45 | JdkRedisSerializer jdkRedisSerializer = new JdkRedisSerializer(); 46 | ProtostuffRedisSerializer protostuffRedisSerializer = new ProtostuffRedisSerializer(); 47 | 48 | StringRedisSerializer keyRedisSerializer = new StringRedisSerializer(); 49 | 50 | SingleRedisClient redisClient = new SingleRedisClient(redisProperties); 51 | redisClient.setKeySerializer(keyRedisSerializer); 52 | redisClient.setValueSerializer(protostuffRedisSerializer); 53 | return redisClient; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /layering-cache-core/src/test/resources/application.properties: -------------------------------------------------------------------------------- 1 | layering-cache.redis.database=0 2 | layering-cache.redis.password= 3 | layering-cache.redis.host=127.0.0.1 4 | layering-cache.redis.port=6379 -------------------------------------------------------------------------------- /layering-cache-core/src/test/resources/log4j.properties: -------------------------------------------------------------------------------- 1 | log4j.rootLogger=debug, stdout 2 | log4j.appender.stdout=org.apache.log4j.ConsoleAppender 3 | log4j.appender.stdout.layout=org.apache.log4j.PatternLayout 4 | log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n -------------------------------------------------------------------------------- /layering-cache-starter/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | layering-cache 7 | com.github.xiaolyuh 8 | 3.5.0 9 | 10 | 4.0.0 11 | 12 | layering-cache-starter 13 | layering-cache-starter 14 | 多级缓存 Starter 15 | jar 16 | 17 | 18 | 19 | com.github.xiaolyuh 20 | layering-cache-aspectj 21 | 3.5.0 22 | 23 | 24 | 25 | org.slf4j 26 | slf4j-api 27 | true 28 | 29 | 30 | 31 | com.github.ben-manes.caffeine 32 | caffeine 33 | true 34 | 35 | 36 | 37 | org.springframework 38 | spring-core 39 | true 40 | 41 | 42 | 43 | org.springframework 44 | spring-aop 45 | true 46 | 47 | 48 | 49 | org.springframework 50 | spring-context 51 | true 52 | 53 | 54 | 55 | org.aspectj 56 | aspectjweaver 57 | true 58 | 59 | 60 | 61 | org.springframework.boot 62 | spring-boot-autoconfigure 63 | true 64 | 65 | 66 | 67 | org.projectlombok 68 | lombok 69 | provided 70 | 71 | 72 | 73 | org.springframework.boot 74 | spring-boot-configuration-processor 75 | true 76 | 77 | 78 | io.lettuce 79 | lettuce-core 80 | true 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /layering-cache-starter/src/main/java/com/github/xiaolyuh/cache/config/EnableLayeringCache.java: -------------------------------------------------------------------------------- 1 | package com.github.xiaolyuh.cache.config; 2 | 3 | import java.lang.annotation.Documented; 4 | import java.lang.annotation.ElementType; 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | import java.lang.annotation.Target; 8 | import org.springframework.context.annotation.Import; 9 | 10 | @Target({ElementType.TYPE}) 11 | @Retention(RetentionPolicy.RUNTIME) 12 | @Documented 13 | @Import({LayeringCacheAutoConfig.class}) 14 | public @interface EnableLayeringCache { 15 | 16 | } -------------------------------------------------------------------------------- /layering-cache-starter/src/main/java/com/github/xiaolyuh/cache/properties/LayeringCacheProperties.java: -------------------------------------------------------------------------------- 1 | package com.github.xiaolyuh.cache.properties; 2 | 3 | import org.springframework.boot.context.properties.ConfigurationProperties; 4 | 5 | /** 6 | * @author yuhao.wang3 7 | */ 8 | @ConfigurationProperties("layering-cache") 9 | public class LayeringCacheProperties { 10 | 11 | /** 12 | * 是否开启缓存统计 13 | */ 14 | private boolean stats = true; 15 | 16 | /** 17 | * 命名空间,必须唯一般使用服务名 18 | */ 19 | private String namespace; 20 | 21 | public boolean isStats() { 22 | return stats; 23 | } 24 | 25 | public void setStats(boolean stats) { 26 | this.stats = stats; 27 | } 28 | 29 | public String getNamespace() { 30 | return namespace; 31 | } 32 | 33 | public void setNamespace(String namespace) { 34 | this.namespace = namespace; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /layering-cache-starter/src/main/java/com/github/xiaolyuh/cache/properties/LayeringCacheRedisProperties.java: -------------------------------------------------------------------------------- 1 | package com.github.xiaolyuh.cache.properties; 2 | 3 | 4 | import com.github.xiaolyuh.util.StringUtils; 5 | import lombok.Data; 6 | import org.springframework.boot.context.properties.ConfigurationProperties; 7 | 8 | /** 9 | * Redis 配置 10 | * 11 | * @author wangyuhao 12 | */ 13 | @Data 14 | @ConfigurationProperties(prefix = "layering-cache.redis") 15 | public class LayeringCacheRedisProperties { 16 | //********************单机配置项**************************/ 17 | /** 18 | * 单机配置项 19 | */ 20 | String host = "localhost"; 21 | Integer port = 6379; 22 | 23 | //********************集群配置**************************/ 24 | /** 25 | * 不为空表示集群版,示例 26 | * localhost:7379,localhost2:7379 27 | */ 28 | String cluster = ""; 29 | 30 | //********************哨兵配置**************************/ 31 | /** 32 | * 哨兵master名称,示例:mymaster 33 | */ 34 | String sentinelMaster = "mymaster"; 35 | 36 | /** 37 | * 哨兵节点,示例:localhost:26397,localhost2:26397 38 | */ 39 | String sentinelNodes = ""; 40 | 41 | //********************通用配置**************************/ 42 | /** 43 | * 使用数据库 44 | */ 45 | Integer database = 0; 46 | 47 | /** 48 | * 密码 49 | */ 50 | String password = null; 51 | 52 | /** 53 | * 超时时间 单位秒 默认一个小时 54 | */ 55 | Integer timeout = 3600; 56 | /** 57 | * 序列化方式: 58 | * com.github.xiaolyuh.redis.serializer.KryoRedisSerializer 59 | * com.github.xiaolyuh.redis.serializer.FastJsonRedisSerializer 60 | * com.github.xiaolyuh.redis.serializer.JacksonRedisSerializer 61 | * com.github.xiaolyuh.redis.serializer.JdkRedisSerializer 62 | * com.github.xiaolyuh.redis.serializer.ProtostuffRedisSerializer 63 | */ 64 | String serializer = "com.github.xiaolyuh.redis.serializer.ProtostuffRedisSerializer"; 65 | 66 | public String getPassword() { 67 | return StringUtils.isBlank(password) ? null : password; 68 | } 69 | } -------------------------------------------------------------------------------- /layering-cache-starter/src/main/resources/META-INF/spring.factories: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /layering-cache-web/.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | 4 | ### STS ### 5 | .apt_generated 6 | .classpath 7 | .factorypath 8 | .project 9 | .settings 10 | .springBeans 11 | .sts4-cache 12 | 13 | ### IntelliJ IDEA ### 14 | .idea 15 | *.iws 16 | *.iml 17 | *.ipr 18 | 19 | ### NetBeans ### 20 | /nbproject/private/ 21 | /build/ 22 | /nbbuild/ 23 | /dist/ 24 | /nbdist/ 25 | /.nb-gradle/ -------------------------------------------------------------------------------- /layering-cache-web/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | com.github.xiaolyuh 7 | layering-cache-web 8 | 3.2.0 9 | jar 10 | 11 | layering-cache-web 12 | layering-cache web 13 | 14 | 15 | org.springframework.boot 16 | spring-boot-starter-parent 17 | 2.1.1.RELEASE 18 | 19 | 20 | 21 | 22 | UTF-8 23 | UTF-8 24 | 1.8 25 | 26 | 27 | 28 | 29 | 30 | org.springframework.boot 31 | spring-boot-starter-web 32 | 33 | 34 | 35 | org.springframework.boot 36 | spring-boot-starter-test 37 | test 38 | 39 | 40 | 41 | com.alibaba 42 | fastjson 43 | 1.2.58 44 | 45 | 46 | 47 | io.lettuce 48 | lettuce-core 49 | 50 | 51 | 52 | com.github.ben-manes.caffeine 53 | caffeine 54 | 55 | 56 | 57 | org.springframework.boot 58 | spring-boot-starter-thymeleaf 59 | 2.1.1.RELEASE 60 | 61 | 62 | 63 | com.squareup.okhttp3 64 | okhttp 65 | 3.6.0 66 | 67 | 68 | 69 | com.esotericsoftware 70 | kryo-shaded 71 | 3.0.3 72 | 73 | 74 | 75 | org.projectlombok 76 | lombok 77 | true 78 | 79 | 80 | 81 | io.protostuff 82 | protostuff-core 83 | 1.7.2 84 | 85 | 86 | 87 | io.protostuff 88 | protostuff-runtime 89 | 1.7.2 90 | 91 | 92 | 93 | com.github.xiaolyuh 94 | layering-cache-core 95 | 3.5.0 96 | 97 | 98 | 99 | 100 | 101 | 102 | org.springframework.boot 103 | spring-boot-maven-plugin 104 | 105 | 106 | 107 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /layering-cache-web/src/main/java/com/github/xiaolyuh/web/LayeringCacheWebApplication.java: -------------------------------------------------------------------------------- 1 | package com.github.xiaolyuh.web; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class LayeringCacheWebApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(LayeringCacheWebApplication.class, args); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /layering-cache-web/src/main/java/com/github/xiaolyuh/web/config/WebMvcConfig.java: -------------------------------------------------------------------------------- 1 | package com.github.xiaolyuh.web.config; 2 | 3 | import com.github.xiaolyuh.web.interceptor.LoginInterceptor; 4 | import com.github.xiaolyuh.web.service.UserService; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | import org.springframework.web.servlet.config.annotation.InterceptorRegistry; 9 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 10 | 11 | @Configuration 12 | public class WebMvcConfig implements WebMvcConfigurer { 13 | 14 | @Autowired 15 | private UserService userService; 16 | 17 | @Bean 18 | public WebMvcConfig getMyWebMvcConfig() { 19 | WebMvcConfig webMvcConfig = new WebMvcConfig() { 20 | //注册拦截器 21 | @Override 22 | public void addInterceptors(InterceptorRegistry registry) { 23 | registry.addInterceptor(new LoginInterceptor(userService)) 24 | .addPathPatterns("/**") 25 | .excludePathPatterns("/login**", "/user/submit-login", "/toLogin", "/redis/redis-config", "/css/**", "/js/**", "/fonts/**", "/i/**"); 26 | } 27 | }; 28 | return webMvcConfig; 29 | } 30 | } -------------------------------------------------------------------------------- /layering-cache-web/src/main/java/com/github/xiaolyuh/web/constant/URLConstant.java: -------------------------------------------------------------------------------- 1 | package com.github.xiaolyuh.web.constant; 2 | 3 | /** 4 | * URL 常量 5 | * 6 | * @author yuhao.wang3 7 | */ 8 | public class URLConstant { 9 | 10 | /** 11 | * 用户登录页面 12 | */ 13 | public static final String USER_LOGIN_PAGE = "/login.html"; 14 | 15 | /** 16 | * 用户登录URL 17 | */ 18 | public static final String USER_SUBMIT_LOGIN = "/user/submit-login"; 19 | 20 | /** 21 | * 用户登出URL 22 | */ 23 | public static final String USER_LOGIN_OUT = "/user/login-out"; 24 | 25 | /** 26 | * 重置缓存统计数据 27 | */ 28 | public static final String RESET_CACHE_STAT = "/cache-stats/reset-stats"; 29 | 30 | /** 31 | * 缓存统计列表 32 | */ 33 | public static final String CACHE_STATS_LIST = "/cache-stats/list"; 34 | 35 | /** 36 | * 删除缓存统计 37 | */ 38 | public static final String CACHE_STATS_DELETE_CACHW = "/cache-stats/delete-cache"; 39 | 40 | } 41 | -------------------------------------------------------------------------------- /layering-cache-web/src/main/java/com/github/xiaolyuh/web/controller/RedisController.java: -------------------------------------------------------------------------------- 1 | package com.github.xiaolyuh.web.controller; 2 | 3 | import com.github.xiaolyuh.redis.clinet.ClusterRedisClient; 4 | import com.github.xiaolyuh.redis.clinet.RedisClient; 5 | import com.github.xiaolyuh.redis.clinet.RedisProperties; 6 | import com.github.xiaolyuh.redis.clinet.SingleRedisClient; 7 | import com.github.xiaolyuh.redis.serializer.StringRedisSerializer; 8 | import com.github.xiaolyuh.util.GlobalConfig; 9 | import com.github.xiaolyuh.util.StringUtils; 10 | import com.github.xiaolyuh.web.utils.Result; 11 | import org.springframework.stereotype.Controller; 12 | import org.springframework.web.bind.annotation.RequestMapping; 13 | import org.springframework.web.bind.annotation.ResponseBody; 14 | 15 | import java.util.ArrayList; 16 | import java.util.HashMap; 17 | import java.util.List; 18 | import java.util.Map; 19 | import java.util.concurrent.ConcurrentHashMap; 20 | 21 | @Controller 22 | public class RedisController { 23 | public static final Map redisClientMap = new ConcurrentHashMap<>(); 24 | 25 | @RequestMapping("/redis/redis-config") 26 | @ResponseBody 27 | public Result login(String address, String password, Integer port, Integer database, String serializer) { 28 | try { 29 | RedisProperties redisProperties = new RedisProperties(); 30 | if (address.contains(":")) { 31 | redisProperties.setCluster(address); 32 | } else { 33 | redisProperties.setHost(address); 34 | } 35 | redisProperties.setPassword(StringUtils.isBlank(password) ? null : password); 36 | redisProperties.setPort(port); 37 | redisProperties.setDatabase(database); 38 | redisProperties.setSerializer("com.github.xiaolyuh.redis.serializer.JacksonRedisSerializer"); 39 | 40 | String key = address + ":" + port + ":" + database; 41 | redisClientMap.put(key, getRedisClient(redisProperties)); 42 | 43 | RedisClient redisClient = redisClientMap.get(key); 44 | redisClient.get("test", String.class); 45 | return Result.success(); 46 | } catch (Exception e) { 47 | return Result.error("配置redis失败" + e.getMessage()); 48 | } 49 | } 50 | 51 | @RequestMapping("/redis/redis-list") 52 | @ResponseBody 53 | public Result redisList() { 54 | List> list = new ArrayList<>(); 55 | try { 56 | for (String key : redisClientMap.keySet()) { 57 | Map map = new HashMap<>(); 58 | map.put("address", key); 59 | list.add(map); 60 | } 61 | return Result.success(list); 62 | } catch (Exception e) { 63 | return Result.error("配置redis失败" + e.getMessage()); 64 | } 65 | } 66 | 67 | 68 | private RedisClient getRedisClient(RedisProperties redisProperties) { 69 | StringRedisSerializer keyRedisSerializer = new StringRedisSerializer(); 70 | 71 | RedisClient redisClient; 72 | if (StringUtils.isNotBlank(redisProperties.getCluster())) { 73 | redisClient = new ClusterRedisClient(redisProperties); 74 | } else { 75 | redisClient = new SingleRedisClient(redisProperties); 76 | } 77 | 78 | redisClient.setKeySerializer(keyRedisSerializer); 79 | redisClient.setValueSerializer(GlobalConfig.GLOBAL_REDIS_SERIALIZER); 80 | return redisClient; 81 | } 82 | } -------------------------------------------------------------------------------- /layering-cache-web/src/main/java/com/github/xiaolyuh/web/controller/UserController.java: -------------------------------------------------------------------------------- 1 | package com.github.xiaolyuh.web.controller; 2 | 3 | import com.github.xiaolyuh.web.service.UserService; 4 | import com.github.xiaolyuh.web.utils.Result; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.stereotype.Controller; 7 | import org.springframework.web.bind.annotation.RequestMapping; 8 | import org.springframework.web.bind.annotation.ResponseBody; 9 | 10 | import java.util.UUID; 11 | 12 | @Controller 13 | public class UserController { 14 | 15 | @Autowired 16 | private UserService userService; 17 | 18 | @RequestMapping("/index") 19 | public String index() { 20 | return "index"; 21 | } 22 | 23 | @RequestMapping("/toLogin") 24 | public String toLogin() { 25 | return "login"; 26 | } 27 | 28 | @RequestMapping("/user/login-out") 29 | @ResponseBody 30 | public Result loginOut(String token) { 31 | userService.loginOut(token); 32 | return Result.success(); 33 | } 34 | 35 | @RequestMapping("/user/submit-login") 36 | @ResponseBody 37 | public Result login(String loginUsername, String loginPassword) { 38 | String token = UUID.randomUUID().toString(); 39 | if (userService.login(loginUsername, loginPassword, token)) { 40 | return Result.success(token); 41 | } 42 | return Result.error("用户名或密码错误"); 43 | } 44 | } -------------------------------------------------------------------------------- /layering-cache-web/src/main/java/com/github/xiaolyuh/web/interceptor/LoginInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.github.xiaolyuh.web.interceptor; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.github.xiaolyuh.web.service.UserService; 5 | import com.github.xiaolyuh.web.utils.Result; 6 | import org.springframework.web.servlet.HandlerInterceptor; 7 | 8 | import javax.servlet.http.HttpServletRequest; 9 | import javax.servlet.http.HttpServletResponse; 10 | 11 | /** 12 | * @author olafwang 13 | * @since 2020/7/10 3:00 下午 14 | */ 15 | public class LoginInterceptor implements HandlerInterceptor { 16 | public static final String PARAM_NAME_TOKEN = "token"; 17 | 18 | private UserService userService; 19 | 20 | public LoginInterceptor(UserService userService) { 21 | this.userService = userService; 22 | } 23 | 24 | @Override 25 | public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) 26 | throws Exception { 27 | 28 | String contextPath = request.getContextPath(); 29 | String servletPath = request.getServletPath(); 30 | String requestURI = request.getRequestURI(); 31 | 32 | response.setCharacterEncoding("utf-8"); 33 | 34 | // root context 35 | if (contextPath == null) { 36 | contextPath = ""; 37 | } 38 | String path = requestURI.substring(contextPath.length() + servletPath.length()); 39 | 40 | // 登录校验 41 | String token = request.getParameter(PARAM_NAME_TOKEN); 42 | 43 | if (!userService.checkLogin(token)) { 44 | response.sendRedirect("/toLogin"); 45 | // request.getRequestDispatcher("/toLogin").forward(request, response); 46 | // response.getWriter().write(JSON.toJSONString(Result.error("请登录"))); 47 | return false; 48 | } 49 | 50 | return true; 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /layering-cache-web/src/main/java/com/github/xiaolyuh/web/service/CacheService.java: -------------------------------------------------------------------------------- 1 | package com.github.xiaolyuh.web.service; 2 | 3 | import com.github.xiaolyuh.cache.Cache; 4 | import com.github.xiaolyuh.manager.AbstractCacheManager; 5 | import com.github.xiaolyuh.setting.FirstCacheSetting; 6 | import com.github.xiaolyuh.setting.LayeringCacheSetting; 7 | import com.github.xiaolyuh.setting.SecondaryCacheSetting; 8 | import com.github.xiaolyuh.stats.StatsService; 9 | import com.github.xiaolyuh.support.CacheMode; 10 | import com.github.xiaolyuh.util.BeanFactory; 11 | import com.github.xiaolyuh.util.StringUtils; 12 | import org.springframework.stereotype.Service; 13 | import org.springframework.util.CollectionUtils; 14 | 15 | import java.util.Collection; 16 | import java.util.Set; 17 | 18 | /** 19 | * 操作缓存的服务 20 | * 21 | * @author yuhao.wang3 22 | */ 23 | @Service 24 | public class CacheService { 25 | /** 26 | * 删除缓存 27 | * 28 | * @param cacheName 缓存名称 29 | * @param internalKey 内部缓存名,由[一级缓存有效时间-二级缓存有效时间]组成 30 | * @param key key,可以为NULL,如果是NULL则清空缓存 31 | */ 32 | public void deleteCache(String cacheName, String internalKey, String key) { 33 | if (StringUtils.isBlank(cacheName) || StringUtils.isBlank(internalKey)) { 34 | return; 35 | } 36 | LayeringCacheSetting defaultSetting = new LayeringCacheSetting(new FirstCacheSetting(), new SecondaryCacheSetting(), "默认缓存配置(删除时生成)", CacheMode.ALL); 37 | Set cacheManagers = AbstractCacheManager.getCacheManager(); 38 | if (StringUtils.isBlank(key)) { 39 | // 清空缓存 40 | for (AbstractCacheManager cacheManager : cacheManagers) { 41 | // 删除缓存统计信息 42 | String redisKey = StatsService.CACHE_STATS_KEY_PREFIX + cacheName + internalKey; 43 | BeanFactory.getBean(StatsService.class).resetCacheStat(redisKey); 44 | 45 | // 删除缓存 46 | Collection caches = cacheManager.getCache(cacheName); 47 | if (CollectionUtils.isEmpty(caches)) { 48 | // 如果没有找到Cache就新建一个默认的 49 | Cache cache = cacheManager.getCache(cacheName, defaultSetting); 50 | cache.clear(); 51 | 52 | // 删除统计信息 53 | redisKey = StatsService.CACHE_STATS_KEY_PREFIX + cacheName + defaultSetting.getInternalKey(); 54 | BeanFactory.getBean(StatsService.class).resetCacheStat(redisKey); 55 | } else { 56 | for (Cache cache : caches) { 57 | cache.clear(); 58 | } 59 | } 60 | } 61 | 62 | return; 63 | } 64 | 65 | // 删除指定key 66 | for (AbstractCacheManager cacheManager : cacheManagers) { 67 | Collection caches = cacheManager.getCache(cacheName); 68 | if (CollectionUtils.isEmpty(caches)) { 69 | // 如果没有找到Cache就新建一个默认的 70 | Cache cache = cacheManager.getCache(cacheName, defaultSetting); 71 | cache.evict(key); 72 | } else { 73 | for (Cache cache : caches) { 74 | cache.evict(key); 75 | } 76 | } 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /layering-cache-web/src/main/java/com/github/xiaolyuh/web/service/UserService.java: -------------------------------------------------------------------------------- 1 | package com.github.xiaolyuh.web.service; 2 | 3 | import com.github.benmanes.caffeine.cache.Cache; 4 | import com.github.benmanes.caffeine.cache.Caffeine; 5 | import com.github.xiaolyuh.util.StringUtils; 6 | import org.springframework.beans.factory.annotation.Value; 7 | import org.springframework.stereotype.Service; 8 | 9 | import java.io.IOException; 10 | import java.util.Objects; 11 | import java.util.concurrent.TimeUnit; 12 | 13 | /** 14 | * 用户服务 15 | * 16 | * @author yuhao.wang3 17 | */ 18 | @Service 19 | public class UserService { 20 | private static Cache manualCache = Caffeine.newBuilder() 21 | .expireAfterWrite(30, TimeUnit.MINUTES) 22 | .maximumSize(1000) 23 | .build(); 24 | 25 | 26 | @Value("${layering-cache.web.user-name:admin}") 27 | private String userName; 28 | 29 | @Value("${layering-cache.web.password:admin}") 30 | private String password; 31 | 32 | /** 33 | * 登录校验 34 | * 35 | * @param token 唯一标示 36 | */ 37 | public boolean checkLogin(String token) { 38 | if (StringUtils.isBlank(token)) { 39 | return false; 40 | } 41 | // 检查是否登录 42 | if (!isLogin(token)) { 43 | return false; 44 | } 45 | 46 | return true; 47 | } 48 | 49 | /** 50 | * 登录 51 | * 52 | * @param usernameParam 用户名 53 | * @param passwordParam 密码 54 | * @param token 唯一标示 55 | * @return 56 | * @throws IOException 57 | */ 58 | public boolean login(String usernameParam, String passwordParam, String token) { 59 | if (userName.equals(usernameParam) && password.equals(passwordParam)) { 60 | manualCache.put(token, userName); 61 | return true; 62 | } 63 | return false; 64 | } 65 | 66 | /** 67 | * 是否登录 68 | * 69 | * @param token 唯一标示 70 | * @return 71 | */ 72 | public boolean isLogin(String token) { 73 | if (Objects.nonNull(manualCache.getIfPresent(token))) { 74 | refreshSession(token); 75 | return true; 76 | } 77 | return false; 78 | } 79 | 80 | /** 81 | * 退出 82 | * 83 | * @param token 唯一标示 84 | * @return boolean 85 | */ 86 | public boolean loginOut(String token) { 87 | manualCache.invalidate(token); 88 | return true; 89 | } 90 | 91 | /** 92 | * 刷新session 93 | * 94 | * @param token 唯一标示 95 | * @return boolean 96 | */ 97 | public boolean refreshSession(String token) { 98 | manualCache.put(token, token); 99 | return true; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /layering-cache-web/src/main/java/com/github/xiaolyuh/web/service/WebStatsService.java: -------------------------------------------------------------------------------- 1 | package com.github.xiaolyuh.web.service; 2 | 3 | import com.github.xiaolyuh.redis.clinet.RedisClient; 4 | import com.github.xiaolyuh.stats.CacheStatsInfo; 5 | import com.github.xiaolyuh.stats.StatsService; 6 | import com.github.xiaolyuh.util.GlobalConfig; 7 | import com.github.xiaolyuh.util.StringUtils; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | import org.springframework.stereotype.Service; 11 | import org.springframework.util.CollectionUtils; 12 | 13 | import java.util.ArrayList; 14 | import java.util.Collections; 15 | import java.util.Comparator; 16 | import java.util.List; 17 | import java.util.Objects; 18 | import java.util.Set; 19 | import java.util.concurrent.TimeUnit; 20 | import java.util.stream.Collectors; 21 | 22 | /** 23 | * 统计服务 24 | * 25 | * @author yuhao.wang3 26 | */ 27 | @Service 28 | public class WebStatsService { 29 | private static Logger logger = LoggerFactory.getLogger(WebStatsService.class); 30 | 31 | /** 32 | * 获取缓存统计list 33 | * 34 | * @param redisClient redisClient 35 | * @param cacheNameParam 缓存名称 36 | * @return List<CacheStatsInfo> 37 | */ 38 | public List listCacheStats(RedisClient redisClient, String cacheNameParam) { 39 | logger.debug("获取缓存统计数据"); 40 | 41 | Set layeringCacheKeys = redisClient.scan(StatsService.CACHE_STATS_KEY_PREFIX + "*"); 42 | if (CollectionUtils.isEmpty(layeringCacheKeys)) { 43 | return Collections.emptyList(); 44 | } 45 | // 遍历找出对应统计数据 46 | List statsList = new ArrayList<>(); 47 | for (String key : layeringCacheKeys) { 48 | if (StringUtils.isNotBlank(cacheNameParam) && !key.startsWith(StatsService.CACHE_STATS_KEY_PREFIX + cacheNameParam)) { 49 | continue; 50 | } 51 | 52 | try { 53 | CacheStatsInfo cacheStats = redisClient.get(key, CacheStatsInfo.class, GlobalConfig.GLOBAL_REDIS_SERIALIZER); 54 | if (!Objects.isNull(cacheStats)) { 55 | statsList.add(cacheStats); 56 | } 57 | } catch (Exception e) { 58 | redisClient.delete(key); 59 | logger.error(e.getMessage(), e); 60 | } 61 | } 62 | 63 | return statsList.stream().sorted(Comparator.comparing(CacheStatsInfo::getHitRate)).collect(Collectors.toList()); 64 | } 65 | 66 | 67 | /** 68 | * 重置缓存统计数据 69 | */ 70 | public void resetCacheStat(RedisClient redisClient) { 71 | Set layeringCacheKeys = redisClient.scan(StatsService.CACHE_STATS_KEY_PREFIX + "*"); 72 | 73 | for (String key : layeringCacheKeys) { 74 | resetCacheStat(redisClient, key); 75 | } 76 | } 77 | 78 | /** 79 | * 重置缓存统计数据 80 | * 81 | * @param redisKey redisKey 82 | */ 83 | public void resetCacheStat(RedisClient redisClient, String redisKey) { 84 | try { 85 | CacheStatsInfo cacheStats = redisClient.get(redisKey, CacheStatsInfo.class, GlobalConfig.GLOBAL_REDIS_SERIALIZER); 86 | if (Objects.nonNull(cacheStats)) { 87 | cacheStats.clearStatsInfo(); 88 | // 将缓存统计数据写到redis 89 | redisClient.set(redisKey, cacheStats, 24, TimeUnit.HOURS); 90 | } 91 | } catch (Exception e) { 92 | redisClient.delete(redisKey); 93 | } 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /layering-cache-web/src/main/java/com/github/xiaolyuh/web/utils/OkHttpClientUtil.java: -------------------------------------------------------------------------------- 1 | package com.github.xiaolyuh.web.utils; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import okhttp3.FormBody; 5 | import okhttp3.MediaType; 6 | import okhttp3.OkHttpClient; 7 | import okhttp3.Request; 8 | import okhttp3.RequestBody; 9 | import okhttp3.Response; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | import org.springframework.remoting.RemoteAccessException; 13 | 14 | import java.io.IOException; 15 | import java.util.Map; 16 | import java.util.concurrent.TimeUnit; 17 | 18 | /** 19 | * OkHttpClient工具 20 | * 21 | * @author yuhao.wang3 22 | */ 23 | public abstract class OkHttpClientUtil { 24 | private static final Logger logger = LoggerFactory.getLogger(OkHttpClientUtil.class); 25 | 26 | private static OkHttpClient okHttpClient = new OkHttpClient.Builder() 27 | .connectTimeout(10, TimeUnit.SECONDS) 28 | .writeTimeout(10, TimeUnit.SECONDS) 29 | .readTimeout(20, TimeUnit.SECONDS) 30 | .build(); 31 | 32 | 33 | /** 34 | * 发起post请求,不做任何签名 35 | * 36 | * @param url 发送请求的URL 37 | * @param requestBody 请求体 38 | * @throws IOException 39 | */ 40 | public static String post(String url, RequestBody requestBody) throws IOException { 41 | Request request = new Request.Builder() 42 | //请求的url 43 | .url(url) 44 | .post(requestBody) 45 | .build(); 46 | 47 | //创建/Call 48 | Response response = okHttpClient.newCall(request).execute(); 49 | if (!response.isSuccessful()) { 50 | logger.error("访问外部系统异常 {}: {}", url, JSON.toJSONString(response)); 51 | throw new RemoteAccessException("访问外部系统异常 " + url); 52 | } 53 | return response.body().string(); 54 | } 55 | 56 | /** 57 | * 发起post请求,不做任何签名 宽松的参数构造 58 | * 59 | * @param url 发送请求的URL 60 | * @param builder 请求体 61 | * @throws IOException 62 | */ 63 | public static String post(String url, Request.Builder builder) throws IOException { 64 | 65 | Request request = builder.url(url).build(); 66 | //创建/Call 67 | Response response = okHttpClient.newCall(request).execute(); 68 | if (!response.isSuccessful()) { 69 | logger.error("访问外部系统异常 {}: {}", url, JSON.toJSONString(response)); 70 | throw new RemoteAccessException("访问外部系统异常 " + url); 71 | } 72 | return response.body().string(); 73 | } 74 | 75 | 76 | public static String post(String url, Map param, Map header) throws Exception { 77 | // 生成requestBody 78 | RequestBody requestBody = FormBody.create(MediaType.parse("application/json; charset=utf-8") 79 | , JSON.toJSONString(param)); 80 | 81 | Request.Builder builder = new Request.Builder() 82 | //请求的url 83 | .url(url) 84 | .post(requestBody); 85 | 86 | for (String key : header.keySet()) { 87 | builder.header(key, header.get(key)); 88 | } 89 | 90 | Request request = builder.build(); 91 | 92 | //创建/Call 93 | Response response = okHttpClient.newCall(request).execute(); 94 | if (!response.isSuccessful()) { 95 | logger.error("访问外部系统异常 {}: {}", url, JSON.toJSONString(response)); 96 | throw new RemoteAccessException("访问外部系统异常 " + url); 97 | } 98 | return response.body().string(); 99 | } 100 | 101 | public static String get(String url) throws IOException { 102 | Request request = new Request.Builder() 103 | //请求的url 104 | .url(url) 105 | .get() 106 | .build(); 107 | 108 | Response response = okHttpClient.newCall(request).execute(); 109 | if (!response.isSuccessful()) { 110 | logger.error("访问外部系统异常 {}: {}", url, JSON.toJSONString(response)); 111 | throw new RemoteAccessException("访问外部系统异常 " + url); 112 | } 113 | return response.body().string(); 114 | } 115 | 116 | } 117 | -------------------------------------------------------------------------------- /layering-cache-web/src/main/java/com/github/xiaolyuh/web/utils/Result.java: -------------------------------------------------------------------------------- 1 | package com.github.xiaolyuh.web.utils; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * 与前端交互对象 7 | * 8 | * @param 9 | */ 10 | public class Result implements Serializable { 11 | private static final long serialVersionUID = 5925101851082556646L; 12 | private T data; 13 | private Status status; 14 | private String code; 15 | private String message; 16 | 17 | public static Result success() { 18 | Result result = new Result<>(); 19 | result.setCode("200"); 20 | result.setStatus(Status.SUCCESS); 21 | result.setMessage(Status.SUCCESS.name()); 22 | return result; 23 | } 24 | 25 | public static Result success(T data) { 26 | Result result = new Result<>(); 27 | result.setCode("200"); 28 | result.setStatus(Status.SUCCESS); 29 | result.setMessage(Status.SUCCESS.name()); 30 | result.setData(data); 31 | return result; 32 | } 33 | 34 | public static Result error(String msg) { 35 | Result result = new Result<>(); 36 | result.setCode("500"); 37 | result.setStatus(Status.ERROR); 38 | result.setMessage(msg); 39 | return result; 40 | } 41 | 42 | public T getData() { 43 | return data; 44 | } 45 | 46 | public void setData(T data) { 47 | this.data = data; 48 | } 49 | 50 | public String getCode() { 51 | return code; 52 | } 53 | 54 | public void setCode(String code) { 55 | this.code = code; 56 | } 57 | 58 | public String getMessage() { 59 | return message; 60 | } 61 | 62 | public void setMessage(String message) { 63 | this.message = message; 64 | } 65 | 66 | public Status getStatus() { 67 | return status; 68 | } 69 | 70 | public void setStatus(Status status) { 71 | this.status = status; 72 | } 73 | 74 | public static enum Status { 75 | SUCCESS("OK"), 76 | ERROR("ERROR"); 77 | 78 | private String code; 79 | 80 | private Status(String code) { 81 | this.code = code; 82 | } 83 | 84 | public String code() { 85 | return this.code; 86 | } 87 | } 88 | 89 | } -------------------------------------------------------------------------------- /layering-cache-web/src/main/java/com/github/xiaolyuh/web/vo/CacheStatsInfoVo.java: -------------------------------------------------------------------------------- 1 | package com.github.xiaolyuh.web.vo; 2 | 3 | import com.github.xiaolyuh.stats.CacheStatsInfo; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * @author olafwang 9 | * @since 2020/9/25 3:07 下午 10 | */ 11 | public class CacheStatsInfoVo { 12 | List cacheStats; 13 | 14 | long time; 15 | 16 | public List getCacheStats() { 17 | return cacheStats; 18 | } 19 | 20 | public void setCacheStats(List cacheStats) { 21 | this.cacheStats = cacheStats; 22 | } 23 | 24 | public long getTime() { 25 | return time; 26 | } 27 | 28 | public void setTime(long time) { 29 | this.time = time; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /layering-cache-web/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | # 不能改 2 | spring.thymeleaf.prefix=classpath:/html/ 3 | 4 | # 端口号 5 | server.port=8081 6 | spring.application.name=layering-cache-web 7 | 8 | # 管理界面登录的用户名密码 9 | layering-cache.web.user-name=admin 10 | layering-cache.web.password=admin 11 | 12 | -------------------------------------------------------------------------------- /layering-cache-web/src/main/resources/html/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Layering Cache 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 26 | 27 | 28 |
29 |
30 |

Layering Cache

31 |

为监控而生的多级缓存框架

32 |
33 |
34 |
35 |
36 |
37 |

登录

38 |
39 |
40 | Github 41 |
42 |
43 |
44 | 45 |
46 | 47 | 48 |
49 | 50 | 51 |
52 |
53 | 54 |
55 |
56 |
57 |
58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /layering-cache-web/src/main/resources/html/nopermit.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Layering Cache 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 22 | 23 |
24 |
25 | Layering Cache 26 | 监控统计 27 |
28 | 29 | 30 | 31 |
32 |
33 |
34 | 35 |
36 | 37 |
38 |
39 | 40 |
41 |
42 |

没有权限访问该页面

43 |

Sorry, you are not permitted to view this page.

44 |
45 |           .----.
46 |        _.'__    `.
47 |    .--($)($$)---/#\
48 |  .' @          /###\
49 |  :         ,   #####
50 |   `-..__.-' _.-\###/
51 |         `;_:    `"'
52 |       .'"""""`.
53 |      /,  ya ,\\
54 |     // 没权限! \\
55 |     `-._______.-'
56 |     ___`. | .'___
57 |    (______|______)
58 |         
59 |
60 |
61 |
62 | 63 |
64 |
65 |
66 |
67 | 68 | 69 |
70 | 71 | 72 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /layering-cache-web/src/main/resources/static/css/app.css: -------------------------------------------------------------------------------- 1 | /* Write your styles */ -------------------------------------------------------------------------------- /layering-cache-web/src/main/resources/static/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaolyuh/layering-cache/282d3c10fc935bbfd981397a0997bb27ffd9aeae/layering-cache-web/src/main/resources/static/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /layering-cache-web/src/main/resources/static/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaolyuh/layering-cache/282d3c10fc935bbfd981397a0997bb27ffd9aeae/layering-cache-web/src/main/resources/static/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /layering-cache-web/src/main/resources/static/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaolyuh/layering-cache/282d3c10fc935bbfd981397a0997bb27ffd9aeae/layering-cache-web/src/main/resources/static/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /layering-cache-web/src/main/resources/static/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaolyuh/layering-cache/282d3c10fc935bbfd981397a0997bb27ffd9aeae/layering-cache-web/src/main/resources/static/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /layering-cache-web/src/main/resources/static/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaolyuh/layering-cache/282d3c10fc935bbfd981397a0997bb27ffd9aeae/layering-cache-web/src/main/resources/static/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /layering-cache-web/src/main/resources/static/i/app-icon72x72@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaolyuh/layering-cache/282d3c10fc935bbfd981397a0997bb27ffd9aeae/layering-cache-web/src/main/resources/static/i/app-icon72x72@2x.png -------------------------------------------------------------------------------- /layering-cache-web/src/main/resources/static/i/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaolyuh/layering-cache/282d3c10fc935bbfd981397a0997bb27ffd9aeae/layering-cache-web/src/main/resources/static/i/favicon.png -------------------------------------------------------------------------------- /layering-cache-web/src/main/resources/static/i/startup-640x1096.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaolyuh/layering-cache/282d3c10fc935bbfd981397a0997bb27ffd9aeae/layering-cache-web/src/main/resources/static/i/startup-640x1096.png -------------------------------------------------------------------------------- /layering-cache-web/src/main/resources/static/js/lib/app.js: -------------------------------------------------------------------------------- 1 | (function($) { 2 | 'use strict'; 3 | 4 | $(function() { 5 | var $fullText = $('.admin-fullText'); 6 | $('#admin-fullscreen').on('click', function() { 7 | $.AMUI.fullscreen.toggle(); 8 | }); 9 | 10 | $(document).on($.AMUI.fullscreen.raw.fullscreenchange, function() { 11 | $fullText.text($.AMUI.fullscreen.isFullscreen ? '退出全屏' : '开启全屏'); 12 | }); 13 | }); 14 | })(jQuery); 15 | 16 | 17 | -------------------------------------------------------------------------------- /layering-cache-web/src/main/resources/static/js/views/login.js: -------------------------------------------------------------------------------- 1 | (function ($) { 2 | 3 | var constant = { 4 | LOGIN_FORM: '#login-form', 5 | LOGIN_BUTTON: '#login-button', 6 | }; 7 | 8 | var bindEvent = { 9 | bindLogin:function () { 10 | $(constant.LOGIN_BUTTON).on("click", function () { 11 | $.ajax({ 12 | type: 'POST', 13 | url: 'user/submit-login', 14 | dataType: 'JSON', 15 | data: $(constant.LOGIN_FORM).serialize(), 16 | success: function (data) { 17 | if (data.status == "SUCCESS") { 18 | window.location.href = "/index?token=" + data.data; 19 | } else { 20 | alert("用户名或密码错误"); 21 | } 22 | } 23 | }); 24 | }); 25 | } 26 | }; 27 | 28 | var index = { 29 | init: function () { 30 | bindEvent.bindLogin(); 31 | } 32 | }; 33 | 34 | $(function () { 35 | index.init(); 36 | }); 37 | })(jQuery); -------------------------------------------------------------------------------- /layering-cache-web/src/test/java/com/github/xiaolyuh/demo/LayeringCacheWebApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.github.xiaolyuh.demo; 2 | 3 | import org.junit.runner.RunWith; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | import org.springframework.test.context.junit4.SpringRunner; 6 | 7 | @RunWith(SpringRunner.class) 8 | @SpringBootTest 9 | public class LayeringCacheWebApplicationTests { 10 | 11 | } 12 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | Copyright 1999-2018 Alibaba Group Holding Ltd. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | --------------------------------------------------------------------------------