Photo by John O'Nolan on Unsplash
Spring Series - Picking Up Spring-Managed Beans through Class Path Scanning
Introduction
In the previous article, we learned about the Spring Container. In this article, we check how to add new beans to the Spring container. There are two different approaches that we can use to add beans to our container.
Declaratively (through annotations or XML).
Registering components manually
Let’s first check how to use the declarative mechanism with the annotations approach.
Using Annotations
Let’s create a new file named, DemoComponent.java. To create that file, first create a new package named, demo
and add the following file there.
public class DemoComponent{
public void demoFunction(){
System.out.println("Inside Demo Function!");
}
}
The DemoComponent class has a parameterless constructor and we can create a new object of it with the keyword new
. However, we need it to be created and registered by the Spring Framework. In Spring we can do that easily by using annotations.
@Component
public class DemoComponent{
public void demoFunction(){
System.out.println("Inside Demo Function!");
}
}
This annotation comes from the org.springframework.stereotype
package and if we investigate it more we can find the following.
@TargetType(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Indexed
public @interface Component{
...
}
As you can see, the annotation is itself annotated. We call these types of annotations meta-annotations.
@Target(ElementType.TYPE)
→That@Component
may only be attached to type declarations.@Retention
→ That the annotation is accessible at runtime via reflection.@Documented
→That a placed@Component
annotation itself appears in the Java documentation of that class.@Indexed
→Indicate that the annotated element represents a stereotype for the index.
Now, if we start our application with the following, we should be able to see our component with the name, demoComponent
.
@SpringBootApplication
public class DemoApplication{
public static void main(String args[]){
ApplicationContext ctx = SpringApplication.run(DemoApplication.class, args);
Arrays.stream(ctx.getBeanDefinitions()).forEach(System.out::println);
}
}
In summary, all classes that are annotated with @Component
are automatically detected, instantiated, and come into the container as a Spring-managed bean. This is due to the @SpringBootApplication
annotation in our DemoApplication class.
Let’s take a closer look at the @SpringBootApplication
annotation to get an idea about it.
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
@Filter(type=FilterType.CUSTOM, classes=TypeExcludeFilter.class),
@Filter(type=FilterType.CUSTOM, classes=AutoConfigurationExcludeFilter.class)
})
public @interface SpringBootApplication{
...
}
The annotation @ComponentScan
is responsible for getting all the classes that are annotated with @Component
(there are some other stereotypes as well, we’ll check them later)to the Spring container.
@Repository, @Service, @Controller
Besides the @Component
annotation, there are other annotations, that lead to a new Spring-managed bean. @Component
only says that this is just a component. But components have a purpose, and to better document them, we can use these other annotations.
@Service
→ Classes that execute the business logic@Repository
→ Classes that go to data stores@Controller
→ Classes that accept requests from the front end.
As these annotations are intended for proper documentation, there is no specific distinction between them if we consider their functionality. We can use @Repository
for classes that execute business logic, and it will work without any issues.
@ComponentScan
The run(…)
method in our DemoApplication is passed as a start configuration. The start configuration is either marked with @Configuration
or annotated with @Component
. Instead of passing it into run(…)
, this configuration can be passed to the constructor of SpringApplication
.
SpringApplication(Class<?>… primarySources)
static ConfigurableApplicationContext run(Class<?> primarySource, String … args)
static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args)
The second option is the default option selected when we create our initial application through Spring Initializr.
Since our class, DemoApplication is used as the primary source for the run(…)
method, it is the start configuration. The start configuration was the class annotated with @SpringBootApplication
. And as we saw it has the following three annotations.
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan
The annotation @SpringBootConfiguration
is a special @Configuration
annotation, and that in turn is a @Component
. And components are automatically recognized through @ComponentScan
annotation.
basePackages
The @ComponentScan
annotation scans all the components in our project by default. However, we can limit that by adding another @Configuration
class, that uses @ComponentScan
with the basePackages
argument.
@Configuration
@ComponentScan(basePackages={"com.example.demo.demo", "com.example.demo.demo1"})
The basePackages
is an alias in the context of @ComponentScan
. Therefore, we can even use it as shown below.
@Configuration
@ComponentScan({"com.example.demo.demo", "com.example.demo.demo1"})
Similarly, we can use this with @SpringBootApplication
annotation as well, since it has @SpringBootConfiguration
annotation inside it. This will scan only the components in the mentioned packages.
@SpringBootApplication(scanBasePackages = {"com.example.demo.demo"})
...
basePackageClasses
Rather than specifying the packages as an array of strings, we can also specify classes as well.
@Configuration
@ComponentScan(basePackageClasses={A.class, B.class})
This is also possible with @SpringBootApplication
annotation.
@SpringBootApplication(scanBasePackageClasses = {A.class, B.class})
...
However, the A and B classes can be moved to another package at some time. Therefore, to avoid any issues that may occur with that kind of scenario we can define an empty interface and use it in the @ComponentScan
.
package com.example.demo.demo;
public @interface CoreModule{}
@ComponentScan(basePackageClasses = {CoreModule.class})
includeFilters
With basePackages
and basePackageClasses
,we can use filters to control which types should be included in the packages.
@Configuration
@ComponentScan(
useDefaultFilters=false,
includeFilters = @ComponentScan.Filter(
type = FilterType.ASSIGNABLE_TYPE,
classes = CoreModule.class
)
)
The useDefaultFilters=false
controls that not every @Component
is automatically detected and logged in.
excludeFilters
Similar to the includeFilters
, there is an argument which does the exact opposite of includeFilters
. If we don’t want to include the types of specific classes, we can use this.
@Configuraion
@ComponentScan(
excludeFilters = @ComponentScan.Filter(
type = FilterType.ASSIGNABLE_TYPE,
classes = CoreModule.class
)
)
With both includeFilters
and excludeFilters
we can use FilterType.REGEX
which uses regex patterns to find the classes.
@Configuraion
@ComponentScan(
excludeFilters = @ComponentScan.Filter(
type = FilterType.REGEX,
pattern = ".*(Core)"
)
)
If the mentioned filters do not work, you can create a custom filter and use it with FilterType.CUSTOM
. For that, you need to create a class and implement the TypeFilter
interface.
public class ComponentScanCustomFilter implements TypeFilter {
@Override
public boolean match(MetadataReader metadataReader,
MetadataReaderFactory metadataReaderFactory) throws IOException {
ClassMetadata classMetadata = metadataReader.getClassMetadata();
String fullyQualifiedName = classMetadata.getClassName();
String className = fullyQualifiedName.substring(fullyQualifiedName.lastIndexOf(".") + 1);
return className.length() > 5 ? true : false;
}
}
@Configuration
@ComponentScan(includeFilters = @ComponentScan.Filter(
type = FilterType.CUSTOM,
classes = ComponentScanCustomFilter.class
)
)
So this is it regarding, the Picking Up Spring-Managed Beans through Class Path Scanning. In the next article, let’s look at how you can build an interactive application with Spring Shell.