如何为所有控制器配置默认的@RestController URI前缀?

时间:2016-01-14 22:57:31

标签: java spring-mvc groovy spring-boot

我知道您可以在SELECT * FROM ( SELECT productID, productType, MIN(ProductSalesdate) as FirstSale, MAX(ProductSalesdate) as LastSale, COUNT(productType) AS ProductCount FROM Product GROUP BY productID,productType ) t PIVOT ( SUM(ProductCount) FOR productType IN ([Electronic],[books],[Vehicle]) ) p 中设置server.contextPath来更改根上下文。

另外,我可以在Spring Boot的应用程序配置中添加一个额外的上下文,如下例(在Groovy中)中添加" / api"到根上下文的URL映射:

application.properties

我正在尝试使用单独的基本URI" / api"特别是对于Web服务调用,我可以利用它来保证安全性等。但是使用上述方法将意味着可以使用" /"来访问我的任何URI,无论是否为Web服务。或" / api",并没有提供具体的隔离。

是否有人知道使用配置为所有@Bean ServletRegistrationBean dispatcherServlet() { ServletRegistrationBean reg = new ServletRegistrationBean(new DispatcherServlet(), "/") reg.name = "dispatcherServlet" reg.addInitParameter("contextConfigLocation", "") reg.addUrlMappings("/api/*") reg.loadOnStartup = 2 reg } } 设置基本路径的更好方法,而无需使用/ api /正式为每个控制器添加前缀?如果我被迫为每个控制器手动添加前缀URI,则可能会错误地省略它并绕过我特定于Web服务的安全措施。

以下是Stack Overflow中对同一类型问题的引用,该问题从未得到完全解答:

Spring Boot: Configure a url prefix for RestControllers

6 个答案:

答案 0 :(得分:14)

自Spring Boot 1.4.0.RC1(详情见https://github.com/spring-projects/spring-boot/issues/5004

以来,有一种解决此类问题的新解决方案

Shahin ASkari的解决方案会禁用部分自动配置,因此可能会导致其他问题。

以下解决方案采用了他的想法并将其正确地集成到弹簧启动中。对于我的情况,我希望所有RestControllers都具有基本路径api,但仍然使用根路径提供静态内容(f.e. angular webapp)

修改:我在博客文章中对其进行了总结,其版本略有改进,请参阅https://mhdevelopment.wordpress.com/2016/10/03/spring-restcontroller-specific-basepath/

@Configuration
public class WebConfig {

    @Bean
    public WebMvcRegistrationsAdapter webMvcRegistrationsHandlerMapping() {
        return new WebMvcRegistrationsAdapter() {
            @Override
            public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
                return new RequestMappingHandlerMapping() {
                    private final static String API_BASE_PATH = "api";

                    @Override
                    protected void registerHandlerMethod(Object handler, Method method, RequestMappingInfo mapping) {
                        Class<?> beanType = method.getDeclaringClass();
                        RestController restApiController = beanType.getAnnotation(RestController.class);
                        if (restApiController != null) {
                            PatternsRequestCondition apiPattern = new PatternsRequestCondition(API_BASE_PATH)
                                    .combine(mapping.getPatternsCondition());

                            mapping = new RequestMappingInfo(mapping.getName(), apiPattern,
                                    mapping.getMethodsCondition(), mapping.getParamsCondition(),
                                    mapping.getHeadersCondition(), mapping.getConsumesCondition(),
                                    mapping.getProducesCondition(), mapping.getCustomCondition());
                        }

                        super.registerHandlerMethod(handler, method, mapping);
                    }
                };
            }
        };
    }

}

答案 1 :(得分:3)

我有同样的担忧,并且由于记录的问题而不是Spring EL选项的粉丝,我希望在控制器中严格控制前缀,但我不想依赖开发人员做正确的事情。

这些天可能有更好的方法,但这就是我所做的。你们有没有看到任何缺点,我仍然在测试任何副作用。

  1. 定义自定义注释 这允许开发人员显式提供类型化的属性,例如int apiVersion(),String resourceName()。这些值将在以后作为前缀的基础。
  2. 带有此新注释的带注释的休止控制器
  3. 实施了自定义RequestMappingHandlerMapping
  4. 在RequestMappingHandlerMapping中,我可以读取自定义注释的属性,并根据需要修改最终的RequestMappingInfo。以下是一些代码段:

    @Configuration
    public class MyWebMvcConfigurationSupport extends WebMvcConfigurationSupport {
    
        @Bean
        public RequestMappingHandlerMapping requestMappingHandlerMapping() {
            return new MyCustomRequestMappingHandlerMapping();
        }
    }
    

    在MyCustomRequestMappingHandlerMapping中,覆盖registerHandlerMethod:

    private class MyCustomRequestMappingHandlerMapping extends RequestMappingHandlerMapping {
    
        private Logger myLogger = LoggerFactory.getLogger(MyCustomRequestMappingHandlerMapping.class);
    
        public MyCustomRequestMappingHandlerMapping() {
            super();
        }
    
        @Override
        protected void registerHandlerMethod(Object handler, Method method, RequestMappingInfo mapping) {
    
            // find the class declaring this method
            Class<?> beanType = method.getDeclaringClass();
    
            // check for the My rest controller annotation
            MyRestController myRestAnnotation = beanType.getAnnotation(MyRestController.class);
    
            if (myRestAnnotation != null) {
                // this is a My annotated rest service, lets modify the URL mapping 
    
                PatternsRequestCondition oldPattern = mapping.getPatternsCondition();
    
                // create a pattern such as /api/v${apiVersion}/${resourceName}
                String urlPattern = String.format("/api/v%d/%s", 
                        myRestAnnotation.apiVersion(), 
                        myRestAnnotation.resourceName());
    
                // create a new condition
                PatternsRequestCondition apiPattern = 
                        new PatternsRequestCondition(urlPattern);
    
                // ask our condition to be the core, but import all settinsg from the old 
                // pattern
                PatternsRequestCondition updatedFinalPattern = apiPattern.combine(oldPattern);
    
                myLogger.info("re-writing mapping for {}, myRestAnnotation={}, original={}, final={}", 
                        beanType, myRestAnnotation, oldPattern, updatedFinalPattern);
    
                mapping = new RequestMappingInfo(
                        mapping.getName(),
                        updatedFinalPattern,
                        mapping.getMethodsCondition(),
                        mapping.getParamsCondition(),
                        mapping.getHeadersCondition(),
                        mapping.getConsumesCondition(),
                        mapping.getProducesCondition(),
                        mapping.getCustomCondition()
                        );
            }
    
            super.registerHandlerMethod(handler, method, mapping);
        }
    }
    

答案 2 :(得分:3)

稍微不那么冗长的解决方案,它不会复制检查注释的逻辑,只会改变映射路径:

private static final String API_PREFIX = "api";

@Bean
WebMvcRegistrationsAdapter restPrefixAppender() {
    return new WebMvcRegistrationsAdapter() {
        @Override
        public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
            return new RequestMappingHandlerMapping() {
                @Override
                protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
                    RequestMappingInfo mappingForMethod = super.getMappingForMethod(method, handlerType);
                    if (mappingForMethod != null) {
                        return RequestMappingInfo.paths(API_PREFIX).build().combine(mappingForMethod);
                    } else {
                        return null;
                    }
                }
            };
        }
    };
}

副作用

你的错误控制器也将被映射到/ api / error,这会破坏错误处理(DispatcherServlet仍然会将错误重定向到/ error而没有前缀!)。

可能的解决方案是在上面的代码中添加/ api前缀时跳过/错误路径(另外一个&#34;如果&#34;)。

答案 3 :(得分:2)

还可以通过配置WebMVC来达到相同的结果:

@Configuration
public class PluginConfig implements WebMvcConfigurer {

public static final String PREFIX = "/myprefix";

@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
    configurer.addPathPrefix(PREFIX, c -> c.isAnnotationPresent(MyCustomAnnotation.class));
}

}

  1. 在任何WebMvcConfigurer类上实施@Configuration
  2. 覆盖configurePathMatch方法。
  3. 您可以使用PathMatchConfigurer做很多有用的事情,例如为满足谓词条件的多个类添加前缀。

答案 4 :(得分:2)

github issue是当前接受的解决方案的延续。

在Spring 5.1及更高版本中,您可以实现WebMvcConfigurer并覆盖如下所示的configurePathMatch方法

@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
        configurer.addPathPrefix("/api",
                    HandlerTypePredicate.forAnnotation(RestController.class));
    }

}

现在,所有@RestControllers的前缀路径都将以/api作为配置路径。

Official Documentation

答案 5 :(得分:1)

有人在Spring MVC Jira中提出了一个问题,并提出了一个很好的解决方案,我现在正在使用它。我们的想法是在每个RestController文件中使用前缀中的Spring Expression Language,并引用Spring Boot application.properties文件中的单个属性。

以下是问题的链接:https://jira.spring.io/browse/SPR-13882