美文网首页
深度解析Filter、Interceptor、 Aspect之间

深度解析Filter、Interceptor、 Aspect之间

作者: 木木子丶 | 来源:发表于2021-02-02 13:26 被阅读0次

    Filter,Interceptor,aspect应该如何选择

    前言

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

    Filter 过滤器

    • 过滤器可以拦截到方法的请求和响应(ServletRequest request,ServletResponse response),并对请求响应做出过滤操作

    • 过滤器依赖servlet容器,在实现上,基于函数回调,他可以对几乎所有的请求进行过滤,一个过滤器实例只能在容器初始化时调用一次

    • 使用过滤器的目的是用来做一些过滤操作,获取我们想要获取的数据,比如:在过滤器中修改字符编码;在过滤器中修改HttpServletRequest的一些参数,包括:过滤低俗文字,危险字符等

    代码

    /**
     * @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() {
    
        }
    }
    

    加载

    • 第一种方式加@Component
    • 第二种(推荐)
       @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

    相关文章

      网友评论

          本文标题:深度解析Filter、Interceptor、 Aspect之间

          本文链接:https://www.haomeiwen.com/subject/lypazktx.html