java—我可以在servlet过滤器中确定httpservletrequest是否Map到特定的spring控制器类吗

2g32fytz  于 2021-07-13  发布在  Java
关注(0)|答案(2)|浏览(328)

我正在开发一个使用 OncePerRequestFilter 使用传入的web请求执行一些自定义的类似日志的行为。此行为使用 HttpServletRequest & HttpServletResponse . 此外,过滤器同时使用 ContentCachingRequestWrapper & ContentCachingResponseWrapper 访问请求/响应机构。
已经决定,我们只想在调用了特定spring控制器的方法时执行此行为,因为我们不想对其他控制器/执行器端点等执行此操作。是否有方法可以判断传入的请求是否将(或曾经)Map到控制器?

public class ExampleFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(
            HttpServletRequest request, HttpServletResponse response,
            FilterChain filterChain) throws ServletException, IOException {
        // Can I tell here whether this will be mapping to an endpoint in
        // ExampleController or NestedExampleController?

        ContentCachingRequestWrapper requestToUse = new ContentCachingRequestWrapper(request);
        ContentCachingResponseWrapper responseToUse = new ContentCachingResponseWrapper(response);

        try {
            filterChain.doFilter(requestToUse, responseToUse);

            // Can I tell here whether this was mapped to an endpoint in
            // ExampleController or OtherExampleController?
        } finally {
            responseToUse.copyBodyToResponse(); // Write the cached body back to the real response
        }
    }
}
@RestController
@RequestMapping("/example")
public class ExampleController {
  @GetMapping("/{id}")
  public Example retrieveExample() {
    return getValue(); // Retrieve the value
  }

  // ...
}
@RestController
@RequestMapping("/example/{id}/nested")
public class NestedExampleController {
  @GetMapping("/{nestedId}")
  public NestedExample retrieveNestedExample() {
    return getValue(); // Retrieve the value
  }

  // ...
}

我对springmvc/boot的内部结构做了一些研究,我不确定是否有一种方法可以轻松地做到这一点。作为替代,我可以做一些手动url模式匹配,这可能不一定与控制器中的方法完全匹配,但可能会使我接近一个可以接受的解决方案。
总而言之:在web过滤器中有没有一种方法来判断传入请求是Map到控制器(在执行过滤器链之前)还是Map到控制器(在执行过滤器链之后)?

vsikbqxv

vsikbqxv1#

您需要的基本上是一个面向应用程序特定部分的横切关注点—在本例中是日志记录。
这是面向方面编程最常见的用例之一,spring使用aspectj风格的切入点为其提供了内置支持。
您需要:
要在配置类的spring配置中启用aop,请执行以下操作:

@Configuration
@EnableAspectJAutoProxy
public class AopConfiguration {
}

定义一个方面,例如:

@Aspect
public class LoggingAspect {

    Logger log = ...; // define logger

    // Matches all executions in com.example.ExampleController,
    // with any return value, using any parameters   
    @Pointcut("execution(* com.example.ExampleController.*(..))")
    public void controllerExecutionPointcut() {}

    @Around("controllerExecutionPointcut()")
    public Object aroundTargetControllerInvocation(ProceedingJoinPoint pjp) {

        log.debug("About to invoke method: {}", pjp.getSignature().getName());

        try {
            return pjp.proceed(); 
        } catch (Throwable t) {
            // note that getArgs() returns an Object[],
            // so you may want to map it to a more readable format
            log.debug("Encountered exception while invoking method with args {}", pjp.getArgs());
            throw t;
        }

        log.debug("Sucessfully finished invocation");
    }
}

有关切入点表达式的详细信息,请参见本指南。
另一个常见的用例是对方法调用进行计时,尽管对于这种情况,类似于使用测微计(以及用于spring的测微计适配器) @Timed 可能会更好。
您还可以阅读参考文档,其中提供了大量关于spring中aop如何工作的信息。
注意:和几乎所有其他spring代理机制一样,来自目标对象内部的调用不会被代理,即。 this.otherControllerMethod() 不会被上述建议截取。同样地, private 方法也不能被拦截。有关更多信息,请参阅参考文件的第5.4.3节。
最后一点,如果性能非常重要,您应该检查aspectj编译时或加载时编织,它消除了spring代理机制(spring aop在引擎盖下使用的机制)带来的一些开销。在您的情况下,这很可能不是必需的,但最好记住。
编辑以供评论:
谢谢!这种方法的一个警告是,它不允许我访问httpservletrequest或httpservletresponse,我正在使用它。如果我不需要的话,我知道这会有什么帮助。我发现我的问题中没有明确说明这个要求,所以我会相应地更新。
事实上,不幸的是,这种方法不可能直接做到这一点。如果你真的需要请求,那么 HandlerInterceptor @darrenforsythe提到的方法是另一种可能的方法。如果你只想记录,我看不出你有什么理由绝对需要这个请求——除非你想提取特定的头并记录它们。
在这种情况下,国际海事组织 OncePerRequestFilter 正如您最初尝试的那样,这会更好,因为您可以控制应用过滤器的请求(使用 shouldNotFilter(HttpServletRequest request) 并在url上匹配)。

pkbketx9

pkbketx92#

经过一些额外的摸索和尝试,我发现控制器可以通过 RequestMappingHandlerMapping 豆子。当请求可以由控制器处理时,这将把请求Map到 HandlerMethod 控制器的请求处理方法。

public class ExampleFilter extends OncePerRequestFilter {
    private RequestMappingHandlerMapping requestMappingHandlerMapping;

    @Override
    protected void doFilterInternal(
            HttpServletRequest request, HttpServletResponse response,
            FilterChain filterChain) throws ServletException, IOException {
        Object handler = getHandlerBean(request);
        boolean isHandledController = handler instanceof ExampleController
                || handler instanceof NestedEampleController;

        if (!isHandledController) {
            filterChain.doFilter(request, response);
            return;
        }

        // ...
    }

    private Object getHandlerBean(HttpServletRequest request) {
        try {
            HandlerExecutionChain handlerChain = requestMappingHandlerMapping.getHandler(request);
            if (handlerChain != null) {
                Object handler = handlerChain.getHandler();
                if (handler instanceof HandlerMethod) {
                    return ((HandlerMethod) handler).getBean();
                }
            }
            return null;
        } catch (Exception e) {
            return null;
        }
    }

    @Override
    protected void initFilterBean() {
        WebApplicationContext appContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext());
        requestMappingHandlerMapping = appContext.getBean(RequestMappingHandlerMapping.class);
    }
}

为了更彻底、更真实地模拟spring的处理程序逻辑 DispatcherServlet 可以使用/模仿逻辑,而不是直接引用 RequestMappingHandlerMapping . 这将咨询所有处理程序,而不仅仅是 RequestMappingHandlerMapping .

public class ExampleFilter extends OncePerRequestFilter {
    private DispatcherServlet dispatcherServlet;

    @Override
    protected void doFilterInternal(
            HttpServletRequest request, HttpServletResponse response,
            FilterChain filterChain) throws ServletException, IOException {
        Object handler = getHandlerBean(request);
        boolean isHandledController = handler instanceof ExampleController
                || handler instanceof NestedEampleController;

        if (!isHandledController) {
            filterChain.doFilter(request, response);
            return;
        }

        // ...
    }

    private Object getHandlerBean(HttpServletRequest request) {
        try {
            HandlerExecutionChain handlerChain = getHandler(request);
            if (handlerChain != null) {
                Object handler = handlerChain.getHandler();
                if (handler instanceof HandlerMethod) {
                    return ((HandlerMethod) handler).getBean();
                }
            }
            return null;
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * Duplicates the protected "getHandler" method logic from DispatcherServlet.
     */
    private HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        List<HandlerMapping> handlerMappings = dispatcherServlet.getHandlerMappings();
        if (handlerMappings != null) {
            for (HandlerMapping mapping : handlerMappings) {
                HandlerExecutionChain handler = mapping.getHandler(request);
                if (handler != null) {
                    return handler;
                }
            }
        }
        return null;
    }

    @Override
    protected void initFilterBean() {
        WebApplicationContext appContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext());
        dispatcherServlet = appContext.getBean(DispatcherServlet.class);
    }
}

我不确定是否有一个更惯用的方法,它肯定觉得它跳过一些箍和挖掘到Spring内部有点太多。但至少在springweb5.2.7.release上,它似乎确实有效。

相关问题