How to replace an existing bean in spring boot application?

15,428

Solution 1

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).

Solution 2

Target achieved by implementing a BeanPostProcessor:

@Component
@Slf4j
public class StaticRouteLocatorPostBeanProcessor implements BeanPostProcessor {

  @Autowired
  private TurbineProperties properties;

  @Autowired
  private ServerProperties server;

  @Autowired
  private ZuulProperties zuulProperties;

  @Autowired
  private AdminServerProperties adminServerProperties;

  @Override
  public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
    return bean;
  }

  @Override
  public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    if (StaticRouteLocator.class.isAssignableFrom(bean.getClass())) {
      Collection<ZuulProperties.ZuulRoute> routes = Collections
          .singleton(new ZuulProperties.ZuulRoute(adminServerProperties.getContextPath(),
              properties.getUrl().toString()));
      log.info("Began to replace the StaticRouteLocator bean.");
      return new StaticRouteLocator(routes, server.getServletPrefix(), zuulProperties);
    }
    return bean;
  }
}
Share:
15,428

Related videos on Youtube

leo
Author by

leo

Updated on September 15, 2022

Comments

  • leo
    leo over 1 year

    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.

  • leo
    leo over 7 years
    Thanks for above instructions which cleared up some of my confusions though this solution didn't worked as expected. However I got it done by implementing a BeanPostProcessor component.
  • Kevin Peters
    Kevin Peters over 7 years
    You are welcome. Nevertheless nice that you found a working solution.
  • mahfuj asif
    mahfuj asif almost 4 years
    @leo please add a answer how you have solved your problem and mark it as accepted. you can accept your own anwser. It will help others