深度解析Filter、Interceptor、 Aspect之间

2021-02-02  本文已影响0人  木木子丶

Filter,Interceptor,aspect应该如何选择

前言

大家应该都清除过滤器,拦截器,切面,他们都能起到阶段拦截的作用,但是不管是自己业务上的选择,还是面试官的提问,我们应该如何取舍

Filter 过滤器

代码

/**
 * @PROJECT_NAME: 杭州
 * @DESCRIPTION:
 * @author: 徐子木
 * @DATE: 2021/2/2 10:04 上午
 */
public class MyFilter implements Filter {

    private static final Logger LOGGER = LoggerFactory.getLogger(MyFilter.class);

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        LOGGER.debug("filter init");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        LOGGER.debug("do filter Wu Yue Ge Ge");

        chain.doFilter(request,response);
    }

    @Override
    public void destroy() {

    }
}

加载

   @Bean
    @Order(1)
    public FilterRegistrationBean configFilter(){
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
        filterRegistrationBean.setFilter(new MyFilter());
        filterRegistrationBean.addUrlPatterns("/*");
        filterRegistrationBean.setName("loginFilter");
        return filterRegistrationBean;
    }

Filter随web应用的启动而启动,只初始化一次,随web应用的停止而销毁

  1. 启动服务器加载过滤器的实例,并调用init()方法来初始化实例;
  2. 每一次请求时都只调用方法doFilter()进行处理;
  3. 停止服务器时调用destroy()方法,销毁实例.

注意

  1. filter里面是能够获取到请求的参数和响应的数据;但此方法是无法知道哪一个Controller类中的哪个方法被执行.
  2. 还有一点需要注意的是filter中是没发使用注入的bean的,也就是无法使用@Autowired, 为什么呢? 其实Spring中,web应用的启动顺序是: Listener->filter->servlet 先初始化listener,然后再来初始化filter的,再接着才到我们的dispathServlet的初始化,因此当我们需要在filter里注入一个bean时,就会注入失败,因为filter初始化时,注解的bean还没初始化,没法注入

Interceptor拦截器

依赖于web框架,在SpringMVC中就是依赖于SpringMVC框架.在实现上,基于Java的反射机制,属于面向切面编程(AOP)的一种运用,就是在一个方法前调用一个方法,或者在方法后调用一个方法

/**
 * @PROJECT_NAME: 杭州
 * @DESCRIPTION:
 * @author: 徐子木
 * @DATE: 2021/2/2 10:23 上午
 */
@Slf4j
public class LoginInterceptor extends HandlerInterceptorAdapter {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        if (request.getMethod().equals("OPTIONS")) {
            return true;
        }

        Optional<CurrentUser> optionalCurrentUser = LoginUserUtil.get(request);
        if (optionalCurrentUser.isPresent()) {
            CurrentUserHolder.set(optionalCurrentUser.get());
        } else {
            throw new AuthException("not login!");
        }
        return super.preHandle(request, response, handler);
    }
}

加载

  @Bean
    public WebMvcConfigurer webMvcConfigurer() {
        return new WebMvcConfigurer() {
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/**")
                        .allowedOrigins("http://babyface-test.galalive.vip","http://localhost:8000","http://console.galalive.vip","https://console.galalive.vip")
                        .allowedMethods("*")
                        .allowedHeaders("*")
                        .allowCredentials(true);
            }
        };
    }

拦截器中可以获取到Controller对象

        HandlerMethod method = (HandlerMethod) handler;
        //类名
        String name = method.getBean().getClass().getName();
        //方法名
        method.getMethod().getName();

注意

我们获取不到方法的参数值,为什么呢? 在DispatcherServlet类中,方法doDispatch

if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                    return;
                }

                // Actually invoke the handler.
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

                if (asyncManager.isConcurrentHandlingStarted()) {
                    return;
                }

                applyDefaultViewName(processedRequest, mv);
                mappedHandler.applyPostHandle(processedRequest, response, mv);

applyPreHandle这个方法执行,就是执行拦截器的preHandler方法,但这个过程中,controller方法没有从request中获取请求参数,组装方法参数;而是ha.handle这个方法的时候,才会组装参数,虽然没法得到方法的参数,但是可以获得IOC的bean

postHandler方法的执行,当Controller内部有异常,posthandler方法时不会执行的.

afterCompletion方法,不管controller内部是否有异常,都会执行此方法;此方法还会有个Exception ex这个参数,如果有异常,ex会有异常值;没有异常 此值为null,并且controoler内部有异常,但异常被@ControllerAdvice异常统一捕获的话,ex也会为null

Aspect切片

AOP操作可以对操作进行横向的拦截,最大的优势在于他可以获取执行方法的参数,对方法进行统一的处理.常见使用日志,事务,请求参数安全验证

/**
 * @program: 杭州品茗信息技术有限公司
 * @description
 * @author: 徐子木
 * @create: 2020-06-10 20:10
 **/

@Aspect
@Slf4j
@Component
public class PermissionAspect {

    private final CaServerService caServerService;

    private final SystemFunctionService systemFunctionService;

    private final BidOpenProperties bidOpenProperties;

    private final OperatingRecordService recordService;

    public PermissionAspect(CaServerService caServerService, SystemFunctionService systemFunctionService,
                            BidOpenProperties bidOpenProperties, OperatingRecordService recordService) {
        this.caServerService = caServerService;
        this.systemFunctionService = systemFunctionService;
        this.bidOpenProperties = bidOpenProperties;
        this.recordService = recordService;
    }


    @Pointcut("@annotation(cn.pinming.bidopening.aop.PermissionCheck)")
    public void permissionAnnotationCut() {
    }


    @Around("permissionAnnotationCut()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        if (!(joinPoint instanceof MethodInvocationProceedingJoinPoint)) {
            return joinPoint.proceed();
        }
        Signature signature = joinPoint.getSignature();
        if (!(signature instanceof MethodSignature)) {
            return joinPoint.proceed();
        }
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();
        PermissionCheck check = method.getAnnotation(PermissionCheck.class);
        if (check == null) {
            return joinPoint.proceed();
        }
        // url 的 path参数不好equals ,直接用methodName
        String methodName = method.getName();
        log.debug("permission check method: {}" ,methodName);
        UserRoleEnum[] roles = check.value();

        Boolean hasPerm = Boolean.FALSE;
        String userGroupId = BidopeningContext.getUserGroupId();
        if (StringUtils.isEmpty(userGroupId)) {
            throw new AccountException("请重新登录");
        }
        if(!systemFunctionService.isChecked(SystemFunctionEnum.CA_VERIFY.getCode())) {
            return joinPoint.proceed();
        }
        List<String> methodCollection = systemFunctionService.findByParentCode(SystemFunctionEnum.CA_VERIFY.getCode())
                .get()
                .stream()
                .filter(x->x.getChecked())
                .map(SystemFunctionResponse::getConfigValue)
                .collect(Collectors.toList());

        if (userGroupId.equals(UserRoleEnum.BID.getCode()) && methodCollection.contains(methodName)) {
            if (!caVerify(methodName)) {
                throw new PermissionException("请检查CA签名是否与开发平台签名一致");
            }
        }
        if (roles != null && roles.length > 0) {
            for (UserRoleEnum s : roles) {
                if (userGroupId.equals(s.getCode()) || userGroupId.equals(UserRoleEnum.JGR.getCode())
                        || userGroupId.equals(UserRoleEnum.TENDER.getCode()) || userGroupId.equals(UserRoleEnum.ADMIN.getCode())) {
                    hasPerm = Boolean.TRUE;
                }
            }
        }
        if (!hasPerm) {
            log.debug("当前用户无权进行该请求,方法名,方法名:{},当前角色:{},用户:{}",method,userGroupId,BidopeningContext.getCurrentEntId());
            throw new PermissionException("当前用户无权进行该请求");
        }
        return joinPoint.proceed();
    }


    private boolean caVerify(String methodName) {
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes sra = (ServletRequestAttributes) requestAttributes;
        HttpServletRequest request = sra.getRequest();
        String caVerify = request.getHeader("caVerify");
        CaSignInfo caSignInfo = JsonUtils.parseToObj(caVerify, CaSignInfo.class);
        if (caSignInfo == null || StringUtils.isNullOrEmpty(caSignInfo.getSignData()) || StringUtils.isNullOrEmpty(caSignInfo.getOriginData())) {
            log.warn("CA操作签名无效 methodName:{}", methodName);
            return false;
        }
        ApiResult apiResult = caServerService.verifyCert(caSignInfo);
        if (!apiResult.equalsSuccess()) {
            log.error("验签失败,签名信息:{},原因:{}",caSignInfo,apiResult.getMsg());
            return false;
        }

        OperatingRecord operatingRecord = OperatingRecord.builder()
                .supplierId(BidopeningContext.getCurrentEntId())
                .method(methodName)
                .signCert(caSignInfo.getSignCert())
                .signData(caSignInfo.getSignData())
                .originData(caSignInfo.getOriginData())
                .caCode(caSignInfo.getCaCode())
                .build();
        recordService.create(operatingRecord);
        return true;
    }


}

上述操作是本人所在公司要对方法进行权限验证或者CA锁信息验证,并结合注解定义切面使用

总结

这里最后总结一下过滤器,拦截器,Aspect的区别

\ Filter Interceptor Aspect
参数 servletRequest request,ServletResponse response HttpServletRequest request,HttpServletResponse response,Object handler ProceedingJoinPoint pjp
解释 可以拿到原始的Http请求,但无法获取请求控制器和控制器中的方法信息 可以拿到请求控制器和方法,但拿不到方法参数 可以拿到方法参数,但拿不到http请求和响应参数

他们的执行顺序是

filter->interceptor->ControllerAdvice->aspect->controller

返回值顺序,或异常返回顺序

Controller->aspect->ControllerAdvice->Interceptor->Filter

上一篇 下一篇

猜你喜欢

热点阅读