我知道您可以在application.properties
中设置server.contextPath
以更改根上下文。
此外,我可以在 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
}
}
我正在尝试有一个单独的基本 URI"/api"专门用于 Web 服务调用,我可以利用它来确保安全性等。但是,使用上述方法将意味着我的任何 URI(无论是否为 Web 服务)都可以使用"/"或"/api"访问,并且不提供具体的隔离。
有没有人知道使用配置为所有@RestController
设置基本路径的更好方法,而不必为每个控制器正式添加前缀/api/?如果我被迫为每个控制器手动添加 URI 前缀,则可能会错误地省略它并绕过特定于 Web 服务的安全措施。
以下是Stack Overflow中对相同类型问题的引用,该问题从未得到完全回答:
Spring 引导:为 RestController 配置 url 前缀
作为当前接受的解决方案的延续,github问题解决了相同的问题。
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
作为配置路径旁边的前缀路径。
官方文档
自 Spring Boot 1.4.0.RC1 以来,有一个新的解决方案可以解决此类问题(详细信息见 https://github.com/spring-projects/spring-boot/issues/5004)
Shahin ASkari 的解决方案禁用了部分自动配置,因此可能会导致其他问题。
以下解决方案采纳了他的想法,并将其正确集成到弹簧引导中。就我而言,我希望所有具有基本路径 API 的 RestController,但仍使用根路径(例如角度网络应用程序)提供静态内容
编辑:我在一篇博客文章中总结了它,版本略有改进,请参阅 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);
}
};
}
};
}
}
您也可以通过像这样配置 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));
}
}
- 在任何
@Configuration
类上实现WebMvcConfigurer
。 - 重写
configurePathMatch
方法。 - 您可以使用
PathMatchConfigurer
做许多有用的事情,例如为满足谓词条件的多个类添加前缀。
我也有同样的担忧,并且由于记录的问题,我不喜欢Spring EL选项,我希望前缀在控制器中得到严格控制,但我不想依赖开发人员做正确的事情。
这些天可能有更好的方法,但这就是我所做的。 你们能看到任何缺点吗,我仍在测试任何副作用。
- 定义自定义批注。
这允许开发人员显式提供类型化属性,例如 int apiVersion()、String resourceName()。这些值将成为稍后前缀的基础。 - 带有此新注释的带注释的 REST 控制器
- 实现了自定义请求映射处理程序映射
在 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);
}
}
稍微不那么冗长的解决方案,它不重复检查注释的逻辑,而只更改映射路径:
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 前缀时跳过/error 路径(再增加一个"if")。
有人在Spring MVC Jira中提出了一个问题,并提出了一个很好的解决方案,我现在正在使用。这个想法是在每个 RestController 文件中的前缀中使用 Spring 表达式语言,并引用 Spring Boot application.properties 文件中的单个属性。
这是问题的链接:https://jira.spring.io/browse/SPR-13882