具有两个MVC配置的Spring Boot

时间:2016-01-11 18:47:16

标签: spring spring-mvc spring-boot

我有一个带有REST API的Spring Boot应用程序,使用Jackson进行JSON视图配置。它工作得很好,我可以获得Spring Boot的所有优点。

但是,我需要添加一个类似但具有不同设置的其他REST API。例如,除其他外,它需要一个不同的Jackson对象映射器配置,因为JSON看起来会有点不同(例如没有JSON数组)。这只是一个例子,但有很多不同之处。每个API都有不同的上下文(例如/ api / current和/ api / legacy)。

理想情况下,我希望两个MVC配置映射到这些不同的上下文,而不必放弃启动时任何自动连接的东西。

到目前为止,所有我能够接近的是使用两个调度程序servlet,每个都有自己的MVC配置,但这导致Boot丢弃了我自动得到的一大堆内容,基本上无法使用它的原因引导。

我无法将应用分解为多个应用。

答案"你不能用Boot做到这一点并仍然得到它的所有魔力"是一个可接受的答案。看起来它应该能够解决这个问题。

4 个答案:

答案 0 :(得分:4)

有几种方法可以实现这一目标。根据您的要求,Id说这是管理REST API版本的情况。 有几种方法可以对REST API进行版本控制,其中一些是流行的版本URL,以及评论链接中提到的其他技术。 基于URL的方法更倾向于拥有多个版本的地址:

例如 对于V1

/path/v1/resource

和V2

/path/v2/resource

这些将解析Spring MVC Controller bean中的两个不同方法,调用将被委托给它们。

解析API版本的另一个选项是使用标头,这样只有URL,基于版本的多种方法。 例如:

/path/resource

标题

X-API-Version: 1.0

标题

X-API-Version: 2.0

这也将在控制器上的两个单独操作中解决。

现在这些是可以处理多个休息版本的策略。

以下解释了上述方法:git example

注意:以上是春季启动应用程序。

这两种方法的共同点是,需要有不同的POJOS,基于哪个Jackson JSON库自动将指定类型的实例编组为JSON。

即。假设代码使用@RestController [org.springframework.web.bind.annotation.RestController]

现在,如果您的要求是使用不同的JSON Mapper,即不同的JSON映射器配置,那么无论Spring上下文如何,您都需要一个不同的序列化/反序列化策略。

在这种情况下,您需要实现一个自定义反序列化器{CustomDeSerializer},它将扩展JsonDeserializer<T> [com.fasterxml.jackson.databind.JsonDeserializer]并在deserialize()中实现您的自定义策略。

在目标POJO上使用@JsonDeserialize(using = CustomDeSerializer.class)注释。

这样可以使用不同的De-Serializers管理多个JSON方案。

通过结合静态版本控制+自定义序列化策略,每个API都可以在自己的上下文中进行管理,而无需连接多个调度程序Servlet配置。

答案 1 :(得分:4)

扩展我对昨天的评论和@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;
}

请注意其中一个转换器的自定义媒体类型。

如果您愿意,可以使用Interceptor将@Ashoka提出的版本标题重写为自定义媒体类型,如下所示:

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

答案 2 :(得分:2)

如果您可以为每个上下文使用不同的端口,那么您只需要覆盖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属性。

答案 3 :(得分:-3)

在Spring-boot中,ypu可以使用不同的配置文件(例如devtest)。

启动应用程序 -Dspring.profiles.active=dev-Dspring.profiles.active=test 并在application-dev.properties目录中使用名为application-test.propertiesproperties的不同属性文件。 这可能会解决问题。