Spring進階案例之註解和IoC案例

李一二發表於2020-07-30

Spring進階案例之註解和IoC案例

一、常見的註解分類及其作用

從此前的基於xml的IoC開發案例和依賴注入案例中,我們可以將xml配置歸納為:

<bean id="" class="" scope="" init-method = "" destroy-method = "">
  <property name = "" value = "" | ref = ""></property>
</bean>

註解按照作用可以分為四類:

1.用於建立物件的註解:相當於xml配置的bean標籤

建立物件的註解有如下幾個:

註解 作用 屬性
@Component 把當前類物件存入Spring容器中 value: 用於指定bean的id。當不寫value屬性時,預設值是當前類名,且首字母改小寫
@Controller 一般用在表現層建立bean 同上
@Service 一般用在業務層建立bean 同上
@Repository 一般用在持久層建立bean 同上

最後三個註解的作用和屬性與Component註解一模一樣,他們是Spring框架為我們提供明確三層架構的註解,可以使三層架構更加清晰。

如果我們在AccoutServiceImpl類上加上@Component("accountService")或者@Service("accountService"),都可以起到將AccountServiceImpl類的物件加入的IoC容器中的效果。此時,ui.Client類中的main方法還不能執行:

public static void main(String[] args) {
    //驗證依賴注入
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
    IAccountService accountService = (IAccountService) applicationContext.getBean("accountService");
    System.out.println(accountService);
}

會顯示No bean named 'accountService' ,這是因為我還沒有對Spring要掃描的包進行配置,如果不配置,Spring是不知道哪些類需要掃描註解。

<!-- 配置Spring在建立容器時要掃描的包,在一個名為context名稱空間的約束中進行配置-->
<context:component-scan base-package="service"></context:component-scan>
<context:component-scan base-package="dao"></context:component-scan>

此時,專案結構為:

dao包:
public interface IAccountDao
dao.impl包:
@Repository("accountDao")
public class AccountDaoImpl implements IAccountDao

service包:
public interface IAccountService
service.impl包:
@Service("accountService")
public class AccountServiceImpl implements IAccountService

ui包:
public class Client
2.用於注入物件的註解:相當於xml配置的bean標籤中property標籤,使用註解進行注入時,不再需要set方法

如果我們需要呼叫accountService中的saveAccount方法,就需要對AccountServiceImpl類中的accountDao成員變數進行注入。同時需要在AccountDaoImpl類的accountDao變數上加上@Autowired("accountDao")註解。用於注入資料的註解有如下幾個:前三個註解只能注入其他bean型別的注入,基本型別和String型別的注入無法通過它們來實現。集合型別的注入只能通過xml配置來實現,不能通過註解來實現。

註解 作用 屬性
@Autowired 自動按照型別注入,可以出現在變數上,也可以出現在在方法上。
@Qualifier 按照類中注入的基礎之上再按照名稱注入。在給類成員注入時不能單獨注入,在給方法引數注入時可以單獨注入 value:用於指定注入bean的id
@Resource 直接按照bean的id注入,可以單獨使用 name:用於指定注入bean的id
@Value 用於注入String型別和基本型別 value:用於指定注入資料的值,可以使用Spring中的el表示式(SpEL,寫法為:${表示式})

這個時候,我們在ui.Client類的main方法中,就可以執行saveAccount方法了。

public static void main(String[] args) {
    //驗證依賴注入
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
    IAccountService accountService = (IAccountService) applicationContext.getBean("accountService");
    System.out.println(accountService);
    IAccountDao accountDao = applicationContext.getBean("accountDao", IAccountDao.class);
    System.out.println(accountDao);
    //呼叫saveAccount方法
    accountService.saveAccounts();
}

在使用@Autowired註解時,需要注意的是:

  1. 只要IoC容器中有唯一的一個bean物件和要注入的變數型別匹配,就可以注入成功。
  2. 如果IoC容器中任何bean物件和要注入的變數型別都不匹配,就會報錯。
  3. 如果IoC容器中有多個bean物件和要注入的變數型別匹配,則按變數名稱和bean類名進行匹配,若有唯一一個匹配,則注入成功,否則注入失敗。

例如,在dao.impl包下,有兩個IAccountDao介面的實現類,分別是是AccountDaoImpl1和AccountDaoImpl2,在這兩個類上分別加入註解@Repository("accountDao1")和@Repository("accountDao2")。

此時,專案結構為:

dao包:
public interface IAccountDao
dao.impl包:
@Repository("accountDao1")
public class AccountDaoImpl1 implements IAccountDao
@Repository("accountDao2")
public class AccountDaoImpl2 implements IAccountDao

service包:
public interface IAccountService
service.impl包:
@Service("accountService")
public class AccountServiceImpl implements IAccountService

ui包:
public class Client

如果還使用Autowired註解對AccountServiceImpl類中的accountDao變數進行注入,就會報錯。這個時候,有三種方式可以選擇:

  • 只使用@Autowired註解,並修改accountDao變數名為accountDao1,此時注入的是dao.impl.AccountDaoImpl1

  • 同時使用@Autowired和@Qualifier("accountDao2")註解,變數名可以任意,注入的是dao.impl.AccountDaoImpl2

  • 只使用@Resource(name = "accountDao1")註解,變數名可以任意,注入的是dao.impl.AccountDaoImpl1

    //方法一
    @Autowired
    private IAccountDao accountDao1;
    
    //方法二
    @Autowired
    @Qualifier("accountDao2")
    private IAccountDao accountDao22;
    
    //方法三
    @Resource(name = "accountDao1")
    private IAccountDao accountDao;
    

為了看的更清楚,我們進行如下改造:

//AccountDaoImpl1和AccountDaoImpl2中的saveAccounts方法:
public void saveAccounts() {
    System.out.println(this.getClass());
    System.out.println("向資料庫寫入賬戶資料!!!");
}
//AccountServiceImpl中的saveAccounts方法:
public void saveAccounts() {
  	System.out.println("執行儲存賬戶操作");
    //呼叫持久層介面函式
    accountDao.saveAccounts();
    accountDao1.saveAccounts();
    accountDao22.saveAccounts();
}
//Client類中的main方法:
public static void main(String[] args) {
    //驗證依賴注入
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
    IAccountService accountService = (IAccountService) applicationContext.getBean("accountService");
    //呼叫saveAccount方法
    accountService.saveAccounts();
}

輸出為:

執行儲存賬戶操作
class dao.impl.AccountDaoImpl1
向資料庫寫入賬戶資料!!!
class dao.impl.AccountDaoImpl1
向資料庫寫入賬戶資料!!!
class dao.impl.AccountDaoImpl2
向資料庫寫入賬戶資料!!!
3.用於改變物件作用範圍的註解:相當於xml配置的bean標籤中的scope屬性

如果我們要改變bean的作用範圍,就需要用到scope屬性:

註解 作用 屬性
@Scope 用於指定bean的作用範圍 value: 指定範圍的取值。常用取值:singleton(單例,也是預設值)、prototype(多例)、

例如,我們在AccountServiceImpl2類上加上註解@Scope("prototype"),然後在main方法中執行:

IAccountDao accountDao11 = applicationContext.getBean("accountDao1", IAccountDao.class);
System.out.println(accountDao11);
IAccountDao accountDao12 = applicationContext.getBean("accountDao1", IAccountDao.class);
System.out.println(accountDao12);
IAccountDao accountDao21 = applicationContext.getBean("accountDao2", IAccountDao.class);
System.out.println(accountDao21);
IAccountDao accountDao22 = applicationContext.getBean("accountDao2", IAccountDao.class);
System.out.println(accountDao22);

可以看到輸出中,前兩個accountDao是同一個物件,後兩個是不同物件:

驗證@Scope註解

4.用於改變物件生命週期的註解:相當於xml配置的bean標籤中的init-method屬性和destroy-method屬性
註解 作用 使用位置
@PostConstruct 用於指定初始化方法 用在方法上
@PreDestroy 用於指定銷燬方法 用在方法上

注意:多例物件的銷燬仍然由JVM執行,無法通過關閉容器來銷燬

二、基於XML的IoC案例
1.準備工作

這個IoC案例,主要演示對資料庫的crud操作,所以首先需要建立資料庫,sql檔案如下:

create table account(
	id int primary key auto_increment,
	name varchar(40),
	money float
)character set utf8 collate utf8_general_ci;

insert into account(name,money) values('aaa',1000);
insert into account(name,money) values('bbb',1000);
insert into account(name,money) values('ccc',1000);

整個專案的結構如下:

基於xml的IoC案例的專案結構

IAccountDao和IAccountService這兩個介面類中的方法如下,因為只是演示,簡單起見這兩個介面中的方法一模一樣:

    /** 查詢所有 */
    List<Account> findAllAccounts();

    /** 根據id查詢賬戶 */
    Account findAccountById(Integer id);

    /** 儲存賬戶 */
    void saveAccount(Account account);

    /** 修改賬戶*/
    void updateAccount(Account account);

    /** 根據id刪除賬戶 */
    void deleteAccountById(Integer id);

我們還需要一個實體類用於封裝查詢結果,簡單起見,省略了get和set方法,以及toString方法:

public class Account implements Serializable {
    private Integer id;
    private String name;
    private float money;
  //還有set方法和get方法,以及toString方法沒有顯示
}
2.編寫介面實現類
public class AccountDaoImpl implements IAccountDao {

    private QueryRunner queryRunner;

  	//由於需要通過配置檔案對QueryRunner進行注入,所以需要提供set方法
    public void setQueryRunner(QueryRunner queryRunner) {
        this.queryRunner = queryRunner;
    }

    @Override
    public List<Account> findAllAccounts() {
        try {
            return queryRunner.query("select * from account",
                    new BeanListHandler<>(Account.class));
        }catch (Exception e){
            throw new RuntimeException(e);
        }
    }

    @Override
    public Account findAccountById(Integer id) {
        try {
            return queryRunner.query("select * from account where id = ?",
                    new BeanHandler<>(Account.class), id);
        }catch (Exception e){
            throw new RuntimeException(e);
        }
    }

    @Override
    public void saveAccount(Account account) {
        try {
            queryRunner.update("insert into account(name, money) values (?,?)",
                    account.getName(), account.getMoney());
        }catch (Exception e){
            throw new RuntimeException(e);
        }
    }

    @Override
    public void updateAccount(Account account) {
        try {
            queryRunner.update("update account set name=?, money=? where id = ?",
                    account.getName(), account.getMoney(), account.getId());
        }catch (Exception e){
            throw new RuntimeException(e);
        }
    }

    @Override
    public void deleteAccountById(Integer id) {
        try {
            queryRunner.update("delete from account s where id = ?", id);
        }catch (Exception e){
            throw new RuntimeException(e);
        }
    }
}

public class AccountServiceImpl implements IAccountService {
    
    private IAccountDao accountDao;
		//set方法用於注入
    public void setAccountDao(IAccountDao accountDao) {
        this.accountDao = accountDao;
    }

    @Override
    public List<Account> findAllAccounts() {
        return accountDao.findAllAccounts();
    }

    @Override
    public Account findAccountById(Integer id) {
        return accountDao.findAccountById(id);
    }

    @Override
    public void saveAccount(Account account) {
        accountDao.saveAccount(account);
    }

    @Override
    public void updateAccount(Account account) {
        accountDao.updateAccount(account);
    }

    @Override
    public void deleteAccountById(Integer id) {
        accountDao.deleteAccountById(id);
    }
}
3.編寫配置檔案

前面的部分都不用太關心,只需要瞭解即可。QueryRunner是java程式設計中的資料庫操作實用工具類DBUtils中的一個核心類,可以用於連線資料庫執行SQL語句。beans.xml檔案是Spring的配置檔案,重點應該放在配置檔案上。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!-- 配置service -->
    <bean id = "accountService" class="com.crud.service.impl.AccountServiceImpl">
        <!-- 注入dao -->
        <property name="accountDao" ref="accountDao"></property>
    </bean>

    <!-- 配置dao -->
    <bean id = "accountDao" class="com.crud.dao.impl.AccountDaoImpl">
        <!-- 注入queryRunner -->
        <property name="queryRunner" ref="queryRunner"></property>
    </bean>

    <!-- 配置queryRunner,必須是多例物件,否則會出現執行緒安全問題 -->
    <bean id = "queryRunner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
        <!-- 注入資料來源 -->
        <constructor-arg name="ds" ref="dataSource"></constructor-arg>
    </bean>

    <!-- 配置資料來源 -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <!-- 配置連線資料庫的必備資訊 -->
        <property name="driverClass" value="com.mysql.cj.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring"></property>
        <property name="user" value="root"></property>
        <property name="password" value="12345678"></property>
    </bean>
</beans>

配置檔案中需要注意的是,因為每一個連線都會用到QueryRunner物件,所以必須是多例模式,否則就會出現執行緒安全問題。此外,在配置資料來源時,property中的value可以從其他檔案中讀取。該配置檔案,基本上綜合了之前講的用xml進行依賴注入。

例如:

首先在src/main/resources目錄下編寫:jdbc.properties檔案

jdbc.driver = com.mysql.cj.jdbc.Driver
jdbc.url = jdbc:mysql://localhost:3306/spring
jdbc.user = root
jdbc.password = 12345678

然後對beans.xml檔案進行修改:

<context:property-placeholder location="classpath:jdbc.properties"/>

<!-- 配置資料來源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
    <!-- 配置連線資料庫的必備資訊 -->
    <property name="driverClass" value="${jdbc.driver}"></property>
    <property name="jdbcUrl" value="${jdbc.url}"></property>
    <property name="user" value="${jdbc.user}"></property>
    <property name="password" value="${jdbc.password}"></property>
</bean>

對於maven的配置檔案pom.xml,主要需要匯入的包有:Spring、dbutils(用於執行sql語句)、mysql、c3p0(用於資料來源)、junit(用於單元測試)

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.2.5.RELEASE</version>
    </dependency>

    <dependency>
        <groupId>commons-dbutils</groupId>
        <artifactId>commons-dbutils</artifactId>
        <version>1.7</version>
    </dependency>

    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.20</version>
    </dependency>

    <dependency>
        <groupId>c3p0</groupId>
        <artifactId>c3p0</artifactId>
        <version>0.9.1.2</version>
    </dependency>

    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.13</version>
        <scope>test</scope>
    </dependency>
</dependencies>
3.單元測試

編寫AccountServiceTest測試類用於測試,只測試能否正常執行crud操作,這裡只演示了測試查詢所有方法:

public class AccountServiceTest {

    @Test
    public void testFinaAll() {
        //1.獲取容器
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
        //2.從容器中獲取業務層物件
        IAccountService accountService = applicationContext.getBean("accountService", IAccountService.class);
        //3.執行方法
        List<Account> accounts = accountService.findAllAccounts();
        for (Account account : accounts) {
            System.out.println(account);
        }
    }
}    
三、基於註解和xml的IoC案例
1.修改AccountServiceImpl和AccountDaoImpl類

在上述案例中,採用的xml進行依賴注入。本案例中,使用註解的方式進行依賴注入,所以不再需要main方法。這兩個類僅進行如下修改,其餘部分不變:

@Repository("accountDao")
public class AccountDaoImpl implements IAccountDao {

    @Autowired
    private QueryRunner queryRunner;
}

@Service("accountService")
public class AccountServiceImpl implements IAccountService {

    @Autowired
    private IAccountDao accountDao;
}
2.修改beans.xml配置檔案

和之前一樣,要告知Spring要掃描的包,所以約束空間要加入context名稱空間,此外之前service和dao的xml配置就不再需要了,但是和連線資料庫相關的QueryRunner的配置仍然需要。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 配置Spring在建立容器時要掃描的包,在一個名為context名稱空間的約束中進行配置-->
    <context:component-scan base-package="com.crud"></context:component-scan>

    <!-- 配置queryRunner,必須是多例物件,否則會出現執行緒安全問題 -->
    <bean id = "queryRunner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
        <!-- 注入資料來源 -->
        <constructor-arg name="ds" ref="dataSource"></constructor-arg>
    </bean>

    <!-- 配置資料來源 -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <!-- 配置連線資料庫的必備資訊 -->
        <property name="driverClass" value="com.mysql.cj.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring"></property>
        <property name="user" value="root"></property>
        <property name="password" value="12345678"></property>
    </bean>
</beans>
3.測試執行

由於多個單元測試都需要用到accountService物件,所以這裡抽取出來作類物件,這樣就只需要初始化一次。

public class AccountServiceTest {

  	private ApplicationContext applicationContext;
    private IAccountService accountService;

    //手動獲取accountService物件
    @Before
    public void init(){
        //1.獲取容器
        applicationContext = new ClassPathXmlApplicationContext("beans.xml");
        //2.從容器中獲取業務層物件
        accountService = applicationContext.getBean("accountService", IAccountService.class);
    }

    @Test
    public void testFinaAll() {
       //3.執行方法
        List<Account> accounts = accountService.findAllAccounts();
        for (Account account : accounts) {
            System.out.println(account);
        }
    }
}
四、基於純註解的IoC案例
1.配置類

如果不寫xml配置檔案,我們如何去指定Spring的相關配置呢?這個時候就需要一個配置類來代替xml配置檔案。在src目錄下,新建com.crud.conf包,該包下新建配置類SpringConfig。依照前面註解結合xml配置檔案的案例。我們需要解決的問題有:

1.告知Spring容器要掃描的包,對應<context:component-scan>標籤
2.配置queryRunner
3.配置資料來源
2.Spring新註解
註解 作用 屬性
@Configuration 指定當前類是一個配置類。
@Import 用於匯入其他的配置類 value:用於指定其他配置類的位元組碼
@ComponentScan/@ComponentScans 指定Spring在建立容器時要掃描的包 value/basePackages:用於指定包
@Bean 把當前方法的返回值作為Bean物件存入IoC容器 name:用於指定bean的id,不寫是預設值就是當前方法的名稱
@PropertySource 用於指定要使用的properties檔案的位置 value:指定檔案的名稱和路徑,classpath關鍵字表示類路徑
3.新註解的使用

整個專案的結構如下:

純註解的IoC案例專案結構

接下來我們一個一個詳細理解這些註解的作用:

  1. @Configuration註解用於指定配置類,一般所有的配置類都需要加上這個註解。但是也有例外:如果不寫@Configuration註解,那麼會將Spring的IoC容器會將AnnotationConfigApplicationContext構造方法中的引數作為配置類;因此,作為引數傳入的配置類的@Configuration註解可用不寫。
  2. @Import註解用於匯入其他配置類。在實際開發過程中,可能不止一個配置類。例如和Spring相關的配置作為一個住配置類,和連線資料庫相關的配置類作為一個子類。當使用@Import註解之後,使用@Import註解的配置類就是主配置類,匯入的配置類就是子配置類。此時,子類就不需要再寫@Configuration註解。
  3. @ComponentScan/@ComponentScans註解用於指定Spring在建立容器時要掃描的包,前者用於指定一個包,後者用於指定多個包。這就相當於xml配置檔案中的<context:component-scan>中的basePackages屬性。
  4. @Bean註解用於把當前方法的返回值作為Bean物件存入IoC容器。當方法上有@Bean註解時,Spring會在容器中查詢有沒有和引數對應的可用bean物件,查詢方式和@Autowired註解的方式相同。這就相當於xml配置檔案中的Bean標籤。
  5. @PropertySource註解用於指定要使用的properties檔案的位置。

最終這些註解在程式碼中的使用如下:

@Configuration
@ComponentScan("com.crud")
@Import(JdbcConfig.class)
/** 主配置類 */
public class SpringConfig { }

/** jdbc配置類 */
@PropertySource("classpath:jdbcConfig.properties")
public class JdbcConfig {

    @Value("${jdbc.driver}")
    private String driver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.user}")
    private String user;
    @Value("${jdbc.password}")
    private String password;

    /**  根據資料來源建立QueryRunner物件,必須是多例的,否則會出現執行緒安全問題 */
    @Bean("queryRunner")
    @Scope("prototype")
    public QueryRunner createQueryRunner(DataSource dataSource){
        return new QueryRunner(dataSource);
    }

    /** 建立資料來源物件 */
    @Bean("dataSource")
    public DataSource createDataSource(){
        try{
            ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
            comboPooledDataSource.setDriverClass(driver);
            comboPooledDataSource.setJdbcUrl(url);
            comboPooledDataSource.setUser(user);
            comboPooledDataSource.setPassword(password);
            return comboPooledDataSource;
        }catch (Exception e){
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }
}
4.測試

有一點需要修改,就是在獲取容器時,不能再使用ClassPathXmlApplicationContext類,而應該使用AnnotationConfigApplicationContext,如下:

//引數是類位元組碼,可以傳入多個類的位元組碼,作為引數的配置類可以不寫@Configuration註解
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
5.@Bean註解的查詢方式

@Bean建立Bean物件的方式後的查詢方法和@Autowired註解相同,如果有多個名稱匹配,就需要用到@Qualifier註解來指定用哪個匹配。例如,jdbcConfig類可以修改為如下:

/**
 * 根據資料來源建立QueryRunner物件,必須是多例的,否則會出現執行緒安全問題
 * @Qualifier註解用於解決多個型別匹配時的問題
 * @param dataSource
 * @return
 */
@Bean("queryRunner")
@Scope("prototype")
public QueryRunner createQueryRunner(@Qualifier("ds2") DataSource dataSource){
    return new QueryRunner(dataSource);
}

/**
 * 建立資料來源物件
 * @return
 */
@Bean("ds1")
public DataSource createDataSource(){
    try{
        ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
        comboPooledDataSource.setDriverClass(driver);
        comboPooledDataSource.setJdbcUrl(url);
        comboPooledDataSource.setUser(user);
        comboPooledDataSource.setPassword(password);
        return comboPooledDataSource;
    }catch (Exception e){
        e.printStackTrace();
        throw new RuntimeException(e);
    }
}

@Bean("ds2")
public DataSource createDataSource1(){
    try{
        ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
        comboPooledDataSource.setDriverClass(driver);
        comboPooledDataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");
        comboPooledDataSource.setUser(user);
        comboPooledDataSource.setPassword(password);
        return comboPooledDataSource;
    }catch (Exception e){
        e.printStackTrace();
        throw new RuntimeException(e);
    }
}
五、Spring整合Junit

在測試類中,獲取Spring的IoC容器和AccountService物件時,我們一直都是手動獲取。有沒有辦法讓Spring為我們自動注入呢?當然是有的,但是如果直接沒有在accountService成員變數之上,加上@Autowired註解,執行就會報錯。這是因為:

1.Junit單元測試中,沒有main方法也能執行。因為Junit整合了一個main方法,該方法會判斷當前測試類中哪些方法有@Test註解,Junit就會執行有@Test註解的方法。
2.Junit不會管使用者使用何種框架,因此在執行測試方法是,junit不知道使用者是否使用Spring框架,所以也就不會讀取配置檔案/配置來建立Spring的IoC核心容器。因此在測試類中,就算寫了@Autowired註解,也無法實現注入。

因此要在Junit中使用Spring,就要遵循一下步驟:

1.匯入Spring整合junit的配置

在pom.xml配置檔案中<dependencies>的匯入:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>5.2.5.RELEASE</version>
</dependency>
2.使用Junit提供的@RunWith註解,將原有的main方法替換為Spring提供的main方法

在AccountServiceTest類之上加上@RunWith(SpringJUnit4ClassRunner.class)註解

3.告知Spring的執行器,是基於xml還是註解進行配置,並說明位置

在AccountServiceTest類之上加上@ContextConfiguration(classes = SpringConfig.class)註解。對於@ContextConfiguration註解,屬性locations用於指定xml配置檔案的位置,要加上classpath關鍵字表示在類路徑下,屬性classes:指定配置類所在的位置。

4.注意事項

當Spring的版本是5.x時,junit的版本必須是4.12及以上版本。最終整個測試類程式碼如下:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class AccountServiceTest {

    @Autowired
    private IAccountService accountService;
    
    @Test
    public void testFinaAll() {
       //3.執行方法
        List<Account> accounts = accountService.findAllAccounts();
        for (Account account : accounts) {
            System.out.println(account);
        }
    }
}

相關文章