【问题标题】:ResourceHandler conflict with wildcards in ControllersResourceHandler 与控制器中的通配符冲突
【发布时间】:2018-08-09 13:24:28
【问题描述】:

我正在尝试为资源处理程序保留所有以/static/** 开头的路径。 不幸的是,我在请求映射中有一些从根路径/ 派生的通配符。像这样的:

我尝试了什么?

  • ResourceHandlerRegistry#setOrder:

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/static/**")
                .addResourceLocations("classpath:/resources/static/");
    
        registry.setOrder(1);
    }
    
  • 各种版本的拦截器(有或无顺序):

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new ResourcesInterceptor())
                .excludePathPatterns("/static/**")
                .order(2);
    }
    

这是一个半心半意的成功(如果我将映射更改为/{profile}/{project}/**,它甚至可能都行不通),因为:

/static/style/home.css      # works
/static/style/home.cssxxx   # 404, works
/static/style               # catched by controller, expected: 404
/static                     # catched by controller, expected: 404

我发现了一些类似的问题,大部分没有答案或有一些肮脏的解决方案,例如:

总结:

  • 我不想用正则表达式,因为以后会很痛苦
  • 我也无法更改映射。我知道,这是最简单的方法,但我就是做不到。
  • 更改订单不起作用
  • 创建专用控制器仍然存在一些路径问题

我正在寻找一个简单的解决方案,完全自动化,最好从配置中获得。问题是:实现这一目标的正确方法是什么?

【问题讨论】:

  • 非常可爱的图片! :3 你用什么工具画的?
  • Gimp + Gaegu 字体 :)
  • 最简单的解决方案:将映射从 /{profile} 更改为 /profiles/{profile}。这还具有巨大的优势,可以确保您不会与将来可能添加的所有其他端点(执行器、用户、问题、常见问题解答等)发生任何冲突
  • 这不是休息服务,我应该提一下,我无法重新定义我的路径
  • 您是在避免,而不是在解决问题。正如我所说,我无法更改映射,因此这些考虑毫无意义。我知道冲突,我正在寻找最好的方法,让它发挥作用。这是一个类似 github 的服务,这些路径非常重要。

标签: java spring spring-mvc spring-boot


【解决方案1】:

这个问题是由于 Spring 处理用户请求的方式造成的。有几个HandlerMappings,它们按指定的顺序执行。对我们来说最重要的是这两个:

  1. RequestMappingHandlerMappingWebMvcConfigurationSupport 中注册了order=0(我们可以在源代码和文档中看到这一点)

    /**
     * Return a {@link RequestMappingHandlerMapping} ordered at 0 for mapping
     * requests to annotated controllers.
     */
    
  2. AbstractHandlerMappingResourceHandleRegistry 中实例化,默认顺序为Integer.MAX_VALUE-1

    private int order = Ordered.LOWEST_PRECEDENCE - 1;
    

当你创建一个路径为/{profile}/{project}的RequestMapping并尝试到达资源/static/somefile.css时,你发送的请求被RequestMappingHandlerMapping抓取,并没有到达ResourceHandlerRegistry创建的HandlerMapping。

解决此问题的一个简单方法是addResourceHandlers 中将顺序设置为-1

@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
    registry.addResourceHandler("/static/**")
        .addResourceLocations("classpath:/resources/static/");

    registry.setOrder(-1);
}

然后适当的 HandlerMapping 将提供静态文件,如果没有这样的文件,它会将执行传递给您的控制器。

【讨论】:

    【解决方案2】:

    有点老套,但您可以在 profile 映射中使用正则表达式来排除静态:

    对于/{profile}

    @RequestMapping("/{profile:^(?:static.+|(?!static).*)$}")
    

    对于/{profile}/{project}

    @RequestMapping("/{profile:^(?:static.+|(?!static).*)$}/{project}")
    

    编辑

    啊,我刚刚看到您已经发现 regex 作为一种可能的解决方案,并且想知道(在其他解决方案中)这是否是正确的方法。

    就个人而言,我首选的解决方案是更改控制器的 URI。我发现所有其他解决方案都有些相似和 hacky:使用控制器前端静态,使用正则表达式作为配置文件 URI...

    如果无法更改 URI,我想我会使用上面的正则表达式。我觉得它很明确。

    【讨论】:

    • 我已经提到我不想使用正则表达式,但我会解释原因。首先,它很难维护,我必须将它复制粘贴到我所有基于通配符的控制器中,并且我必须记住将它添加到我的所有子映射中。其次,我想要没有正则表达式的简单映射(性能问题)。每个请求都会匹配这个表达式,我期待很多请求。我认为,更好的解决方案是创建映射到 /static 的专用控制器,但无论如何,感谢您的回复,+1 :)
    • @dzikoysk 不幸的是,没有 适当的 方法可以实现这一目标。这只是糟糕的 API 设计。通常的良好做法是,当 REST 端点有一些预定义的前缀(如 /api)时,显然不会与 /static 冲突
    • 是的,我知道,所有控制器都有前缀期望这个 - 路径通常是用户友好的 url,如 github.com/dzikoysk/ExampleProject。我想我会尝试为静态内容定义专用控制器,但让我们等一下,也许有人有一个很好的解决方法。
    【解决方案3】:

    我发现了基于自定义处理程序映射和伪控制器的有趣解决方案。 如果我们创建AbstractHandlerMapping bean,如果Spring不匹配任何其他Controller,它将被调用:

    @Bean
    public AbstractHandlerMapping profileHandlerMapping(RequestMappingHandlerAdapter handlerAdapter) {
        HandlerMethodArgumentResolverComposite argumentResolvers = new HandlerMethodArgumentResolverComposite();
        argumentResolvers.addResolvers(handlerAdapter.getArgumentResolvers());
    
        ProfileController profileHandlerMapping = new ProfileController(argumentResolvers);
        profileHandlerMapping.setOrder(2);
    
        return profileHandlerMapping;
    }
    

    伪控制器:

    public class ProfileController extends AbstractHandlerMapping {
    
        private final HandlerMethodArgumentResolverComposite argumentResolvers;
    
        public ProfileController(HandlerMethodArgumentResolverComposite argumentResolvers) {
            this.argumentResolvers = argumentResolvers;
        }
    
        @Override
        protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
            InvocableHandlerMethod handlerMethod = new InvocableHandlerMethod(this, getClass().getMethod("profile", HttpServletRequest.class));
            handlerMethod.setHandlerMethodArgumentResolvers(argumentResolvers);
            return handlerMethod;
        }
    
        @ResponseBody
        public String profile(HttpServletRequest request) {
            return "Profile: " + request.getRequestURI();
        }
    
    }
    

    优点:

    • 不存在与通配符的冲突
    • 以后易于维护,自动化
    • 仅在没有匹配控制器的情况下调度,也不会发生与 404 冲突

    缺点:

    • 缺乏对某些基于动态注释的变量的支持,例如@PathVariable

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-09-15
      • 2013-12-05
      • 1970-01-01
      • 2016-07-21
      • 2014-09-14
      • 1970-01-01
      • 2019-03-21
      • 1970-01-01
      相关资源
      最近更新 更多