Bean Factory and Dependency Injection

jim stafford

Introduction

Goals

The student will learn:

  • to decouple an application through the separation of interface and implementation

  • to configure an application using dependency injection and factory methods of a configuration class

Objectives

At the conclusion of this lecture and related exercises, the student will be able to:

  1. implement a service interface and implementation component

  2. package a service within a Maven module separate from the application module

  3. implement a Maven module dependency to make the component class available to the application module

  4. use a @Bean factory method of a @Configuration class to instantiate a Spring-managed component

Hello Service

  • create a sample Hello service

    • implement an interface

    • a single implementation right off the bat

  • two separate modules

    • hello-service-api

    • hello-service-stdout

app beanfactory participants

We will start out by creating two separate module directories.

Hello Service API

The Hello Service API module will contain a single interface and pom.xml.

hello-service-api/
|-- pom.xml
`-- src
    `-- main
        `-- java
            `-- info
                `-- ejava
                    `-- examples
                        `-- app
                            `-- hello
                                `-- Hello.java (1)
1Service interface

Hello Service StdOut

The Hello Service StdOut module will contain a single implementation class and pom.xml.

hello-service-stdout/
|-- pom.xml
`-- src
    `-- main
        `-- java
            `-- info
                `-- ejava
                    `-- examples
                        `-- app
                            `-- hello
                                `-- stdout
                                    `-- StdOutHello.java (1)
1Service implementation

Hello Service API pom.xml

  • Building normal Java JAR

  • No direct dependencies on Spring Boot or Spring

hello-service-api pom.xml
#pom.xml
...
    <groupId>info.ejava.examples.app</groupId>
    <version>6.1.0-SNAPSHOT</version>
    <artifactId>hello-service-api</artifactId>
    <packaging>jar</packaging>
...

Hello Service StdOut pom.xml

The implementation will be similar to the interface’s pom.xml except it requires a dependency on the interface module.

hello-service-stdout pom.xml
#pom.xml
...
    <groupId>info.ejava.examples.app</groupId>
    <version>6.1.0-SNAPSHOT</version>
    <artifactId>hello-service-stdout</artifactId>
    <packaging>jar</packaging>

    <dependencies>
        <dependency>
            <groupId>${project.groupId}</groupId> (1)
            <artifactId>hello-service-api</artifactId>
            <version>${project.version}</version> (1)
        </dependency>
    </dependencies>
...
1Dependency references leveraging ${project} variables module shares with dependency
Since we are using the same source tree, we can leverage ${project} variables. This will not be the case when declaring dependencies on external modules.

Hello Service Interface

  • Quite simple

  • Pass in the String name to say hello to

package info.ejava.examples.app.hello;

public interface Hello {
    void sayHello(String name);
}

The service instance will be responsible for

  • the greeting

  • the implementation — how we say hello

app beanfactory target

Hello Service Sample Implementation

package info.ejava.examples.app.hello.stdout; (1)

public class StdOutHello implements Hello {
    private final String greeting; (2)

    public StdOutHello(String greeting) { (3)
        this.greeting = greeting;
    }

    @Override (4)
    public void sayHello(String name) {
        System.out.println(greeting + " " + name);
    }
}
1Implementation defined within own package
2greeting will hold our phrase for saying hello and is made final to highlight it is required and will not change during the lifetime of the class instance
3A single constructor is provided to define a means to initialize the instance. Remember — the greeting is final and must be set during class instantiation and not later during a setter.
4The sayHello() method provides implementation of method defined in interface
final requires the value set when the instance is created and never change
Spring recommends constructor injection. This provides an immutable object and better assures that required dependencies are not null. [1]

Hello Service Modules Complete

Hello Service API Maven Build

$ mvn clean install -f hello-service-api
[INFO] Scanning for projects...
[INFO]
[INFO] -------------< info.ejava.examples.app:hello-service-api >--------------
[INFO] Building App::Config::Hello Service API 6.1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-clean-plugin:3.1.0:clean (default-clean) @ hello-service-api ---
[INFO]
[INFO] --- maven-resources-plugin:3.1.0:resources (default-resources) @ hello-service-api ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory .../app-config/hello-service-api/src/main/resources
[INFO]
[INFO] --- maven-compiler-plugin:3.8.1:compile (default-compile) @ hello-service-api ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to .../app-config/hello-service-api/target/classes
[INFO]
[INFO] --- maven-resources-plugin:3.1.0:testResources (default-testResources) @ hello-service-api ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory .../app-config/hello-service-api/src/test/resources
[INFO]
[INFO] --- maven-compiler-plugin:3.8.1:testCompile (default-testCompile) @ hello-service-api ---
[INFO] No sources to compile
[INFO]
[INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ hello-service-api ---
[INFO] No tests to run.
[INFO]
[INFO] --- maven-jar-plugin:3.1.2:jar (default-jar) @ hello-service-api ---
[INFO] Building jar: .../app-config/hello-service-api/target/hello-service-api-6.1.0-SNAPSHOT.jar
[INFO]
[INFO] --- maven-install-plugin:3.0.0-M1:install (default-install) @ hello-service-api ---
[INFO] Installing .../app-config/hello-service-api/target/hello-service-api-6.1.0-SNAPSHOT.jar to .../.m2/repository/info/ejava/examples/app/hello-service-api/6.1.0-SNAPSHOT/hello-service-api-6.1.0-SNAPSHOT.jar
[INFO] Installing .../app-config/hello-service-api/pom.xml to .../.m2/repository/info/ejava/examples/app/hello-service-api/6.1.0-SNAPSHOT/hello-service-api-6.1.0-SNAPSHOT.pom
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  2.070 s

Hello Service StdOut Maven Build

$ mvn clean install -f hello-service-stdout
[INFO] Scanning for projects...
[INFO]
[INFO] ------------< info.ejava.examples.app:hello-service-stdout >------------
[INFO] Building App::Config::Hello Service StdOut 6.1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-clean-plugin:3.1.0:clean (default-clean) @ hello-service-stdout ---
[INFO]
[INFO] --- maven-resources-plugin:3.1.0:resources (default-resources) @ hello-service-stdout ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory .../app-config/hello-service-stdout/src/main/resources
[INFO]
[INFO] --- maven-compiler-plugin:3.8.1:compile (default-compile) @ hello-service-stdout ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to .../app-config/hello-service-stdout/target/classes
[INFO]
[INFO] --- maven-resources-plugin:3.1.0:testResources (default-testResources) @ hello-service-stdout ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory .../app-config/hello-service-stdout/src/test/resources
[INFO]
[INFO] --- maven-compiler-plugin:3.8.1:testCompile (default-testCompile) @ hello-service-stdout ---
[INFO] No sources to compile
[INFO]
[INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ hello-service-stdout ---
[INFO] No tests to run.
[INFO]
[INFO] --- maven-jar-plugin:3.1.2:jar (default-jar) @ hello-service-stdout ---
[INFO] Building jar: .../app-config/hello-service-stdout/target/hello-service-stdout-6.1.0-SNAPSHOT.jar
[INFO]
[INFO] --- maven-install-plugin:3.0.0-M1:install (default-install) @ hello-service-stdout ---
[INFO] Installing .../app-config/hello-service-stdout/target/hello-service-stdout-6.1.0-SNAPSHOT.jar to .../.m2/repository/info/ejava/examples/app/hello-service-stdout/6.1.0-SNAPSHOT/hello-service-stdout-6.1.0-SNAPSHOT.jar
[INFO] Installing .../app-config/hello-service-stdout/pom.xml to .../.m2/repository/info/ejava/examples/app/hello-service-stdout/6.1.0-SNAPSHOT/hello-service-stdout-6.1.0-SNAPSHOT.pom
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  2.658 s

Application Module

We now move on to developing our application within its own module containing two (2) classes similar to earlier examples.

|-- pom.xml
`-- src
    ``-- main
        `-- java
            `-- info
                `-- ejava
                    `-- examples
                        `-- app
                            `-- config
                                `-- beanfactory
                                    |-- AppCommand.java (2)
                                    `-- SelfConfiguredApp.java (1)
1Class with Java main() that starts Spring
2Class containing our first component that will be the focus of our injection

Application Maven Dependency

<groupId>info.ejava.examples.app</groupId>
<artifactId>appconfig-beanfactory-example</artifactId>
<name>App::Config::Bean Factory Example</name>

<dependencies>
  <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter</artifactId>
  </dependency>
  <dependency>
      <groupId>${project.groupId}</groupId>
      <artifactId>hello-service-stdout</artifactId> (1)
      <version>${project.version}</version>
  </dependency>
</dependencies>
1Dependency on implementation creates dependency on both implementation and interface


In this case, the module we are depending upon is in the same groupId and shares the same version. For simplicity of reference and versioning, I used the ${project} variables to reference it. That will not always be the case.

Module Dependencies

app beanfactory dependencies

Viewing Dependencies

You can verify the dependencies exist using the tree goal of the dependency plugin.

Artifact Dependency Tree
$ mvn dependency:tree -f hello-service-stdout
...
[INFO] --- maven-dependency-plugin:3.1.1:tree (default-cli) @ hello-service-stdout ---
[INFO] info.ejava.examples.app:hello-service-stdout:jar:6.1.0-SNAPSHOT
[INFO] \- info.ejava.examples.app:hello-service-api:jar:6.1.0-SNAPSHOT:compile

Application Java Dependency

package info.ejava.examples.app.config.beanfactory;

import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

import info.ejava.examples.app.hello.Hello;

@Component
public class AppCommand implements CommandLineRunner {
    private final Hello greeter; (1)

    public AppCommand(Hello greeter) { (2)
        this.greeter = greeter;
    }

    public void run(String... args) throws Exception {
        greeter.sayHello("World");
    }
}
1Add a reference to the Hello interface. Java attribute defined as final to help assure that the value is assigned during the constructor.
2Using contructor injection where the instance is supplied to the class through a parameter to the constructor

Dependency Injection

Our AppCommand class has been defined only with the interface to Hello and not a specific implementation.

Separation of Concerns

  • we want our code broken into separate modular areas of concern

    • modules (JARs)

    • packages

    • classes

    • methods

  • helps improve modularity, testability, reuse, and many other desirable features

  • we do this using encapsulation (modularity) and information hiding (well defined interfaces)

  • improves simplicity and understandability

Factory Mechanism

But how do does our client class (AppCommand) get an instance of the implementation (StdOutHello)?

  • client class directly instantiates the implementation

    public AppCommand() {
        this.greeter = new StdOutHello("World");
    }
    • client becomes coupled to that specific implementation.

  • client class procedurally delegates to a factory

    public AppCommand() {
        this.greeter = BeanFactory.makeGreeter();
    }
    • client runs the risk of violating Separation of Concerns by adding complex initialization code to its primary business purpose

Inversion of Control

Most frameworks, including Spring, implement dependency injection through a form of inversion of control (IoC)

  • traditional procedural code

    • normally makes calls to libraries in order to perform a specific purpose

  • inverted control

    • application code is part of a framework that calls the application code when it is time to do something

      • In this case the framework is for application assembly.

Spring Dependency Injection

We defined the dependency using the Hello interface and have three primary ways to have dependencies injected into an instance.

import org.springframework.beans.factory.annotation.Autowired;

public class AppCommand implements CommandLineRunner {
    //@Autowired -- FIELD injection (3)
    private Hello greeter;

    @Autowired //-- Constructor injection (1)
    public AppCommand(Hello greeter) {
        this.greeter = greeter;
    }

    //@Autowired -- PROPERTY injection (2)
    public void setGreeter(Hello hello) {
        this.greeter = hello;
    }
1constructor injection - injected values required prior to instance being created
2field injection - value injected directly into attribute
3setter or property injection - setter() called with value

@Autowired Annotation

  • may be applied to fields, methods, constructors

  • @Autowired(required=true) - default value for required attribute

    • successful injection mandatory when applied to a property

    • specific constructor use required when applied to a constructor

      • only a single constructor per class may have this annotation

  • @Autowired(required=false)

    • injected bean not required to exist when applied to a property

    • specific constructor an option for container to use

    • multiple constructors may have this annotation applied

      • container will determine best based on number of matches

    • single constructor has an implied @Autowired(required=false) - making annotation optional

I selected constructor injection since the dependency is required for component to be valid

Dependency Injection Flow

In our example:

  • Spring will detect the AppCommand component and look for ways to instantiate it

  • The only constructor requires a Hello instance

  • Spring will then look for a way to instantiate an instance of Hello

Bean Missing

When we go to run the application, we get the following error

$ mvn clean package
...
***************************
APPLICATION FAILED TO START
***************************

Description:

Parameter 0 of constructor in AppCommand required a bean of type 'Hello' that could not be found.

Action:

Consider defining a bean of type 'Hello' in your configuration.
  • Container has no knowledge of any beans that can satisfy the only available constructor

  • StdOutHello class is not defined in a way that allows Spring to use it

Bean Missing Error Solution(s)

We can solve this in at least two (2) ways.

  1. Add @Component to the StdOutHello class. This will trigger Spring to directly instantiate the class.

    @Component
    public class StdOutHello implements Hello {
    • problem: It may be one of many implementations of Hello

  2. Define what is needed using a @Bean factory method of a @Configuration class. This will trigger Spring to call a method that is in charge of instantiating an object of the type identified in the method return signature.

    @Configuration
    public class AConfigurationClass {
        @Bean
        public Hello hello() {
            return new StdOutHello("...");
        }
    }

@Configuration classes

  • classes that Spring expects to have one or more @Bean factory methods

@SpringBootApplication (1)
//==> wraps @SpringBootConfiguration (2)
//  ==> wraps @Configuration
public class SelfConfiguredApp {
    public static void main(String...args) {
        SpringApplication.run(SelfConfiguredApp.class, args);
    }
    //...
}
1@SpringBootApplication is a wrapper around a few annotations including @SpringBootConfiguration
2@SpringBootConfiguration is an alternative annotation to using @Configuration with the caveat that there be only one @SpringBootConfiguration per application


Therefore, we have the option to use our Spring Boot application class to host the configuration and the @Bean factory.

@Bean Factory Method

@SpringBootApplication (4) (5)
public class SelfConfiguredApp {
    public static void main(String...args) {
        SpringApplication.run(SelfConfiguredApp.class, args);
    }

    @Bean (1)
    public Hello hello() { (2)
        return new StdOutHello("Application @Bean says Hey"); (3)
    }
}
1method annotated with @Bean implementation
2method returns Hello type required by container
3method returns a fully instantiated instance.
4method hosted within class with @Configuration annotation
5@SpringBootConfiguration annotation included the capability defined for @Configuration
Anything missing to create instance gets declared as an input to the method and, it will get created in the same manner and passed as a parameter.

@Bean Factory Used

$ java -jar target/appconfig-beanfactory-example-*-SNAPSHOT-bootexec.jar
...
Application @Bean says Hey World
  • the container

    • obtained an instance of a Hello bean

    • passed that bean to the AppCommand class' constructor to instantiate that @Component

  • the @Bean factory method

    • chose the implementation of the Hello service (StdOutHello)

    • chose the greeting to be used ("Application @Bean says Hey")

      return new StdOutHello("Application @Bean says Hey");
  • the AppCommand CommandLineRunner determined who to say hello to ("World")

    greeter.sayHello("World");

Factory Alternative: XML Configuration

import org.springframework.context.annotation.ImportResource;

@SpringBootApplication
@ImportResource({"classpath:contexts/applicationContext.xml"}) (1)
public class XmlConfiguredApp {
    public static void main(String...args) {
        SpringApplication.run(XmlConfiguredApp.class, args);
    }
}
1@ImportResource will enact the contents of context/applicationContext.xml
|-- pom.xml
`-- src
    `-- main
        |-- java
        |   `-- info
        |       `-- ejava
        |           `-- examples
        |               `-- app
        |                   `-- config
        |                       `-- xmlconfig
        |                           |-- AppCommand.java
        |                           `-- XmlConfiguredApp.java
        `-- resources
            `-- contexts
                `-- applicationContext.xml

$ jar tf target/appconfig-xmlconfig-example-*-SNAPSHOT-bootexec.jar | grep applicationContext.xml
BOOT-INF/classes/contexts/applicationContext.xml

XML Configuration (cont.)

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean class="info.ejava.examples.app.hello.stdout.StdOutHello"> (1)
        <constructor-arg value="Xml @Bean says Hey" />  (2)
    </bean>
</beans>
1A specific implementation of ther Hello interface is defined
2Text is injected into the constructor when container instantiates

Output:

$ java -jar target/appconfig-xmlconfig-example-*-SNAPSHOT-bootexec.jar
...
Xml @Bean says Hey World

@Configuration Alternatives

Two other common options for a @Bean factory: scope and proxyBeanMethods.

  • Spring will instantiate a single component to represent the bean (singleton)

  • Spring will create a (CGLIB) proxy for each @Configuration class to assure the result is processed by Spring and that each bean client for singleton-scoped beans get the same copy.

    • proxy can add needless complexity depending on how sibling methods are designed

Concepts/Issues:

  • shared (singleton) or unique (prototype) component instances

  • (unnecessary) role of the Spring proxy (proxyBeanMethods)

  • potential consequences of calling sibling @Bean methods over injection

Example Lifecycle POJO

Example POJO to identify its instance and track its component lifecycle

  • Java constructor called for each POJO instance created

  • @PostConstruct methods are called by Spring after dependencies have been injected — with the potential for Spring container interpose

Example POJO Class Created and Used by @Bean Factory
public class Example {
    private final int exampleValue;
    public Example(int value) { this.exampleValue = value; } (1)
    @PostConstruct (2)
    void init() {
        System.out.println("@PostConstruct called for: " + exampleValue);
    }
    public String toString() { return Integer.toString(exampleValue); }
}
1Constructor will be called for every instance created
2@PostConstruct will only get called by when POJO becomes a component

Example Lifecycle @Configuration Class

  • @Configuration and @Bean factory annotations are modified during example

Example @Configuration and Source @Bean Factory
@Configuration //default proxyBeanMethods=true
//@Configuration(proxyBeanMethods = true)
//@Configuration(proxyBeanMethods = false)
public class BeanFactoryProxyBeansConfiguration {
    private int value = 0;

    @Bean //default is singleton
    //@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON) //"singleton"
    Example bean() {
        return new Example(value++);
    }

Consuming @Bean Factories within Same Class

Two pairs of Example-consuming @Bean factories:

  • calling - make a direct call to the supporting @Bean factory method

  • injected - simply declare their requirement in method input parameters

Example @Bean Consumers
    @Bean (1)
    String calling1()              { return "calling1=" + bean(); }
    @Bean (1)
    String calling2()              { return "calling2=" + bean(); }
    @Bean (2)
    String injected1(Example bean) { return "injected1=" + bean; }
    @Bean (2)
    String injected2(Example bean) { return "injected2=" + bean; }
1calling consumers call the sibling @Bean factory method directly
2injected consumers are passed an instance of requirements when called

Using Defaults (Singleton, Proxy)

By default:

  • @Bean factories use singleton scope

  • @Configuration classes use proxyBeanMethods=true

That means that:

  • @Bean factory method will be called only once by Spring

  • @Configuration class instance will be proxied and direct calls (calling1 and calling2) will receive the same singleton result

Proxied @Configuration Class Emits Same Singleton to all Consumers
@PostConstruct called for: 0 (2)
calling1=0 (1)
calling2=0 (1)
injected1=0 (1)
injected2=0 (1)
1only one POJO instance was created
2only one component was initialized

Prototype Scope, Proxy True

If we change component scope created by the bean() method to "prototype"…​

@Bean factory Scope set to non-default "prototype"
@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) //"prototype"
Example bean() {
    return new Example(value++);
}

…​we get a unique POJO instance/component for each consumer (calling and injected) while proxyBeanMethods is still true.

Different Instance Produced for each Consumer and all Consumer Types
@PostConstruct called for: 0 (2)
@PostConstruct called for: 1 (2)
@PostConstruct called for: 2 (2)
@PostConstruct called for: 3 (2)
calling1=0 (1)
calling2=1 (1)
injected1=2 (1)
injected2=3 (1)
1unique POJO instances were created
2each instance was a component

Prototype Scope, Proxy False

If we drop the CGLIB proxy, our configuration instance gets lighter, but …​

Spring Proxy Off, Prototype Scope
@Configuration(proxyBeanMethods = false)
public class BeanFactoryProxyBeansConfiguration {
    @Bean @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) //"prototype"
    Example bean() {

…​only injected consumers are given "component" beans. The "calling" consumers are given "POJO" beans, that lack the potential for interpose.

@PostConstruct called for: 2 (2)
@PostConstruct called for: 3 (2)
calling1=0 (1)
calling2=1 (1)
injected1=2 (1)
injected2=3 (1)
1each consumer is given a unique instance
2only the injected callers are given components (with interpose potential)

Singleton Scope, Proxy False

Keeping the proxy eliminated and reverting back to the default singleton scope for the bean …​

Spring Proxy Off, Singleton Scope
@Configuration(proxyBeanMethods = false)
public class BeanFactoryProxyBeansConfiguration {

    @Bean @Scope(ConfigurableBeanFactory.SCOPE_SINGLETON) //"singleton" - default
    Example bean() {

…​shows that only the injected consumers are receiving a singleton instance — initialized as a component, with the potential for interpose.

@PostConstruct called for: 0 (3)
calling1=1 (2)
calling2=2 (2)
injected1=0 (1)
injected2=0 (1)
1injected consumers get the same instance
2calling consumers get unique instances independent of @Scope
3only the injected consumers are getting a component (with interpose potential)

@Configuration Takeaways

  • Spring instantiates all components, by default, as singletons — with the option to instantiate unique instances on demand when @Scope is set to "prototype".

  • Spring, by default, constructs a CGLIB proxy to enforce those semantics for both calling and injected consumers.

    • Since @Configuration classes are only called once at start-up, it can be a waste of resources to construct a CGLIB proxy.

      • Using injection-only consumers, with no direct calls to @Configuration class methods, eliminates the need for the proxy.

      • adding proxyFactoryBeans=false eliminates the CGLIB proxy. Spring will enforce semantics for injected consumers

Summary

In this module we

  • decoupled part of our application into three Maven modules (app, iface, and impl1)

  • decoupled the implementation details (StdOutHello) of a service from the caller (AppCommand) of that service

  • injected the implementation of the service into a component using constructor injection

  • defined a @Bean factory method to make the determination of what to inject

  • showed an alternative using XML-based configuration and @ImportResource

  • explored the differences between calling and injected sibling component consumers