有時候在寫 Spring config 時,新手都會搞不清楚 @Bean@ComponentScan 到底差在哪。我自己也踩過不少坑,今天就來把這兩個概念理清楚。

先說結論

簡單講:

  • @Bean 是手動註冊,你明確地在 @Configuration 裡面寫程式碼,告訴 Spring 怎麼建立這個 Bean
  • @ComponentScan 是自動掃描,Spring 自動找出你用 @Component、@Service、@Repository 這些註解標注的類別,然後註冊成 Bean

兩者各有用途,也可以混用。

@Bean 怎麼用

@Bean 通常用在 @Configuration class 裡面。你寫一個方法,用 @Bean 標注,Spring 就會把這個方法回傳的物件當成 Bean 註冊進去。

1
2
3
4
5
6
7
8
9
10
11
12
13
@Configuration
public class AppConfig {

@Bean
public DataSource dataSource() {
return new DataSource();
}

@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
}

看到了沒,方法的名字就變成 Bean 的名字。上面的例子裡,Bean name 分別是 dataSourcejdbcTemplate

如果你想自訂 Bean name,可以這樣:

1
2
3
4
@Bean(name = "myDataSource")
public DataSource dataSource() {
return new DataSource();
}

你也可以指定多個名字:

1
2
3
4
@Bean(name = {"ds", "dataSource", "myDataSource"})
public DataSource dataSource() {
return new DataSource();
}

@ComponentScan 怎麼用

@ComponentScan 就簡單多了,它告訴 Spring 去掃某個 package,把裡面所有用 @Component、@Service、@Repository、@Controller 標注的類別都拿去註冊成 Bean。

1
2
3
4
@Configuration
@ComponentScan(basePackages = {"com.example.service", "com.example.repository"})
public class AppConfig {
}

或是用 basePackageClasses,這樣可以避免 hardcode package name:

1
2
3
4
@Configuration
@ComponentScan(basePackageClasses = {ServiceMarker.class, RepositoryMarker.class})
public class AppConfig {
}

如果你在標注 @Component 的類別上面寫:

1
2
3
@Component
public class UserService {
}

Bean name 就會自動用類別名的首字母小寫,所以上面的 Bean name 是 userService

當然你也可以指定:

1
2
3
@Component("myUserService")
public class UserService {
}

差異在哪?

說到關鍵的差異了。

@Bean 最大的優點就是彈性高,你可以處理那些你改不了的第三方 library class。 比如說你用了一個外部的 library,提供了一個 DataSource class,你沒辦法在那個 class 上面加 @Component。這時候 @Bean 就派上用場了:

1
2
3
4
5
6
7
8
9
10
11
@Configuration
public class AppConfig {

@Bean
public DataSource thirdPartyDataSource() {
DataSource ds = new ThirdPartyDataSource();
ds.setUrl("jdbc:mysql://localhost:3306/mydb");
ds.setUsername("root");
return ds;
}
}

@ComponentScan 就比較適合你自己寫的 class。 只要你把 @Component 或 @Service 之類的註解標上去,Spring 就會自動掃描註冊,簡潔有力。

可以混用嗎?

可以啊,完全沒問題。一般的專案都是這樣做的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Configuration
@ComponentScan(basePackages = "com.example")
public class AppConfig {

// 第三方 library 的設定用 @Bean
@Bean
public RedisTemplate redisTemplate(LettuceConnectionFactory connectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
return template;
}

// 自己的 service 就讓 @ComponentScan 去掃
}

然後在自己的 service class 上面:

1
2
3
4
5
@Service
public class UserService {
@Autowired
private RedisTemplate redisTemplate;
}

Spring Boot 的預設行為

順便提一下,如果你用 Spring Boot,有個 @SpringBootApplication 註解幫你自動做了很多事。它其實就包含了 @Configuration 和 @ComponentScan。預設會掃描 main class 所在的 package 及其所有子 package。

所以很多時候你根本不用手動寫 @ComponentScan,Spring Boot 已經幫你做好了。但如果你要掃描其他 package,就得自己加設定。

重點整理

  • @Bean:手動註冊,適合第三方 library class
  • @ComponentScan:自動掃描,適合自己寫的 class
  • Bean name:@Bean 用方法名(或自訂),@ComponentScan 用首字母小寫的類別名(或自訂)
  • 兩者可以混用,很常見
  • Spring Boot 預設已經有 @ComponentScan 了

這樣下次再看到有人糾結 @Bean vs @ComponentScan,你就可以輕鬆解釋了。