京淘后端商品管理-Day12

1. 商品分类缓存实现

1.1 业务需求

当用户点击商品分类按钮时,应该实现缓存查询.
在这里插入图片描述
思路:
1).准备key=“ITEM_CAT::”+parentID
2).准备value="JSON"串
3).首先查询redis缓存
有: 直接获取缓存数据返回给用户.
没有: 直接查询数据库,之后将返回值结果保存到redis中,方便下次使用.

1.2 编辑ItemCatController

	/**
	 * 业务:查询商品分类信息,返回VO对象
	 * url地址: /item/cat/list
	 * 参数:  id:一级分类id值
	 * 返回值: EasyUITree对象     
	 * json格式:
	 * 	[{"id":"2","text":"王者荣耀","state":"closed"},{"id":"3","text":"王者荣耀","state":"closed"}]`
	 * sql语句:
	 * 		一级商品分类信息 parent_id=0 SELECT * FROM tb_item_cat WHERE parent_id=0
	 */
	@RequestMapping("/list")
	public List<EasyUITree>  findItemCatByParentId
	(@RequestParam(value = "id",defaultValue = "0") Long parentId){
		//初始化时应该设定默认值.
		//1.查询一级商品分类信息   
		//Long parentId = id==null?0L:id;
		
		//return itemCatService.findItemCatByParentId(parentId);
		//通过缓存的方式获取数据.
		return itemCatService.findItemCatByCache(parentId);
	}

1.3 编辑ItemCatService

	/**
	 * 通过缓存的方式查询数据库.
	 * 1).定义key
	 * 2).根据key查询redis.
	 */
	@SuppressWarnings("unchecked")
	@Override
	public List<EasyUITree> findItemCatByCache(Long parentId) {
		//1.定义key
		String key = "ITEM_CAT_LIST::"+parentId;
		List<EasyUITree> treeList = new ArrayList<EasyUITree>();
		Long  startTime = System.currentTimeMillis();
		//2.判断redis中是否有值
		if(jedis.exists(key)) {
			//不是第一次查询,则获取缓存数据之后直接返回
			String json = jedis.get(key);
			Long endTime = System.currentTimeMillis();
			treeList = 
					ObjectMapperUtil.toObject(json, treeList.getClass());
			System.out.println("redis查询缓存的时间为:"+(endTime-startTime)+"毫秒");
		}else {
			//redis中没有这个key,表示用户第一次查询.
			treeList = findItemCatByParentId(parentId);
			Long endTime = System.currentTimeMillis();
			//需要将list集合转化为json
			String json = ObjectMapperUtil.toJSON(treeList);
			//将数据保存到redis中
			jedis.set(key, json);
			System.out.println("查询数据库的时间为:"+(endTime-startTime)+"毫秒");
		}
		return treeList;
	}

1.4 速度测试

在这里插入图片描述
在这里插入图片描述

2.利用AOP实现redis缓存

2.1 传统项目弊端

说明:
1).由于将redis的操作写到service层中,必须导致业务的耦合性高
2).如果采用上述的方式完成缓存,则改缓存不通用,并且代码冗余.效率低.

2.2 AOP的核心理念

在这里插入图片描述
公式: AOP = 切入点表达式 + 通知方法

2.3 切入点表达式

1). bean(bean的ID) 按照指定的bean名称拦截用户的请求,之后执行通知方法. 只能匹配单个bean对象
2).within(包名.类名) 可以按照类通配的方式去拦截用户的请求. 控制粒度较粗.
3).execution(返回值类型 包名.类名.方法名(参数列表)) 方法参数级别 控制粒度较细
4).@annotation(包名.注解名称) 按照注解的方式去拦截用户请求.

2.4 通知方法

1.前置通知: 主要在 目标方法执行之前执行
2.后置通知: 在目标方法执行之后执行
3.异常通知: 在目标方法执行的过程中报了异常之后执行.
4.最终通知: 无论什么时候都要执行的通知方法.
上述的通知方法,无法控制目标方法是否执行.所以一般"只做记录不做改变"
5.环绕通知: 一般采用环绕通知 实现对业务的控制.

2.5 AOP入门案例

//1.将对象交给容器管理
@Component
//2.定义aop切面
@Aspect
public class CacheAOP {
	
	//公式:   切面 = 切入点表达式 + 通知方法.
	/**
	 * 业务需求: 要求拦截ItemCatServiceImpl类中的业务
	 * @Pointcut 切入点表达式 可以理解为就是一个if判断,只有满足条件,才能执行通知方法.
	 */
	//@Pointcut("bean(itemCatServiceImpl)")  //按类匹配,控制的粒度较粗   单个bean
	//@Pointcut("within(com.jt.service..*)")  //按类匹配,控制的粒度较粗     多个bean
	@Pointcut("execution(* com.jt.service..*.*(..))") //细粒度的匹配方式
	public void pointCut() {
		
	}
	
	//joinPoint 方法执行切恰好被切入点表达式匹配,该方法的执行就称之为连接点.
	@Before("pointCut()")
	public void before(JoinPoint joinPoint) {
		System.out.println("我是前置通知!!!!");
		String typeName = 
				joinPoint.getSignature().getDeclaringTypeName();
		String methodName = joinPoint.getSignature().getName();
		Object[] objs = joinPoint.getArgs();
		Object target = joinPoint.getTarget();
		System.out.println("方法执行的全路径为:"+typeName+"."+methodName);
		System.out.println("获取方法参数:"+objs);
		System.out.println("获取目标对象:"+target);
	}
	
	
	//添加环绕通知  可以控制目标方法执行 要求添加参数
	@Around("pointCut()")
	public Object around(ProceedingJoinPoint joinPoint) {
		
		System.out.println("我是环绕通知开始");
		try {
			//Object result = joinPoint.proceed();
			System.out.println("我是环绕通知结束");
			return null;
		} catch (Throwable e) {
			e.printStackTrace();
			throw new RuntimeException(e);
		}  //指定目标方法
		
	}
	
}

2.6 实现AOP 缓存处理

2.6.1 自定义注解

@Target(ElementType.METHOD)	//标识注解 对谁生效
@Retention(RetentionPolicy.RUNTIME) //注解使用的有效期
public @interface CacheFind {
	
	public String key();			  //标识存入redis的key的前缀
	public int seconds() default 0;  //标识保存的时间 单位是秒
	
}

2.6.2 注解标识

在这里插入图片描述

2.6.3 AOP实现缓存业务处理

	package com.jt.aop;

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.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

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

import redis.clients.jedis.Jedis;

//1.将对象交给容器管理
@Component
//2.定义aop切面
@Aspect
public class CacheAOP {
	
	@Autowired(required = false)
	private Jedis jedis;
	/**
	 * 实现思路:  拦截被@CacheFind标识的方法 之后利用aop进行缓存的控制
	 * 通知方法:  环绕通知
	 * 实现步骤:
	 * 		1.准备查询redis的key   ITEM_CAT_LIST::第一个参数
	 *      2.@annotation(cacheFind) 动态获取注解的语法.
	 *        拦截指定注解类型的注解并且将注解对象当做参数进行传递.
	 */
	@SuppressWarnings("unchecked")
	@Around("@annotation(cacheFind)")
	public Object around(ProceedingJoinPoint joinPoint,CacheFind cacheFind) {
		
		//1.获取用户注解中的key     ITEM_CAT_LIST::0
		String key = cacheFind.key();
		//2.动态获取第一个参数当做key
		String firstArg = joinPoint.getArgs()[0].toString();
		key += "::"+firstArg; 
		
		Object result = null;
		//3.根据key查询redis.
		if(jedis.exists(key)) {
			
			//根据redis获取数据信息
			String json = jedis.get(key);
			//如何获取返回值类型
			MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
			result = ObjectMapperUtil.toObject(json, methodSignature.getReturnType());
			System.out.println("aop查询redis缓存");
		}else {
			//如果key不存在,则证明是第一次查询.  应该查询数据库
			try {
				result = joinPoint.proceed(); //目标方法返回值
				System.out.println("AOP查询数据库获取返回值结果");
				//将数据保存到redis中
				String json = ObjectMapperUtil.toJSON(result);
				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(e);
			}
		}
		return result;
	}

	
	
	
	
	
	
	
	
	//公式:   切面 = 切入点表达式 + 通知方法.
	/**
	 * 业务需求: 要求拦截ItemCatServiceImpl类中的业务
	 * @Pointcut 切入点表达式 可以理解为就是一个if判断,只有满足条件,才能执行通知方法.
	 */
	/**
	//@Pointcut("bean(itemCatServiceImpl)")  //按类匹配,控制的粒度较粗   单个bean
	//@Pointcut("within(com.jt.service..*)")  //按类匹配,控制的粒度较粗     多个bean
	@Pointcut("execution(* com.jt.service..*.*(..))") //细粒度的匹配方式
	public void pointCut() {
		
	}
	
	//joinPoint 方法执行切恰好被切入点表达式匹配,该方法的执行就称之为连接点.
	@Before("pointCut()")
	public void before(JoinPoint joinPoint) {
		System.out.println("我是前置通知!!!!");
		String typeName = 
				joinPoint.getSignature().getDeclaringTypeName();
		String methodName = joinPoint.getSignature().getName();
		Object[] objs = joinPoint.getArgs();
		Object target = joinPoint.getTarget();
		System.out.println("方法执行的全路径为:"+typeName+"."+methodName);
		System.out.println("获取方法参数:"+objs);
		System.out.println("获取目标对象:"+target);
	}
	
	
	//添加环绕通知  可以控制目标方法执行 要求添加参数
	@Around("pointCut()")
	public Object around(ProceedingJoinPoint joinPoint) {
		
		System.out.println("我是环绕通知开始");
		try {
			//Object result = joinPoint.proceed();
			System.out.println("我是环绕通知结束");
			return null;
		} catch (Throwable e) {
			e.printStackTrace();
			throw new RuntimeException(e);
		}  //指定目标方法
		
	}
	**/
}

2.6.4 AOP和代理的关系

在这里插入图片描述

2.6.5 完成商品分类的缓存实现

在itemCatServiceImpl中添加注解。实现商品列表查询时的缓存处理。
在这里插入图片描述
在这里插入图片描述

作业

  1. 独立完成关于redis缓存实现

  2. 拦截所有的业务层的方式 service 要求打印 sys目标方法类名/方法名称/参数相关信息.并且要求记录各个方法的执行时间 环绕通知

  3. 实现异常的记录 但凡service层报错,需要打印目标方法类名/方法名/异常信息
    异常通知

  4. 记录业务层service层调用的返回值信息。 输出 类名/方法名/参数信息/返回值信息

猜你喜欢

转载自blog.csdn.net/qq_16804847/article/details/107424610
今日推荐