Today, Spring Framework is used as an umbrella term for Spring projects and Spring ecosystem, However, Spring Framework is basically a Dependency Injection Framework and we can use its Dependency Injection capabilities in any Java project, without using Spring Boot or any other Spring project.
Example Java Project
Let's say we have a Java project with an EmployeeService
that has a dependency on DepartmentService
and an Application
class to run the application:
package com.thebackendguy.com.service;
public class DepartmentService {
public String getDepartmentName(String employeeId) {
System.out.println(this + ": getDepartmentName");
return "Accounts";
}
}
package com.thebackendguy.com.service;
public class EmployeeService {
private final DepartmentService departmentService;
public EmployeeService(DepartmentService departmentService) {
this.departmentService = departmentService;
}
public String checkDepartment() {
System.out.println(this + ": checkDepartment");
return departmentService.getDepartmentName("EMP-0098");
}
}
package com.thebackendguy.com;
import com.thebackendguy.com.service.DepartmentService;
import com.thebackendguy.com.service.EmployeeService;
public class Application {
public static void main(String[] args) {
DepartmentService departmentService = new DepartmentService();
EmployeeService employeeService = new EmployeeService(departmentService);
System.out.println(employeeService.checkDepartment());
}
}
Here we are manually creating and injecting dependencies, we want Spring framework to do this, let's add Spring Framework to the project
Add Spring Framework Dependency
We only need spring-context
dependency to be added in the project. It is will provide all dependency injection capabilities Spring Framework has to offer. At the time of writing this article, the latest stable version is 5.2.12.RELEASE
.
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.12.RELEASE</version>
</dependency>
Creating and Managing Dependencies (Beans)
Spring managed dependencies are called Beans. Since EmployeeService
depends on DepartmentService
we want Spring to manage that dependency. We need to have a configuration class where we can define dependencies and let Spring know about them.
Spring Beans Configuration class:
Create a new ApplicationConfig
class that will hold dependency (Bean) configurations:
package com.thebackendguy.com.config;
import com.thebackendguy.com.service.DepartmentService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration // 1
public class ApplicationConfig {
@Bean // 2
public DepartmentService departmentService() {
return new DepartmentService();
}
}
- @Configuration is a Spring annotation to mark a class as Bean configuration class, it will help Spring framework to find Beans.
-
@Bean is a Spring annotation to let Spring know about dependency. This annotation is used on a method that Spring will call to obtain that dependency. Here a new
DepartmentService
instance is returned for Spring to manage. That instance is now called a Bean.
Application Context:
Spring Framework use ApplicationContext
to manage the beans. ApplicationContext
represents a container, also called Spring Inversion of Control (IoC) container, that contains all of Spring managed instances or beans.
We need to create an Application Context for our application to access beans. Let's update our main Application
class.
package com.thebackendguy.com;
import com.thebackendguy.com.config.ApplicationConfig;
import com.thebackendguy.com.service.DepartmentService;
import com.thebackendguy.com.service.EmployeeService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Application {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(ApplicationConfig.class); // 1
DepartmentService departmentService = context.getBean(DepartmentService.class); // 2
System.out.println(departmentService); // 3
EmployeeService employeeService = new EmployeeService(departmentService);
System.out.println(employeeService.checkDepartment());
}
}
Create a new
ApplicationContext
using ourApplicationConfig
class (this class contains ourDepartmentService
bean). We need to specify configuration class(s) while creating context so that Spring can find our beans. We can use XML based configuration too instead of Annotation based configurations, but for simplicity we will stick with Annotation based configurations.We are getting
DepartmentService
bean from application context (or IoC container), instead of creating it here.We are simply printing
departmentService
object.
We are passing departmentService
to EmployeeService
same as before.
Auto Injecting Dependencies (Autowiring Beans)
So far we have created DepartmentService
Bean and an ApplicationContext
to manage that bean. We can access that Bean using context
and pass it to another service, but it would be much cleaner if the Bean "gets injected" automatically, rather than we get it from the application context and inject manually.
This auto injection of dependencies (or Beans) is called Autowiring and Spring provides it out of the box, given that both of the instances (the dependent and the dependency) are Spring Beans. In other words, DepartmentService
gets injected in EmployeeService
automatically if both of them are Beans. It won't work otherwise. So, let's make EmployeeService
a bean too.
Update ApplicationConfig class
to add EmployeeService
bean:
package com.thebackendguy.com.config;
import com.thebackendguy.com.service.DepartmentService;
import com.thebackendguy.com.service.EmployeeService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ApplicationConfig {
@Bean
public DepartmentService departmentService() {
return new DepartmentService();
}
@Bean
public EmployeeService employeeService() {
return new EmployeeService(departmentService()); // 1
}
}
-
EmployeeService
needs an instance ofDepartmentService
at the time of creation, so we are callingdepartmentService()
to fulfill that dependency, but this will not create a new DepartmentService instance rather it will automatically Inject the previously existingDepartmentService
bean. Both of the methods are actually Spring Bean configuration and default Bean scope is singleton (single shared instance per context) and Spring is smart enough to realize and inject existing beans where ever required. (For brevity of this article I am not going into detail of bean scope)
Let's update our Application
class to get EmployeeService
instance rather than creating it.
public class Application {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(ApplicationConfig.class);
EmployeeService employeeService = context.getBean(EmployeeService.class); // 1
System.out.println(employeeService.checkDepartment());
}
}
- Now we don't need to manually create an instance of
EmployeeService
and injectDepartmentService
dependency. It is now getting handled by the Spring framework.
These are the minimal steps required to use Spring Framework without Spring boot. Spring Framework Dependency Injection has a lot to offer in addition to these basic Dependency Injection capabilities.
Find code on GitHub: https://github.com/thebackendguy-code-examples/java-spring-demo-without-spring-boot
Top comments (1)
I've been using Spring Framework for so long, years before "boot" was even a concept, that this article feels really weird :)