Spring Dependency Injection and Inversion of Control Container Concepts

From NovaOrdis Knowledge Base
Jump to navigation Jump to search

External

  • www.tutorialspoint.com/spring/index.htm

Internal

Overview

Spring is a dependency injection framework. Its main responsibilities are to provide a configuration model that specifies how components should be defined and what are the dependencies between defined components, detect conforming components in the libraries available on the classpath, instantiate the component graph while transitively honoring the dependency relationships - by instantiating the dependency before the dependent and injecting the appropriate dependency references into dependents - and finally expose the components to the application. Instantiation, dependency injection and component life cycle management take place within the Spring IoC container. Support for different application architectures, including messaging, transactions and persistence is built in top of the core container.

Dependency Injection

Dependency Injection a pattern to address object coupling problem. An approach that does not involve dependency injection usually uses the main program to explicitly create dependencies with new, assign them to variables carried by their dependents and proceed to execute methods. A dependency injection runtime proceeds differently: it first creates the dependencies, which are then injected transparently into the components who request them and into the main program. Tight coupling is bad when the goal is to build testable, maintenable and scalable systems. Rather than have individual components create and manage the lifecycle of their dependency components, a dependency-injected application relies on container to first create all components, then to inject them into other components that need them.

There are several kinds of injection types:

Constructor Injection

The dependency reference is injected via a constructor argument. The constructors have the advantage that save us from writing extra setter code. However, a large number of arguments on a constructor is probably a sign that the class takes on too many responsibilities. These are examples how dependency are injected with constructor injection in XML and Java. Prefer constructor injection to ensure that objects can be instantiate directly. Constructor injection used instead of field injection makes test easier to write.

Setter Injection

The dependency reference is injected via a setter method. These are examples how dependency are injected with setter injection in XML and Java.

Member Variable (Field) Injection

The Spring team recommends avoiding field injection and to always use constructor injection in the beans. It is recommended to always use assertions for mandatory dependencies.

Inversion of Control

See:

Dependency Injection

Design to Interface

"Design to Interface" is a development approach where we express the interaction between the components in terms of contracts of interfaces they implement. When we design to interface, concrete classes bind themselves to an interface contract, rather than to a specific implementation. The implementation can be modified transparently, as long as it honors the contract. Spring dependencies can be fully expressed using interface types, leaving to the container binding to concrete implementation at runtime. However, the concrete classes implementing those interfaces must be declared as components. Spring dependency injection provides a solution to matching appropriate concrete implementation of an interface at runtime.

Playground Design to Interface Example

Configuration Model

This section refers to component configuration, or bean wiring. Configuration as in external data that is provided to the application in form of properties or environment variables, and which potentially modifies the behavior of the application, is addressed in the "Property Injection Concepts" section.

Spring runtime expects to receive information about what components it should create and how those components should be wired together either in form of XML files or, since Spring 2.5, as annotation-based metadata embedded within the bytecode of the component classes themselves and referred to as Java-based configuration. This information that tells Spring how to build its components and what other components they depend on is called configuration metadata. Configuration metadata expressed as XML is equivalent with configuration metadata provided in form of Java annotations. Either can be used to the same effect.

XML-based configuration, Java-based configuration and annotation-based configuration can also be combined and used together. However, when they are used together, annotation injection is performed before XML injection, thus the XML configuration overrides the annotations for properties wired through both approaches.

Configuration metadata includes the definitions of what constitutes a component - the Java fully qualified classname of the component class, the component's dependencies on other components, the component lifecycle details and where to find component classes. Depending on the metadata format, this information is provided in different formats. For example, in case of XML-based configuration, the fully qualified class name is provided in the "class" attribute of the <bean> element. However, for Java-based configuration, a component class is annotated as a @Component or a similar annotation, so Spring runtime has already the class information when it detects the annotation in the class' bytecode.

XML-based Configuration Metadata

Playground XML-based Configuration Example

The XML-based configuration historically precedes Java-based configuration. First Spring releases came with only XML configuration support.

This is an example of a simple Spring XML configuration file:

beans.xml

XML-based Component Definition

When XML-based metadata is used, Spring components are defined using a <bean> element:

<beans ...>
    <bean name="red" class="playground.Red"/>
</beans>

In this example, the Red class is declared as a recipe to build a bean which has no other bean dependencies:

public class Red {
  public Red() {
    ...
  }
}

One essential element of component definition is the specification of the component's dependencies, using the ref attribute. Once the dependencies are specified in the XML configuration metadata, the Spring IoC container automatically injects dependency instances into its managed components. For XML-based metadata, dependencies of a component can be injected via constructor or via setter.

This is an example of constructor injection:

<bean name="blue" class="playground.Blue">
  <constructor-arg ref = "red"/>
</bean>

The corresponding component Java class is:

public class Blue {
  private Red red;
  ...
  public Blue(Red red) {
    this.red = red;
  }
  ...
}

This is an example of setter injection:

<bean name="green" class="playground.Green">
  <property ref = "red"/>
</bean>

The corresponding component Java class is:

public class Green {
  private Red red;
  ...
  public void setRed(Red red) {
    this.red = red;
  }
  ...
}

Note that dependencies are injected only into container-managed components. Components created with new are not managed by the container, so they will not have their dependencies injected. The transitive dependency injection is triggered when the component is obtained from the container with one of the getBean(...) methods, as in the following example. The application context must be one of the XML-enabled application context implementations, such as ClassPathXmlApplicationContext or FileSystemXmlApplicationContext.

ApplicationContext context = new ClassPathXmlApplicationContext("red-blue-grean-beans.xml");

// this component will get its "red" dependency injected by the container
Blue blue = (Blue)context.getBean("blue");

<context:annotation-config>

The <context:annotation-config> element activates detection of @Autowired and @Required, JSR-250 @PostConstruct, @PostDestroy, @PreDestroy and @Resource, @EJB, @PersistenceContext, etc. It also activate detection of @Configuration-annotated classes. Note that by default, annotation configuration detection is not enabled and the element has to be explicitly specified.

<context:annotation-config/>

<context:component-scan>

The <context:component-scan> element configures the context to scans the classpath for annotated stereotype components that will be auto-registered Spring beans: @Component, @Repository, @Service, @Controller, @RestController, @ControllerAdvice, as well as Java-based configuration classes annotated with @Configuration:

<context:component-scan base-package="io.playground"/>

This tag requires <context:annotation-config> to be active. Once a base package is specified with <context:component-scan/>, Spring will search the base package and all its sub-packages for components. The equivalent annotation-based configuration annotation is @ComponentScan.

Java-based Configuration Metadata @Configuration/@Bean

Playground Java-based Configuration Example

Java-based configuration is semantically equivalent with XML-based configuration and it is an alternative to it, or it can be used together with it. The place of the XML bean definition file, whose root is the <beans> element, is taken by a Java class annotated with @Configuration. Individual XML <bean> definitions are replaced with @Bean-annotated methods of the configuration class. The application context's constructor accepts configuration class types as argument, instead of XML file names.

The @Configuration annotation indicates that the class it annotates is a configuration class that will provide beans to the Spring application context. @Configuration triggers the configuration of the Spring infrastructure. A @Configuration-annotated class must expose one or more methods annotated with @Bean. A @Bean-annotated method is the equivalent of an <bean> element in an Spring XML configuration file. The objects returned by these methods are bean instances, and when the methods are invoked by the Spring container, the corresponding beans are added to the application context. By default, their respective bean IDs will be the same as the names of the methods that define them. The Spring container invokes these methods with the goal of obtaining bean definitions. It is important to point out that even if regular client code, and not the Spring container calls @Bean-annotated methods, the calls will be intercepted by the container and they will return the bean instance stored in the application context, not a new object instance.

It is possible to extend the application bootstrap configuration to add more configuration. However, it is good practice to create a new configuration class for each kind of configuration (web, data, security, etc.), keeping the application bootstrap configuration clean and simple. Example: Java-Based Spring Security Configuration.

Java-based Component Definition

This section presents the equivalent Java-based component definition for the components defined with XML in the previous section. Concepts behind Java-based configuration are presented in the Java-based Configuration Metadata section.

The bean classes presented in the previous example do not change. The equivalent of the XML file is a @Configuration-annotated class:

@Configuration
public class ApplicationConfiguration {
  @Bean
  public Red red() {
    return new Red();
  }
  @Bean
  public Blue blue(Red red) {
    // constructor injection
    return new Blue(red);
  }
  @Bean
  public Green green(Red red) {
    Green green = new Green();
    // setting injection
    green.setRed(red);
    return green;
  }
}

The application context must be an AnnotationConfigApplicationContext:

ApplicationContext applicationContext = new AnnotationConfigApplicationContext(ApplicationConfiguration.class);
Blue blue = (Blue)applicationContext.getBean("blue");

Annotation-based Configuration with @Autowired and Stereotype Annotations

Playground Annotation-based Configuration Example

Annotation-based configuration refers to the possibility of using annotations in the component class, on the relevant class, method and field declaration, to express the fact that the class is a Spring component, and also other Spring semantics, such as dependency on another component. Annotation-based configuration can be used to provide all metadata that would otherwise be provided with XML-based configuration or Java-based configuration. Components can be declared using stereotype annotations. A stereotype annotation denotes the role of the annotated bean in the overall architecture at a conceptual, rather than implementation, level:

@Autowired

Dependency between components can be declared with the @Autowired annotation. When a member variable, method or constructor is annotated with @Autowired, the framework will look for the relevant dependency in the container and inject it. From this perspective, @Autowired is analogous to a "ref=" declaration in an XML metadata file. Note that the injected components must also be managed by the application context. An instance created with new cannot be autowired into a Spring component, as the application context does not know about it.

Spring-specific @Autowired is equivalent with JSR-330 @Inject annotation.

If a class has just one constructor, it will be considered automatically an autowiring target, even if @Autowired is not declared.

When a constructor used for autowiring also contains non-component arguments, default values for those can be specified with the @Value annotation. A detailed example of how to configure components is available below.

@ComponentScan

@ComponentScan is the annotation-based configuration equivalent of the XML <context:component-scan>. It allows specifying a base package, or multiple base packages to use for component scanning:

@ComponentScan("playground")
...

@ComponentScans is available If we need to specify more than one base packages:

@ComponentScans(@ComponentScan(basePackages = "some.experimental.dependency"))

Once one or more base packages are specified with @ComponentScan, Spring will search the base package and all its sub-packages for components.

The <context:component-scan> element requires the <context:annotation-config> element; however, @ComponentScan annotation does not. This is because in almost all cases when using @ComponentScan, default annotation config processing (e.g. processing @Autowired) is assumed. Furthermore, when using AnnotationConfigApplicationContext, annotation config processors are always registered, meaning that any attempt to disable them at the @ComponentScan level would be ignored.

Annotation-based Component Definition

This section presents the equivalent annotation-based component definition for components defined with XML in the XML-based Component Definition section or with Java-based configuration in the Java-based Component Definition section. Concepts behind annotation-based configuration are presented in the Annotation-based Configuration section.

First off, there is no @Configuration-annotated class, nor an XML file. The components are detected dynamically in the classpath, being singled out by their stereotype annotations. The dependency relationships are not expressed in the @Bean-annotated methods, or <bean> XML elements. They are expressed with @Autowired annotations instead. Aside from getting extra annotations, the bean classes presented in the previous example do not change. The application context must be an AnnotationConfigApplicationContext configured with the name of the base package that contains the annotated classes; this is how the component are actually identified:

ApplicationContext applicationContext = new AnnotationConfigApplicationContext("io.playground.spring");
Blue blue = (Blue)applicationContext.getBean(Blue.class);

The component classes used in the previous examples will stay almost unchanged, with the exception of stereotype, @Autowired and @Value annotations:

@Component
public class Red {
  private String payload;
  public Red(@Value("bright") String payload) {
    this.payload = payload;
  }
  ...
}

This is an example of constructor injection.

@Component
public class Blue {
  private Red red;
  private String payload;
  @Autowired
  public Blue(Red red, @Value("some default value") String payload) {
    this.red = red;
    this.payload = payload;
  }
  ...
}

This is an example of setter injection.

@Component
public class Green {
  private Red red;
  private String payload;
  public Green(@Value("fresh") String payload) {
    this.payload = payload;
  }
  @Autowired
  public void setRed(Red red) {
    this.red = red;
  }
  ...
}

XML-based, Java-based and Annotation-based Configuration Combinations

Metadata specified in an XML file can be used by an application that instantiates a Java-based-configuration application context if the XML metadata is exposed via a special @Configuration class:

@Configuration
@ImportResource({"classpath*:extra-beans.xml"})
public class SupplementalXmlConfiguration {}

This configuration class should be registered with the application context alongside the proper Java-based @Configuration classes:

ApplicationContext applicationContext = new AnnotationConfigApplicationContext(
  ApplicationConfiguration.class, SupplementalXmlConfiguration.class);

Beans specified in the extra-beans.xml XML metadata file and those specified in the ApplicationConfiguration class can interoperate with each other and can be declared dependencies of each other.

Playground XML-based Configuration from a Java-based Configuration Application Context

Symmetrically, an XML-based application context detects @Configuration-annotated classes, as well as stereotype annotations and @Autowired injection, and allows beans declared by them to interoperate with beans declared in the XML metadata and declare dependencies on each other. The detection is configured by specifying <context:annotation-config> and <context:component-scan> tags in the XML file.

Playground Java-based Configuration from an XML-based-configuration Application Context

All three types of configuration can be mixed together:

ApplicationContext applicationContext = new AnnotationConfigApplicationContext("io.playground");
applicationContext.register(JavaBasedApplicationConfiguration.class);
applicationContext.register(SupplementalXmlConfiguration.class);

where JavaBasedApplicationConfiguration.class is a regular @Configuration-annotated configuration class, and SupplementalXmlConfiguration.class is also a configuration class that exposes XML metadata read from files, as described above.

Playground Annotation-based and Java-based Configuration from an XML-based-configuration Application Context

Spring IoC Container

The Spring IoC container is alo referred to as the core container. The container is a pool of beans created in a memory space by the framework when the application starts up. Some of the beans, such as the singletons, are instantiated right away, when the container is created and started. Other beans are lazily loaded: unless the bean is requested by the application or some other beans as part of their dependency graph, the framework will not instantiate the bean. The container exposes an API that has methods to query these beans.

If the dependency graph cannot be fulfilled during initialization, Spring fails fast. When the framework encounters any issues in initializing a bean, it quits. No half-configured beans are ever created.

Bean Factory

The simplest type of container is a bean factory, described by the BeanFactory interface. A bean factory provided dependency injection to its beans, initialization and destroy callback invocations.

Application Context

An application context, described by the ApplicationContext interface, extends a bean factory, so anything a bean factory can do, the application context can do too. Additionally, an application context provides ability to load file resources, inherited from ResourceLoader, ability to publish application events to registered listeners, inherited from ApplicationEventPublisher, ability to resolve messages, inherited from MessageSource, inheritance from a parent context and aspects support. The Spring Framework comes with several types of application context implementations:

  • ClassPathXmlApplicationContext is a standalone XML application context that reads the the configuration metadata from XML files from the classpath.
  • FileSystemXmlApplicationContext is a standalone application context that reads the the configuration metadata from XML files loaded from the file system or from URLs, interpreting plain paths as relative file system location.
  • XmlWebApplicationContext.
  • AnnotationConfigApplicationContext is a standalone application context that accepts @Configuration-annotated classes, as well as stereotype-annotated classes and JSR-330 compliant classes using javax.inject annotations as input. In case of multiple @Configuration classes, @Bean methods defined in later classes will override those defined in earlier classes. This can be leveraged to deliberately override certain bean definitions via an extra @Configuration class. Component classes can be registered one by one using register(Class), or they can be detected via the component scan mechanism, triggered by scan(String) method. This application context is used by the Spring Boot applications.

Who Creates the Application Context?

A "classic" Spring Framework application explicitly creates its own application context, usually in its own main class. In the example provided below, the application statically creates an ClassPathXmlApplicationContext, which takes the bean definitions to be created and the dependency relationships between them from an XML file found in the classpath.

public class Main {
    static ApplicationContext APPLICATION_CONTEXT = new ClassPathXmlApplicationContext("some-beans.xml");
    public static void main(String[] args) {
        ...
    }
}

One improvement brought forward by Spring Boot is that Spring Boot applications are not required to do that. The Spring Boot runtime transparently creates the application context.

How to I find an application context that already exists?

Spring Components

A Spring component is a Java object that is created and managed by the Spring Framework. It is also known as Spring bean. Any non-trivial application consists of many components, each responsible for its own piece of the overall application functionality. These components coordinate with each other. The Spring container creates and introduces the beans to each other.

Component Detection

Spring needs to know which are the components to work with, and where their classes are; it does not know about a bean unless it knows where to search for it. As it has been described in the "Configuration Model" section, Spring gets this information in form of configuration metadata, from XML files, @Configuration-annotated Java classes and directly from the JARs in its classpath, in case of classes annotated with stereotype annotations and @Autowired. In the case of beans defined in the XML file, Spring runtime learns how to find the components from the fully qualified class name specified in the <bean> definition, expecting to find the class in the classpath. However, there is an alternative component detection mechanism, named component scanning:

Component Scanning

Component scanning is the process of inspecting JARs on the classpath in an attempt to find relevant annotations: stereotype annotations, @Autowired and @Configuration. Component scanning is enabled with <context:component-scan>, @ComponentScan or @ComponentScans annotations. When component scanning is on, the Spring runtime is given a list of base packages to scan, and it looks for stereotype-annotated classes in those package and in their sub-packages. The base packages are specified with the <context:component-scan> XML configuration element, the AnnotationConfigApplicationContext constructor that gets the base package or as annotation-based configuration, in form of @ComponentScan/@ComponentScans annotations.

Component Scanning and Spring Boot

Spring Boot Component Scanning

Component Definition

See XML-based component definition, Java-based Component Definition and Annotation-based Component Definition above.

Component Attributes

Bean Name

The bean name is specified in the "name" XML attribute.

Bean ID

The name and ID fields both serve the same purpose, except that the ID is specified by the XML specification for IDs – no special characters.

Bean Alias

<alias name=”original-name” alias=”something-else”>

Anonymous (Inner) Beans

An anonymous bean does not have a name or an ID. Its existence is associated with the referencing bean only. Because they do not have a name, the anonymous bean are not available to the program to query them.

Component Lifecycle

Spring framework leans about each component definition and creates a "plan" (a DAG) for how the component should be instantiated. This graph of beans, defined by the configuration metadata, is instantiated and placed under container management. Beans are fully initialized with their transitively also fully-initialized dependencies before being placed in the container, and a bean obtained from the container is guaranteed to be fully initialized. Beans can be obtained from the container via an ApplicationContext.getBean(...) call, or they can be injected into another component via an @Autowired annotation. If a component class is instantiated with the new operator, that results in an un-managed instance that won't have its dependencies injected into it, as the container does not know about it.

A component's life cycle consists of:

  • The framework factory loads the bean definition and instantiates the class.
  • The instance is populated with the properties declared in the bean definition. If a property is a reference to another bean, the other bean will be created and populated, recursively, then its reference is injected into the bean.
  • If the bean implements BeanNameAware or BeanFactoryAware, the appropriate callbacks will be invoked.
  • The framework invokes any BeanPostProcessor associated with the bean.
  • The init-metod, if specified, is invoked on the bean.
  • The post-initialization is performed.

Where does the constructor execution fit in here? It seems that when the constructor is executed, the dependencies are NOT present yet.

Component Instantiation

A bean can be instantiated:

  • Via reflection.
  • Via static methods.
  • Via factory (non-static) methods.

Obtaining Components

Aside from injection, one way to get components from the container is to use:

BeanFactory.getBean(...)

The bean can be looked up as follows:

  • byType. If the type of the field is an interface, and the bean is obviously a concrete class, wiring byType still works. If more than one bean with the same type, an exception is thrown. Also see Design to Interface section.
  • byName. The framework tries injecting the dependencies by matching bean names to the property field names. The name match has to be exact.
  • byConstructor. The bean’s constructor arguments will be satisfied by searching for those types in the context.

Component Scope

Singleton

Singleton is the default scope. There is just one singleton per container.

@Component
@Scope(value = ConfigurableBeanFactory.SCOPE_SINGLETON)
public class MySingletonComponent {
...
}
@Configuration
public class MyConfiguration {
  @Bean("mySingletonComponent")
  @Scope(value = ConfigurableBeanFactory.SCOPE_SINGLETON)
  public MySingletonComponent getMySingletonComponent() {
    return new MySingletonComponent();
 }
}

Prototype

A new instance is created every time a call is made to fetch the bean instance.

Component Callbacks

init-method

Can be declared in XML. There is also a default-init-method that is attempted to be invoked on all beans. Can be declared in XML in the <beans …> element.

destroy-method

Can be declared in XML. There is also a default-destroy-method that is attempted to be invoked on all beans. Can be declared in XML in the <beans …> element.

Spring Boot

Spring Boot Autoconfiguration

Spring Boot Autoconfiguration