springboot使用j2cache框架和aspectj自定义缓存

springboot使用j2cache框架和aspectj自定义缓存,第1张

springboot使用j2cache框架和aspectj自定义缓存

文章目录
  • 依赖
  • 项目代码
    • spring上下文工具类:
    • 自定义缓存注解
    • 自定义缓存拦截器
    • 缓存处理器
    • 缓存结果和缓存信息实体封装
    • 开启声明式注解
    • controller层使用缓存
  • 总结

依赖
 

             org.aspectj

             aspectjweaver

             1.8.7

        
        
            net.oschina.j2cache
            j2cache-core
            2.8.0-release
        

配置:

j2cache:
  cache-clean-mode: passive
  allow-null-values: true
  redis-client: lettuce #指定redis客户端使用lettuce,也可以使用Jedis
  l2-cache-open: true #开启二级缓存
  broadcast: net.oschina.j2cache.cache.support.redis.SpringRedisPubSubPolicy
  #  broadcast: jgroups
  L1: #指定一级缓存提供者为caffeine
    provider_class: caffeine
  L2: #指定二级缓存提供者为redis
    provider_class: net.oschina.j2cache.cache.support.redis.SpringRedisProvider
    config_section: lettuce
  sync_ttl_to_redis: true
  default_cache_null_object: false
  serialization: fst  #序列化方式:fst、kyro、Java
caffeine:
  properties: /caffeine.properties   # 这个配置文件需要放在项目中
lettuce:
  mode: single
  namespace:
  storage: generic
  channel: j2cache
  scheme: redis
  hosts: ${pinda.redis.ip}:${pinda.redis.port}
  password: ${pinda.redis.password}
  database: ${pinda.redis.database}
  sentinelMasterId:
  maxTotal: 100
  maxIdle: 10
  minIdle: 10
  timeout: 10000

caffeine.properties

default=2000, 2h
rx=2000, 2h
addressBook=2000, 2h

关于j2cache的region概念:
J2Cache 的 Region 来源于 Ehcache 的 Region 概念。

一般我们在使用像 Redis、Caffeine、Guava Cache 时都没有 Region 这样的概念,特别是 Redis 是一个大哈希表,更没有这个概念。

在实际的缓存场景中,不同的数据会有不同的 TTL 策略,例如有些缓存数据可以永不失效,而有些缓存我们希望是 30 分钟的有效期,有些是 60 分钟等不同的失效时间策略。在 Redis 我们可以针对不同的 key 设置不同的 TTL 时间。但是一般的 Java 内存缓存框架(如 Ehcache、Caffeine、Guava Cache 等),它没法为每一个 key 设置不同 TTL,因为这样管理起来会非常复杂,而且会检查缓存数据是否失效时性能极差。所以一般内存缓存框架会把一组相同 TTL 策略的缓存数据放在一起进行管理。
像 Caffeine 和 Guava Cache 在存放缓存数据时需要先构建一个 Cache 实例,设定好缓存的时间策略,如下代码所示:

Caffeine caffeine = Caffeine.newBuilder();
caffeine = caffeine.maximumSize(size).expireAfterWrite(expire, TimeUnit.SECONDS);
Cache theCache = caffeine.build()
这时候你才可以往 theCache 写入缓存数据,而不能再单独针对某一个 key 设定不同的 TTL 时间。

而 Redis 可以让你非常随意的给不同的 key 设置不同的 TTL。

J2Cache 是内存缓存和 Redis 这类集中式缓存的一个桥梁,因此它只能是兼容两者的特性。
J2Cache 默认使用 Caffeine 作为一级缓存,其配置文件位于 caffeine.properties 中。一个基本使用场景如下:

#########################################

# Caffeine configuration
# [name] = size, xxxx[s|m|h|d]

#########################################

default = 1000, 30m
users = 2000, 10m
blogs = 5000, 1h
上面的配置定义了三个缓存 Region ,分别是:

默认缓存,大小是 1000 个对象,TTL 是 30 分钟
users 缓存,大小是 2000 个对象,TTL 是 10 分钟
blogs 缓存,大小是 5000 个对象,TTL 是 1 个小时
例如我们可以用 users 来存放用户对象的缓存,用 blogs 来存放博客对象缓存,两种的 TTL 是不同的。

项目代码

现在上代码,Springbootzhenghej2cache进行缓存:

spring上下文工具类:

@Primary
@Component
public class SpringApplicationContextUtils {
    private static ApplicationContext springContext;
    @Autowired
    private ApplicationContext applicationContext;
    @PostConstruct
    private void init() {
        springContext = applicationContext;
    }

    
    public static ApplicationContext getApplicationContext() {
        return springContext;
    }

}

自定义缓存注解
@documented
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Cache {
    String region() default "rx";
    String key() default "";
    String params() default "";
}

@documented
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface CacheEvictor {
    Cache[] value() default {};
}

自定义了上面注解以后只需要在controller层需要注解的方法上加对应注解即可

自定义缓存拦截器

注意这里的Interceptor是org.aopalliance.intercept包下的
Spring的AOP只能支持到方法级别的切入。换句话说,切入点只能是某个方法。

package com.itheima.j2cache.aop;

import com.itheima.j2cache.annotation.Cache;
import com.itheima.j2cache.annotation.CacheEvictor;
import com.itheima.j2cache.aop.processor.AbstractCacheAnnotationProcessor;
import com.itheima.j2cache.aop.processor.CacheEvictorAnnotationProcessor;
import com.itheima.j2cache.aop.processor.CachesAnnotationProcessor;
import com.itheima.j2cache.utils.SpringApplicationContextUtils;
import org.aopalliance.intercept.Interceptor;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.context.annotation.import;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;



@Aspect
@Component
@EnableAspectJAutoProxy(proxyTargetClass = true)//指定使用cglib方式为Controller创建代理对象,代理对象其实是目标对象的子类
@import(SpringApplicationContextUtils.class)
public class CacheMethodInterceptor implements Interceptor{

    
    @Around("@annotation(com.itheima.j2cache.annotation.Cache)")//环绕通知
    //@Around注解,表示这是一个环绕通知。环绕通知是所有通知里功能最为强大的通知,可以实现前置通知、后置通知、异常通知以及返回通知的功能。目标方法进入环绕通知后,通过调用ProceedingJoinPoint对象的proceed方法使目标方法继续执行,开发者可以在此修改目标方法的执行参数、返回值等,并且可以在此处理目标方法的异常。


    public Object invokeCacheAllMethod(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
        //获得方法前面对象
        MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();
        //获得当前拦截到的Controller方法对象
        Method method = signature.getMethod();
        //获得方法上的Cache注解信息
        Cache cache = AnnotationUtils.findAnnotation(method, Cache.class);
        if(cache != null){
            System.out.println("需要进行设置缓存数据处理...");
            //创建处理器,具体处理缓存逻辑
            CachesAnnotationProcessor processor = AbstractCacheAnnotationProcessor.getProcessor(proceedingJoinPoint, cache);
            return processor.process(proceedingJoinPoint);
        }
        //没有获取到Cache注解信息,直接放行
        return proceedingJoinPoint.proceed(proceedingJoinPoint.getArgs());
    }

    
    @Around("@annotation(com.itheima.j2cache.annotation.CacheEvictor)")//环绕通知
    public Object invokeCacheEvictorAllMethod(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
        MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();
        Method method = signature.getMethod();
        CacheEvictor cacheEvictor = AnnotationUtils.findAnnotation(method, CacheEvictor.class);
        if(cacheEvictor != null){
            System.out.println("清理缓存处理...");
            //创建清理缓存的处理器
            CacheEvictorAnnotationProcessor processor = AbstractCacheAnnotationProcessor.getProcessor(proceedingJoinPoint, cacheEvictor);
            return processor.process(proceedingJoinPoint);
        }

        return proceedingJoinPoint.proceed(proceedingJoinPoint.getArgs());
    }

}

在上面的拦截器中使用了 CachesAnnotationProcessor processor = AbstractCacheAnnotationProcessor.getProcessor(proceedingJoinPoint, cache);来对缓存进行处理,需要自定义缓存处理器:

缓存处理器
import com.itheima.j2cache.annotation.Cache;
import com.itheima.j2cache.annotation.CacheEvictor;
import com.itheima.j2cache.model.AnnotationInfo;
import com.itheima.j2cache.utils.CacheKeyBuilder;
import com.itheima.j2cache.utils.SpringApplicationContextUtils;
import net.oschina.j2cache.CacheChannel;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.context.ApplicationContext;
import org.springframework.util.StringUtils;


public abstract class AbstractCacheAnnotationProcessor {
    protected CacheChannel cacheChannel;

    
     //注意这里使用应用上下文来获得对应的CacheChannel这个bean
    public AbstractCacheAnnotationProcessor(){
        ApplicationContext applicationContext = SpringApplicationContextUtils.getApplicationContext();
        cacheChannel = applicationContext.getBean(CacheChannel.class);
    }

    
    protected AnnotationInfo getAnnotationInfo(ProceedingJoinPoint proceedingJoinPoint,Cache cache){
        AnnotationInfo annotationInfo = new AnnotationInfo<>();
        annotationInfo.setAnnotation(cache);
        annotationInfo.setRegion(cache.region());
        try{
            annotationInfo.setKey(generateKey(proceedingJoinPoint,cache));
        }catch (Exception e){
            e.printStackTrace();
        }
        return annotationInfo;
    }

    
    protected String generateKey(ProceedingJoinPoint proceedingJoinPoint,Cache cache) throws IllegalAccessException{
        String key = cache.key();//ab
        if(!StringUtils.hasText(key)){
            //如果当前key为空串,重新设置当前可以为:目标Controller类名:方法名
            String className = proceedingJoinPoint.getTarget().getClass().getSimpleName();
            MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();
            String methodName = signature.getMethod().getName();
            key = className + ":" + methodName;
        }
        //ab:100
        key = CacheKeyBuilder.generate(key,cache.params(),proceedingJoinPoint.getArgs());
        return key;
    }

    
    public abstract Object process(ProceedingJoinPoint proceedingJoinPoint) throws Throwable;

    
    public static CachesAnnotationProcessor getProcessor(ProceedingJoinPoint proceedingJoinPoint, Cache cache){
        return new CachesAnnotationProcessor(proceedingJoinPoint,cache);
    }

    
    public static CacheEvictorAnnotationProcessor getProcessor(ProceedingJoinPoint proceedingJoinPoint, CacheEvictor cacheEvictor){
        return new CacheEvictorAnnotationProcessor(proceedingJoinPoint,cacheEvictor);
    }
}

清理缓存注解的处理器:


public class CacheEvictorAnnotationProcessor extends AbstractCacheAnnotationProcessor{
    
    private List> cacheList = new ArrayList<>();

    
    public CacheEvictorAnnotationProcessor(ProceedingJoinPoint proceedingJoinPoint, CacheEvictor cacheEvictor) {
        super();
        Cache[] value = cacheEvictor.value();
        for(Cache cache : value){
            AnnotationInfo annotationInfo = getAnnotationInfo(proceedingJoinPoint, cache);
            cacheList.add(annotationInfo);
        }
    }

    
    public Object process(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
        for (AnnotationInfo annotationInfo : cacheList) {
            String region = annotationInfo.getRegion();
            String key = annotationInfo.getKey();
            //清理缓存数据
            cacheChannel.evict(region,key);
        }
        //调用目标方法(就是Controller中的方法)
        return proceedingJoinPoint.proceed(proceedingJoinPoint.getArgs());
    }
}

缓存注解处理器:


public class CachesAnnotationProcessor extends AbstractCacheAnnotationProcessor {
    private static final Logger logger = LoggerFactory.getLogger(CachesAnnotationProcessor.class);
    private AnnotationInfo annotationInfo;

    
    public CachesAnnotationProcessor(ProceedingJoinPoint proceedingJoinPoint, Cache cache) {
        super();
        //创建注解信息对象
        annotationInfo = getAnnotationInfo(proceedingJoinPoint,cache);
    }

    
    public Object process(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
        Object result = null;
        boolean existsCache = false;
        //1、获取缓存数据
        CacheHolder cacheHolder = getCache(annotationInfo);
        if(cacheHolder.isExistsCache()){
            //2、如果缓存数据存在则直接返回(相当于controller的目标方法没有执行)
            result = cacheHolder.getValue();//缓存结果数据
            existsCache = true;
        }

        if(!existsCache){
            //3、如何缓存数据不存在,放行调用Controller的目标方法
            result = invoke(proceedingJoinPoint);
            //4、将目标方法的返回值载入缓存
            setCache(result);
        }
        //5、将结果返回
        return result;
    }

    
    private CacheHolder getCache(AnnotationInfo annotationInfo){
        Object value = null;
        String region = annotationInfo.getRegion();
        String key = annotationInfo.getKey();
        boolean exists = cacheChannel.exists(region, key);
        if(exists){
            CacheObject cacheObject = cacheChannel.get(region, key);
            value = cacheObject.getValue();//获得缓存结果数据
            return CacheHolder.newResult(value,true);
        }

        return CacheHolder.newResult(value,false);
    }

    
    private Object invoke(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
        return proceedingJoinPoint.proceed(proceedingJoinPoint.getArgs());
    }

    
    private void setCache(Object result){
        cacheChannel.set(annotationInfo.getRegion(),annotationInfo.getKey(),result);
    }
}

在上面的处理器中用 return CacheHolder.newResult(value,true);来获得缓存结果,需要自定义一个结果封装类

缓存结果和缓存信息实体封装

缓存信息封装


public class AnnotationInfo {
    private T annotation;
    private String region;
    private String key;//region:key:params

    public T getAnnotation() {
        return annotation;
    }

    public void setAnnotation(T annotation) {
        this.annotation = annotation;
    }

    public String getRegion() {
        return region;
    }

    public void setRegion(String region) {
        this.region = region;
    }

    public String getKey() {
        return key;
    }

    public void setKey(String key) {
        this.key = key;
    }

    public String toString() {
        if (annotation == null) {
            return null;
        }
        return JSONObject.toJSONString(this);
    }
}

缓存结果封装


public class CacheHolder {
    private Object value;//缓存的数据
    private boolean existsCache;//缓存数据是否存在
    private Throwable throwable;

    
    private CacheHolder() {
    }

    
    public Object getValue() {
        return value;
    }

    
    public boolean isExistsCache() {
        return existsCache;
    }

    
    public boolean hasError() {
        return throwable != null;
    }

    
    public static CacheHolder newResult(Object value, boolean existsCache) {
        CacheHolder cacheHolder = new CacheHolder();
        cacheHolder.value = value;
        cacheHolder.existsCache = existsCache;
        return cacheHolder;
    }

    
    public static CacheHolder newError(Throwable throwable) {
        CacheHolder cacheHolder = new CacheHolder();
        cacheHolder.throwable = throwable;
        return cacheHolder;
    }
}

开启声明式注解

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@documented
@import(CacheMethodInterceptor.class)
public @interface EnableCache {
}

注意自定义这个注解以后在主启动类上加@EnableCache即可表示开启注解

controller层使用缓存

@Log4j2
@RestController
@RequestMapping("addressBook")
public class AddressBookController {
    @Autowired
    private IAddressBookService addressBookService;

    @Autowired
    private CacheChannel cacheChannel;

    private String region = "addressBook";

    
    @PostMapping("")
    public Result save(@RequestBody AddressBook entity) {
        if (1 == entity.getIsDefault()) {
            addressBookService.lambdaUpdate().set(AddressBook::getIsDefault, 0).eq(AddressBook::getUserId, entity.getUserId()).update();
        }

        boolean result = addressBookService.save(entity);
        if (result) {
            //载入缓存
            cacheChannel.set(region,entity.getId(),entity);
            return Result.ok();
        }
        return Result.error();
    }

    
    @GetMapping("detail/{id}")
    @Cache(region = "addressBook",key = "ab",params = "id")
    public AddressBook detail(@PathVariable(name = "id") String id) {
       AddressBook addressBook = addressBookService.getById(id);
        return addressBook;
    }

    
    @GetMapping("page")
    public PageResponse page(Integer page, Integer pageSize, String userId, String keyword) {
        Page iPage = new Page(page, pageSize);
        Page pageResult = addressBookService.lambdaQuery()
                .eq(StringUtils.isNotEmpty(userId), AddressBook::getUserId, userId)
                .and(StringUtils.isNotEmpty(keyword), wrapper ->
                        wrapper.like(AddressBook::getName, keyword).or()
                                .like(AddressBook::getPhoneNumber, keyword).or()
                                .like(AddressBook::getCompanyName, keyword))
                .page(iPage);

        return PageResponse.builder()
                .items(pageResult.getRecords())
                .page(page)
                .pagesize(pageSize)
                .pages(pageResult.getPages())
                .counts(pageResult.getTotal())
                .build();
    }

    
    @PutMapping("/{id}")
    @CacheEvictor(value = {@Cache(region = "addressBook",key = "ab",params = "1.id")})
    public Result update(@PathVariable(name = "id") String id, @RequestBody AddressBook entity) {
        entity.setId(id);
        if (1 == entity.getIsDefault()) {
            addressBookService.lambdaUpdate().set(AddressBook::getIsDefault, 0).eq(AddressBook::getUserId, entity.getUserId()).update();
        }
        boolean result = addressBookService.updateById(entity);
        if (result) {
            return Result.ok();
        }
        return Result.error();
    }

    
    @DeleteMapping("/{id}")
    @CacheEvictor({@Cache(region = "addressBook",key = "ab",params = "id")})
    public Result del(@PathVariable(name = "id") String id) {
        boolean result = addressBookService.removeById(id);
        if (result) {
            return Result.ok();
        }
        return Result.error();
    }
}
总结

使用aspectj:AOP 技术利用一种称为"横切"的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可 *** 作性和可维护性。
这里的切面即用户请求时先查询缓存这一过程。
注意:这里使用的是aspectj而非Springaop,故使用时用法有不一样。
使用j2cache框架的整体逻辑:自定义缓存注解,类似springboot自带的cache,但是这里粒度更细,而且更好控制超时时间

缓存层类似如下图:

然后需要用到aspectj的aop逻辑,自定义横切关注点,这里的连接点即是controller层的方法,需要判断每个方法上是否存在cahce注解,如果不存在则直接放行( proceedingJoinPoint.proceed),如果存在则交给缓存处理器进行处理,这里添加和删除缓存主要用的是j2cache组件的cachechannel,个人理解它这里类似一个连接到缓存服务器的通道,且有相应的api可以供增删 *** 作(cacheChannel.set(annotationInfo.getRegion(),annotationInfo.getKey(),result))。在读取缓存时首先是从一级缓存中取,然后从二级缓存中取,如果没找到则查询数据库。对于缓存结果的获得通过封装一个缓存结果类和获得cache注解的信息类来获得( AnnotationInfo ,制定了这个类的数据类型是Annotation的子类)。

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/zaji/5677046.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-16

发表评论

登录后才能评论

评论列表(0条)

保存