springBoot 面向切面AOP的原理和使用

spring项目中经常有统一处理日志,对某些参数统一加解密、统一异常处理、统一监控接口请求等需求,此时,就可以使用AOP切面的功能增强。承接上一篇文章,自定义注解分析,今天继续进行。

1、切面的核心类伪代码:

@Aspect //核心AOP注解
@Configuration //扫描生成对象的注解,二选一
//@Component
@Slf4j
public class TestAspect {


    @Autowired
    private RedisUtil redisUtil;

    private List<String> urlList = new ArrayList<>();//本地一级缓存

    @Pointcut(value = "@annotation(com.nandao.demo.config.ReplaceUrl)")
    public void pointcutTest(){

    }

    @Around(value = "pointcutTest()")//核心注解,执行过程中触发
    public Object doAround(ProceedingJoinPoint pjp)throws Throwable{
       Long start = System.currentTimeMillis();
        Object proceed = pjp.proceed();
        String s = proceed.toString();
      // boolean flag = s.contains(ConstantsUtils.REPLACE_IMAGE_FLAG);
        boolean flag = s.contains("aa");
        log.info("是否包含需要替换的参数:{}",flag);
        String replaceUrl = null;//有效的某个新域名
        if(flag){
           // replaceUrl = getUsableUrl();
            replaceUrl = getUsableUrl();
            if(StringUtil.isEmpty(replaceUrl)){
                return proceed;
            }
            if(proceed instanceof R){
                R r = (R)proceed;
                Object object = r.getData();
                /**
                 * R中直接包含JSONObject
                 */
                if(object instanceof JSONObject){//控制层用的是阿里的JSONObject
                    JSONObject jsonObject = (JSONObject)object;
                    Set<String> keySet = jsonObject.keySet();
                    for(String key : keySet){
                        object = jsonObject.get(key);
                        //抽取为公共方法
                        replaceObjet(object,replaceUrl);
                    }
                }else {
                    /**
                     * R中可能直接包含对象、list、str、map
                     */
                    String returnClassName = object.getClass().getName();
                    if(!object.getClass().isAnnotationPresent(ReplaceObject.class) && !(object instanceof List) && !(object instanceof Map) && !(object instanceof PageUtils)){
                        log.info("不需要替换的类名",returnClassName);
                        return proceed;
                    }
                    log.info("需要替换的类名",returnClassName);
                    replaceObjet(object,replaceUrl);
                }
            }else {
                //如果返回值最外层不是R对象,此种情况暂时不存在
            }
        }
        Long end = System.currentTimeMillis();
        log.info("替换此接口的名耗时为[{}]毫秒",end-start);
        return proceed;
   }
}

判断返回的对象类型:

private <T> T replaceObjet(T object,String replaceUrl) throws Exception{
        int count = 0;
        if(object instanceof List){
            log.info("开始扫描[{}]集合",List.class.getName());
            /** 处理List类型 */
            List list = (List) object;
            for(Object item : list){
                log.info("[{}]中对象的第[{}]个元素",List.class.getSimpleName(),++count);
                if(item.getClass().isAnnotationPresent(ReplaceObject.class)){
                    processSingle(item,replaceUrl);
                }
            }
        }else if (object instanceof Map){
            Map<Object,Object> objectMap = (Map)object;
            for(Map.Entry<Object,Object> entry : objectMap.entrySet()){
                Object key = entry.getKey();
                Object v = entry.getValue();
                if(Objects.isNull(v)){
                    continue;
                }
                if(v instanceof String){
                    String s = (String)v;
                    s = replaceUrl(s,replaceUrl);
                    objectMap.put(key,s);
                }
                if(!(v.getClass().isAnnotationPresent(ReplaceObject.class)) && !(v instanceof List) && !(v instanceof Map)){
                    continue;
                }
                /** Map的Value可能是VO/LIST/MAP/String */
                replaceObjet(v,replaceUrl);
            }
        }else if (object instanceof PageUtils){
            PageUtils pageUtils = (PageUtils)object;
            List list = pageUtils.getList();
            for(Object item : list){
                log.info("[{}]中对象的第[{}]个元素",List.class.getSimpleName(),++count);
                if(item.getClass().isAnnotationPresent(ReplaceObject.class)){
                    processSingle(item,replaceUrl);
                }
            }
        }else if (object.getClass().isAnnotationPresent(ReplaceObject.class)){
            processSingle(object,replaceUrl);
        }
        return null;
    }

遍历对象中的参数及类型:

private <T> T processSingle(T single,String replaceUrl) throws Exception{
        /** 单个VO */
        Class<?> clazz = single.getClass();
        String className = clazz.getName();
        Field[] fields = clazz.getDeclaredFields();
        Class<?> classType = null;
        for(Field field : fields){
            field.setAccessible(true);
            classType = field.getType();
            /** 嵌套LIST、嵌套Map */
            if(Objects.equals(List.class,classType)){
                Class<?> actualClass = (Class<?>) getActualTypes(field)[0];
                if(Objects.isNull(field.get(single))){
                    log.info("扫描到[{}]对象中存在嵌套集合[{}<{}> {}]但值为空,不予处理",className,classType.getSimpleName(),actualClass.getSimpleName(),field.getName());
                    continue;
                }
                log.info("扫描到[{}]对象中存在嵌套集合[{}<{}> {}]",className,classType.getSimpleName(),actualClass.getSimpleName(),field.getName());
                replaceObjet(field.get(single),replaceUrl);
            }else if(Objects.equals(Map.class,classType)){
                Map<Object,Object> objectMap = (Map) single;
                Type[] actualTypes = getActualTypes(field);
                Class<?> keyActualClass = (Class<?>) actualTypes[0];
                Class<?> valActualClass = (Class<?>) actualTypes[1];
                if(Objects.isNull(field.get(single))){
                    log.info("扫描到[{}]对象中存在嵌套K-V集合[{}<{},{}> {}]但值为空,不予处理",className,classType.getSimpleName(),keyActualClass.getSimpleName(),valActualClass.getSimpleName(),field.getName());
                    continue;
                }
                log.info("扫描到[{}]对象中存在嵌套集合[{}<{},{}> {}]",className,classType.getSimpleName(),keyActualClass.getSimpleName(),valActualClass.getSimpleName(),field.getName());
                for(Map.Entry<Object,Object> entry : objectMap.entrySet()){
                    Object v = entry.getValue();
                    Object key = entry.getKey();
                    if(Objects.isNull(v)){
                        continue;
                    }
                    if(v instanceof String){
                        String s = (String)v;
                        s = replaceUrl(s,replaceUrl);
                        objectMap.put(key,s);
                    }
                    if(!(v.getClass().isAnnotationPresent(ReplaceObject.class)) && !(v instanceof List) && !(v instanceof Map)){
                        continue;
                    }
                    /** Map的Value可能是PO/VO/LIST/MAP */
                    replaceObjet(v,replaceUrl);
                }
            }else if(classType.isAnnotationPresent(ReplaceObject.class)){
                /** 嵌套VO/PO */
                if(Objects.isNull(field.get(single))){
                    log.info("扫描到[{}]对象中存在嵌套属性[{} {}]但值为空,不予处理",className,classType.getSimpleName(),field.getName());
                    continue;
                }
                log.info("扫描到[{}]对象中存在嵌套属性[{} {}]",className,classType.getSimpleName(),field.getName());
                processSingle(field.get(single),replaceUrl);
            }

            /** 处理被替换的字段 */
            if(!field.isAnnotationPresent(ReplaceValue.class)){
                continue;
            }
            Object original = field.get(single);
            String s = String.valueOf(original);
            if(s.contains(ConstantsUtils.REPLACE_IMAGE_FLAG)){//https://hu.com/fileservice/file/oss/?aaaaa
                if(s.contains(ConstantsUtils.HTTP_REQUEST_FLAG)){
                    //获得第一个点的位置
                    int index=s.indexOf("/");
                    log.info(index+"");
                    //根据第一个点的位置 获得第二个点的位置
                    index=s.indexOf("/", index+1);
                    //根据第二个点的位置,获得第三个点的位置
                    index = s.indexOf("/",index+1);
                    //根据第三个点的位置,截取 字符串。得到结果 result
                    String r=s.substring(index);
                    s = replaceUrl+r;
                }else {
                    s = replaceUrl+s;
                }
                field.set(single,s);
            }else {
                //asas
            }
            log.info("扫描到[{}]对象中需要被[{}]的属性[{}],[{}] => [{}]",className,field.getName(),original, s);
        }
        return single;
    }

获取有效的参数:

private String getUsableUrl(){
        String usableUrl = null;//有效的域名
        boolean status = false;//域名是否有效的标识
        usableUrl= (String)redisUtil.get(ConstantsUtils.REPLACE_LINK_KEY);
        if(StringUtil.isNotEmpty(usableUrl)){
            return usableUrl;
        }
        /**
         * 本地缓存中如果没有有效的域名,就去数据库查询
         */
        long s = System.currentTimeMillis();
        usableUrl = isysConfigService.getConfigByKey(ConstantsUtils.REPLACE_LINK_KEY);
        log.info("一次RPC调用耗时[{}]",(System.currentTimeMillis()-s));
        if(StringUtil.isEmpty(usableUrl)){
            log.info("后台没有配置新域名,传的参数是[{}]",ConstantsUtils.REPLACE_LINK_KEY);
            /**
             * redis缓存中如果没有有效的域名,就去本地缓存中取
             */
            if(!CollectionUtils.isEmpty(urlList)){
                usableUrl = urlList.get(0);
                if(StringUtil.isNotEmpty(usableUrl)){
                    log.info("本地缓存的域名可能无法使用");
                    return usableUrl;
                }
            }
                return null;
            }
            redisUtil.setEx(ConstantsUtils.REPLACE_LINK_KEY,usableUrl,ConstantsUtils.REPLACE_URL_TIME_OUT);//加超时时间,并发量很高时使用
            urlList.add(usableUrl);
            return usableUrl;
    }

2、执行前触发:

/**
     * 执行方法前
     * @param pjp
     * @return
     */
    @Before(value = "pointcutImage()")
    public Object doBefore(JoinPoint pjp){
        return new Object();
    }

3、接口执行后触发:

    /**
     * 方法执行后
     * @return
     */
    @AfterReturning(value = "pointcutImage()", returning = "result")
    public Object afterReturning(JoinPoint joinPoint, Object result) {
        System.out.println("执行了afterReturning方法: result=");
        return null;
    }

   /**
     * 后置通知
     * @param joinPoint 包含了目标方法的所有关键信息
     * @After 表示这是一个后置通知,即在目标方法执行之后执行
     */
    @After("@annotation(Action)")
    public void after(JoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        String name = signature.getName();
        System.out.println(name + "方法执行结束了...");
    }
 
     
 

4、接口执行后抛出异常:

/**
     * 方法执行后 并抛出异常
     * @param joinPoint
     * @return
     */
    @AfterThrowing(value = "pointcutImage()" , throwing = "ex")
    public Object afterThrowing(JoinPoint joinPoint, Exception ex) {
        System.out.println("执行了afterReturning方法: result=");
          log.info("替换图片地址报错的接口名[{}],类名[{}]",joinPoint.getSignature().getName(),joinPoint.getSignature().getDeclaringTypeName());
        log.info("报错日志{}",exception.getStackTrace());
        ex.printStackTrace();
        return null;
    }

5、切面的接口经常返回object ,需要判断类型、范型之类的、比如:

/**
     * 获取字段泛型
     * @param field 字段
     */
    private static Type[] getActualTypes(Field field){
        Type[] actualTypes = null;
        Type genericType = field.getGenericType();
        /** 无泛型 */
        if(Objects.isNull(genericType)) return actualTypes;
        /** 有泛型 */
        if(genericType instanceof ParameterizedType){
            ParameterizedType pt = (ParameterizedType) genericType;
            /** 泛型Class */
            actualTypes = pt.getActualTypeArguments();
        }
        return actualTypes;
    }

6、在切面里获取请求的域名、uri等信息:

/**
     * 获取请求的方法名称
     * @param joinPoint
     * @return
     */
    private String getClassMethodName(JoinPoint joinPoint) {
        /**
         * 后台管理admin服务中所有的控制层接口名被代理,只能取uri
         */
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        // 请求路径
      //  log.info("请求的uri"+request.getRequestURI());
        return request.getRequestURI();
        /**
         * 控制层的接口没有被代理可以走这里
         */
        //类方法
       /* log.info("classMethod={}", joinPoint.getSignature().getDeclaringTypeName());
        String declaringTypeName = joinPoint.getSignature().getDeclaringTypeName();
        String className = declaringTypeName.substring(declaringTypeName.lastIndexOf(".")+1);
        log.info("className={}", className);
        log.info("Method={}", joinPoint.getSignature().getName());
        return className +"."+ joinPoint.getSignature().getName();*/

    }

7、切面里获取请求的参数:

/**
     * 获取请求的参数
     * @param joinPoint
     * @return
     */
    private String getMethodArgs(JoinPoint joinPoint){
        // 构造参数组集合
        List<Object> argList = new ArrayList<>();
        for (Object arg : joinPoint.getArgs()) {
            // request/response无法使用toJSON
            if (arg instanceof HttpServletRequest) {
                argList.add("request");
            } else if (arg instanceof HttpServletResponse) {
                argList.add("response");
            } else {
                argList.add(JSON.toJSON(arg));
            }
        }

        return JSON.toJSON(argList).toString();
    }

到此AOP切面的分享告一段落,切面中涉及到的注意点,也都给大家呈现了出来,不明白的地方可以留言。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

寅灯

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值