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

依赖和工具介绍

 <dependency>

            <groupId> org.aspectj</groupId >

            <artifactId> aspectjweaver</artifactId >

            <version> 1.8.7</version >

        </dependency>
        <dependency>
            <groupId>net.oschina.j2cache</groupId>
            <artifactId>j2cache-core</artifactId>
            <version>2.8.0-release</version>
        </dependency>

配置:


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是OSChina目前正在使用的两级缓存框架。

j2cache的两级缓存结构:

L1: 进程内缓存 caffeine/ehcache
L2: 集中式缓存 Redis/Memcached
j2cache其实并不是在重复造轮子,而是作资源整合,即将Ehcache、Caffeine、redis、Spring Cache等进行整合。

由于大量的缓存读取会导致L2的网络成为整个系统的瓶颈,因此L1的目标是降低对L2的读取次数。该缓存框架主要用于集群环境中。单机也可使用,用于避免应用重启导致的ehcache缓存数据丢失。

j2cache从1.3.0版本开始支持JGroups和Redis Pub/Sub两种方式进行缓存事件的通知。

数据读取顺序 -> L1 -> L2 -> DB

关于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<Object, Object> caffeine = Caffeine.newBuilder();
caffeine = caffeine.maximumSize(size).expireAfterWrite(expire, TimeUnit.SECONDS);
Cache<String, Object> 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上下文工具类:


/**
 * Spring上下文工具类
 */
@Primary
@Component
public class SpringApplicationContextUtils {
    private static ApplicationContext springContext;
    @Autowired
    private ApplicationContext applicationContext;
    @PostConstruct
    private void init() {
        springContext = applicationContext;
    }

    /**
     * 获取当前ApplicationContext
     *
     * @return ApplicationContext
     */
    public static ApplicationContext getApplicationContext() {
        return springContext;
    }

}

自定义缓存注解

如果项目中很多模块都需要使用缓存功能,这些模块都需要调用j2cache的API来进行缓存操作,这种j2cache提供的原生API使用起来就比较繁琐了,并且操作缓存的代码和我们的业务代码混合到一起,即j2cache的API对我们的业务代码具有侵入性。那么我们如何更加简洁、优雅的使用j2cache提供的缓存功能呢?

答案就是使用声明式缓存。所谓声明式缓存,就是定义缓存注解,在需要使用缓存功能的方法上加入缓存注解即可自动进行缓存操作。

这种使用方式类似于我们以前使用的声明式事务,即在类的方法上加入事务注解就可以实现事务控制。

注意:j2cache原生API和我们实现的声明式缓存可以兼容,即在项目中可以同时使用,互为补充。例如在Controller的方法中需要将多类业务数据载入缓存,此时通过声明式缓存就无法做到(因为声明式缓存只能将方法的返回值载入缓存),这种场景下就需要调用j2cache的原生API来完成。

/**
 * 缓存注解
 */
@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层需要注解的方法上加对应注解即可

缓存生成键工具类

/**
 * 缓存键生成工具
 */
public class CacheKeyBuilder {
    /**
     * 生成key
     *
     * @param key      键
     * @param params   参数
     * @param args     参数值
     * @return
     * @throws IllegalAccessException 当访问异常时抛出
     */
    public static String generate(String key, String params, Object[] args) throws IllegalAccessException {
        StringBuilder keyBuilder = new StringBuilder("");
        if (StringUtils.hasText(key)) {
            keyBuilder.append(key);
        }
        if (StringUtils.hasText(params)) {
            String paramsResult = ObjectAccessUtils.get(args, params, String.class, "_", "null");
            keyBuilder.append(":");
            keyBuilder.append(paramsResult);
        }
        return keyBuilder.toString();
    }
}

自定义缓存拦截器

注意这里的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{

    /**
     * 拦截方法上使用Cache注解的Controller
     * @param proceedingJoinPoint
     * @return
     * @throws Throwable
     */
    @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());
    }

    /**
     * 拦截方法上使用CacheEvictor注解的Controller
     * @param proceedingJoinPoint
     * @return
     * @throws Throwable
     */
    @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);
    }

    /**
     * 封装注解信息
     * @param proceedingJoinPoint
     * @param cache
     * @return
     */
    protected AnnotationInfo<Cache> getAnnotationInfo(ProceedingJoinPoint proceedingJoinPoint,Cache cache){
        AnnotationInfo<Cache> annotationInfo = new AnnotationInfo<>();
        annotationInfo.setAnnotation(cache);
        annotationInfo.setRegion(cache.region());
        try{
            annotationInfo.setKey(generateKey(proceedingJoinPoint,cache));
        }catch (Exception e){
            e.printStackTrace();
        }
        return annotationInfo;
    }

    /**
     * 动态解析注解信息,生成key
     * @param proceedingJoinPoint
     * @param cache
     * @return
     */
    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;
    }

    /**
     * 抽象方法,处理缓存操作,具体应该由子类具体实现
     * @param proceedingJoinPoint
     * @return
     * @throws Throwable
     */
    public abstract Object process(ProceedingJoinPoint proceedingJoinPoint) throws Throwable;

    /**
     * 获得缓存注解处理器对象
     * @param proceedingJoinPoint
     * @param cache
     * @return
     */
    public static CachesAnnotationProcessor getProcessor(ProceedingJoinPoint proceedingJoinPoint, Cache cache){
        return new CachesAnnotationProcessor(proceedingJoinPoint,cache);
    }

    /**
     * 获得清理缓存注解处理器对象
     * @param proceedingJoinPoint
     * @param cacheEvictor
     * @return
     */
    public static CacheEvictorAnnotationProcessor getProcessor(ProceedingJoinPoint proceedingJoinPoint, CacheEvictor cacheEvictor){
        return new CacheEvictorAnnotationProcessor(proceedingJoinPoint,cacheEvictor);
    }
}

清理缓存注解的处理器:


/**
 * 清理缓存数据处理器
 */
public class CacheEvictorAnnotationProcessor extends AbstractCacheAnnotationProcessor{
    /**
     * 封装注解信息集合
     */
    private List<AnnotationInfo<Cache>> cacheList = new ArrayList<>();

    /**
     * 初始化清理缓存注解处理器对象,同时初始化一些缓存操作的对象
     * @param proceedingJoinPoint
     * @param cacheEvictor
     */
    public CacheEvictorAnnotationProcessor(ProceedingJoinPoint proceedingJoinPoint, CacheEvictor cacheEvictor) {
        super();
        Cache[] value = cacheEvictor.value();
        for(Cache cache : value){
            AnnotationInfo<Cache> annotationInfo = getAnnotationInfo(proceedingJoinPoint, cache);
            cacheList.add(annotationInfo);
        }
    }

    /**
     * 具体清理缓存处理逻辑
     * @param proceedingJoinPoint
     * @return
     * @throws Throwable
     */
    public Object process(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
        for (AnnotationInfo<Cache> 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<Cache> annotationInfo;

    /**
     * 初始化处理器,同时将相关的对象进行初始化
     * @param proceedingJoinPoint
     * @param cache
     */
    public CachesAnnotationProcessor(ProceedingJoinPoint proceedingJoinPoint, Cache cache) {
        super();
        //创建注解信息对象
        annotationInfo = getAnnotationInfo(proceedingJoinPoint,cache);
    }

    /**
     * 具体缓存处理逻辑
     * @param proceedingJoinPoint
     * @return
     * @throws Throwable
     */
    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;
    }

    /**
     * 获取缓存数据
     * @param annotationInfo
     * @return
     */
    private CacheHolder getCache(AnnotationInfo<Cache> 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);
    }

    /**
     * 调用目标方法
     * @param proceedingJoinPoint
     * @return
     */
    private Object invoke(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
        return proceedingJoinPoint.proceed(proceedingJoinPoint.getArgs());
    }

    /**
     * 设置缓存数据
     * @param result
     */
    private void setCache(Object result){
        cacheChannel.set(annotationInfo.getRegion(),annotationInfo.getKey(),result);
    }
}

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

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

缓存信息封装


/**
 * Cache相关信息封装
 */
public class AnnotationInfo<T extends Annotation> {
    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() {
    }

    /**
     * 获取值
     *
     * @return
     */
    public Object getValue() {
        return value;
    }

    /**
     * 是否存在缓存
     *
     * @return
     */
    public boolean isExistsCache() {
        return existsCache;
    }

    /**
     * 是否有错误
     *
     * @return
     */
    public boolean hasError() {
        return throwable != null;
    }

    /**
     * 生成缓存结果的占位
     *
     * @param value       结果
     * @param existsCache 是否存在缓存
     * @return 缓存
     */
    public static CacheHolder newResult(Object value, boolean existsCache) {
        CacheHolder cacheHolder = new CacheHolder();
        cacheHolder.value = value;
        cacheHolder.existsCache = existsCache;
        return cacheHolder;
    }

    /**
     * 生成缓存异常的占位
     *
     * @param throwable 异常
     * @return 缓存
     */
    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";

    /**
     * 新增地址簿
     *
     * @param entity
     * @return
     */
    @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();
    }

    /**
     * 查询地址簿详情
     *
     * @param id
     * @return
     */
    @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;
    }

    /**
     * 分页查询
     *
     * @param page
     * @param pageSize
     * @param userId
     * @return
     */
    @GetMapping("page")
    public PageResponse<AddressBook> page(Integer page, Integer pageSize, String userId, String keyword) {
        Page<AddressBook> iPage = new Page(page, pageSize);
        Page<AddressBook> 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.<AddressBook>builder()
                .items(pageResult.getRecords())
                .page(page)
                .pagesize(pageSize)
                .pages(pageResult.getPages())
                .counts(pageResult.getTotal())
                .build();
    }

    /**
     * 修改
     *
     * @param id
     * @param entity
     * @return
     */
    @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();
    }

    /**
     * 删除
     *
     * @param id
     * @return
     */
    @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的子类)。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值