© Adam L. Davis 2020
A. L. DavisSpring Quick Reference Guidehttps://doi.org/10.1007/978-1-4842-6144-6_3

3. Dependency Injection

Adam L. Davis1 
(1)
Oviedo, FL, USA
 

Dependency Injection (DI) is at the heart of Spring. It refers to plugging in references at runtime between many different objects, either through a constructor, setter, or even directly to a field using runtime reflection. This enables IOC (Inversion of Control) where one class can use an instance of another class without knowing any details about how that object was constructed or its exact implementation class.

Spring’s design allows the use of POJOs (Plain Old Java Objects). In other words, you don’t need to implement a specific interface or extend a class in order to use Spring’s DI. An instance of a class configured by Spring is called a Spring Bean, or sometimes just bean for short.

Decoupling

For example, you can annotate a setter or field with @Autowired on a Spring Bean , and Spring will find the class at runtime that best matches that field or setter. By default, it will search for the class matching the type. If it can’t find a matching bean or there is more than one possible match (after considering any @Qualifier annotation and the name), Spring will throw an Exception and fail to start.

You should use interfaces to further decouple different classes. This way different components can be tested independently and not rely on the implementation of other components. Tight coupling in enterprise applications leads to brittle code and makes it very hard to make changes without breaking anything.

You can use @Qualifier to specify a specific name of an instance to help @Autowired find the right instance when many instances of the same class or interface might exist. We will show an example of this in the next section.

Configuration

Beans can be configured in one of three ways: XML, a configuration Java class annotated with @Configuration and methods annotated with @Bean, or on the Bean class itself with an annotation such as @Component . The most recommended way is using one or more Java configuration classes.

A configuration Java class annotated with @Configuration might look like the following:
@Configuration
public class Configuration {
  @Bean
  public MyService myService() {
    return new MyActualService();
  }

This configuration creates one bean instance of the Configuration class itself and one bean instance named myService of class MyActualService which implements the MyService interface (from the method annotated with @Bean).

Any configuration class must be nonfinal and nonlocal (public) and have a no-argument constructor. Spring proxies the class using CGLIB by default in order to enforce Spring bean dependency rules (which is why the class cannot be final). For example, this allows method calls to always return the singleton Bean instance instead of creating a new instance every time. If this behavior is not needed, you can supply proxyBeanMethods=false like the following:
@Configuration(proxyBeanMethods = false)

../images/498572_1_En_3_Chapter/498572_1_En_3_Figa_HTML.jpg The default scope is “singleton,” meaning one instance or “singleton” of the class will exist for the application. Other scopes such as “application,” “request,” and “session” exist in a web application. The “prototype” scope means a new instance will be created of the bean every time it’s requested. A bean’s scope can be changed using the @Scope annotation. For example, @Scope("prototype") @Bean public MyService myService() {...}

Every parameter to a method annotated with @Bean will be autowired by Spring (using the same rules that apply to @Autowired). For example, in the following configuration, the service2 bean will be wired to the myService bean:
@Configuration
public class Configuration {
  @Bean
  public MyService myService() {
    return new MyActualService();
  }
  @Bean
  public OtherService service2(final MyService myService) {
    return new MyActualOtherService(myService);
  }
Listing 3-1

Configuration.java

By default Spring uses the method name as the Bean’s name. So the preceding example creates a Bean named “myService” and a Bean named “service2”. You can override it by supplying a value to the @Bean annotation (like @Bean("myname")).

Using @Qualifier, the “service2” method could be rewritten as follows (with the same outcome):
  @Bean
  public OtherService service2(@Qualifier("myService") MyService s) {
    return new MyActualOtherService(s);
  }

In this way, even if multiple beans exist that implement MyService, Spring will know to choose the one named “myService”.

../images/498572_1_En_3_Chapter/498572_1_En_3_Figb_HTML.jpg You can also configure a bean to have multiple names. For example, using @Bean(name={"myname1", "myname2"}) would register the same bean under two names, myname1 and myname2.

Application Context

The ApplicationContext is the interface that directly exposes all of the beans configured by Spring.

It has a different concrete class depending on the type of application. For example, a web application will have an implementation of WebApplicationContext.

Component Scanning

You can use component scanning in Spring to scan classes for certain annotations on the class declaration. Those annotations are @Component, @Controller, @Service, and @Repository (and @Configuration). If they are found, Spring will initialize that POJO as a Spring Bean.

Component scanning can be configured through XML like the following:
<context:component-scan base-package="com.example"/>
or in a configuration class like this:
@Configuration
@ComponentScan("com.example")
public class Configuration {
Listing 3-2

Configuration.java

In these examples, the “com.example” package and all of its subpackages will be scanned for Spring annotations to create beans. Be careful not to scan too many classes as this will slow down initialization time.

Import

You can import other configuration files using @Import. Using @ComponentScan can also be used to scan for configuration classes (classes marked with @Configuration).

If you really need to, you can also use an @ImportResource annotation to load XML configuration files, for example:
@Import({WebConfig.class, ServiceConfig.class})
@ImportResource("dao.xml")

This would import the WebConfig and ServiceConfig configuration classes and the dao.xml Spring configuration file (see the next chapter for more about XML).

Laziness

Beans are created eagerly by default – which means Spring will instantiate them and wire them up at start-up time. This makes it faster to find any potential problems. You can make a Bean load lazily using the @Lazy annotation if you don’t want it to load until necessary (when requested for using the ApplicationContext.getBean(String) method or requested by, e.g., autowiring).

Shut Down the ApplicationContext

In a web application, Spring already gracefully shuts down the ApplicationContext. However, in non-web applications, you need to register a shutdown hook.
public static void main(final String[] args) throws Exception {
  AbstractApplicationContext ctx
  = new ClassPathXmlApplicationContext(new String []{"beans.xml"});
  // add a shutdown hook for the above context...
  ctx.registerShutdownHook();
  // app runs here...
}
Listing 3-3

App.java

This way, Spring will gracefully shut down when the application exits.

BeanFactoryPostProcessors

The BeanFactoryPostProcessor interface can be implemented to change bean configurations before the beans are created (of all other beans). This can be useful for adding custom configuration, for example (although Spring handles most useful cases by itself). The BeanFactoryPostProcessor interface has one method to define, postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory). Spring automatically detects beans that implement this interface.

BeanPostProcessors

An ApplicationContext also automatically detects any beans that are defined in the configuration metadata it receives that implement the BeanPostProcessor interface. These beans are special because they are created at the same time as the ApplicationContext and before any other beans so they can process other bean definitions.

The org.springframework.beans.factory.config.BeanPostProcessor interface consists of exactly two callback methods:
Object postProcessBeforeInitialization(Object bean, String beanName)
  throws BeansException
Object postProcessAfterInitialization(Object bean, String beanName)
  throws BeansException

../images/498572_1_En_3_Chapter/498572_1_En_3_Figc_HTML.jpg Spring AOP, which we will cover later, is implemented using the BeanPostProcessor interface. It can replace each bean with a proxy of that bean.

Init and Destroy Methods

You can enable JSR-250 annotations like @PostConstruct and @PreDestroy using the CommonAnnotationBeanPostProcessor in Spring. It’s activated by component scanning but otherwise can be activated directly in your Spring configuration.

An alternative is to use Spring’s built-in configuration. For example, the Bean annotation, @Bean(initMethod = "up", destroyMethod = "down") would cause Spring to call “up” after initializing the class and injecting all dependencies and “down” right before destroying it.

Properties

By default, Spring Boot will load properties from your classpath from a file named application.properties (for standard properties) or application.yml (for YAML-formatted properties).

Additional properties can be loaded into the environment using the @PropertySource annotation.

For example, the following loads a property file named app.properties from the classpath under the /com/acme/ directory:
@Configuration
@PropertySource("classpath:/com/acme/app.properties")
public class AppConfig {
  //configuration code...
}
Listing 3-4

AppConfig.java

You can then use properties from the environment and inject them using the @Value annotation:
@Value("${bean.name}") String beanName;
@Bean
public MyBean myBean() {
  return new MyBean(beanName);
}
The file named app.properties could have the following value:
bean.name=Bob

This would inject “Bob” into the beanName field previously mentioned.

Environment

An alternative to using the @Value annotation is to use the org.springframework.core.env.Environment class. It can be autowired into any class (using, e.g., @Autowired). It has the following methods for getting access to defined properties at runtime:
  • String getProperty(String key) – Gets the value for a given property key or null if not resolved

  • String getProperty(String key, String defaultValue) – Gets the value for a given property key or the given defaultValue if not found

  • String getRequiredProperty(String key) – Gets the value for a given property key or throws an IllegalStateException if not found

Profiles

Spring Profiles allow you to configure different properties and even Beans to be initialized at runtime depending on the active Profile(s). They can be useful when deploying the same application to different environments, such as “Staging,” “Test,” and “Production.” You can have any number of profiles with any names.

You can set the current Profile or Profiles active using the spring.profiles.active system property or spring_profiles_active environment variable. You can have as many profiles active as you want (separate them using commas).

The @Profile annotation can annotate a @Component bean class (or the stereotype annotations, @Service, @Repository, and @Controller) or a @Bean annotated method or even a @Configuration annotated configuration class.

For example, the following configuration class defines two different databases. Which one is active is dependent on the profile active.
@Configuration
public class ProfileDatabaseConfig {
  @Bean("dataSource")
  @Profile("development")
  public DataSource embeddedDatabase() { ... }
  @Bean("dataSource")
  @Profile("production")
  public DataSource productionDatabase() { ... }
}
Listing 3-5

ProfileDatabaseConfig.java

../images/498572_1_En_3_Chapter/498572_1_En_3_Figd_HTML.jpg Make sure you use a different name for each @Bean method in a Configuration class even when those beans are marked for different profiles. Otherwise, you could get unexpected behavior from Spring since it uses the method names for the bean names.

SpEL

What is Spring Expression Language (SpEL)? The Spring Expression Language (SpEL for short) is a powerful expression language that supports querying and manipulating an object graph at runtime.

SpEL can be injected using the @Value annotation with the #{} syntax. Unlike using ${}, which only is interpreted as environment properties, using #{} allows you to use the full expressiveness of an embedded language (SpEL).
@Value("#{ T(java.lang.Math).random() * 100.0 }")
int randomNumber;

The T syntax is used to refer to a Java type (the preceding java.lang.Math class).

You can also refer to system properties using the built-in variable systemProperties:
@Value("#{ systemProperties['user.region'] }")
String region;
SpEL also has the Elvis operator and the safe navigator (much like in Kotlin, Groovy, and other languages), for example:
@Value("#{systemProperties['pop3.port'] ?: 25}")

This would default to 25 if no value was given for pop3.port.

You can also specify String literals using single quotes, for example:
@Value("#{ 'Hello '.concat('World!') }")
String hello;

It would result in hello having the value "Hello World!".

SpEL is also useful for Spring Security annotations which we will cover in a subsequent chapter.

Testing

Spring provides testing support as part of spring-test. For a JUnit 4 test, you can specify how the ApplicationContext should be created for JUnit unit or integration tests using Spring’s SpringRunner and the @ContextConfiguration annotation, for example:
@RunWith(SpringRunner.class)
// ApplicationContext will be loaded from AppConfig and TestConfig
@ContextConfiguration(classes = {AppConfig.class, TestConfig.class})
public class MyTest {
// class body...
}
Listing 3-6

MyTest.java

A JUnit 5 test is similar but uses @ExtendWith(SpringExtension.class) instead of @RunWith:
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.test.context.junit.jupiter.SpringExtension;
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = {AppConfig.class, TestConfig.class})
public class MyTest5 {
// class body...
}
Listing 3-7

MyTest5.java

../images/498572_1_En_3_Chapter/498572_1_En_3_Fige_HTML.jpg Write a Spring application including a JUnit test.