Spring 3.0 引入 JavaConfig,旨在提供類型安全、IDE 友好、重構安全的配置方式。相比 XML,JavaConfig 具有編譯期檢查、代碼補全、易於調試等優勢,且能自然表達複雜的依賴關係。
| 註解 | 作用 | 關鍵屬性 |
|---|---|---|
@Configuration | 聲明配置類,內部含 @Bean 方法 | proxyBeanMethods (默認 true) |
@Bean | 聲明 Bean 定義,相當於 XML <bean> | name, initMethod, destroyMethod, autowire, autowireCandidate |
@ComponentScan | 啟用組件掃描 | basePackages, basePackageClasses, includeFilters, excludeFilters |
@Import | 導入其他配置類 | value (Class[]) |
@ImportResource | 導入 XML 配置文件 | locations |
@PropertySource | 加載屬性文件 | value, encoding |
@Value | 注入屬性值 | value (SpEL 表達式) |
@Configuration
@ComponentScan("com.acme")
public class AppConfig {
@Bean
public MyService myService() {
return new MyServiceImpl();
}
}
// 啟動
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
MyService svc = ctx.getBean(MyService.class);
@Configuration
public class ConfigA {
@Bean public A a() { return new A(); }
}
@Configuration
@Import(ConfigA.class) // 導入 ConfigA
public class ConfigB {
@Bean public B b() { return new B(); }
}
// 僅需啟動 ConfigB
ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigB.class);
// A 和 B 均可獲取
A a = ctx.getBean(A.class);
B b = ctx.getBean(B.class);
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(AppConfig.class, OtherConfig.class);
ctx.register(AdditionalConfig.class);
ctx.refresh(); // 必須調用完成初始化
MyService svc = ctx.getBean(MyService.class);
@Configuration
public class AppConfig {
@Bean(initMethod = "init", destroyMethod = "cleanup")
public Foo foo() { return new Foo(); }
@Bean(destroyMethod = "close")
public DataSource dataSource() { return new HikariDataSource(); }
}
// 等價於直接調用(更直觀)
@Bean
public Foo foo() {
Foo foo = new Foo();
foo.init(); // 手動調用
return foo;
}
@Bean
@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
public Encryptor encryptor() { return new AesEncryptor(); }
// Web 作用域示例
@Bean
@Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
public UserPreferences userPreferences() { return new UserPreferences(); }
@Bean
public Service userService() {
UserService svc = new SimpleUserService();
svc.setUserPreferences(userPreferences()); // 注入代理對象
return svc;
}
proxyMode 解決不同作用域 Bean 互相注入問題:單例注入會話作用域時,注入的是代理,實際調用時委託給當前會話的真實實例。
@Bean(name = "myFoo", aliases = {"fooAlias1", "fooAlias2"})
public Foo foo() { return new Foo(); }
關鍵原則:@Configuration 類本身也是 Bean,可使用 @Autowired
@Configuration
public class ServiceConfig {
@Autowired private AccountRepository accountRepository; // 跨配置類注入
@Bean
public TransferService transferService() {
return new TransferServiceImpl(accountRepository);
}
}
@Configuration
public class RepositoryConfig {
@Autowired private DataSource dataSource;
@Bean
public AccountRepository accountRepository() {
return new JdbcAccountRepository(dataSource);
}
}
@Configuration
@Import({ServiceConfig.class, RepositoryConfig.class})
public class SystemTestConfig {
@Bean
public DataSource dataSource() { return new EmbeddedDatabaseBuilder().build(); }
}
啟動 SystemTestConfig 即可完成全鏈路裝配。Spring 會自動解析配置類間的依賴圖。
<web-app>
<context-param>
<param-name>contextClass</param-name>
<param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
</context-param>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>com.acme.AppConfig</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextClass</param-name>
<param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
</init-param>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>com.acme.web.MvcConfig</param-value>
</init-param>
</servlet>
</web-app>
<beans>
<context:annotation-config/> <!-- 啟用 @Autowired/@Configuration 等 -->
<bean class="com.acme.AppConfig"/> <!-- 作為普通 Bean 註冊 -->
<bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
</beans>
@Configuration
@ImportResource("classpath:/com/acme/properties-config.xml")
public class AppConfig {
@Value("${jdbc.url}") private String url;
@Value("${jdbc.username}") private String username;
@Value("${jdbc.password}") private String password;
@Bean
public DataSource dataSource() {
return new DriverManagerDataSource(url, username, password);
}
}
// properties-config.xml
<beans>
<context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
</beans>
@Configuration
public class AppConfig {
@Bean
public ClientService clientService1() {
ClientServiceImpl svc = new ClientServiceImpl();
svc.setClientDao(clientDao()); // 調用同類 @Bean 方法
return svc;
}
@Bean
public ClientService clientService2() {
ClientServiceImpl svc = new ClientServiceImpl();
svc.setClientDao(clientDao()); // 再次調用
return svc;
}
@Bean
public ClientDao clientDao() {
return new ClientDaoImpl(); // 單例,只會執行一次
}
}
原理:Spring 啟動時通過 CGLIB 生成 AppConfig$$EnhancerBySpringCGLIB$$xxx 子類,重寫 @Bean 方法:首次調用創建實例並緩存,後續調用直接返回緩存對象。這保證了單例語義,也意味著 @Configuration 類不能是 final,方法不能是 private/final/static。
@Import 組合@Bean 方法參數自動按類型裝配,無需 @Autowired@ConditionalOnMissingBean、@ConditionalOnProperty 實現模塊化開關@PostConstruct 或 InitializingBeanJavaConfig 不會完全取代 XML(如命名空間、舊系統遷移),但作為現代 Spring 應用的首選配置方式,它將配置從「數據描述」提升為「可執行代碼」,賦予了開發者前所未有的控制力與表達力。