rdllopes
rdllopes

Reputation: 5120

Overriding Spring definition: Spring Bean Annotation @Primary

Overriding a bean definition appears to me be harder than what I thought. First of all, I would like to keep Open/Close principle.

I am using Springfox that would provide a swagger json based on Spring Mapping. Springfox is loaded by a Config class that I have to provide:

@EnableWebMvc
@Configuration
@Import({Swagger2DocumentationConfiguration.class})
@ComponentScan(basePackages = {"special.package.swagger"})
public class ApplicationSwaggerConfig {

    @Bean
    public Docket swaggerApi() {
        return new Docket(DocumentationType.SWAGGER_2)
                .groupName("rest-api")
                .select()
                .build()
                .pathMapping("/rest-api");
    }
   ...

Of course, I don't wanna change Swagger2DocumentationConfiguration class (following the open/closed principle) but change the behavior of ServiceModelToSwagger2Mapper.

For that, I created a MyServiceModelToSwagger2Mapper class in "special.package.swagger" folder such as:

@Component
@Primary
public class MyServiceModelToSwagger2Mapper extends ServiceModelToSwagger2MapperImpl {

    @Override
    public Swagger mapDocumentation(Documentation from) {
        System.out.println("\n\n\n\n\n\n\n\n\n\n\n\n\n******************** Override works!!!\n");
        return super.mapDocumentation(from);
    }

}

Spring loads the component, what solves the ambiguity problem but at the override does not work. Does anyone has any idea why!?


Edit: @smarquis comments helped a lot. Spring is working properly.

I accidentally added multiple versions of springfox library. The application server loaded both libraries and got lost. Once the classpath issue was solved overriding worked perfectly.

Upvotes: 1

Views: 8931

Answers (2)

smarquis
smarquis

Reputation: 540

It is due to the way spring with java config is build -- it is getting quite complex internally with proxies and stuff.

I do not remember exactly the reason, but if you do it like this your override works:

@EnableWebMvc
@Configuration
@Import({Swagger2DocumentationConfiguration.class})
//@ComponentScan(basePackages = {"springfoxswagger.specialpackageswagger"})
public class ApplicationSwaggerConfig {

    @Bean
    public Docket swaggerApi() {
        return new Docket(DocumentationType.SWAGGER_2)
                .groupName("rest-api")
                .select()
                .build()
                .pathMapping("/rest-api");
    }


    @Bean
    public springfox.documentation.swagger2.mappers.ServiceModelToSwagger2Mapper mapper() {
        return new  ServiceModelToSwagger2MapperImpl() {
            public Swagger mapDocumentation(Documentation from) {
                System.out.println("\n\n\n\n\n\n\n\n\n\n\n\n\n******************** Override works!!!\n");
                return super.mapDocumentation(from);
            }

        };
    }

}

It is also simpler, arguably.

EDIT

It seems not to work for the OP, so I investigated a bit further.

  • If you just add the second implementation of ServiceModelToSwagger2MapperImpl without @Primary, you get the exception "...expected single matching bean but found 2"
  • If you add it with the annotation, you get what the OP says (spring starts, but the override does not work).
  • if you again add the @Bean in my solution, that makes three implementations of ServiceModelToSwagger2MapperImpl, and without any @Primary at all, spring starts and the override works.
  • Note that you do not need the @Primary annotation on the bean, wierdly enough.

    2016-01-26 08:50:26.594  INFO 58548 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization completed in 22 ms
    

    ******************** Override works!!!

  • "Simple" solutions like adding @BeanNameAware or @AnnotationConfigDriven will not do. IMO OP bumped into a Spring Java Config bug or limitation; I do not see any reason why his solution does not work.

Upvotes: 1

ninja.coder
ninja.coder

Reputation: 9648

Try putting @AnnotationDrivenConfig on your class and try this:

@EnableWebMvc
@Configuration
@AnnotationDrivenConfig
@Import({Swagger2DocumentationConfiguration.class})
public class ApplicationSwaggerConfig {

    @Bean
    public Docket swaggerApi() {
        return new Docket(DocumentationType.SWAGGER_2)
                .groupName("rest-api")
                .select()
                .build()
                .pathMapping("/rest-api");
    }

    @Bean
    @Primary
    public MyServiceModelToSwagger2Mapper mapper() {
        return new MyServiceModelToSwagger2Mapper();
    }
}

MyServiceModelToSwagger2Mapper Class:

public class MyServiceModelToSwagger2Mapper extends ServiceModelToSwagger2MapperImpl {

    @Override
    public Swagger mapDocumentation(Documentation from) {
        System.out.println("\n\n\n\n\n\n\n\n\n\n\n\n\n******************** Override works!!!\n");
        return super.mapDocumentation(from);
    }
}

Upvotes: 0

Related Questions