Close

Spring - Required dependency checking

[Updated: Nov 15, 2017, Created: Aug 24, 2016]

For a large project where many developers work in parallel, it's desirable to have some framework level facilities to check that all mandatory fields have been set or not. This check should ideally be performed at compile time, if not possible then as early as start up time before we start having NullPointerException on missing values.

Spring offers various dependency checking mechanism during start up time. Let's explore some aspects and options provided by Spring framework regarding dependency checking.


Constructor vs setter injection

As we discussed in the tutorial, different ways to do dependency injection, we should always use constructors based injection for the mandatory properties and setter based injection for optional properties.

In constructor based injection, we can apply some assertion mechanism to check that if the supplied values are valid or not. Those assertion mechanism could be, for example, throwing exception directly on invalid values or using Java assertion.

There might still be many reasons we want to use setters for mandatory properties rather than constructor. May be our constructor is getting too complex, may be we want to reconfigure some properties later (of course they cannot be final in that case but still are mandatory at wiring time).



How to check required properties?



We have following options to check required properties:

  1. Using the dependency-check attribute

    This has been deprecated and removed since Spring 3.0.

    It is XML based. We can set this attribute in <bean/> or on the global level in <beans/> elements. This attribute has different values to set the level of the dependency checking. Typically, it causes UnsatisfiedDependencyException if the target property(s) is not set via <property/> configuration of a bean.

    If you try to use it with newer versions, you will have this exception:

    Caused by: org.xml.sax.SAXParseException; lineNumber: 12; columnNumber: 124; cvc-complex-type.3.2.2: Attribute 'dependency-check' is not allowed to appear in element 'bean'. 

  2. Using @PostConstruct (init-method) and an assert mechanism.

    The method annotated with @PostConstruct is called after bean has been constructed but client coded hasn't started using it yet.

    This is a good place to do assertion. It's called after all setters supposedly have been called (if all required dependency is configured properly).

    Please see details here how init-method works.

    An example PostConstructExample.java is included in the example project below.


  3. Using InitializingBean and an assert mechanism

    With this option, our bean needs to implement InitializingBean interface and override afterPropertiesSet() method. This method is called after bean has been constructed and about the same time when init-method mentioned above, is called. We should avoid using it as it couples Spring specific interface to the application code. An example InitializingBeanExample.java is included in the example project below anyways.


  4. Using @Required

    Spring 2.0 @Required annotation is used on setters. If a property is not set it will throw:

    Caused by: org.springframework.beans.factory.BeanInitializationException: Property 'serviceBean' is required for bean 'clientBean'

    This annotation doesn't work with JavaConfig by default unless we register RequiredAnnotationBeanPostProcessor as a bean and override shouldSkip method:

     @Bean
        public RequiredAnnotationBeanPostProcessor processor () {
            return new RequiredAnnotationBeanPostProcessor() {
                @Override
                protected boolean shouldSkip (ConfigurableListableBeanFactory beanFactory,
                                              String beanName) {
                    if (beanName.equals("clientBean")) {
                        return false;
                    }
                    return super.shouldSkip(beanFactory, beanName);
                }
            };
        }

    This is not very clean and it's only a work around with little benefit. We should prefer to use init-method instead.

    We have to set following XML configuration for using @Required annotation:

      <context:annotation-config />

    Or

      <context:component-scan />

    Above configurations implicitly register RequiredAnnotationBeanPostProcessor. This bean processor registration doesn't work with JavaConfig unless we provide a custom processor as mentioned above.

    This kind of checking still cannot check if null values or some other invalid values have been set. According to RequiredAnnotationBeanPostProcessorAPI doc:

    Please note that an 'init' method may still need to implemented (and may still be desirable), because all that this class does is enforce that a 'required' property has actually been configured with a value. It does not check anything else. In particular, it does not check that a configured value is not null.

  5. Using @Autowired

    @Autowired annotation defines only one element 'required' which declares whether the annotated dependency is required or not. By default it is set to true. If the dependency is not set, the following exception will be thrown:

    Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [ServiceBean] found for dependency [ServiceBean]: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {}

    All those checking approaches that use some annotations within the class rather than using an external configuration is better because the bean class developer is more aware of what properties are required so he is the best person to declare this kind of checking declarations.

    Please explore more details about autowiring modes and @autowire annotation here


  6. Always use constructor injection for required values

    We should always use constructor injection along with assertions to check the validity of the mandatory properties. But there might be some complex scenarios and reconfiguration requirements (as mentioned before), we cannot always use this option. If we are going to have many mandatory properties with a bean constructor, we can choose to use the builder design pattern along with some Spring factory approach (like JavaConfig @Configuration methods) to simplify our configuration metadata.


  7. Don't check required dependencies at all

    For a small project, maintained by a small team, it's OK not to do any kind of required field checking. There we can make sure that we are not missing some required properties in configuration metadata. But believe me, a small project can grow in future. A missing dependency may cause a deadly NullPointerException, which might be caught very late as compare to just during startup time. This might happen in production as well. We should always focus on a maintainable and scalable project from day one.


  8. Using some compile time checking framework

    This kind of frameworks can do some enhanced compile time checking which is not provided by JDK by default. For example Checker Framework provides compile time type checking based on Java 8 type annotations (JSR 308). We can put some type annotations for example @NotNull (and many more) on the setter or constructor parameters to delegate the checking responsibilities to the framework.

    The checker framework is a compile time checking tool, whereas Spring framework is runtime dependency injection. We probably can use JavaConfig annotation metadata during compile time to integrate it with Checker framework.

    Read more about Java 8 type annotations and Checker framework



Example project


Example Project

Dependencies and Technologies Used:

  • Spring Context 4.3.2.RELEASE: Spring Context.
  • JDK 1.8
  • Maven 3.0.4

Dependency Check Select All Download
  • spring-dependency-checking
    • src
      • main
        • java
          • com
            • logicbig
              • example
        • resources

See Also