- Overview
- Dependency Injection in Spring
- Dependency Injection with XML Configuration (Constructor Injection)
- Dependency Injection with XML Configuration (Setter Injection)
- Dependency Injection with Java Annotations (Constructor Injection)
- Dependency Injection with Java Annotations (Setter Injection)
- Dependency Injection with Java Annotations (Field Injection)
- Dependency Injection with Java Configuration Class
Dependency Injection (DI) allows a program design to follow the dependency inversion principle. The client will delegate to call to another object the responsibility of providing its dependencies.
Spring can use Auto Wiring for dependency injection.
- Spring will look for a class that matches the property. (matches by type: class or interface)
- Spring will inject it automatically hence it is autowired.
Example: Injecting
FortuneService
into aCoach
implementation
- Spring will scan
@Components
- Any one implemenets
FortuneService
interface?- If so, let's inject them. e.g.
HappyFortuneService
.
If there are multiple implementations, we need to tell Spring which bean to use by @Qualifier
annotations.
There're many types of injection with Spring:
- Constructor Injection: inject dependencies by calling constructor of the class.
- Setter Injection: inject dependencies by calling setter methods on the class.
- Field Injections: inject dependencies by setting the field values on the class. (even private fields)
The development process of Spring DI can be summaried as steps below:
- Define the dependency interface and class.
- Create a constructor or setter methods in our class for injections.
- Configure the dependency injection in Spring config file.
The development process of Spring DI can be summaried as steps below:
- Define the dependency interface and class.
- Create a constructor in the class for injections.
- Configure the dependency injection with
@Autowired
Annotation.
First create the FortuneService
interface:
// [FILE] FortuneService.java
package com.luv2code.springdemo;
public interface FortuneService {
public String getFortune();
}
Then create HappyFortuneService
class which implement the FortuneService
interface:
// [FILE] HappyFortuneService.java
package com.luv2code.springdemo;
public class HappyFortuneService implements FortuneService {
@Override
public String getFortune() {
return "Today is your lucky day!";
}
}
Add the getFortune()
method to Coach
interface:
package com.luv2code.springdemo;
public interface Coach {
public String getDailyWorkout();
public String getDailyFortune();
}
// [FILE] BaseballCoach
package com.luv2code.springdemo;
public class BaseballCoach implements Coach {
// define a private field for the dependency
private FortuneService fortuneService;
// define a constructor for dependency injection
public BaseballCoach(FortuneService theFortuneService) {
fortuneService = theFortuneService;
}
@Override
public String getDailyWorkout() {
return "Spend 30 minutes on batting practice";
}
@Override
public String getDailyFortune() {
// use my fortuneService to get a fortune
return fortuneService.getFortune();
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- Define the dependency-->
<bean id="myFortuneService" class="com.luv2code.springdemo.HappyFortuneService">
</bean>
<!-- Define your beans here -->
<bean id="myCoach" class="com.luv2code.springdemo.TrackCoach">
<!-- Set up constructor injection -->
<constructor-arg ref="myFortuneService" />
</bean>
</beans>
- The
id
is like an alias. - The
class
should be the fully qualified class name of implementation class. - The no-arg constructor
- When we don’t define any constructor in your class, compiler defines default one for us.
- When we declare any constructor (in the example we have already defined a parameterized constructor), compiler doesn’t do it for you.
- Since we have defined a constructor in class code, compiler didn’t create default one.
- While creating object we are invoking default one, which doesn’t exist in class code. Then the code gives an compilation error.
Create CricketCoach
class and add the setter methods for injections:
package com.luv2code.springdemo;
public class CricketCoach implements Coach {
private FortuneService fortuneService;
// create a no-arg constructor
public CricketCoach() {
System.out.println("CricketCoach: inside no-arg constructor");
}
// create setter methods for injections
public void setFortuneService(FortuneService fortuneService) {
System.out.println("CricketCoach: inside setter method - setFortuneService");
this.fortuneService = fortuneService;
}
@Override
public String getDailyWorkout() {
return "Practice fast bowling for 15 minutes";
}
@Override
public String getDailyFortune() {
return fortuneService.getFortune();
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- Define the dependency-->
<bean id="myFortuneService" class="com.luv2code.springdemo.HappyFortuneService"></bean>
<!-- Define your beans here -->
<bean id="myCoach" class="com.luv2code.springdemo.TrackCoach">
<!-- Set up constructor injection -->
<constructor-arg ref="myFortuneService" />
</bean>
<bean id="myCricketCoach" class="com.luv2code.springdemo.CricketCoach">
<!-- Set up setter injection -->
<property name="fortuneService" ref="myfortuneService" />
</bean>
</beans>
- The
id
is like an alias. - The
class
should be the fully qualified class name of implementation class. - The
name
equalsfortuneService
but the Spring Framework will actually callsetFortuneService
.
Create a new class named SetterDemoApp
:
package com.luv2code.springdemo;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SetterDemoApp {
public static void main(String[] args) {
// load the spring configuration file
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// retrieve bean from spring container
CricketCoach theCoach = context.getBean("myCricketCoach", CricketCoach.class);
// call methods on the bean
System.out.println(theCoach.getDailyWorkout());
System.out.println(theCoach.getDailyFortune());
// close the context
context.close();
}
}
The steps for injecting literal values could are:
-
Create private fields for setting methods.
package com.luv2code.springdemo; public class CricketCoach implements Coach { // add new fields for email address and team private String emailAddress; private String team; // add methods for injections private FortuneService fortuneService; // create a no-arg constructor public CricketCoach() { System.out.println("CricketCoach: inside no-arg constructor"); } // getter and setter for EmailAddress public String getEmailAddress() { return emailAddress; } public void setEmailAddress(String emailAddress) { System.out.println("CricketCoach: inside setter method - setEmailAddress"); this.emailAddress = emailAddress; } // getter and setter for Team public String getTeam() { return team; } public void setTeam(String team) { System.out.println("CricketCoach: inside setter method - setTeam"); this.team = team; } // create setter methods for injections public void setFortuneService(FortuneService fortuneService) { System.out.println("CricketCoach: inside setter method - setFortuneService"); this.fortuneService = fortuneService; } @Override public String getDailyWorkout() { return "Practice fast bowling for 15 minutes"; } @Override public String getDailyFortune() { return fortuneService.getFortune(); } }
-
Use
<property>
to apply thename
andref
for the values.<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- define the dependency--> <bean id="myFortuneService" class="com.luv2code.springdemo.HappyFortuneService"></bean> <!-- define your beans here --> <bean id="myCoach" class="com.luv2code.springdemo.TrackCoach"> <!-- set up constructor injection --> <constructor-arg ref="myFortuneService" /> </bean> <bean id="myCricketCoach" class="com.luv2code.springdemo.CricketCoach"> <!-- set up setter injection --> <property name="fortuneService" ref="myFortuneService" /> <!-- inject literal values --> <property name="emailAddress" value="[email protected]" /> <property name="team" value="Sunrisers Hyderabad" /> </bean> </beans>
If we don't want to hard coded the literal values in the config file, we can read this information from the application file by following steps:
-
Create Properties File
foo.email[email protected] foo.team=Royal Challengers Bangalore
-
Load Properties File in Spring config File
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" ... > ... <!-- load the properties file: sport.properties --> <context:property-placeholder location="classpath:sport.properties" /> ... </beans>
-
Reference values from Properties File
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" ... > ... <bean id="myCricketCoach" class="com.luv2code.springdemo.CricketCoach"> ... <!-- inject literal values --> <property name="emailAddress" value="${foo.email}" /> <property name="team" value="${foo.team}" /> </bean> ... </beans>
Create the FortuneService
interface:
package com.luv2code.springdemo;
public interface FortuneService {
public String getFortune();
}
Then create the class HappyFortuneService
to implement the FortuneService
interface:
package com.luv2code.springdemo;
import org.springframework.stereotype.Component;
@Component
public class HappyFortuneService implements FortuneService {
@Override
public String getFortune() {
return "Today is your lucky day!";
}
}
package com.luv2code.springdemo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class TennisCoach implements Coach {
private FortuneService fortuneService;
@Autowired
public TennisCoach(FortuneService theFortuneService) {
fortuneService = theFortuneService;
}
@Override
public String getDailyWorkout() {
return "Practice your backhand volley";
}
@Override
public String getDailyFortune() {
return fortuneService.getFortune();
}
}
package com.luv2code.springdemo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class TennisCoach implements Coach {
private FortuneService fortuneService;
// define a default constructor
public TennisCoach() {
System.out.println(">> TennisCoach: inside default constructor");
}
// define a setter method
@Autowired
public void setFortuneService(FortuneService theFortuneService) {
System.out.println(">> TennisCoach: inside setFortuneService() method");
fortuneService = theFortuneService;
}
@Override
public String getDailyWorkout() {
return "Practice your backhand volley";
}
@Override
public String getDailyFortune() {
return fortuneService.getFortune();
}
}
package com.luv2code.springdemo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class TennisCoach implements Coach {
@Autowired
private FortuneService fortuneService;
// define a default constructor
public TennisCoach() {
System.out.println(">> TennisCoach: inside default constructor");
}
@Override
public String getDailyWorkout() {
return "Practice your backhand volley";
}
@Override
public String getDailyFortune() {
return fortuneService.getFortune();
}
}
Add the SwimCoach
class:
package com.luv2code.springdemo;
public class SwimCoach implements Coach {
private FortuneService fortuneService;
public SwimCoach(FortuneService theFortuneService) {
fortuneService = theFortuneService;
}
@Override
public String getDailyWorkout() {
return "Swim 1000 meters as a warm up.";
}
@Override
public String getDailyFortune() {
return fortuneService.getFortune();
}
}
package com.luv2code.springdemo;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class SportConfig {
// define bean for our sad fortune service
@Bean
public FortuneService sadFortuneService() {
return new SadFortuneSerivce();
}
// define bean for our swim coach and inject dependency
@Bean
public Coach swimCoach() {
return new SwimCoach(sadFortuneService());
}
}
For this code:
@Bean
public Coach swimCoach() {
SwimCoach mySwimCoach = new SwimCoach();
return mySwimCoach;
}
- The
@Bean
annotation tells Spring that we're creating a bean component manually. The default scope is singleton since we don't specify a scope. - The
public Coach swimCoach()
will specify the bean use bean idswimCoach
.- The method name determines the bean id.
- The return type is the
Coach
interface. - This helps Spring find any dependencies that implement the
Coach
interface.
- The
@Bean
annotation will intercept any requests forswimCoach
bean for singleton scope. As a result, it will give the same instance of the bean for any requests.
Behind the scenes, during the @Bean
interception, it will check in memory of the Spring container (applicationContext
) and see if this given bean has already been created.
-
Add Properties File named
sport.properties
.[email protected] foo.team=Awesome Java Coders
-
Load Properties File in Spring with
@PropertySource
Annotationpackage com.luv2code.springdemo; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; @Configuration @PropertySource("classpath:sport.properties") public class SportConfig { // define bean for our sad fortune service @Bean public FortuneService sadFortuneService() { return new SadFortuneSerivce(); } // define bean for our swim coach and inject dependency @Bean public Coach swimCoach() { return new SwimCoach(sadFortuneService()); } }
-
Reference values from Properties File with
@Value
Annotationpackage com.luv2code.springdemo; import org.springframework.beans.factory.annotation.Value; public class SwimCoach implements Coach { @Value("${foo.email}") private String email; @Value("${foo.team}") private String team; public String getEmail() { return email; } public String getTeam() { return team; } private FortuneService fortuneService; public SwimCoach(FortuneService theFortuneService) { fortuneService = theFortuneService; } @Override public String getDailyWorkout() { return "Swim 1000 meters as a warm up."; } @Override public String getDailyFortune() { return fortuneService.getFortune(); } }