Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to replace an existing bean in spring boot application?

I spring boot application, There already an bean creation in one auto configuraton class from a dependent jar like bellow:

@Bean
@Order(100)
public StaticRouteLocator staticRouteLocator(AdminServerProperties admin) {
    Collection<ZuulRoute> routes = Collections
            .singleton(new ZuulRoute(admin.getContextPath() + "/api/turbine/stream/**",
                    properties.getUrl().toString()));
    return new StaticRouteLocator(routes, server.getServletPrefix(), zuulProperties);
}

Now I want to replace this bean, but I still need this jar which has this unwanted Bean creation. So I added another bean creation method in my main auto configuration class like this:

  @Bean(name="patchedStaticRouteLocator")
  @Order(10)
  @Primary
  @ConditionalOnMissingBean
  public StaticRouteLocator patchedStaticRouteLocator(AdminServerProperties admin) {
    Collection<ZuulProperties.ZuulRoute> routes = Collections
        .singleton(new ZuulProperties.ZuulRoute(admin.getContextPath(),
            properties.getUrl().toString()));
    return new StaticRouteLocator(routes, server.getServletPrefix(), zuulProperties);
  }

But this failed to replace the target bean. The error msg is clear and easy to understand:

org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [org.springframework.cloud.netflix.zuul.filters.RouteLocator] is defined: more than one 'primary' bean found among candidates: [routeLocator, patchedStaticRouteLocator, staticRouteLocator, compositeRouteLocator, applicationRouteLocator]

My question what is the right way to replace such existing bean in spring boot? Thanks in advance.

like image 888
leo Avatar asked Jan 17 '17 10:01

leo


People also ask

How do you override a bean in a Spring boot?

Bean Overriding Spring beans are identified by their names within an ApplicationContext. Therefore, bean overriding is a default behavior that happens when we define a bean within an ApplicationContext that has the same name as another bean. It works by simply replacing the former bean in case of a name conflict.

Can we use @component instead of @bean?

@Component is a class-level annotation, but @Bean is at the method level, so @Component is only an option when a class's source code is editable. @Bean can always be used, but it's more verbose. @Component is compatible with Spring's auto-detection, but @Bean requires manual class instantiation.

What is @primary annotation in Spring boot?

Annotation Type PrimaryIndicates that a bean should be given preference when multiple candidates are qualified to autowire a single-valued dependency. If exactly one 'primary' bean exists among the candidates, it will be the autowired value.


1 Answers

The problem in this case is, that you are not replacing this bean with the name staticRouteLocator. You are creating another bean with the name patchedStaticRouteLocator. This would not be a problem in general, but it seems that's not what you want.

The NoUniqueBeanDefinitionException is raised because you also added @Primary annotation and now there are at least two beans marked as primary wiring candidate. Spring does not know what it should do now.

If you really want to override the first bean, give it the same name. The default name (if there was no other name specified explicitly) would be the name of the method you define in your @Configuration class. In your case this would be patchedStaticRouteLocator. (At the moment you also define the same name again with the @Bean annotations name property, that's kind of redundant and not needed.)

If you want to replace the bean with name / alias staticRouteLocator, give your new bean the same name, so define it like:

@Bean(name="staticRouteLocator")

That should override the first bean.

You can count your beans with a check like this:

import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest
public class BeanConfigTest {

    @Autowired
    private ApplicationContext applicationContext;

    @Test
    public void countBeanNames() {
        final String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
        assertThat(beanDefinitionNames.length, is(1));
    }

}

Just replace the 1 with the count you expect (before and after).

like image 185
Kevin Peters Avatar answered Sep 28 '22 03:09

Kevin Peters