Spring AOP结合Redis利用自定义注解实现通用缓存

Spring AOP结合Redis利用自定义注解实现通用缓存

1.目的
1. 缓存: 减少与数据库的交互, 提升查询性能;
2. 降低代码的侵入性
3. 提高代码的复用性
2.业务逻辑
1. 去缓存查询;
2. 如果缓存中没有数据, 则代表缓存穿透, 则去数据库查询, 再将数据存入缓存;
3. 如果缓存中有数据, 则直接返回;
3.技术点
1. spring AOP
2. Redis缓存
4.准备操作
1. 对象必须实现serializable接口, 可以被序列化;
2. 在spring核心配置文件中实现cglib动态代理;
3. redis的Template配置为JdkSerializationRedisSerializer; 目的可以将对象序列化为字节数组
<!-- spring配置文件中配置, 实现cglib动态代理 -->
<aop:aspectj-autoproxy proxy-target-class="true">
5.spring整合redis的配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:jpa="http://www.springframework.org/schema/data/jpa" xmlns:jaxws="http://cxf.apache.org/jaxws"
       xmlns:cache="http://www.springframework.org/schema/cache"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context.xsd
    http://www.springframework.org/schema/aop
    http://www.springframework.org/schema/aop/spring-aop.xsd
    http://www.springframework.org/schema/tx
    http://www.springframework.org/schema/tx/spring-tx.xsd
    http://www.springframework.org/schema/data/jpa
    http://www.springframework.org/schema/data/jpa/spring-jpa.xsd
    http://cxf.apache.org/jaxws
    http://cxf.apache.org/schemas/jaxws.xsd
    http://www.springframework.org/schema/cache
    http://www.springframework.org/schema/cache/spring-cache.xsd">

    <!-- jedis 连接池配置 -->
    <bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
        <property name="maxIdle" value="300"/>
        <property name="maxWaitMillis" value="3000"/>
        <property name="testOnBorrow" value="true"/>
    </bean>

    <!-- jedis 连接工厂 -->
    <bean id="redisConnectionFactory"
          class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"
          p:host-name="localhost" p:port="6379" p:pool-config-ref="poolConfig"
          p:database="0"/>

    <!-- spring data 提供 redis模板  -->
    <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
        <property name="connectionFactory" ref="redisConnectionFactory"/>
        <!-- 如果不指定 Serializer   -->
        <property name="keySerializer">
            <bean class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer"/>
        </property>
        <!-- 将对象转换为字节数据 -->
        <property name="valueSerializer">
            <bean class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer">
            </bean>
        </property>
    </bean>
</beans>
6.自定注解
package cn.itcast.bos.aop;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @Retention(RetentionPolicy.RUNTIME) : 访问时机运行时生效
 * @Target(ElementType.METHOD)  : 在哪里生效(方法上,类上或者...)
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface NeedCache {
}
7.配置一个通知
package cn.itcast.bos.aop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

/**
 * 定义一个通知
 * @Component: 被spring容器管理
 * @Aspect: 声明一个切面(切点 + 通知)
 */
@Component
@Aspect
public class RedisAdvice {

    /**
     * 注入Template
     * 1. string 为存储在redis中的key
     * 2. value 我们希望将对象(list集合)按照特定的格式存储在redis中,再按照特定的格式取出来.
     * -- 这里使用对象序列化, 实现的方式是将redis配置文件中的template的值设置为JdkSerializationRedisSerializer
     * -- 详细键配置文件
     */
    private RedisTemplate<String, byte[] > redisTemplate;

    /**
     * 配置环绕通知 + annotation的切点表达式
     * @annotation 注解形式的通知, 那个方法上添加了该注解, 则执行该通知
     */
    @Around("@annotation(cn.itcast.bos.aop.NeedCache)")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("自定义注解执行了...");
        /**
         * 确定redis的 key 和 value
         * key : 类名 + 方法名 + 参数列表(确定唯一的key)
         * value : 对象序列化的的byte数组(redis天然支持byte数组)
         */

        /**
         *  1. 组装key
         */
        StringBuilder sb = new StringBuilder();
        String className = joinPoint.getTarget().getClass().getName();      //类名
        String methodName = joinPoint.getSignature().getName();             //方法名
        // 拼接字符串
        sb.append(className);
        sb.append(methodName);

        Object[] args = joinPoint.getArgs();                                // 循环遍历得到参数的类型
        // 判断避免空指针
        if(args != null && args.length > 0){
            for (Object arg : args) {
                String argName = arg.getClass().getName();
                sb.append(argName);
            }
        }
        // 最终得到的key
        String key = sb.toString();

        /**
         * 2. 去查询缓存
         */
        // 查询缓存
        byte[] value = redisTemplate.opsForValue().get(key);
        // 判断是否获取到数据
        if(value != null && value.length > 0){
            // 说明缓存中查询到了数据, 将数据转换为对象返回
            // 将字节数据转换为对象
            ByteArrayInputStream bais = new ByteArrayInputStream(value);
            ObjectInputStream ois = new ObjectInputStream(bais);
            Object o = ois.readObject();
            return o;
        }else {
            // 如果缓存中没有数据, 则代表缓存穿透, 则去数据库查询, 再将数据存入缓存;
            Object proceed = joinPoint.proceed();   //放行, 执行目标方法    proceed = 返回值目标对象
            /**
             *  将查询到的对象序列化为字节数组
             *  对象序列化流: ObjectOutputStream,可以将对象, 序列化存入任意一个地方,
             *  目标是内存的一个字节数组 : ByteArrayOutputStream
             */
            // 内存中的字节数组流
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            // 对象序列化流
            ObjectOutputStream oos = new ObjectOutputStream(bos);
            oos.writeObject(proceed);
            // 获取字节数组
            byte[] bytes = bos.toByteArray();
            // 存入redis
            redisTemplate.opsForValue().set(key , bytes);

            // 将查询到的数据返回
            return proceed;
        }
    }
}
注: 相关概念
1. @Aspect: 声明一个切面(切点 + 通知)
2. redis的存储 key : string, value : 字节数组
3. @Around("@annotation(cn.orange.bos.aop.NeedCache)") 环绕通知 + 注解形式
    @annotation 注解形式的通知, 那个方法上添加了该注解, 则执行该通知
4. 对象序列化流: ObjectOutputStream,可以将对象, 序列化存入任意一个地方
5. 将目标转换为内存的一个字节数组 : ByteArrayOutputStream

猜你喜欢

转载自blog.csdn.net/Orangesss_/article/details/82564077