Spring Boot with Two MVC Configurations

后端 未结 4 1796
名媛妹妹
名媛妹妹 2020-12-24 08:13

I have a Spring Boot app with a REST API, using Jackson for the JSON view configuration. It works great and I can get all the Spring Boot goodness.

However, I need t

相关标签:
4条回答
  • 2020-12-24 08:26

    Expanding on my comment of yesterday and @Ashoka Header idea i would propose to register 2 MessageConverters (legacy and current) for custom media types. You can do this like that:

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

    Pay attention to the custom media-type for one of the converters.

    If you like , you can use an Interceptor to rewrite the Version-Headers proposed by @Ashoka to a custom Media-Type like so:

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

    This might not be the exact answer you were looking for, but maybe it can provide some inspiration. An interceptor is registered like so.

    0 讨论(0)
  • 2020-12-24 08:39

    If you can live with a different port for each context, then you only have to overwrite the DispatcherServletAutoConfiguration beans. All the rest of the magic works, multpart, Jackson etc. You can configure the Servlet and Jackson/Multipart etc. for each child-context separately and inject bean of the parent context.

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

    The context1/2.properties files currently only contain a server.port=8080/8081 but you can set all the other spring properties for the child contexts there.

    0 讨论(0)
  • 2020-12-24 08:39

    In Spring-boot ypu can use different profiles (like dev and test).

    Start application with -Dspring.profiles.active=dev or -Dspring.profiles.active=test and use different properties files named application-dev.properties or application-test.properties inside your properties directory. That could do the problem.

    0 讨论(0)
  • 2020-12-24 08:52

    There's several ways to achieve this. Based on your requirement , Id say this is a case of managing REST API versions. There's several ways to version the REST API, some the popular ones being version urls and other techniques mentioned in the links of the comments. The URL Based approach is more driven towards having multiple versions of the address:

    For example For V1 :

    /path/v1/resource
    

    and V2 :

    /path/v2/resource
    

    These will resolve to 2 different methods in the Spring MVC Controller bean, to which the calls get delegated.

    The other option to resolve the versions of the API is to use the headers, this way there is only URL, multiple methods based on the version. For example:

    /path/resource
    

    HEADER:

    X-API-Version: 1.0
    

    HEADER:

    X-API-Version: 2.0
    

    This will also resolve in two separate operations on the controller.

    Now these are the strategies based on which multiple rest versions can be handled.

    The above approaches are explained well in the following: git example

    Note: The above is a spring boot application.

    The commonality in both these approaches is that there will need to be different POJOS based on which Jackson JSON library to automatically marshal instances of the specified type into JSON.

    I.e. Assuming that the code uses the @RestController [org.springframework.web.bind.annotation.RestController]

    Now if your requirement is to have different JSON Mapper i.e. different JSON mapper configurations, then irrespective of the Spring contexts you'll need a different strategy for the serialization/De-Serialization.

    In this case, you will need to implement a Custom De-Serializer {CustomDeSerializer} that will extend JsonDeserializer<T> [com.fasterxml.jackson.databind.JsonDeserializer] and in the deserialize() implement your custom startegy.

    Use the @JsonDeserialize(using = CustomDeSerializer.class) annotation on the target POJO.

    This way multiple JSON schemes can be managed with different De-Serializers.

    By Combining Rest Versioning + Custom Serialization Strategy , each API can be managed in it's own context without having to wire multiple dispatcher Servlet configurations.

    0 讨论(0)
提交回复
热议问题