具有两种 MVC 配置的 Spring Boot
Posted
技术标签:
【中文标题】具有两种 MVC 配置的 Spring Boot【英文标题】:Spring Boot with Two MVC Configurations 【发布时间】:2016-04-16 04:35:59 【问题描述】:我有一个带有 REST API 的 Spring Boot 应用程序,使用 Jackson 进行 JSON 视图配置。它工作得很好,我可以获得 Spring Boot 的所有优点。
但是,我需要添加一个类似但具有不同设置的附加 REST API。例如,除其他外,它需要不同的 Jackson 对象映射器配置,因为 JSON 看起来会有很大不同(例如,没有 JSON 数组)。这只是一个例子,但有很多不同之处。每个 API 都有不同的上下文(例如 /api/current 和 /api/legacy)。
理想情况下,我希望将两个 MVC 配置映射到这些不同的上下文,并且不必放弃启动时的任何自动连接。
到目前为止,我所能接近的只是使用两个调度程序 servlet,每个 servlet 都有自己的 MVC 配置,但这会导致 Boot 丢弃一大堆我自动获得的东西,并且基本上破坏了使用 boot 的原因。
我无法将应用拆分为多个应用。
“你不能用 Boot 做到这一点并仍然获得它的所有魔力”的答案是一个可以接受的答案。看来它应该能够处理这个问题。
【问题讨论】:
***.com/questions/29096511/… - 你试过了吗? 也在这里:***.com/questions/21630820/… 澄清一下:他们需要生活在同一个父母环境中吗?所以共享相同的豆子? 具体会失去什么样的魔法? 只是为了完整性。内容协商会不会是一种巧妙的方法?您可以为不同的(自定义)媒体类型注册消息转换器。 【参考方案1】:有几种方法可以实现这一目标。根据您的要求,我说这是管理 REST API 版本的案例。 有几种方法可以对 REST API 进行版本控制,其中一些流行的是版本 url 和 cmets 链接中提到的其他技术。 基于 URL 的方法更倾向于拥有多个版本的地址:
例如 对于 V1:
/path/v1/resource
和 V2:
/path/v2/resource
这些将解析为 Spring MVC Controller bean 中的 2 个不同方法,调用被委托给这些方法。
解决API版本的另一个选项是使用标头,这种方式只有URL,基于版本的多种方法。 例如:
/path/resource
标题:
X-API-Version: 1.0
标题:
X-API-Version: 2.0
这也将在控制器上的两个单独操作中解决。
现在这些是可以处理多个 rest 版本的策略。
以下方法很好地解释了上述方法:git example
注意:以上是一个spring boot的应用。
这两种方法的共同点是需要不同的 POJOS,基于哪个 Jackson JSON 库自动将指定类型的实例编组为 JSON。
即假设代码使用@RestController [org.springframework.web.bind.annotation.RestController]
现在,如果您的要求是具有不同的 JSON 映射器,即不同的 JSON 映射器配置,那么无论 Spring 上下文如何,您都需要不同的序列化/反序列化策略。
在这种情况下,您需要实现自定义反序列化器 CustomDeSerializer,它将扩展 JsonDeserializer<T>
[com.fasterxml.jackson.databind.JsonDeserializer]
并在 deserialize()
中实现您的自定义 startegy。
在目标 POJO 上使用@JsonDeserialize(using = CustomDeSerializer.class)
注解。
这样可以使用不同的反序列化器管理多个 JSON 方案。
通过结合 Rest 版本控制 + 自定义序列化策略,每个 API 都可以在其自己的上下文中进行管理,而无需连接多个调度程序 Servlet 配置。
【讨论】:
除了我的意思是 JSON 格式之外,每个 API 的 MVC 配置还有一些差异,但我认为这个答案对于我给你的信息量是有意义的,并且是唯一真正的答案我收到了这个问题和我问过的其他问题。你得到了赏金 ;-) 谢谢,我会用你回答的精神来解决我的问题的其他方面。【参考方案2】:扩展我昨天的评论和@Ashoka Header 的想法,我建议为自定义媒体类型注册 2 个 MessageConverters(旧版和当前)。你可以这样做:
@Bean
MappingJackson2HttpMessageConverter currentMappingJackson2HttpMessageConverter()
MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter();
ObjectMapper objectMapper = new ObjectMapper();
// set features
jsonConverter.setObjectMapper(objectMapper);
jsonConverter.setSupportedMediaTypes(Arrays.asList(new MediaType("json", "v2")));
return jsonConverter;
@Bean
MappingJackson2HttpMessageConverter legacyMappingJackson2HttpMessageConverter()
MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter();
ObjectMapper objectMapper = new ObjectMapper();
// set features
jsonConverter.setObjectMapper(objectMapper);
return jsonConverter;
注意其中一个转换器的自定义媒体类型。
如果您愿意,可以使用拦截器将@Ashoka 提出的 Version-Headers 重写为自定义的 Media-Type,如下所示:
public class ApiVersionMediaTypeMappingInterceptor extends HandlerInterceptorAdapter
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception
try
if(request.getHeader("X-API-Version") == "2")
request.setAttribute("Accept:","json/v2");
.....
这可能不是您要寻找的确切答案,但也许它可以提供一些灵感。一个拦截器注册了like so。
【讨论】:
这确实给了我一些想法。我实际上无法更改客户端以添加标头等,但是我可以拦截并查看基本 URL,然后自己添加标头或类似内容。谢谢你的想法。【参考方案3】:如果您可以为每个上下文使用不同的端口,那么您只需覆盖 DispatcherServletAutoConfiguration
bean。所有其余的魔法作品,multpart,Jackson 等。您可以为每个子上下文分别配置 Servlet 和 Jackson/Multipart 等,并注入父上下文的 bean。
package test;
import static org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration.DEFAULT_DISPATCHER_SERVLET_BEAN_NAME;
import static org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration.DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.embedded.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
@Configuration
@EnableAutoConfiguration(exclude =
Application.Context1.class,
Application.Context2.class
)
public class Application extends WebMvcConfigurerAdapter
@Bean
public TestBean testBean()
return new TestBean();
public static void main(String[] args)
final SpringApplicationBuilder builder = new SpringApplicationBuilder().parent(Application.class);
builder.child(Context1.class).run();
builder.child(Context2.class).run();
public static class TestBean
@Configuration
@EnableAutoConfiguration(exclude = Application.class, Context2.class)
@PropertySource("classpath:context1.properties")
public static class Context1
@Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
DispatcherServlet dispatcherServlet()
DispatcherServlet dispatcherServlet = new DispatcherServlet();
// custom config here
return dispatcherServlet;
@Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
ServletRegistrationBean dispatcherServletRegistration()
ServletRegistrationBean registration = new ServletRegistrationBean(dispatcherServlet(), "/test1");
registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
// custom config here
return registration;
@Bean
Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder(TestBean testBean)
System.out.println(testBean);
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
// custom config here
return builder;
@Configuration
@EnableAutoConfiguration(exclude = Application.class, Context1.class)
@PropertySource("classpath:context2.properties")
public static class Context2
@Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
DispatcherServlet dispatcherServlet()
DispatcherServlet dispatcherServlet = new DispatcherServlet();
// custom config here
return dispatcherServlet;
@Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
ServletRegistrationBean dispatcherServletRegistration()
ServletRegistrationBean registration = new ServletRegistrationBean(dispatcherServlet(), "/test2");
registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
// custom config here
return registration;
@Bean
Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder(TestBean testBean)
System.out.println(testBean);
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
// custom config here
return builder;
context1/2.properties
文件目前仅包含一个 server.port=8080/8081
,但您可以在那里为子上下文设置所有其他 spring 属性。
【讨论】:
【参考方案4】:在 Spring-boot 中,ypu 可以使用不同的配置文件(例如 dev
和 test
)。
启动应用程序
-Dspring.profiles.active=dev
或-Dspring.profiles.active=test
并在您的properties
目录中使用名为application-dev.properties
或application-test.properties
的不同属性文件。
这可以解决问题。
【讨论】:
感谢您的回答。不幸的是,这与我要解决的问题无关。以上是关于具有两种 MVC 配置的 Spring Boot的主要内容,如果未能解决你的问题,请参考以下文章