The highlight of Spring Boot is its auto-configuration feature whereby it automatically includes all the dependencies of a project based on property files and JAR classpaths. Spring Boot is basically the Spring framework along with embedded servers. Spring boot removes the need for XML configuration.
public interface Filter {
public String[]
getRecommendations(String movie);
}
public class ContentBasedFilter implements Filter{
//...
}
public class CollaborativeFilter implements Filter{
//...
}
public class RecommenderImplementation {
//use filter interface to select filter
private Filter filter;
public RecommenderImplementation(Filter filter) {
super();
this.filter = filter;
}
//use a filter to find recommendations
public String [] recommendMovies (String movie) {
String[] results = filter.getRecommendations("Finding Dory");
return results;
}
}
@SpringBootApplication
public class MovieRecommenderSystemApplication {
public static void main(String[] args) {
RecommenderImplementation recommender = new RecommenderImplementation(new ContentBasedFilter());
String[] result = recommender.recommendMovies("Finding Dory");
}
}
Spring automates the above process of creating objects and binding them together. It takes the responsibility of creating instances of classes and binding instances based on their dependencies. The instances or objects that Spring manages are called beans. To manage objects and dependencies, Spring requires information about three things:
- Beans
- Dependencies
- Location of beans
If we want Spring to create and manage objects, we can do so by adding the @Component annotation at the beginning of the class and importing org.springframework.stereotype.Component.
For now, we want Spring to manage objects of RecommenderImplementation and ContentBasedFilter class only, so we will add the @Component annotation at two places in the code:
@Component
public class RecommenderImplementation {
//...
}
@Component
public class ContentBasedFilter implements Filter {
//...
}
In our application, the ContentBasedFilter class (which implements the Filter interface) is a dependency of the RecommenderImplementation class.
@Component
public class RecommenderImplementation {
@Autowired
private Filter filter;
// ...
}
The @Autowired annotation tells Spring that RecommenderImplementation needs an object of type Filter. In other words, Filter is a dependency of RecommenderImplementation.
The third thing that Spring requires from the developer, is the location of the beans so that it can find them and autowire the dependencies. The @ComponentScan annotation is used for this purpose. This annotation can be used with or without arguments. It tells Spring to scan a specific package and all of its sub-packages.
@SpringBootApplication is equivalent to the following three annotations:
- @Configuration, which declares a class as the source for bean definitions
- @EnableAutoConfiguration, which allows the application to add beans using classpath definitions
- @ComponentScan, which directs Spring to search for components in the path specified
The NoUniqueBeanDefinitionException occurs. The error message says: Required a single bean but two were found.
One way Spring can choose between two beans of the same type is by giving one bean priority over the other. The @Primary annotation is used for making a component the default choice when multiple beans of the same type are found.
Using @Primary is called autowiring by type
. We are looking for instances of type Filter.
If we make both beans primary by adding the @Primary annotation to both implementations of the Filter interface, we will get an error.
public class RecommenderImplementation {
@Autowired
private Filter contentBasedFilter;
public String [] recommendMovies (String movie) {
System.out.println("\nName of the filter in use: " + contentBasedFilter + "\n");
String[] results = contentBasedFilter.getRecommendations("Finding Dory");
return results;
}
}
Like @Primary, the @Qualifier annotation gives priority to one bean over the other if two beans of the same type are found. The bean whose name is specified in the @Qualifier annotation qualifies to be injected as a dependency. The @Qualifier annotation can be used in a scenario when we have multiple objects of the same type and autowiring by name cannot be used because the variable name doesn’t match any bean name.
@Component("CBF")
public class ContentBasedFilter implements Filter{
//...
}
// OR
@Component
@Qualifier("CBF")
public class ContentBasedFilter implements Filter{
//...
}
public class RecommenderImplementation {
@Autowired
@Qualifier("CBF")
private Filter filter;
public String [] recommendMovies (String movie) {
//...
}
}
The @Qualifier annotation takes precedence over the @Primary annotation.
@Autowired annotation resolves dependencies by type. If there are more than one beans of the same type, a conflict arises. We have seen three different approaches to resolve conflicts. They can be resolved using the @Primary annotation, renaming the variable to match the name of the class, or by using the @Qualifier annotation.
In Spring framework, there are several types of dependency injection techniques. Dependency injection is a fundamental principle of the Spring framework, which helps in achieving loose coupling between objects and their dependencies. Here are the different types of injections in Spring, their uses, pros, and cons, along with examples:
Constructor injection is a way of injecting dependencies through a class constructor.
Example:
public class EmployeeService {
private EmployeeRepository employeeRepository;
public EmployeeService(EmployeeRepository employeeRepository) {
this.employeeRepository = employeeRepository;
}
// Other methods
}
Pros:
- It ensures that the required dependencies are available when the object is created.
- It makes the class immutable since the dependencies cannot be changed once the object is constructed.
Cons:
- It can lead to a large number of constructors if there are many dependencies.
- It can make the code more verbose and harder to read.
Setter injection is a way of injecting dependencies by calling setter methods on the class.
Example:
public class EmployeeService {
private EmployeeRepository employeeRepository;
public void setEmployeeRepository(EmployeeRepository employeeRepository) {
this.employeeRepository = employeeRepository;
}
// Other methods
}
Pros:
- It provides a way to inject dependencies after the object is created.
- It is more flexible than constructor injection, as dependencies can be changed at runtime.
Cons:
- It can lead to partially initialized objects if dependencies are not set correctly.
- It requires additional boilerplate code for setter methods.
Field injection is a way of injecting dependencies directly into the class fields.
Example:
public class EmployeeService {
@Autowired
private EmployeeRepository employeeRepository;
// Other methods
}
Pros:
- It is concise and requires less code compared to other injection types.
Cons:
- It violates encapsulation principles, as dependencies are directly exposed.
- It can lead to tight coupling between classes, making it harder to unit test.
- It is generally not recommended and should be avoided in favor of constructor or setter injection.
Method injection is a way of injecting dependencies through a method parameter.
Example:
public class EmployeeService {
private EmployeeRepository employeeRepository;
public void setEmployeeRepository(EmployeeRepository employeeRepository) {
this.employeeRepository = employeeRepository;
}
public void processEmployees(List<Employee> employees, @Qualifier("specialRepository") EmployeeRepository specialRepository) {
// Use specialRepository for specific operations
}
}
Pros:
- It allows for flexible dependency injection based on method parameters.
- It can be useful for injecting specific dependencies for certain use cases.
Cons:
- It can make the code harder to read and maintain if overused.
- It can lead to tight coupling between classes if not used carefully.
In general, constructor injection is considered the best practice for most cases, as it ensures that the required dependencies are available when the object is created and makes the class immutable. Setter injection can be useful in certain scenarios where dependencies need to be changed at runtime or for optional dependencies.
Field injection and method injection are generally not recommended due to potential issues with encapsulation, tight coupling, and code maintainability. However, they can be useful in specific scenarios where the benefits outweigh the drawbacks.