6

Default @Autowired Spring implementation throws error when bean that should be autowired is not defined. Is it possible to configure Spring, that will assign null to object instead of throwing exception?

EDIT:

I've add required=false to Autowired but it still not working properly. Thats my code:

@Autowired
private ApplicationContext applicationContext;

@Autowired(required = false)
private HelloService helloService;

public HelloController() {
    message = "Hello World";
    System.out.println("Controller constructor");
}

@RequestMapping(method = RequestMethod.GET)
public ModelAndView helloWorld() {
    ModelAndView modelAndView = new ModelAndView("hello");
    if (helloService == null) {
        System.out.println(message);
    } else {
        helloService.hello();
        BeanDefinitionRegistry factory = (BeanDefinitionRegistry) applicationContext.getAutowireCapableBeanFactory();
        factory.removeBeanDefinition("helloService");
    }
    return modelAndView;
}

In first request it's autowired, but in next request after removing bean with factory.removeBeanDefinition("helloService"), controller bean is construct again, and i get NoSuchBeanDefinitionException

EDIT2:

I've created another controller with the following body:

@Autowired(required = false)
private TestService testService;

@RequestMapping(method = RequestMethod.GET)
public ModelAndView hello() {
    ModelAndView modelAndView = new ModelAndView("hello");
    return modelAndView;
}

and it works properly - Object is null and it doesn't get error. Maybe i should use different method to remove bean from Spring context?

Stacktrace:

org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'helloService' is defined
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanDefinition(DefaultListableBeanFactory.java:698) ~[spring-beans-4.2.7.RELEASE.jar:4.2.7.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.getMergedLocalBeanDefinition(AbstractBeanFactory.java:1175) ~[spring-beans-4.2.7.RELEASE.jar:4.2.7.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:284) ~[spring-beans-4.2.7.RELEASE.jar:4.2.7.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197) ~[spring-beans-4.2.7.RELEASE.jar:4.2.7.RELEASE]
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.resolvedCachedArgument(AutowiredAnnotationBeanPostProcessor.java:508) ~[spring-beans-4.2.7.RELEASE.jar:4.2.7.RELEASE]
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.access$200(AutowiredAnnotationBeanPostProcessor.java:115) ~[spring-beans-4.2.7.RELEASE.jar:4.2.7.RELEASE]
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:538) ~[spring-beans-4.2.7.RELEASE.jar:4.2.7.RELEASE] 
...

STEPS TO REPRODUCE:

https://github.com/nowszy94/Autowired-null

nowszy94
  • 3,151
  • 4
  • 18
  • 33
  • Please show us the stacktrace of your exception – xenteros Aug 19 '16 at 13:08
  • Possible duplicate of [@Autowired(required=false) on constructor giving NoSuchBeanDefinitionException](http://stackoverflow.com/questions/23267440/autowiredrequired-false-on-constructor-giving-nosuchbeandefinitionexception) – xenteros Aug 19 '16 at 13:13
  • I can't reproduce your problem. Please post a [mcve]. – Sotirios Delimanolis Aug 19 '16 at 14:34
  • @SotiriosDelimanolis: https://github.com/nowszy94/Autowired-null – nowszy94 Aug 19 '16 at 15:00
  • Your question is not about the `@Autowired`, it's about `removeBeanDefinition` and its repercussion on dependent beans. – Sotirios Delimanolis Aug 19 '16 at 15:16
  • You have right, when i was asking a question i was thought that this is `@Autowired` problem – nowszy94 Aug 19 '16 at 15:29
  • @SotiriosDelimanolis Do you think i should ask another question for `removeBeanDefinition`? – nowszy94 Aug 19 '16 at 15:29
  • Kind of. You already have an appropriate answer for `@Autowired`, but even that is duplicated. I would ask a new one, yes, framed around `removeBeanDefinition`. – Sotirios Delimanolis Aug 19 '16 at 15:31
  • Regarding the removeBeanDefinition, see @NikitaBakaev's answer. I'm guessing it fails the second time because even though the service is removed from the factory, it's not being nulled in the service. There's probably some spring cleaing to be done after it is removed... which isn't being done – Koos Gadellaa Aug 20 '16 at 19:27

2 Answers2

7

you can disable this, by setting the required attribute to false.

@Autowired(required=false)

If Spring can’t find bean, it will leave field unset as null.

ByeBye
  • 6,650
  • 5
  • 30
  • 63
4

The problem is that AutowiredAnnotationBeanPostProcessor cache injection result. So, when you delete bean from context, this class think that this object is actually exists(see private class AutowiredFieldElement extends InjectionMetadata.InjectedElement in AutowiredAnnotationBeanPostProcessor.class and method inject).So, you should clear that cache.

The most stupid way, that i found is, but looks like that you want to do

@Controller
@RequestMapping("/hello")
public class HelloController {

    @Autowired(required = false)
    private HelloService helloService;

    @Autowired
    private ApplicationContext applicationContext;

    @RequestMapping(method = RequestMethod.GET)
    public ModelAndView modelAndView() {
        ModelAndView modelAndView = new ModelAndView("hello");
        if (helloService != null) {
            helloService.hello();
            removeBean("helloService");
        }

        return modelAndView;
    }

    private void removeBean(String beanName) {
        BeanDefinitionRegistry factory = (BeanDefinitionRegistry) applicationContext
                .getAutowireCapableBeanFactory();
        factory.removeBeanDefinition(beanName);
        clearCache(factory);
    }

    private void clearCache(BeanDefinitionRegistry beanFactory){
        AutowiredAnnotationBeanPostProcessor processor = null;

        for (BeanPostProcessor beanPostProcessor : ((DefaultListableBeanFactory) beanFactory).getBeanPostProcessors()){
            if (beanPostProcessor.getClass().equals(AutowiredAnnotationBeanPostProcessor.class)){
                processor = (AutowiredAnnotationBeanPostProcessor) beanPostProcessor;
            }
        }

        try {
            Field injectionMetadataCache = processor.getClass().getDeclaredField("injectionMetadataCache");
            injectionMetadataCache.setAccessible(true);
            Method clear = Map.class.getMethod("clear");
            clear.invoke( injectionMetadataCache.get(processor));
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }


    }

}
  • 1
    this huge reflection is just just call `processor.injectionMetadataCache.clear();` but this is private method, so do this with reflection – Nikita Bakaev Aug 20 '16 at 19:05
  • Thank you, that works. Can you post your answer also here - http://stackoverflow.com/questions/39043038/autowiredrequired-false-fails-after-removing-bean ? – nowszy94 Aug 22 '16 at 07:33