Spring 通過單表 CURD 認識配置 IOC的“兩兄弟”(XML&註解)

Spring 通過單表 CURD 認識配置 IOC的“兩兄弟”(XML&註解)

開篇絮叨

前面一篇文章花大量內容,重點學習了 Spring入門 的一些思想,以及簡單的學習了 IOC基礎 以及基於基於 XML 的配置方式,大家應該清楚,XML與註解常常是形影不離的,他們就像一對雙胞胎,但兄弟兩個的想法都是一致的,那就是幫助開發者實現想要的功能,我們所說的IOC 技術,無疑是為了降低程序間的耦合,那麼,今天就來聊一聊,基於註解的IOC配置,當然為了大家有對比學習,兩種配置同時講解,同時我把例舉得儘量完整一些,就來完成一個對單表進行 CURD 的案例

(一) 準備代碼與環境

(1) 添加必要的依賴

  • spring-context
  • mysql-connector-java
  • c3p0(數據庫連接池)
  • commons-dbutils(簡化JDBC的工具)—後面會簡單介紹一下
  • junit (單元自測)

說明:由於我這裡創建的是一個Maven項目,所以在這裡修改 pom.xml 添加一些必要的依賴座標就可以

如果創建時沒有使用依賴的朋友,去下載我們所需要的 jar 包導入就可以了

<code><dependencies>        <dependency>            <groupid>org.springframework/<groupid>            <artifactid>spring-context/<artifactid>            <version>5.0.2.RELEASE/<version>        /<dependency>        <dependency>            <groupid>mysql/<groupid>            <artifactid>mysql-connector-java/<artifactid>            <version>5.1.6/<version>        /<dependency>        <dependency>            <groupid>c3p0/<groupid>            <artifactid>c3p0/<artifactid>            <version>0.9.1.2/<version>        /<dependency>        <dependency>            <groupid>commons-dbutils/<groupid>            <artifactid>commons-dbutils/<artifactid>            <version>1.4/<version>        /<dependency>        <dependency>            <groupid>junit/<groupid>            <artifactid>junit/<artifactid>            <version>4.12/<version>            <scope>test/<scope>        /<dependency>    /<dependencies>/<code>

簡單看一下,spring核心的一些依賴,以及數據庫相關的依賴等就都導入進來了

Spring 通過單表 CURD 認識配置 IOC的“兩兄弟”(XML&註解)

(2) 創建賬戶表以及實體

A:創建 Account 表

<code>-- ------------------------------ Table structure for account-- ----------------------------CREATE TABLE `account`  (  `id` int(11) NOT NULL AUTO_INCREMENT,  `name` varchar(32),  `balance` float,  PRIMARY KEY (`id`))/<code>

B:創建 Account 類

沒什麼好說的,對應著我們的表創出實體

<code>public class Account implements Serializable {    private  Integer id;    private String name;    private Float balance;    ......補充 get set toString 方法/<code>

(3) 創建Service以及Dao

A:AccountService 接口

<code>public interface AccountService {    void add(Account account);    void delete(Integer accpuntId);    void update(Account account);    List<account> findAll();    Account findById(Integer accountId);}/<account>/<code>

B:AccountServiceImpl 實現類

<code>public class AccountServiceImpl implements AccountService {    private AccountDao accountDao;    public void setAccountDao(AccountDao accountDao) {        this.accountDao = accountDao;    }    public void add(Account account) {        accountDao.addAccount(account);    }    public void delete(Integer accpuntId) {        accountDao.deleteAccount(accpuntId);    }    public void update(Account account) {        accountDao.updateAccount(account);    }    public List<account> findAll() {        return accountDao.findAllAccount();    }    public Account findById(Integer accountId) {        return accountDao.findAccountById(accountId);    }}/<account>/<code>

C:AccountDao 接口

<code>public interface AccountDao {    void addAccount(Account account);    void deleteAccount(Integer accountId);    void updateAccount(Account account);    List<account> findAllAccount();    Account findAccountById(Integer accountId);}/<account>/<code>

D:AccountDaoImpl 實現類

由於今天要完成的是一個增刪改查的操作,所以我們引入了 DBUtils 這樣一個操作數據庫的工具,它的作用就是封裝代碼,達到簡化 JDBC 操作的目的,由於以後整合 SSM 框架的時候,持久層的事情就可以交給 MyBatis 來做,而今天我們重點還是講解 Spring 中的知識,所以這部分會用就可以了,重點看 XML 與 註解 兩種配置方式

用到的內容基本講解:

QueryRunner 提供對 sql 語句進行操作的 API (insert delete update)

ResultSetHander 接口,定義了查詢後,如何封裝結果集(僅提供了我們用到的)

  • BeanHander:將結果集中第第一條記錄封裝到指定的 JavaBean 中
  • BeanListHandler:將結果集中的所有記錄封裝到指定的 JavaBean 中,並且將每一個 JavaBean封裝到 List 中去
<code>public class AccountDaoImpl implements AccountDao {        private QueryRunner runner;    public void setRunner(QueryRunner runner) {        this.runner = runner;    }    public void addAccount(Account account) {        try {            runner.update("insert into account(name,balance)values(?,?)", account.getName(), account.getBalance());        } catch (Exception e) {            throw new RuntimeException(e);        }    }    public void updateAccount(Account account) {        try {            runner.update("update account set name=?,balance=? where id=?", account.getName(), account.getBalance(), account.getId());        } catch (Exception e) {            throw new RuntimeException(e);        }    }    public void deleteAccount(Integer accountId) {        try {            runner.update("delete from account where id=?", accountId);        } catch (Exception e) {            throw new RuntimeException(e);        }    }    public List<account> findAllAccount() {        try {            return runner.query("select * from account", new BeanListHandler<account>(Account.class));        } catch (Exception e) {            throw new RuntimeException(e);        }    }    public Account findAccountById(Integer accountId) {        try {            return runner.query("select * from account where id = ? ", new BeanHandler<account>(Account.class), accountId);        } catch (Exception e) {            throw new RuntimeException(e);        }    }}/<account>/<account>/<account>/<code>

(二) XML 配置方式

在這裡有兩基本的方式,一是通過構造函數注入,另一種就是通過Set注入,實際上所做的就是,使用類的構造函數或者Set給成員變量進行賦值,但特別的是,這裡是通過配置,使用 Spring 框架進行注入首先就是頭部的依賴信息,順便提一句,當然我們可以去官網查找貼過來

Spring 通過單表 CURD 認識配置 IOC的“兩兄弟”(XML&註解)

先把針對上面功能的具體配置代碼貼出來

<code><beans>        <bean>        <property>    /<bean>        <bean>        <property>    /<bean>        <bean>                <constructor-arg>    /<bean>        <bean>        <property>        <property>        <property>        <property>    /<bean>/<beans>/<code>

分析一下:

配置 Bean 標籤的時候,我們見到了兩種形式 property、constructor-arg 也就是對應著 set 方式 與構造函形式,先說一下比較常見的 set 方式,用上面的代碼中距離:

(1) Set 方式

顧名思義,就是通過去找你給出對應的 Set 方法,然後對成員變量進行賦值,先看下類中的代碼

<code>public class AccountServiceImpl implements AccountService {//成員    private AccountDao accountDao;//Set方法    public void setAccountDao(AccountDao accountDao) {        this.accountDao = accountDao;    }     ...... 下面是增刪改查的方法/<code>

這是 bean.xml 中的配置

<code><bean>        <property>/<bean>/<code>

然後 property 配置的過程中,有一些屬性需要說一下

  • name:與成員變量名無關,與set方法後的名稱有關,例如 setAccountDao() 獲取到的就是accountDao,並且已經小寫了開頭
  • value:這裡可以寫基本數據類型和 String
  • ref:這裡可以引入另一個bean,幫助我們給其他類型賦值 (例如這裡就通過 ref 引入了下面 id 值為accountDao的 bean)

當然,以後可能會見到一種方式就是 使用 p名稱空間注入數據 (本質還是set)

頭部中需要修改引入這一句

xmlns:p="http://www.springframework.org/schema/p"

我直接拿以前文章中的一個例子:

<code><beans> <bean><bean>/<beans>/<code>

(2) 構造函數方式

下面就是使用構造函數的一種方式,這一種的前提就是:類中必須提供一個和參數列表相對應的構造函數

由於我們選擇的是 DBUtils 這樣一個工具,而它為我們提供了兩種構造函數,即帶參和無參,所以我們可以在其中注入數據源,也可以使得每一條語句都獨立事務

還有一點需要說明的就是:我們下面的數據源使用了 c3p0 這只是一種選擇方式,並不是一定的,是因為使用 DBUtils 的時候需要手動傳遞一個 Connection 對象

<code><bean>        <constructor-arg>/<bean>/<code>

來說一下所涉及到的標籤:

  • constructor-arg(放在 bean 標籤內) 再說一說其中的一些屬性值 給誰賦值: index:指定參數在構造函數參數列表的索引位置 type:指定參數在構造函數中的數據類型 name:指定參數在構造函數中的名稱(更常用) 賦什麼值: value:這裡可以寫基本數據類型和 String ref:這裡可以引入另一個bean,幫助我們給其他類型賦值

(3) 注入集合屬性

為了演示這些方式,我們在一個實體成員中將常見的一些集合都寫出來,然後補充其 set 方法

<code>private String[] strs;private List<string> list;private Set<string> set;private Map<string> map;private Properties props;/<string>/<string>/<string>/<code>

在配置中也是很簡單的,只需要按照下列格式寫標籤就可以了,由於增刪改查中並沒有涉及到集合的相關信息,這裡就是簡單提一下,可以自己測試一下

<code><bean><property><array>            <value>張三/<value>            <value>李四/<value>            <value>王五/<value>        /<array>    /<property>    <property>        <list>            <value>張三/<value>            <value>李四/<value>            <value>王五/<value>        /<list>    /<property>    <property>                    <value>張三/<value>            <value>李四/<value>            <value>王五/<value>            /<property>    <property>                    <entry>            <entry>            /<property>    <property>        <props>            <prop>張三/<prop>            <prop>21/<prop>        /<props>    /<property> /<bean>/<code>

(4) 測試代碼

<code>public class AccountServiceTest {    private ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");    private AccountService as = ac.getBean("accountService", AccountService.class);    @Test    public void testAdd(){        Account account = new Account();        account.setName("jack");        account.setBalance(1000f);        as.add(account);    }    @Test    public void testUpdate(){        Account account = as.findById(4);        account.setName("傑克");        account.setBalance(1500f);        as.update(account);    }    @Test    public void testFindAll(){        List<account> list = as.findAll();        for(Account account : list) {            System.out.println(account);        }    }    @Test    public void testDelete(){        as.delete(4);    }}/<account>/<code>

(5) 執行效果

添加,修改(包含了查詢指定id),刪除

Spring 通過單表 CURD 認識配置 IOC的“兩兄弟”(XML&註解)

Spring 通過單表 CURD 認識配置 IOC的“兩兄弟”(XML&註解)

Spring 通過單表 CURD 認識配置 IOC的“兩兄弟”(XML&註解)

查詢所有

Spring 通過單表 CURD 認識配置 IOC的“兩兄弟”(XML&註解)

(三) 註解配置方式

首先,我們先將上面的例子使用註解來實現一下,再來具體的講解:

(1) 改造原程序為註解配置

首先需要為 Dao 和 Service 的實現類中 添加註解

<code>@Service("accountService")public class AccountServiceImpl implements AccountService {    @Autowired    private AccountDao accountDao;    下面的原封不動}/<code> 
<code>@Repository("accountDao")public class AccountDaoImpl implements AccountDao {    @Autowired    private QueryRunner runner;       下面的原封不動}/<code>
<code><beans>    <component-scan>        <bean>                <constructor-arg>    /<bean>        <bean>        <property>        <property>        <property>        <property>    /<bean>/<beans>/<code>

到這裡,一個最基本的註解改造就完成了,大家可以用前面的測試類進行一下測試

下面我們正式說一下註解配置相關的知識

(2) 常用註解

A:創建對象

@Component

  • 讓Spring 來管理資源,相當於XML 中配置一個 bean <bean>
  • 可以在括號內指定 value 值,即指定 bean 的 id ,如果不指定就會默認的使用當前類的類名
  • 如果註解中只有一個value屬性要賦值,value可以不寫,直接寫名稱,如上面例子中

@Controller @Service @Repository

對於創建對象的註解,Spring 還提供了三種更加明確的說法,作用是完全相同的,但是針對不同的場景起了不同的叫法罷了

  • @Controller:一般用於表現層
  • @Service:一般用於業務層
  • @Repository:一般用於持久層

B:注入數據

@Autowired

  • 自動按類型注入,相當於XML 中配置一個 bean <property> 或者 <property>/<property>
  • 容器中有一個唯一的 bean 對象類型和注入的變量類型一致,則注入成功 @Autowired private AccountDao accountDao; @Repository("accountDao") public class AccountDaoImpl implements AccountDao {......} 複製代碼 比如上面的例子,Spring的IOC中容器是一個Map的結構,字符串“accountDao” 以及這個可以認為是 AccountDao 類型的 AccountDaoImpl 類就被以鍵值對的形式存起來,被註解 @Autowired的地方,會直接去容器的 value 部分去找 AccountDao 這個類型的類 當 IoC 中匹配到了多個符合的,就會根據變量名去找,找不到則報錯:例如下面,根據 AccountDao類型匹配到了兩個類,所以根據變量名去找找到了 AccountDaoImplA 這個類 @Autowired private AccountDao accountDaoA; @Repository("accountDaoA") public class AccountDaoImplA implements AccountDao {......} @Repository("accountDaoB") public class AccountDaoImplB implements AccountDao {......} 複製代碼
  • 可以對類的成員變量、方法以及構造函數進行標註,完成自動裝配
  • 使用此註解可以省略 set 方法

@Qualifier

  • 在自動按類型注入的基礎之上,按照 Bean 的 id 注入,給字段注入的時候不能夠單獨使用,需要配合上面的 @Autiwire 使用,但是給方法參數注入的時候,可以獨立使用
  • 使用時:value 值指定 bean 的 id

它有時候必須配合別的註解使用,有沒有一個標籤可以解決這個問題呢?答案就是 @Resource

@Resource

  • 直接按照 bean 的 id 注入,不過只能注入其他 bean 類型
  • 使用時:name 值指定 bean 的 id

前面三個都是用來注入其他的 bean 類型的數據,下面來說一說,基本類型以及String的實現

(特別說明:集合類型的注入只能通過 XML 來實現)

@Value

  • 這個註解就是用來注入基本數據類型和 String 類型數據的
  • 使用時:value 屬性用於指定值

C:改變作用範圍

@Scope

  • 指定 bean 的作用範圍 相當於XML 中配置一個 <bean>
  • 使用時:value 屬性用於指定範圍的值(singleton prototype request session globalsession)
  • /<bean>

D:生命週期相關

相當於:<bean>

@PostConstruct

  • 指定初始化方法

@PreDestroy

  • 指定銷燬方法

(四) XML和註解 的對比與選擇

(1) 優缺點

一般來說,我們兩種配置方式都是有人使用的,不過我個人更習慣使用註解的方式

  • XML 類之間的松耦合關係,擴展性強,利於更換修改 對象之間的關係清晰明瞭
  • 註解: 簡化配置,並且使用起來也容易,效率會高一些 在類中就能找對配置,清晰明瞭 類型安全

(2) 兩者對比

XML配置 註解配置 創建對象 <bean> @Controller @Service @Repository@Component 指定名稱 通過 id 或者 name 值指定 @Controller("指定的名稱") 注入數據 <property> @Autowired @Qualifier @Resource @Value 作用範圍 <bean> @Scope 生命週期 <bean> @PostConstruct @PreDestroy/<bean>/<property>/<bean>

(五) 補充新註解

為什麼要補充新註解呢? 在我們使用註解時,在書寫代碼時,簡化了很多,但是我們在 bean.xml 文件中 仍然需要 開啟掃描、 進行配置QueryRunner 以及 數據源,如何徹底擺脫 xml 配置全面使用註解呢?

這也就是我們將要補充的幾個新註解,作用就是讓我們全面使用註解進行開發

(1) 註解講解

A: 配置類註解

@Configuration

  • 指定當前類是 spring 的一個配置類,相當於 XML中的 bean.xml 文件
  • 獲取容器時需要使用下列形式
  • private ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class); 複製代碼

依舊使用上方的 CURD 的案例代碼進行修改,首先與cn同級創建了一個名為 config 的包,然後編寫一個名為 SpringConfiguration 的類,當然實際上這兩個名字無所謂的,添加註解

<code>@Configurationpublic class SpringConfiguration {}/<code>

B: 指定掃描包註解

@ComponentScan

@Configuration 相當於已經幫我們把 bean.xml 文件創立好了,按照我們往常的步驟,應該指定掃描的包了,這也就是我們這個註解的作用

  • 指定 spring 在初始化容器時要掃描的包,在 XML 中相當於:
  • <component-scan> 複製代碼
  • 其中 basePackages 用於指定掃描的包,和這個註解中value屬性的作用是一致的

具體使用:

<code>@Configuration@ComponentScan("cn.ideal")public class SpringConfiguration {}/<code>

C: 創建對象

@Bean

寫好了配置類,以及指定了掃描的包,下面該做的就是配置 QueryRunner 以及數據源了,在 XML 中我們會通過書寫 bean 標籤來配置,而 Spring 為我們提供了 @Bean 這個註解來替代原來的標籤

  • 將註解寫在方法上(只能是方法),也就是代表用這個方法創建一個對象,然後放到 Spring 的容器中去
  • 通過 name 屬性 給這個方法指定名稱,也就是我們 XML 中 bean 的 id

具體使用:

<code>package config;import com.mchange.v2.c3p0.ComboPooledDataSource;import org.apache.commons.dbutils.QueryRunner;import org.springframework.beans.factory.annotation.Value;import org.springframework.context.annotation.Bean;import javax.sql.DataSource;public class JdbcConfig {/**     * 創建一個 QueryRunner對象     * @param dataSource     * @return     */    @Bean(name = "runner")    public QueryRunner creatQueryRunner(DataSource dataSource){        return  new QueryRunner(dataSource);    }    /**     * 創建數據源,並且存入spring     * @return     */    @Bean(name = "dataSource")    public DataSource createDataSource() {        try {            ComboPooledDataSource ds = new ComboPooledDataSource();            ds.setUser("root");            ds.setPassword("1234");            ds.setDriverClass("com.mysql.jdbc.Driver");            ds.setJdbcUrl("jdbc:mysql:///spring_day02");            return ds;        } catch (Exception e) {            throw new RuntimeException(e);        }    }}/<code>

D: 配置 properties 文件

@PropertySource

上面在創建數據源的時候,都是直接把配置信息寫死了,如果想要使用 properties 進行內容的配置,在這時候就需要,使用 @PropertySource 這個註解

  • 用於加載 .properties 文件中的配置
  • value [] 指定 properties 文件位置,在類路徑下,就需要加上 classpath
<code>@Configuration@ComponentScan("cn.ideal")@PropertySource("classpath:jdbcConfig.properties")public class SpringConfiguration {}/<code>
<code>public class JdbcConfig {    @Value("${jdbc.driver}")    private String driver;    @Value("${jdbc.url}")    private String url;    @Value("${jdbc.username}")    private String username;    @Value("${jdbc.password}")    private String password;    /**     * 創建一個 QueryRunner對象     * @param dataSource     * @return     */    @Bean(name = "runner")    public QueryRunner creatQueryRunner(DataSource dataSource){        return  new QueryRunner(dataSource);    }    /**     * 創建數據源,並且存入spring     * @return     */    @Bean(name = "dataSource")    public DataSource createDataSource() {        try {            ComboPooledDataSource ds = new ComboPooledDataSource();            ds.setUser(username);            ds.setPassword(password);            ds.setDriverClass(driver);            ds.setJdbcUrl(url);            return ds;        } catch (Exception e) {            throw new RuntimeException(e);        }    }複製代碼/<code>

E:導入其他配置類

@Import

這樣看來一個 JdbcConfig 就基本寫好了,我們在其中配置了 QueryRunner 對象,以及數據源,這個時候,實際上我們原先的 bean.xml 就可以刪掉了,但是我們雖然寫好了 JdbcConfig 但是如何將兩個配置文件聯繫起來呢?這也就是這個註解的作用

<code>@Configuration@ComponentScan("cn.ideal")@Import(JdbcConfig.class)@PropertySource("classpath:jdbcConfig.properties")public class SpringConfiguration {}/<code>

(2) 註解獲取容器

修改獲取容器的方式後,就可以進行測試了

<code>private ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class);private AccountService as = ac.getBean("accountService", AccountService.class);/<code>

(五) Spring 單元測試改進

由於我們需要通過上面測試中兩行代獲取到容器,為了不每一次都寫這兩行代碼,所以我們在前面將其定義在了成員位置,但是有沒有辦法可以省掉這個步驟呢?

也就是說,我們想要程序自動創建容器,但是原來的 junit 很顯然是實現不了的,因為它並不會知道我們是否使用了 spring ,不過 junit 提供了一個註解讓我們替換它的運行器,轉而由 spring 提供

首先需要導入 jar 包 或者說導入依賴座標

<code><dependency><groupid>org.springframework/<groupid><artifactid>spring-test/<artifactid><version>5.0.2.RELEASE/<version>/<dependency>/<code>

使用 @RunWith 註解替換原有運行器 然後使用 @ContextConfiguration 指定 spring 配置文件的位置,然後使用 @Autowired 給變量注入數據

<code>@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration(classes = SpringConfiguration.class)public class AccountServiceTest {    @Autowired    private AccountService as;    }/<code>

(六) 總結

到這裡,這篇文章就結束了,這篇文章主要講解的內容接著我們上一篇文章講解,上一篇更多的是從傳統循序漸進的提到 Spring,更多的是幫助大家理解為什麼用Spring,而這篇文章是基於上一篇的基礎,更多的提到的是如何去用 Spring,所以看起來更像是一個工具書一樣的文章,並沒有提到很多的思想原理,而是幫助大家快速的上手用 XML 以及註解的方式,當然大神自然很多,不敢說什麼有技術,但總歸是希望能給不太熟悉 spring 的朋友一些幫助,或者臨時當做一篇工具文來查找,再次感謝大家的訪問與贊,謝謝朋友們的支持!再次感謝!

鏈接:https://juejin.im/post/5e537b2851882549431febe2


分享到:


相關文章: