Redis高级用法--AOP实现

1.利用AOP实现Redis缓存

1.1 为什么要使用AOP?

1).业务代码与Redis缓存服务,紧紧的耦合在一起. 不方便后期维护.
2).如果采用下列代码的形式添加缓存机制,则不方便.每次使用缓存时,都需要按照缓存的机制重新执行业务代码. 显得特别的繁琐.
3).利用AOP的形式实现该操作.!!

/**
	 * 数据的来源: 数据库中
	 * 数据库中的数据类型:  ItemCat对象信息   POJO
	 * 需要的类型:        EasyUITree对象信息.  VO对象
	 * 思路: 将ItemCat转化为EasyUITree对象.
	 * sql: parent_id=0 SELECT * FROM tb_item_cat WHERE parent_id=0
	 */
	@Override
	public List<EasyUITree> findItemCatByParentId(Long parentId) {
		//1.根据parentId 查询数据库信息  根据父级查询子级信息.
		QueryWrapper<ItemCat> queryWrapper = new QueryWrapper<>();
		queryWrapper.eq("parent_id", parentId);
		List<ItemCat> itemCatList = itemCatMapper.selectList(queryWrapper);
		
		//2.将数据库记录转化为VO数据.
		List<EasyUITree> treeList = new ArrayList<EasyUITree>();
		for (ItemCat itemCat : itemCatList) {
			Long id = itemCat.getId();
			String text = itemCat.getName();
			//如果是父级标题,则默认关闭 closed,否则开启. open
			String state = itemCat.getIsParent()?"closed":"open"; 
			EasyUITree uiTree = new EasyUITree(id, text, state);
			treeList.add(uiTree);
		}
		
		return treeList;
	}
	
	/**
	 * 思路:
	 * 	1.定义查询redis的key, key要求唯一的
	 *  2.第一次查询先查询redis.
	 *  	没有数据: 表示缓存中没有数据, 查询数据库,之后将数据保存到redis中
	 *  	有数据:   证明缓存中有值, 直接返回给用户即可.
	 */
	@SuppressWarnings("unchecked")
	@Override
	public List<EasyUITree> findItemCatCache(Long parentId) {
		//定义key值
		String key = "ITEM_CAT_LIST::" + parentId;
		List<EasyUITree> treeList = new ArrayList<EasyUITree>();
		//判断redis缓存中是否有值
		if (jedis.exists(key)) {
			//表明有值时,直接将数据返回到页面
			String json = jedis.get(key);
			//将json转化为对象,返回到页面上
			treeList = ObjectMapperUtil.toObject(json, treeList.getClass());
			System.out.println("redis缓存查询");
		}else {
			//表明没有值,先查询数据库
			treeList = findItemCatByParentId(parentId);
			//将对象转化为json
			String json = ObjectMapperUtil.toJson(treeList);
			//将json保存到redis缓存中
			jedis.set(key, json);
			System.out.println("查询数据库");
		}
		return treeList;
	}

1.2 AOP核心理念

1.2.1 AOP说明

AOP: 对原有的方法进行扩展,可以将重复的事情,但是右不得不做的事情,放到AOP中执行.可以减少代码的耦合性.降 低维护的成本
Redis高级用法--AOP实现_第1张图片
公式:AOP = 切入点表达式 + 通知方法

1.2.2 切入点表达式

Spring中通过切入点表达式定义具体切入点,其常用AOP切入点表达式定义及说明:
Redis高级用法--AOP实现_第2张图片
bean表达式(重点)
bean表达式一般应用于类级别,实现粗粒度的切入点定义,案例分析:
bean(“userServiceImpl”)指定一个userServiceImpl类中所有方法。
bean("*ServiceImpl")指定所有后缀为ServiceImpl的类中所有方法。
说明:bean表达式内部的对象是由spring容器管理的一个bean对象,表达式内部的名字应该是spring容器中某个bean的name。

within表达式(了解)
within表达式应用于类级别,实现粗粒度的切入点表达式定义,案例分析:
within(“aop.service.UserServiceImpl”)指定当前包中这个类内部的所有方法。
within(“aop.service.") 指定当前目录下的所有类的所有方法。
within("aop.service…
”) 指定当前目录以及子目录中类的所有方法。

execution表达式(了解)
execution表达式应用于方法级别,实现细粒度的切入点表达式定义,案例分析:
语法:execution(返回值类型 包名.类名.方法名(参数列表))。
execution(void aop.service.UserServiceImpl.addUser())匹配addUser方法。
execution(void aop.service.PersonServiceImpl.addUser(String)) 方法参数必须为String的addUser方法。
execution(* aop.service….(…)) 万能配置。

@annotation表达式(重点)
@annotaion表达式应用于方法级别,实现细粒度的切入点表达式定义,案例分析
@annotation(anno.RequiredLog) 匹配有此注解描述的方法。
@annotation(anno.RequiredCache) 匹配有此注解描述的方法。
其中:RequiredLog为我们自己定义的注解,当我们使用@RequiredLog注解修饰业务层方法时,系统底层会在执行此方法时进行扩展操作。

1.2.3 通知方法

在基于Spring AOP编程的过程中,基于AspectJ框架标准,spring中定义了五种类型的通知(通知描述的是一种扩展业务),它们分别是:
@Before
@AfterReturning
@AfterThrowing
@After
@Around.重点掌握(优先级最高)
Redis高级用法--AOP实现_第3张图片

1.2.3 AOP 相关术语

AOP 相关术语分析
切面(aspect): 横切面对象,一般为一个具体类对象(可以借助@Aspect声明)。
通知(Advice):在切面的某个特定连接点上执行的动作(扩展功能),例如around,before,after等。
连接点(joinpoint):程序执行过程中某个特定的点,一般指被拦截到的的方法。
切入点(pointcut):对多个连接点(Joinpoint)一种定义,一般可以理解为多个连接点的集合。

1.2.4 入门案例

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

//1.将对象交给spring容器管理
@Component
//2.定义一个切面
@Aspect
public class CacheAop {
	//切入点表达值
	@Pointcut("bean(itemCatServiceImpl)") //按类匹配
	public void pointCut() {
		
	}
	
	//通知方法
	/**
	 * 记录程序的执行状态  获取哪个类,哪个方法执行的
	 */
	@Before("pointCut()")
	public void before(	JoinPoint joinPoint) {
		System.out.println("我是前置通知");
		//获取方法的所在的包名和类名
		String typeName = joinPoint.getSignature().getDeclaringTypeName();
		String methodName = joinPoint.getSignature().getName();//获取方法的方法名
		Object[] args = joinPoint.getArgs();//获取方法的参数
		Object target = joinPoint.getTarget();//获取目标对象
		System.out.println("方法执行的全路径:" + typeName + "." + methodName);
		System.out.println("方法的参数:" + args);
		System.out.println("目标对象:" + target);
		
	}
	
	//添加环绕通知  可以控制目标方法执行
	@Around("pointCut()")
	public Object around(ProceedingJoinPoint joinPoint) {
		System.out.println("我是环绕通知的开始");
		try {
			Object result = joinPoint.proceed();
			System.out.println("我是环绕通知的结束");
			return result;
		} catch (Throwable e) {
			e.printStackTrace();
			throw new RuntimeException(e);
		}
	}
}

2.实现AOP缓存处理

2.1 自定义注解

import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

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

@Retention(RUNTIME) //表示运行期有效  注解使用的声明周期
@Target(METHOD)//表示注解在方法上有效
public @interface CacheFind {
	public String key();  //表示存入redis的key的前缀
	public int seconds() default 0; //表示保存的时间单位是秒
}

2.2 自定义注解的标识

Redis高级用法--AOP实现_第4张图片

2.3 实现AOP的缓存处理

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.jt.annotation.CacheFind;
import com.jt.util.ObjectMapperUtil;

import redis.clients.jedis.Jedis;

@Component
@Aspect
public class CacheRedis {
	@Autowired
	private Jedis jedis;
	/**
	 * 实现思路: 拦截@CacheFind标识的方法之后利用AOP进行缓存的控制
	 * 通知方法:环绕通知
	 * 实现步骤:
	 * 	1.准备redis查询的key    ITEM_CAT_PARENTID  
	 *  2.@annotation(cacheFind)动态获取注解的语法,拦截指定注解类型的注解并且将注解对象
	 *  	当做参数进行传递
	 */
	
	@SuppressWarnings("unchecked")
	@Around("@annotation(cacheFind)")
	public Object around(ProceedingJoinPoint joinPoint,CacheFind cacheFind) {
		//1.获取用户注解中的key
		String key = cacheFind.key();
		//2.动态获取第一个参数当做key
		String args1 = joinPoint.getArgs()[0].toString();
		key += args1;
		//3.根据key查询redis
		Object result = null;
		if (jedis.exists(key)) {
			//如果缓存中有数据
			//获取redis中的数据
			String json = jedis.get(key);
			//动态获取目标方法的返回值类型
			MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
			Class returnType = methodSignature.getReturnType();
			//将json字符串转化为对象
			result = ObjectMapperUtil.toObject(json, returnType);
			System.out.println("查询AOP缓存");
		}else {
			try {
				//如果key不存在,则需要执行目标方法查询数据库
				result = joinPoint.proceed();
				//将查询到的数据转化为json字符串
				String json = ObjectMapperUtil.toJson(result);
				System.out.println("AOP执行数据库查询");
				//将json保存到redis中
				int seconds = cacheFind.seconds();
				if (seconds > 0) {
					jedis.setex(key, seconds, json);//添加超时时间
				}else {
					jedis.set(key, json);//不需要超时时间
				}
			} catch (Throwable e) {
				e.printStackTrace();
				throw new RuntimeException();
			}
		}
		return result;
	}
}

你可能感兴趣的:(笔记,java)