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

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

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

  1. public class ExampleFilter extends OncePerRequestFilter {
  2. @Override
  3. protected void doFilterInternal(
  4. HttpServletRequest request, HttpServletResponse response,
  5. FilterChain filterChain) throws ServletException, IOException {
  6. // Can I tell here whether this will be mapping to an endpoint in
  7. // ExampleController or NestedExampleController?
  8. ContentCachingRequestWrapper requestToUse = new ContentCachingRequestWrapper(request);
  9. ContentCachingResponseWrapper responseToUse = new ContentCachingResponseWrapper(response);
  10. try {
  11. filterChain.doFilter(requestToUse, responseToUse);
  12. // Can I tell here whether this was mapped to an endpoint in
  13. // ExampleController or OtherExampleController?
  14. } finally {
  15. responseToUse.copyBodyToResponse(); // Write the cached body back to the real response
  16. }
  17. }
  18. }
  1. @RestController
  2. @RequestMapping("/example")
  3. public class ExampleController {
  4. @GetMapping("/{id}")
  5. public Example retrieveExample() {
  6. return getValue(); // Retrieve the value
  7. }
  8. // ...
  9. }
  1. @RestController
  2. @RequestMapping("/example/{id}/nested")
  3. public class NestedExampleController {
  4. @GetMapping("/{nestedId}")
  5. public NestedExample retrieveNestedExample() {
  6. return getValue(); // Retrieve the value
  7. }
  8. // ...
  9. }

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

vsikbqxv

vsikbqxv1#

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

  1. @Configuration
  2. @EnableAspectJAutoProxy
  3. public class AopConfiguration {
  4. }

定义一个方面,例如:

  1. @Aspect
  2. public class LoggingAspect {
  3. Logger log = ...; // define logger
  4. // Matches all executions in com.example.ExampleController,
  5. // with any return value, using any parameters
  6. @Pointcut("execution(* com.example.ExampleController.*(..))")
  7. public void controllerExecutionPointcut() {}
  8. @Around("controllerExecutionPointcut()")
  9. public Object aroundTargetControllerInvocation(ProceedingJoinPoint pjp) {
  10. log.debug("About to invoke method: {}", pjp.getSignature().getName());
  11. try {
  12. return pjp.proceed();
  13. } catch (Throwable t) {
  14. // note that getArgs() returns an Object[],
  15. // so you may want to map it to a more readable format
  16. log.debug("Encountered exception while invoking method with args {}", pjp.getArgs());
  17. throw t;
  18. }
  19. log.debug("Sucessfully finished invocation");
  20. }
  21. }

有关切入点表达式的详细信息,请参见本指南。
另一个常见的用例是对方法调用进行计时,尽管对于这种情况,类似于使用测微计(以及用于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 控制器的请求处理方法。

  1. public class ExampleFilter extends OncePerRequestFilter {
  2. private RequestMappingHandlerMapping requestMappingHandlerMapping;
  3. @Override
  4. protected void doFilterInternal(
  5. HttpServletRequest request, HttpServletResponse response,
  6. FilterChain filterChain) throws ServletException, IOException {
  7. Object handler = getHandlerBean(request);
  8. boolean isHandledController = handler instanceof ExampleController
  9. || handler instanceof NestedEampleController;
  10. if (!isHandledController) {
  11. filterChain.doFilter(request, response);
  12. return;
  13. }
  14. // ...
  15. }
  16. private Object getHandlerBean(HttpServletRequest request) {
  17. try {
  18. HandlerExecutionChain handlerChain = requestMappingHandlerMapping.getHandler(request);
  19. if (handlerChain != null) {
  20. Object handler = handlerChain.getHandler();
  21. if (handler instanceof HandlerMethod) {
  22. return ((HandlerMethod) handler).getBean();
  23. }
  24. }
  25. return null;
  26. } catch (Exception e) {
  27. return null;
  28. }
  29. }
  30. @Override
  31. protected void initFilterBean() {
  32. WebApplicationContext appContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext());
  33. requestMappingHandlerMapping = appContext.getBean(RequestMappingHandlerMapping.class);
  34. }
  35. }

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

  1. public class ExampleFilter extends OncePerRequestFilter {
  2. private DispatcherServlet dispatcherServlet;
  3. @Override
  4. protected void doFilterInternal(
  5. HttpServletRequest request, HttpServletResponse response,
  6. FilterChain filterChain) throws ServletException, IOException {
  7. Object handler = getHandlerBean(request);
  8. boolean isHandledController = handler instanceof ExampleController
  9. || handler instanceof NestedEampleController;
  10. if (!isHandledController) {
  11. filterChain.doFilter(request, response);
  12. return;
  13. }
  14. // ...
  15. }
  16. private Object getHandlerBean(HttpServletRequest request) {
  17. try {
  18. HandlerExecutionChain handlerChain = getHandler(request);
  19. if (handlerChain != null) {
  20. Object handler = handlerChain.getHandler();
  21. if (handler instanceof HandlerMethod) {
  22. return ((HandlerMethod) handler).getBean();
  23. }
  24. }
  25. return null;
  26. } catch (Exception e) {
  27. return null;
  28. }
  29. }
  30. /**
  31. * Duplicates the protected "getHandler" method logic from DispatcherServlet.
  32. */
  33. private HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
  34. List<HandlerMapping> handlerMappings = dispatcherServlet.getHandlerMappings();
  35. if (handlerMappings != null) {
  36. for (HandlerMapping mapping : handlerMappings) {
  37. HandlerExecutionChain handler = mapping.getHandler(request);
  38. if (handler != null) {
  39. return handler;
  40. }
  41. }
  42. }
  43. return null;
  44. }
  45. @Override
  46. protected void initFilterBean() {
  47. WebApplicationContext appContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext());
  48. dispatcherServlet = appContext.getBean(DispatcherServlet.class);
  49. }
  50. }

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

展开查看全部

相关问题