ResourceHandler与控制器中的通配符冲突

时间:2018-08-09 13:24:29

标签: java spring spring-mvc spring-boot

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

Preview

我尝试了什么?

  • 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 个答案:

答案 0 :(得分:3)

Kinda hacky,但是您可以在profile映射中使用正则表达式来排除静态:

对于 /{profile}

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

对于 /{profile}/{project}

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

编辑

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

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

如果无法更改URI,我认为我会回退使用上述正则表达式。我发现它很明显。

答案 1 :(得分:2)

此问题是由于Spring处理用户请求的方式引起的。有多个HandlerMapping,它们以指定的顺序执行。对我们来说最重要的是以下两个:

  1. RequestMappingHandlerMapping已在WebMvcConfigurationSupport = 0的order中注册(我们可以在源代码和文档中看到此信息)

    /**
     * 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。

此问题的一种简单解决方案是-1中将订单设置为addResourceHandlers

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

    registry.setOrder(-1);
}

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

答案 2 :(得分:1)

我已经找到了基于自定义处理程序映射和伪控制器的有趣解决方案。 如果我们创建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