当前位置: 首页 > news >正文

在SpringBoot项目中实现切面执行链功能

1.定义切面执行链顶级接口 AspectHandler

/**
 * 切面执行链
 *
 */
public interface AspectHandler {

    /**
     * 设置排除项
     * @param excludes
     */
    default void setExcludes(List<String> excludes) {

    }

    /**
     * 获取排除项
     * @return
     */
    default List<String> getExcludes() {
        return new ArrayList<>();
    }

    /**
     * 前置处理
     * @param pjp
     * @return
     * @throws Exception
     */
    boolean execute(ProceedingJoinPoint pjp) throws Exception;

    /**
     * 后置处理
     * @param pjp
     * @param response
     * @param exception
     */
    default void afterCompletion(ProceedingJoinPoint pjp, Object response, Exception exception) {
    }
}

2.定义切面处理顺序注解 AspectHandlerOrder

/**
 * 处理器排序
 */
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface AspectHandlerOrder {
    /**
     * The order value.
     * <p>Default is {@link Ordered#LOWEST_PRECEDENCE}.
     *
     * @see Ordered#getOrder()
     */
    int value() default Ordered.LOWEST_PRECEDENCE;
}

3.web层统一处理切面类 AbstractWebAspect

/**
 * web层的统一切面,需要在使用时进行统一配置,设置切点
 */
public abstract class AbstractWebAspect implements InitializingBean {
    private static final Logger logger = LoggerFactory.getLogger(AbstractWebAspect.class);

    private final static String SESSION_KEY = "sessionId";

    @Autowired(required = false)
    @Qualifier("apiAspectHandlers")
    private List<AspectHandler> aspectHandlers;

    private AntPathMatcher antPathMatcher = new AntPathMatcher();

    @Override
    public void afterPropertiesSet() throws Exception {
        if (!CollectionUtils.isEmpty(aspectHandlers)) {
            Collections.sort(aspectHandlers, (o1, o2) -> {
                AspectHandlerOrder o1HandlerOrder = o1.getClass().getAnnotation(AspectHandlerOrder.class);
                AspectHandlerOrder o2HandlerOrder = o2.getClass().getAnnotation(AspectHandlerOrder.class);
                int o1OrderValue = o1HandlerOrder == null ? Ordered.LOWEST_PRECEDENCE : o1HandlerOrder.value();
                int o2OrderValue = o2HandlerOrder == null ? Ordered.LOWEST_PRECEDENCE : o2HandlerOrder.value();
                return o1OrderValue == o2OrderValue ? 0 : o1OrderValue > o2OrderValue ? 1 : -1;
            });
        }
    }


    public List<AspectHandler> getAspectHandlers() {
        return aspectHandlers;
    }


    private List<Class<? extends AspectHandler>> asList(Class<? extends AspectHandler>[] classes) {
        if (classes != null && classes.length > 0) {
            List<Class<? extends AspectHandler>> list = new ArrayList<>();
            for (Class<? extends AspectHandler> aClass : classes) {
                list.add(aClass);
            }
            return list;
        }
        return new ArrayList<>();
    }

    protected Object doAroundMethod(ProceedingJoinPoint pjp, List<AspectHandler> aspectHandlers) throws Exception {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        Exception exception = null;
        Object response = null;
        ArrayList<AspectHandler> executedAspectHandlers = new ArrayList<>();

        String methodName = pjp.toShortString();

        try {
            Object[] args = pjp.getArgs();
            logger.info("Aspect request={},args={}", methodName, Arrays.toString(args));
            // whether declare with AspectHandlerExclude annotation
            // if true pass declare excludes, default empty pass all
            // refactor
            Annotation clazzAnnotation = getClazzAnnotation(pjp, AspectHandlerExclude.class);
            Annotation methodAnnotation = getMethodAnnotation(pjp, AspectHandlerExclude.class);
            boolean result = true;

            Class<? extends AspectHandler>[] methodExcludes = methodAnnotation == null ? new Class[]{} : ((AspectHandlerExclude) methodAnnotation).excludes().length == 0 ? aspectHandlers.stream().map(a -> a.getClass()).collect(Collectors.toList()).toArray(new Class[]{}) : ((AspectHandlerExclude) methodAnnotation).excludes();
            Class<? extends AspectHandler>[] clazzExcludes = clazzAnnotation == null ? new Class[]{} : ((AspectHandlerExclude) clazzAnnotation).excludes().length == 0 ? aspectHandlers.stream().map(a -> a.getClass()).collect(Collectors.toList()).toArray(new Class[]{}) : ((AspectHandlerExclude) clazzAnnotation).excludes();

            if (!CollectionUtils.isEmpty(aspectHandlers)) {
                aspectHandlers = new ArrayList<>(aspectHandlers);
                if (clazzExcludes.length > 0) {
                    List<Class<? extends AspectHandler>> classes = asList(clazzExcludes);
                    aspectHandlers.removeIf(h -> classes.contains(h.getClass()));
                }
                if (methodExcludes.length > 0) {
                    List<Class<? extends AspectHandler>> classes = asList(methodExcludes);
                    aspectHandlers.removeIf(h -> classes.contains(h.getClass()));
                }

                for (AspectHandler aspectHandler : aspectHandlers) {
                    executedAspectHandlers.add(aspectHandler);
                    result = aspectHandler.execute(pjp);
                    if (!result) {
                        break;
                    }
                }
            }
            if (result) {
                response = pjp.proceed();
                if (response != null && response instanceof ApiResponse) {
                    ApiResponse re = (ApiResponse) response;
                    String sessionId = MDC.get(SESSION_KEY);
                    re.setReqId(sessionId);
                    return re;
                }
                return response;
            }
        } catch (Throwable throwable) {
            exception = (Exception) throwable;
            logger.error("execute:[{}],throw exception:[{}],message:[{}]", methodName, exception == null ? "" : exception.getClass().getName(), exception == null ? "" : exception.getMessage());
            throw exception;
        } finally {
            if (executedAspectHandlers.size() > 0) {
                for (int i = executedAspectHandlers.size() - 1; i >= 0; i--) {
                    AspectHandler ah = executedAspectHandlers.get(i);
                    try {
                        ah.afterCompletion(pjp, response, exception);
                    } catch (Exception e) {
                        logger.error("AspectHandler afterCompletion execute error", e);
                    }
                }
            }
            stopWatch.stop();
            long elapseTime = stopWatch.getTotalTimeMillis();
            logger.info("Aspect elapseTime:[{}],methodName:[{}]", elapseTime, methodName);
        }
        return response;

    }

    private void populateReqId(Object response) {

    }

    protected Object doAroundMethod(ProceedingJoinPoint pjp) throws Exception {
        return doAroundMethod(pjp, this.aspectHandlers);
    }

    private boolean matchExclude(AspectHandler aspectHandler, Class<? extends AspectHandler>[] excludes) {
        for (Class<? extends AspectHandler> exclude : excludes) {
            if (aspectHandler.getClass().equals(exclude)) {
                return true;
            }
        }
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        String url = request.getRequestURI();
        List<String> excludeUrls = aspectHandler.getExcludes();
        if (!CollectionUtils.isEmpty(excludeUrls)) {
            for (String excludeUrl : excludeUrls) {
                if (antPathMatcher.match(excludeUrl, url)) {
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * get the annotation declare with method<br/>
     * if declare return the annotation,otherwise return null
     *
     * @param pjp
     * @param annotation the annotation which find
     * @return
     */
    private Annotation getMethodAnnotation(ProceedingJoinPoint pjp, Class<? extends Annotation> annotation) {
        Signature signature = pjp.getSignature();
        if (signature instanceof MethodSignature) {
            MethodSignature methodSignature = (MethodSignature) signature;
            return methodSignature.getMethod().getAnnotation(annotation);
        }
        return null;
    }

    /**
     * get the annotation declare with class<br/>
     * if declare return the annotation,otherwise return null
     *
     * @param pjp
     * @param annotation the annotation which find
     * @return
     */
    private Annotation getClazzAnnotation(ProceedingJoinPoint pjp, Class<? extends Annotation> annotation) {
        return pjp.getTarget().getClass().getAnnotation(annotation);
    }
}

4.切面配置类 AspectConfiguration

@Configuration
public class AspectConfiguration {
	@Bean
	public List<AspectHandler> apiAspectHandlers() {
		return Arrays.asList(new HttpHeaderAspectHandler(),new SecurityAspectHandler());
	}
}

5.定义在Http请求头中添加自定义信息切面 HttpHeaderAspectHandler

@Slf4j
@AspectHandlerOrder(Ordered.HIGHEST_PRECEDENCE)
public class HttpHeaderAspectHandler implements AspectHandler {

    @Override
    public boolean execute(ProceedingJoinPoint proceedingJoinPoint) {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
        Enumeration<String> headerNames = request.getHeaderNames();
        HttpHeader httpHeader = new HttpHeader();
        while (headerNames.hasMoreElements()) {
            String s = headerNames.nextElement();
            String header = request.getHeader(s);
            Field field = ReflectionUtils.findField(HttpHeader.class, s);
            if (field != null) {
                field.setAccessible(true);
                ReflectionUtils.setField(field, httpHeader, header);
            }
        }

        //没有按要求返回的情况,赋值默认值
        if(ObjectUtils.isEmpty(AppSourceEnum.self(httpHeader.getApp_source()))){
            httpHeader.setApp_source(AppSourceEnum.DEFAULT.getCode().toString());
        }
        WebContext.setHttpHeader(httpHeader);
        return true;
    }

    @Override
    public void afterCompletion(ProceedingJoinPoint pjp, Object response, Exception exception) {
        AspectHandler.super.afterCompletion(pjp, response, exception);
        WebContext.removeHttpHeader();
        if (!(response instanceof ApiResponse)) {
            Optional.ofNullable(response).ifPresent((r) -> {
                log.error("controller return wrong type:" + response.getClass().getSimpleName());
            });
            return;
        }
    }
}

6.定义跳过切面注解 AspectHandlerExclude

/**
 * web controller中的方法加上该注解则会跳过切面逻辑
 *
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface AspectHandlerExclude {
    /**
     * 如果excludes为空则跳过所有handler如果不为空则跳过指定的handler
     *
     * @return
     */
    Class<? extends AspectHandler>[] excludes() default {};
}

7.在controller方法上添加跳过切面配置

@AspectHandlerExclude(excludes = { SecurityAspectHandler.class })
public ApiResponse<?> findPageList(searchRequestDTO searchRequestDTO) {

}

相关文章:

  • 【SQL学习进阶】从入门到高级应用【三范式】
  • ChatGPT AI专题资料合集【65GB】
  • [补题记录]LeetCode 167.两数之和 II - 输入有序数组
  • 【自己动手】自制刷题系统(php+layui应用 社区工作者题库)
  • 24年护网工具,今年想参加护网的同学要会用
  • 深度学习训练时混合精度的作用
  • 如何使用AES128位进行视频解密
  • Leetcode:字符串转换整数 (atoi)
  • 软件开发整体介绍
  • Java中连接Mongodb进行操作
  • iOS Hittest 机制和实际应用之一 hittest方法
  • 【魅力网页的背后】:CSS基础魔法,从零打造视觉盛宴
  • ChatGPT-3
  • 【开源】新生报到网站 JAVA+Vue.js+SpringBoot+MySQL
  • 【原创】springboot+mysql员工管理系统
  • springboot基础及上传组件封装
  • 数据结构-堆(带图)详解
  • 制作ChatPDF之Elasticsearch8.13.4搭建(一)
  • 解决TrueNas Scale部署immich后人脸识别失败,后台模型下载异常,immich更换支持中文搜索的CLIP大模型
  • leetcode1:两数之和
  • 方正证券总裁何亚刚到龄退休,54岁副总裁姜志军接棒
  • 850亿元!2025年中央金融机构注资特别国债(一期)拟第一次续发行
  • 洛杉矶奥组委确认2028年奥运会和残奥会开闭幕式场地
  • 大风暴雨致湖南岳阳县6户房屋倒塌、100多户受损
  • 98岁动物学家、北京大学教授杨安峰逝世
  • 特色茶酒、非遗挂面……六安皋品入沪赴“五五购物节”