自動綁定


如〈使用 Spring DI〉中看過的,@Autowired 可以標註在建構式參數上,在〈設定 Profile〉中看過標註在值域上的情況,實際上,它也可以標註在方法的參數上。

預設情況下,被 @Autowired 標註的參數或值域,一定要在 Spring 管理的 Bean 中能夠找到,若希望允許找不到時設定為 null,可以指定 @Autowired(required=false)

@Autowired 預設採用型態來找到對應的 Bean,然而有時,同一型態可以有多個實現,這時就會試著使用參數或值域名稱,與 Bean 的名稱進行比對,因此,對於底下的 AppConfig

package cc.openhome;

import javax.sql.DataSource;

import org.h2.jdbcx.JdbcDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;

@Configuration
@ComponentScan
@PropertySource("classpath:jdbc.properties")
public class AppConfig { 
    @Value("${cc.openhome.jdbcUrl}")
    private String jdbcUrl;

    @Value("${cc.openhome.user}")
    private String user;

    @Value("${cc.openhome.password}")
    private String password;

    @Bean
    public DataSource getDataSource() {
        JdbcDataSource dataSource = new JdbcDataSource();
        dataSource.setURL(jdbcUrl);
        dataSource.setUser(user);
        dataSource.setPassword(password);

        return dataSource;
    }

    @Bean(destroyMethod="shutdown")
    public DataSource dataSource(){
        return new EmbeddedDatabaseBuilder()
                .setType(EmbeddedDatabaseType.H2)
                .addScript("classpath:schema.sql")
                .addScript("classpath:testData.sql")
                .build();
    }

    @Bean
    public static PropertySourcesPlaceholderConfigurer
                       propertySourcesPlaceholderConfigurer() {
       return new PropertySourcesPlaceholderConfigurer();
    }    
}

搭配〈使用 Spring DI〉中的 AccountDAOJdbcImplMessageDAOJdbcImpl 時,由於 DataSource 具有兩個實現,由於 AccountDAOJdbcImplMessageDAOJdbcImpl 的建構式參數上,都是 @Autowired DataSource dataSource,參數名稱為 dataSource,因此會使用 EmbeddedDatabaseBuilder 建構出來的 DataSource 實現,這是因為 @Bean 都沒有指定名稱,因而會使用方法名稱作為預設的 Bean 名稱。

你可以使用 @Primary 搭配 @Bean 或者是 @Component,當 Spring 發現有兩個以上相同類型的 Bean 時,會選擇被標註了 @Primary 的 Bean。例如,上例中若使用:

    ...略
    @Primary
    @Bean
    public DataSource getDataSource() {
        JdbcDataSource dataSource = new JdbcDataSource();
        dataSource.setURL(jdbcUrl);
        dataSource.setUser(user);
        dataSource.setPassword(password);

        return dataSource;
    }
    ...略

那麼 AccountDAOJdbcImplMessageDAOJdbcImpl 被注入的,就會是 JdbcDataSource 的版本。

你也可以使用 @Qualifier 搭配 @Autowired,這可以指定要注入的 Bean 之名稱。例如:

...略
@Component
public class AccountDAOJdbcImpl implements AccountDAO {
    private DataSource dataSource;

    public AccountDAOJdbcImpl(@Autowired @Qualifier("getDataSource") DataSource dataSource) {
        System.out.println(dataSource);
        this.dataSource = dataSource;
    }
...略

這麼一來,AccountDAOJdbcImpl 被注入的會是 JdbcDataSource 的版本,當然,getDataSource 這名稱取得不好,在使用標註進行 JavaConfig 組態的情況下,Bean 名稱實際上是不用遵守 Getter 命名慣例的,可以依實際的需求來為產生 Bean 的方法命名。

@Autowired 不一定要標註在參數或值域上,例如,當方法或建構式上有多個參數都需要自動注入時,像是〈Spring DI〉中的 UserService

...略
@Component
public class UserService {
    private final AccountDAO acctDAO;
    private final MessageDAO messageDAO;

    public UserService(@Autowired AccountDAO acctDAO, @Autowired MessageDAO messageDAO) {
        this.acctDAO = acctDAO;
        this.messageDAO = messageDAO;
    }
...略

如果沒有個別設定上,例如 required=true 或者是 @Qualifier 等的需求,實際上只要在建構式(或方法上)標註一個就可以了:

...略
@Component
public class UserService {
    private final AccountDAO acctDAO;
    private final MessageDAO messageDAO;

    @Autowired 
    public UserService(AccountDAO acctDAO, MessageDAO messageDAO) {
        this.acctDAO = acctDAO;
        this.messageDAO = messageDAO;
    }
...略