How does Spring know where to search for Components or Beans?
I love to nswer interview questions. Read below...
@ComponentScan
If you understand Component Scan, you understand Spring.
Spring is a dependency injection framework. It is all about beans and wiring in dependencies.
The first step of defining Spring Beans is by adding the right annotation — @Component
or @Service
or @Repository
.
However, Spring does not know about the bean unless it knows where to search for it.
This part of “telling Spring where to search” is called a Component Scan.
You define the packages that have to be scanned.
Once you define a Component Scan for a package, Spring would search the package and all its sub packages for components/beans.
Defining a Component Scan
- If you are using Spring Boot, check the configuration in Approach 1.
- If you are doing a JSP/Servlet or a Spring MVC application without using Spring Boot, use Approach 2.
Approach 1: Component Scan in a Spring Boot Project
If your other package hierarchies are below your main app with the @SpringBootApplication
annotation, you’re covered by the implicit Component Scan.
If there are beans/components in other packages that are not sub-packages of the main package, you should manually add them as @ComponentScan
Consider below class
package com.in28minutes.springboot.basics.springbootin10steps;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
@SpringBootApplication
public class SpringbootIn10StepsApplication {
public static void main(String[] args) {
ApplicationContext applicationContext =
SpringApplication.run(SpringbootIn10StepsApplication.class, args);
for (String name: applicationContext.getBeanDefinitionNames()) {
System.out.println(name);
}
}
}
@SpringBootApplication
is defined in the SpringbootIn10StepsApplication
class which is in the package com.in28minutes.springboot.basics.springbootin10steps
@SpringBootApplication
defines an automatic Component Scan on the package com.in28minutes.springboot.basics.springbootin10steps
.
You are fine if all your components are defined in the above package or a sub-package of it.
However, let’s say one of the components is defined in package com.in28minutes.springboot.somethingelse
In this case, you would need to add the new package into Component Scan.
You have two options:
Option 1:
@ComponentScan(“com.in28minutes.springboot”)
@SpringBootApplication
public class SpringbootIn10StepsApplication {...}
Option 2:: Define as array
@ComponentScan({"com.in28minutes.springboot.basics.springbootin10steps","com.in28minutes.springboot.somethingelse"})
@SpringBootApplication
public class SpringbootIn10StepsApplication {...}
Approach 2: Non-Spring Boot Project
Option 1:
@ComponentScan(“com.in28minutes)
@Configuration
public class SpringConfiguration {...}
Option 2:
@ComponentScan({"com.in28minutes.package1","com.in28minutes.package2"})
@Configuration
public class SpringConfiguration {...}
XML application context:
<context:component-scan base-package="com.in28minutes" />
Specific multiple packages:
<context:component-scan base-package="com.in28minutes.package1, com.in28minutes.package2" />
Well where to search for the beans is defined by the @ComponentScan
which can be annotated on the @Configuration
class that is used to bootstrap Spring.
For example , it has an attribute called scanBasePackages
which tells Spring to scan the beans (A class that is annotated with @Component
or its sterotypes such as @Service
, @Repository
, @Controller
etc. ) from certain packages and its sub-packages only.
Then for each bean that are registered , it goes on see if there are any methods annotation with @Bean
.If yes, also register them as beans.
The IoC (Inversion of Control) container, represented in Spring by the class ApplicationContext
, is the brain behind all of it. It all comes down to using reflection in a really powerful way.
To simplify, let's consider the following steps (all done through reflection):
- Search all classes in the classpath
- From those classes, get all classes annotated with
@Component
- For each class annotated with
@Component
, create a new instance of that class - Check for dependencies, i.e, for each created instance, check all fields annotated with
@Autowired
and create an instance for each one of them. - Keep everything in the context so they can be used later.
The remaining of this answer is an oversimplified version of how this happens as if we did it ourselves. Thankfully, Spring exists and we don't need to do this ourselves.
The annotations
@Retention(RetentionPolicy.RUNTIME)
public @interface Node {}
@Retention(RetentionPolicy.RUNTIME)
public @interface Wire { }
Some annotated classes for testing
@Node
public class ServiceA {
@Wire
private ServiceB serviceB;
public void doAStuff() {
System.out.println("A stuff");
serviceB.doBStuff();
}
}
@Node
public class ServiceB {
public void doBStuff() {
System.out.println("B stuff");
}
}
The IoC Container
import org.reflections.Reflections;
/* dependency org.reflections:reflections:0.9.12 */
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class IoC {
private final Map<Class<?>, Object> allNodes = new HashMap<>();
public void start() {
Reflections reflections = new Reflections(IoC.class.getPackageName());
Set<Class<?>> nodeClasses = reflections.getTypesAnnotatedWith(Node.class);
try {
for (Class<?> c : nodeClasses) {
Object thisInstance = c.getDeclaredConstructor().newInstance();
for (Field f : c.getDeclaredFields()) {
f.setAccessible(true);
if (f.getDeclaredAnnotation(Wire.class) != null) {
Object o = f.getType().getDeclaredConstructor().newInstance();
f.set(thisInstance, f.getType().cast(o));
}
}
allNodes.put(c, thisInstance);
}
} catch (Exception e) {
e.printStackTrace();
}
}
public <T> T getNodeByType(Class<T> cls) {
return cls.cast(allNodes.get(cls));
}
}
And the main class to get it all started.
public class Application {
public static void main(String[] args) {
IoC ioc = new IoC();
ioc.start();
ServiceA serviceA = ioc.getNodeByType(ServiceA.class);
serviceA.doAStuff();
}
}
This will output:
A stuff
B stuff
Of course, Spring is a lot more powerful (and robust) than this. It allows for custom package scanning using @ComponentScan
, beans of the same type with different names, singleton/prototype scoped beans, constructor wiring, properties files injection, amongst many other things. When it comes to Spring Boot, the @SpringBootApplication
annotation make sure it finds and wire all @Controller
annotated classes and set up a Netty/Jetty/Tomcat embedded server to listen to the requests and redirect to the proper controller based on the annotated types.