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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/34801351/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-02 23:22:26  来源:igfitidea点击:

How to configure a default @RestController URI prefix for all controllers?

javaspringspring-bootspring-mvcgroovy

提问by pczeus

I know you can set the server.contextPathin application.propertiesto change the root context.

我知道您可以设置server.contextPathinapplication.properties来更改根上下文。

Also, I can add an additional context in the application config for Spring Boot like the following example (in Groovy) to add an "/api" to the URL mappings of the root context:

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

@Bean
ServletRegistrationBean dispatcherServlet() {
     ServletRegistrationBean reg = new ServletRegistrationBean(new DispatcherServlet(), "/")
        reg.name = "dispatcherServlet"
        reg.addInitParameter("contextConfigLocation", "")
        reg.addUrlMappings("/api/*")
        reg.loadOnStartup = 2
        reg
    }
}

I am trying to have a separate base URI "/api" specifically for web service calls, that I can leverage for security, etc. However using the above approach will mean that any of my URIs, web service or not, can be reached with "/" or "/api", and provides no concrete segregation.

我正在尝试有一个单独的基本 URI“/api”,专门用于 Web 服务调用,我可以利用它来提高安全性等。但是使用上述方法将意味着我的任何 URI,无论是否为 Web 服务,都可以通过"/" 或 "/api",并没有提供具体的隔离。

Is anyone aware of a better approach to set a base path for all @RestController(s) using configuration, without having to formally prefix every controller with /api/? If I am forced to manually prefix the URI for each controller, it would be possible to mistakenly omit that and bypass my security measures specific to web services.

有没有人知道@RestController使用配置为所有(多个)设置基本路径的更好方法,而不必为每个控制器正式添加 /api/ 前缀?如果我被迫为每个控制器手动添加 URI 前缀,则可能会错误地忽略它并绕过我特定于 Web 服务的安全措施。

Here is a reference in Stack Overflow to the same type of question, which was never completely answered:

这是 Stack Overflow 中对同一类型问题的引用,但从未完全回答:

Spring Boot: Configure a url prefix for RestControllers

Spring Boot:为 RestControllers 配置一个 url 前缀

采纳答案by Olivier Liechti

Someone has filed an issue in the Spring MVC Jira and come up with a nice solution, which I am now using. The idea is to use the Spring Expression Language in the prefix placed in each RestController file and to refer to a single property in the Spring Boot application.properties file.

有人在 Spring MVC Jira 中提出了一个问题,并提出了一个很好的解决方案,我现在正在使用。这个想法是在放置在每个 RestController 文件中的前缀中使用 Spring 表达式语言,并引用 Spring Boot application.properties 文件中的单个属性。

Here is the link of the issue: https://jira.spring.io/browse/SPR-13882

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

回答by mh-dev

There's a new solution to solve this kind of problem available since Spring Boot 1.4.0.RC1 (Details see https://github.com/spring-projects/spring-boot/issues/5004)

自 Spring Boot 1.4.0.RC1 以来,有一种新的解决方案可以解决此类问题(详情请参阅https://github.com/spring-projects/spring-boot/issues/5004

The solution of Shahin ASkari disables parts of the Auto configuration, so might cause other problems.

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

The following solution takes his idea and integrates it properly into spring boot. For my case I wanted all RestControllers with the base path api, but still serve static content with the root path (f.e. angular webapp)

以下解决方案采用了他的想法并将其正确集成到spring boot中。对于我的情况,我想要所有带有基本路径 api 的 RestControllers,但仍然使用根路径提供静态内容(fe angular webapp)

Edit: I summed it up in a blog post with a slightly improved version see https://mhdevelopment.wordpress.com/2016/10/03/spring-restcontroller-specific-basepath/

编辑:我在博客文章中对其进行了总结,版本略有改进,请参阅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);
                    }
                };
            }
        };
    }

}

回答by Nikolai Kolesnichenko

Also You can achieve the same result by configuring WebMVC like this:

您也可以通过像这样配置 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. Implement WebMvcConfigureron any @Configurationclass.
  2. Override configurePathMatchmethod.
  3. You can do many useful things with PathMatchConfigurere.g. add prefix for several classes, that satisfy predicate conditions.
  1. WebMvcConfigurer在任何@Configuration类上实现。
  2. 覆盖configurePathMatch方法。
  3. 你可以做很多有用的事情,PathMatchConfigurer例如为几个满足谓词条件的类添加前缀。

回答by Hegdekar

In continuation to the currently accepted solution the github issueaddresses the same.

继续当前接受的解决方案,github 问题解决了相同的问题

Spring 5.1 and above you can implement WebMvcConfigurerand override configurePathMatchmethod like below

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));
    }

}

Now all the @RestControllerswill have /apias the prefix path alongside the path configured.

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

Official Documentation

官方文档

回答by Shahin Askari

I had the same concern and was not a fan of the Spring EL option due to the issues documented and I wanted the prefix to be tightly controlled in the controllers but I did not want to depend on the developers doing the right thing.

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

There might be a better way these days but this is what I did. Can you guys see any downsides, I am still in the process of testing any side-effects.

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

  1. Define a custom annotation.
    This allows a developer to explicitly provide typed attributes such as int apiVersion(), String resourceName(). These values would be the basis of the prefix later.
  2. Annotated rest controllers with this new annotation
  3. Implemented a custom RequestMappingHandlerMapping
  1. 定义自定义注释。
    这允许开发人员显式提供类型化属性,例如 int apiVersion()、String resourceName()。这些值将是以后前缀的基础。
  2. 带有此新注释的带注释的休息控制器
  3. 实现了一个自定义的 RequestMappingHandlerMapping

In the RequestMappingHandlerMapping, I could read the attribute of the custom annotation and modify the final RequestMappingInfo as I needed. Here are a few code snippets:

在 RequestMappingHandlerMapping 中,我可以读取自定义注释的属性并根据需要修改最终的 RequestMappingInfo。下面是一些代码片段:

@Configuration
public class MyWebMvcConfigurationSupport extends WebMvcConfigurationSupport {

    @Bean
    public RequestMappingHandlerMapping requestMappingHandlerMapping() {
        return new MyCustomRequestMappingHandlerMapping();
    }
}

And in the MyCustomRequestMappingHandlerMapping, overwrite the registerHandlerMethod:

并在 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);
    }
}

回答by Dmitry Serdiuk

Slightly less verbose solution which doesn't duplicate the logic of checking the annotation, but only changes the mapping path:

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

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;
                    }
                }
            };
        }
    };
}

Side effects

副作用

Your error controller will also be mapped under /api/error, which breaks error handling (DispatcherServlet will still redirect errors to /error without prefix!).

您的错误控制器也将映射到 /api/error 下,这会破坏错误处理(DispatcherServlet 仍会将错误重定向到 /error 而不带前缀!)。

Possible solution is to skip /error path when adding /api prefix in the code above (one more "if").

可能的解决方案是在上面的代码中添加 /api 前缀时跳过 /error 路径(多一个“if”)。