When I’m learning a new framework, I like to look at things that beginners don’t like to read or don’t understand. For example, when I was learning Angular, it was obvious that ng new would be enough to create a new project and start writing programs, but I would go deeper into the whole process of starting it. And when I was learning Spring Boot, even though Spring Initializr was really nice to use, and I could start developing applications by just selecting the packages, I wanted to understand what was going on behind the scenes of this amazing design, so I could understand the core principles of a framework.

In this article, we will explore the principles of @SpringBootApplication, a core annotation in Spring Boot.

spring boot

Create sample applications

  1. Quickly building a project using the Spring Boot CLI

    1
    
    spring init --dependencies=web,lombok --groupId=com.duotify starter1
    

    It can also be created with Spring Initializr

  2. Open the project using Visual Studio Code

    1
    
    code starter1/
    

The current folder and file structure of the project is as follows.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
.
├── HELP.md
├── mvnw
├── mvnw.cmd
├── pom.xml
└── src
    ├── main
    │   ├── java
    │   │   └── com
    │   │       └── duotify
    │   │           └── starter1
    │   │               └── DemoApplication.java
    │   └── resources
    │       ├── application.properties
    │       ├── static
    │       └── templates
    └── test
        └── java
            └── com
                └── duotify
                    └── starter1
                        └── DemoApplicationTests.java

14 directories, 7 files

Application entry points

All Java applications need an entry point to run, so you just need to find the class that contains the main() method, which is our entry point.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
package com.example.myapp;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DemoApplication {

  public static void main(String[] args) {
    SpringApplication.run(DemoApplication.class, args);
  }

}

There is a @SpringBootApplication annotation on this class that declares that this is a Spring Boot application. But this @SpringBootApplication annotation itself has several annotations applied to it, so we can see the source code by pressing F12.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
@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 {
    ....
}

From the above code, we can find three other important annotations.

  1. @SpringBootConfiguration

    This @SpringBootConfiguration annotation is mainly used to add @Configuration annotation to current class (DemoApplication), you can take a look at the source code part of @SpringBootConfiguration, he has a @Configuration annotation applied to it.

    1
    2
    3
    4
    5
    6
    7
    8
    
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Configuration
    @Indexed
    public @interface SpringBootConfiguration {
        ...
    }
    

    You can actually apply the @Configuration annotation directly to DemoApplication, but the semantics of applying @SpringBootConfiguration is much clearer and more readable! 👍

    Basically, applying the @Configuration annotation will allow the DemoApplication class to be added to the Spring IoC container, and if there is a Method (Method) in the class with the @Bean annotation, that method can be executed by Spring Boot at application startup. and the object returned by the method is added to the Spring IoC container so that other components (@Component) can be used by constructive injection, attribute injection (@Autowired) or method injection (@Qualifier).

    This is a manual process of creating and registering Spring Bean components.

  2. @EnableAutoConfiguration

    The @EnableAutoConfiguration annotation first allows Spring Boot to automatically find all classes in the dependent package that have @Component annotations applied to them through the @ComponentScan definition, and automatically create and register them as Spring bean components. This process is like automatically adding @Configuration annotations to these classes for you.

    Dependent packages such as Tomcat or Spring MVC have a similar design, so using @EnableAutoConfiguration can greatly simplify the amount of code.

  3. @ComponentScan

    This @ComponentScan annotation is used to automatically scan the current package and all sub-package classes annotated with the @Component annotation and provide them to @EnableAutoConfiguration for automatic configuration. This process is also known as Component scanning.

    These three tags are important because they serve different and related purposes. In general, @EnableAutoConfiguration and @SpringBootConfiguration will only be applied once to the root class, and @ComponentScan will usually only be applied to the root class. But if you have other libraries, you can decide for yourself if you want to use @ComponentScan to find all the classes under the current class package and sub-packages that have @Component annotated.

    Note: Generally speaking, fewer people apply @Component annotations directly to classes, instead using annotations that inherit from @Component. @Controller, @Service, @Repository, @Configuration are all such annotations. Since @RestController inherits from @Controller, this is also a type of Stereotype Annotations.

Normally, you can just use the @SpringBootApplication annotation on the root class and ignore the underlying technical details in 99% of the cases.

Demonstrate several different application scenarios

  1. There is only one class for the whole application, no need for @ComponentScan to automatically scan sub-packages.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    
    package com.duotify.starter1;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    @EnableAutoConfiguration
    public class DemoApplication {
    
    @RequestMapping("/")
    String home() {
        return "Hello World!123";
    }
    
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
    
    }
    

    Such examples are relatively rare and are mostly found in code examples that you will see when you first learn the Spring Boot framework.

    The @EnableAutoConfiguration annotation here automatically configures all Spring beans, but we don’t apply @ComponentScan, so it doesn’t automatically scan the com.dootify.starter1 and com.dootify.starter1.* classes.

    Here is the @RestController annotation, whose source code looks like this, which contains the @Controller and @ResponseBody annotations.

    1
    2
    3
    4
    5
    6
    7
    8
    
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Controller
    @ResponseBody
    public @interface RestController {
        ...
    }
    

    And the source code of the @Controller annotation looks like this.

    1
    2
    3
    4
    5
    6
    7
    
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Component
    public @interface Controller {
        ...
    }
    

    So in fact, he has applied @Component, so it will be automatically set by @EnableAutoConfiguration annotation, so the DemoApplication class itself will be registered as a Spring bean component, so it can be executed directly as a Controller.

  2. There is more than one class for the whole application, we want to split the Controller class and write it separately, so we need @ComponentScan to automatically scan the classes in sub-packages

    I’ll start by changing DemoApplication.java to this.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    
    package com.duotify.starter1;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
    
    @EnableAutoConfiguration
    public class DemoApplication {
    
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
    
    }
    

    Then add a com.dootify.starter1.controllers.HomeController class.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    
    package com.duotify.starter1.controllers;
    
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    public class HomeController {
    @RequestMapping("/")
    String home() {
        return "Hello World!";
    }
    }
    

    After launching the application, the HomeController controller will not be found because it is not registered as a Spring Bean component.

    1
    2
    
    $ curl localhost:8080
    {"timestamp":"2022-09-19T08:02:19.812+00:00","status":404,"error":"Not Found","path":"/"}
    

    We add the @ComponentScan annotation to the DemoApplication class of DemoApplication.java.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    
    package com.duotify.starter1;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
    import org.springframework.context.annotation.ComponentScan;
    
    @EnableAutoConfiguration
    @ComponentScan
    public class DemoApplication {
    
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
    
    }
    

    Restart the application and you can call the API without any problem!

    1
    2
    
    $ curl localhost:8080
    Hello World!
    
  3. We have a common object that is registered via @Bean and used by other components that are also registered in the Spring IoC container.

    Because all classes annotated with @Component will eventually be registered in the Spring IoC container. And all the components registered to Spring IoC container can be injected into other components. So we can add a Spring bean method to the DemoApplication class.

    1
    2
    3
    4
    
    @Bean
    public String appName() {
    return "Starter1";
    }
    

    Once added, the appName() method, which has the @Bean annotation, does not actually run automatically when the Spring Boot application starts unless you apply the @Configuration annotation to the DemoApplication class.

    The result is as follows.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    
    package com.duotify.starter1;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    
    @EnableAutoConfiguration
    @ComponentScan
    @Configuration
    public class DemoApplication {
    
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
    
    @Bean(name = "appName")
    public String appName() {
        return "Starter1";
    }
    
    }
    

    Since we have collected the @EnableAutoConfiguration , @ComponentScan , @Configuration annotations, we can now merge them directly into the @SpringBootApplication annotation.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    
    package com.duotify.starter1;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    
    @SpringBootApplication
    public class DemoApplication {
    
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
    
    @Bean(name = "appName")
    public String appName() {
        return "Starter1";
    }
    
    }
    

    Finally, we modify the HomeController class and use the constructive injection String Spring bean to use it.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    
    package com.duotify.starter1.controllers;
    
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    public class HomeController {
    
        private String appName;
    
        public HomeController(String appName) {
            this.appName = appName;
        }
    
        @RequestMapping("/")
        String home() {
            return "Hello " + this.appName + "!";
        }
    }
    

    If you want to use “attribute injection”, you can also use @Autowired in the fields and the code will be simpler.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    
    package com.duotify.starter1.controllers;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    public class HomeController {
    
        @Autowired
        private String appName;
    
        @RequestMapping("/")
        String home() {
            return "Hello " + this.appName + "!";
        }
    }
    

    Testing.

    1
    2
    
    $ curl localhost:8080/
    Hello Starter1!
    

Conclusion

In fact, this article has covered many basics, including

Because these are the core mechanisms of Spring, as long as you can understand the above concepts completely, you can understand the other parts very well! 👍

Reference https://blog.miniasp.com/post/2022/09/20/How-Spring-Boot-Works