Search code examples
javaspringspring-bootjavabeans

How can @Configuration class is loaded while @ComponentScan is not annotated for the same class


I come across this tutorial which shows how to use the H2 embedded database in spring application and it is working fine without any issue.

However by checking the code I did not understand how can the configuration class DBConfig is being discovered and treated as configuration class for the ApplicationContext so can the beans inside be created.

Please note that it is not used as argument in AnnotationConfigApplicationContext() and @ComponentScan and @Configuration are not annotated in the same class.

As you can see below for the class Application

@ComponentScan(basePackages = "com.zetcode")
public class Application {

    private static final Logger logger = LoggerFactory.getLogger(Application.class);

    public static void main(String[] args) {

        // the application context is taking as argument the same Class
        var ctx = new AnnotationConfigApplicationContext(Application.class);
        var app = ctx.getBean(Application.class);

        app.run();

        ctx.close();
    }

    @Autowired
    private JdbcTemplate jdbcTemplate;

    private void run() {

        var sql = "SELECT * FROM cars";

        var cars = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Car.class));

        cars.forEach(car -> logger.info("{}", car));
    }
}

and class DBConfig

@Configuration
public class DBConfig  {

    @Bean
    public DataSource dataSource() {

        var builder = new EmbeddedDatabaseBuilder();
        var db = builder
                .setType(EmbeddedDatabaseType.H2) // HSQL or DERBY
                .addScript("db/schema.sql")
                .addScript("db/data.sql")
                .build();
        return db;
    }

    @Bean
    public JdbcTemplate createJdbcTeamplate() {

        var template = new JdbcTemplate();
        template.setDataSource(dataSource());

        return template;
    }
}

I also did some JUnit Tests and I found that the DBConfig was created and so the Beans defined inside it.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=Application.class)
public class EmbeddedTest {
    
    
    @Autowired
    private DBConfig dbConfig;
    
    @Autowired
    private DataSource dataSource;

    @Test
    public void test() {
        assertNotNull(dbConfig);
        assertNotNull(dataSource);
    }

}

Solution

  • I had to do some debugging in order to figure out how spring works for the above case.

    As we did not provide the Configured Class to our Application context, Spring will use the @ComponentScan to fetch it. How ? via the package com.zetcode provided in the annotation. It scans the package and all subpackages under com.zetcode searching for classes:

    src    
    ├───main
    │   ├───java
    │   │   └───com
    │   │       └───zetcode
    │   │           │   Application.java
    │   │           ├───config
    │   │           │       DBConfig.java
    │   │           └───model
    │   │                   Car.java
    │   └───resources
    │       │   logback.xml
    │       └───db
    │               create-db.sql
    │               insert-data.sql
    └───test
        └───java
    

    Thus, it will finds 3 : Application, DBConfig, Car and for each one it sees if it is a candidate components. In our Example, the generic Beans are Application DBConfig only. Once they are identified, the next step was to check the @Bean Method. And for each one identified it will create the dedicated Bean. Finally it will do the autowiring.