Search code examples
javaspringspring-bootdependency-injectionmodifier

Does the "final"-modifier contradict the "@Lazy"-annotation


When using the @Lazy-Annotation on a Spring-Bean, then the used beans should be lazy loaded.

When the bean however uses dependency injection through its constructor and the fields, which contain the used beans, are declared as final, then there should be no way for Spring to lazy load the dependencies.

I think this would happen even without the final-modifier, since the constructor has to be called when creating the bean (expect Spring uses reflection to lazy inject the used beans afterwards).

Is my assumption true?


Solution

  • Lazy dependencies/beans injected once at startup. They are not changed at runtime. Instead, a proxy class is used, which allows Spring to change or look up the actual bean at runtime. There is no need to circumvent final.

    Let's look at an example. Given the following bean and consumer:

    interface YourBean {
      boolean yourMethod();
    }
    
    @Component
    class YourBeanImpl implements YourBean {
      @Override
      public boolean yourMethod() {
        return false;
      }
    }
    
    class YourConsumer {
      private final YourBean yourBean;
      public YourConsumer(@Lazy final YourBean yourBean) {
        this.yourBean = yourBean; // this will be an instance of `LazyYourBeanProxy`
      }
    
      public void doWork() {
        if (this.yourBean.yourMethod()) {
          // ...
        }
      }
    }
    

    The proxy is generated dynamically, but it looks more or less equivalent to the following class:

    class LazyYourBeanProxy implements YourBean {
      private final ApplicationContext appctx;
    
      public LazyYourBeanProxy(final ApplicationContext appctx) {
        this.appctx = appctx;
      }
    
      private YourBean getRealBean() {
        return this.appctx.getBean(YourBean.class);
      }
    
      @Override
      public boolean yourMethod() {
        return getRealBean().yourMethod();
      }
    }
    

    Now, anytime your consumer calls a method on the lazy bean, it goes first to the proxy implementation which can then (lazily; i.e. not at startup time, but at runtime) look up the bean in the application context and delegate the call to it.

    The Spring Framework might employ additional cleverness like caching the real bean inside the proxy when it is looked up the first time, but this is the gist of it.