spring 框架學習-2

spring 框架學習

spring 框架學習安排

1. 第二天(學習前最好能複習一下註解)

1. spring中IOC的常用註解

曾經的xml配置

<bean id="accountService" class="com.liuzeyu.service.impl.IAccountServiceImpl
init-method="" destory-method="" scope=""/>
<property name=""  value="" | ref="">
</property>

</bean>

上述配置可以用於創建對象,用於注入數據,用於改變作用範圍和生命週期
換做註解實現:

  1. 用於創建對象

    1. dao層實現類:
      @Component("accountDao")
      public class AccountDaoImpl implements AccountDao{
      
          public void save() {
              System.out.println("保存了數據庫");
          }
      }
      
    2. service實現類:
      @Component("accountService")
      public class IAccountServiceImpl implements IAccountService{
      
          public void save() {
              ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
              AccountDao adao = (AccountDao) ac.getBean("accountDao");
              adao.save();
          }
      }
      
    3. 測試函數
      public class UITest {
          public static void main(String[] args) {
              //1.獲取核心容器對象
              ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
              //2.根據id獲取bean對象
              //2.1方式1獲取
              IAccountService aservice = (IAccountService) ac.getBean("accountService");
              aservice.save();
          }
      }	
      

    其中@Component
    在這裏插入圖片描述
    註解的作用是把當前類存入spring的IOC容器中
    屬性:value用於指定bean的id
    最後需要再bean.xml配置

    <?xml version="1.0" encoding="UTF-8"?>
    <!--</beans>-->
    <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
            http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context.xsd">
    
        <context:component-scan base-package="com.liuzeyu"></context:component-scan>
    
    </beans>
    
    <context:component-scan base-package="com.liuzeyu"></context:component-scan>
    

    作用怎是告知spring創建容器時要掃描的包,配置所需要的標籤不是再bean約束裏面,而是一個名稱爲context名稱空間和約束裏面。
    補充三個註解:
    1)Controller:一般用於表現層
    2)Service:一般用於業務邏輯層
    3)Repository:一般用於持久層
    以上三個註解的屬性和作用和@Component相同,它們三個是spring框架爲我們提供三層架構使用的註解,使我們三次架構更加清晰。

  2. 用於注入數據

    1. 在1的基礎上,修改service實現類獲取AccountDao 對象的方法,使用注入數據的方式
      @Service("accountService")
      public class IAccountServiceImpl implements IAccountService{
      
          @Autowired
          private AccountDao adao = null;
          public void save() {
              adao.save();
          }
      }
      

    分析:@Autowired會幹一件事,就是用AccountDao 去spring的IOC容器中尋找value值爲AccountDao的實現類(不會去尋找key),如果找到,則反射生成實現類實現的接口對象,即AccountDao 對象。

    1. 如果此時遇到有多個AccountDao的實現類,那麼Autowired第一次尋找就找到了兩個,此時如何區分呢?該使用哪一個接口對象呢?退而求其次,Autowired會再尋找注入的變量名稱和spring的IOC容器中bean對象的被Component標記的vlaue值,也就是bean對象的id值是否相等,如果不等則報錯,如果相等創建其對象。
      在這裏插入圖片描述
    2. 爲什麼解決上面出現的兩個dao層實現類實現同一接口使用,@Qualifier指定特定的key值
      在這裏插入圖片描述
      缺陷就是必須配置@Autowired使用,如何解決這一個捆綁的煩惱呢?
    3. 爲了解決上述捆綁的煩惱,可以使用爲Resource的name屬性配置
      在這裏插入圖片描述
  3. 用於改變作用範圍和生命週期(瞭解)

    1. 單例和多例的配置
      在這裏插入圖片描述
      在這裏插入圖片描述
    2. 生命週期的問題
      在這裏插入圖片描述
      public class UITest {
          public static void main(String[] args) {
              //1.獲取核心容器對象
              ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
              //2.根據id獲取bean對象
              //2.1方式1獲取
              IAccountService aservice = (IAccountService) ac.getBean("accountService");
              aservice.save();
              ((ClassPathXmlApplicationContext) ac).close();
          }
      }
      

2. 案例使用xml方式實現單表的CRUD操作

  1. 準備dao層接口和實現類

    /**
     * 賬戶的持久層接口
     */
    public interface IAccountDao {
    
        /**
         * 查詢所有
         * @return
         */
        List<Account> findAllAccount();
    
        /**
         * 查詢一個
         * @return
         */
        Account findAccountById(Integer accountId);
    
        /**
         * 保存
         * @param account
         */
        void saveAccount(Account account);
    
        /**
         * 更新
         * @param account
         */
        void updateAccount(Account account);
    
        /**
         * 刪除
         * @param acccountId
         */
        void deleteAccount(Integer acccountId);
    }
    
    /**
     * 賬戶的持久層實現類
     */
    public class AccountDaoImpl implements IAccountDao {
    
        private QueryRunner runner;
    
        public void setRunner(QueryRunner runner) {
            this.runner = runner;
        }
    
        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);
            }
        }
    
        public void saveAccount(Account account) {
            try{
                runner.update("insert into account(name,money)values(?,?)",account.getName(),account.getMoney());
            }catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    
    
        public void updateAccount(Account account) {
            try{
                runner.update("update account set name=?,money=? where id=?",account.getName(),account.getMoney(),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);
            }
        }
    }
    
    
  2. 準備service接口和實現類

    package com.liuzeyu.service;
    
    import com.liuzeyu.domain.Account;
    
    import java.util.List;
    
    /**
     * 賬戶的業務層接口
     */
    public interface IAccountService {
    
        /**
         * 查詢所有
         * @return
         */
        List<Account> findAllAccount();
    
        /**
         * 查詢一個
         * @return
         */
        Account findAccountById(Integer accountId);
    
        /**
         * 保存
         * @param account
         */
        void saveAccount(Account account);
    
        /**
         * 更新
         * @param account
         */
        void updateAccount(Account account);
    
        /**
         * 刪除
         * @param acccountId
         */
        void deleteAccount(Integer acccountId);
    
    
    }
    
    
    /**
     * 賬戶的業務層實現類
     */
    @Service("accountService")
    public class AccountServiceImpl implements IAccountService{
    
        private IAccountDao accountDao;
    
        public void setAccountDao(IAccountDao accountDao) {
            this.accountDao = accountDao;
        }
    
    
        public List<Account> findAllAccount() {
            return accountDao.findAllAccount();
        }
    
    
        public Account findAccountById(Integer accountId) {
            return accountDao.findAccountById(accountId);
        }
    
    
        public void saveAccount(Account account) {
            accountDao.saveAccount(account);
        }
    
    
        public void updateAccount(Account account) {
            accountDao.updateAccount(account);
        }
    
    
        public void deleteAccount(Integer acccountId) {
            accountDao.deleteAccount(acccountId);
        }
    }
    
    
  3. 準備實體類

    package com.liuzeyu.domain;
    
    import java.io.Serializable;
    
    /**
     * 賬戶的實體類
     */
    public class Account implements Serializable {
    
        private Integer id;
        private String name;
        private Float money;
    
        public Integer getId() {
            return id;
        }
    
        public void setId(Integer id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public Float getMoney() {
            return money;
        }
    
        public void setMoney(Float money) {
            this.money = money;
        }
    
        @Override
        public String toString() {
            return "Account{" +
                    "id=" + id +
                    ", name='" + name + '\'' +
                    ", money=" + money +
                    '}';
        }
    }
    
    
  4. 準備bean.xml

    <?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.liuzeyu.service.impl.AccountServiceImpl">
            <!-- 注入dao -->
            <property name="accountDao" ref="accountDao"></property>
        </bean>
    
        <!--配置Dao對象-->
        <bean id="accountDao" class="com.liuzeyu.dao.impl.AccountDaoImpl">
            <!-- 注入QueryRunner -->
            <property name="runner" ref="runner"></property>
        </bean>
    
        <!--配置QueryRunner-->
        <bean id="runner" 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.jdbc.Driver"></property>
            <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/eesy"></property>
            <property name="user" value="root"></property>
            <property name="password" value="809080"></property>
        </bean>
    </beans>
    
  5. 準備測試函數

    /**
     * Created by liuzeyu on 2020/4/20.
     */
    public class AnnotationTest {
    
        @Test
        public void testFindAll(){
            ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
            IAccountService ias = ac.getBean("accountService", IAccountService.class);
            List<Account> accounts = ias.findAllAccount();
            for (Account account : accounts) {
                System.out.println(account);
            }
    
        }
    
        @Test
        public void testFindById(){
            ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
            IAccountService ias = ac.getBean("accountService", IAccountService.class);
            Account account = ias.findAccountById(1);
            System.out.println(account);
        }
    
    
        @Test
        public void testInsert(){
            ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
            IAccountService ias = ac.getBean("accountService", IAccountService.class);
            Account account = new Account();
            account.setName("liuzeyu");
            account.setMoney(50000f);
            ias.saveAccount(account);
            testFindAll();  //測試
        }
    
        @Test
        public void testUpdate(){
            ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
            IAccountService ias = ac.getBean("accountService", IAccountService.class);
            Account account = ias.findAccountById(4);
            account.setName("wahaha");
            account.setMoney(88888f);
            ias.updateAccount(account);
            testFindAll();  //測試
        }
    
        @Test
        public void testDelete(){
            ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
            IAccountService ias = ac.getBean("accountService", IAccountService.class);
            ias.deleteAccount(4);
            testFindAll();  //測試
        }
    }
    
    
    1. 依賴pom.xml
     <packaging>jar</packaging>
        <dependencies>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-context</artifactId>
                <version>5.0.2.RELEASE</version>
            </dependency>
    
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.12</version>
            </dependency>
    
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-test</artifactId>
                <version>5.0.2.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>commons-dbutils</groupId>
                <artifactId>commons-dbutils</artifactId>
                <version>1.4</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>
    
        </dependencies>
    

3. 改造基於xml的IOC案例,使用註解和注入的方式實現

  1. 在2的基礎上修改使用註解的方式:
    1. 修改bean,xml的約束和刪除使用xml依賴注入的方式

      <?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
              http://www.springframework.org/schema/beans/spring-beans.xsd
              http://www.springframework.org/schema/context
              http://www.springframework.org/schema/context/spring-context.xsd">
      
          <context:component-scan base-package="com.liuzeyu"></context:component-scan>
      
          <!--配置QureyRunner-->
          <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
              <constructor-arg name="ds" ref="dataSource"/>
          </bean>
      
          <!-- 配置數據源 -->
          <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
              <!--連接數據庫的必備信息-->
              <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
              <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/eesy"></property>
              <property name="user" value="root"></property>
              <property name="password" value="809080"></property>
          </bean>
      </beans>
      
    2. 持久層
      在這裏插入圖片描述

    3. 業務層
      在這裏插入圖片描述

  2. 在上面1的基礎上繼續優化,bean.xml使用註解替換
    1. bean.xml哪些東西需要使用註解替換掉
      在這裏插入圖片描述
    2. 如上圖,需要使用註解替換的地方有1,2,3處
      1. 1處的替換使用一個配置類
        /**
         * Created by liuzeyu on 2020/4/21.
         * spring的配置文件類。使用註解方式用於替換bean.xml的配置信息
         * spring用到的註解:
         *      1.@Configuration用於指定當前類是一個註解類
         *      2.@ComponentScan("com.liuzeyu")指定spring創建IOC容器是需要掃描的包
         */
        	@Configuration
        	@ComponentScan("com.liuzeyu")
        	public class SpringConfig {
        
        	}
        
        
      2. 2處是根據構造函數來注入數據,3處是根據setXXX來注入數據所以我們也可以自己定義構造出函數來注入數據
        在這裏插入圖片描述
        @Bean註解:
        作用:把當前方法的返回值作爲bean對象存入到spring的IOC容器中。
        屬性:name用於指定bean的id,如果不寫默認爲當前方法的名稱
        細節:
        當我們使用註解配置方法是,如果方法有參數,spring框架會去容器中尋找有沒有可用的bean對象,查找的方式和Autowired的方式一樣,自動按照類型注入,如果有唯一匹配,則注入,否則報錯。如果出現多個類型匹配,在進而匹配它的變量名稱。
      3. 可以將QueryRunner改爲多例
        /**
         * Created by liuzeyu on 2020/4/21.
         * 測試QueryRunner是單例還是多例
         */
        public class QueryRunnerTest {
        
            @Test
            public void testQueryRunner(){
                ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfig.class);
                QueryRunner runner1 = ac.getBean("runner", QueryRunner.class);
                QueryRunner runner2 = ac.getBean("runner", QueryRunner.class);
                System.out.println(runner1 == runner2); //true :單例
            }
        }
        
        只需要在QueryRunner注入處添加註解@Scope(“prototype”)即可
        在這裏插入圖片描述
      4. @Import註解的使用
        1. 發現一個問題,配置類缺少@Configuration程序依然可以運行
          這是因爲在測試類中加載類配置文件的字節碼

          ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfig.class);
          
        2. 如果出現多個配置文件,並且將SpringConfig配置文件的信息寫入到JDBCConfig中

          @ComponentScan("com.liuzeyu")
          public class SpringConfig {
          }
          
          @Configuration
          @ComponentScan("com.liuzeyu")
          public class JDBCConfig {
          
              @Bean(name = "runner")
              @Scope(value = "prototype")
              public QueryRunner createQueryRunner(DataSource dataSource){
                  return new QueryRunner(dataSource);
              }
          
          
              @Bean("dataSource")
              public DataSource createDataSource(){
                  try{
                      ComboPooledDataSource source = new ComboPooledDataSource();
                      source.setUser("root");
                      source.setPassword("809080");
                      source.setDriverClass("com.mysql.jdbc.Driver");
                      source.setJdbcUrl("jdbc:mysql://localhost:3306/eesy");
                      return source;
                  }catch (Exception e){
                      throw new RuntimeException(e);
                  }
              }
          }
          
          

          此時如果再次缺少了JDBCConfig的@Configuration,將會報錯。因爲創建ApplicationContext對象時會加載配置類SpringConfig(默認已經當作配置類),開啓掃描包,未發現@Configuration標識的配置類,就會報錯。此時解決辦法有兩個:
          第一種:加載JDBCConfig.class字節碼

          ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfig.class, JDBCConfig.class);
          

          第二種:在使用Import註解

          @ComponentScan("com.liuzeyu")
          @Import(JDBCConfig.class)
          public class SpringConfig {			
          }
          

          Import:
          作用:用於導入其它的配置類
          屬性:value用於指定其它的配置類的字節碼,當我們使用Import註解後,有Import的類就是父配置類,而導入的就是子配置類

      5. @PropertySource的使用
        1. 準備配置文件
        2. 刪除創建DataSource方法中的jdbc連接信息
          在這裏插入圖片描述
        3. 添加JDBCConfig的屬性,通過@Value屬性將外部的配置文件信息加載到屬性值中
          	@Value("${jdbc.driver}")
              private String driver;
          
              @Value("${jdbc.url}")
              private String url;
          
              @Value("${jdbc.username}")
              private String username;
          
              @Value("${jdbc.password}")
              private String password;
          
              @Bean(name = "runner")
              @Scope(value = "prototype")
          
          DataSource方法中的jdbc連接信息在這裏插入圖片描述
        4. 但是現在配置文件中的信息就會被加載進來嗎,不可以,需要在父配類上加上註解@PropertySource
          @ComponentScan("com.liuzeyu")
          @Import(JDBCConfig.class)
          @PropertySource("classpath:jdbc.properties")
          public class SpringConfig {
          
          }
          
          因爲放到resource資源文件下的配置文件部署後都會在類路徑下,這樣就可以從@Value獲取到值
      6. @Qualifier註解在方法上的使用
        在這裏插入圖片描述解決方案:
        在這裏插入圖片描述

4. spring和JUnit的整合

  1. 發現測試方法存在的問題

    public class AnnotationTest {
        private IAccountService ias = null;
    	
        @Before
        public void  init(){
            ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfig.class);
            ias = ac.getBean("accountService", IAccountService.class);
        }
    
        @Test
        public void testFindAll(){
    
            List<Account> accounts = ias.findAllAccount();
            for (Account account : accounts) {
                System.out.println(account);
            }
    
        }
        
    }
    

    上述測試案例可以運行,但是存在一個問題,就是如果測試人員來用的話萬一在init函數裏面出現問題,則測試人員將無法作報告分析,因爲這是開發人員留下的坑,一般的測試人員只對測試方法進行分析。

  2. 進行分析問題
    可以使用註解的方式注入IAccountService對象,如下:

    public class AnnotationTest {
    
        @Autowired
        private IAccountService ias = null;
    
        @Test
        public void testFindAll(){
    
            List<Account> accounts = ias.findAllAccount();
            for (Account account : accounts) {
                System.out.println(account);
            }
    
        }
    
    }
    

    運行會出現空指針異常,爲什麼呢?
    要從應用程序的入口main函數開始分析,其實在junit中默認集成了一個main方法,該方法會判斷當前測試類哪些方法有@Test註解,junit就可以讓這些方法執行。
    爲什麼出現空指針?
    junit是不管我們是否採用spring框架的,它只會使用自己的main入口函數,所以它也就不會爲我們讀取配置然後創建IOC容器了,因此會注入失敗,出現空指針。

  3. 解決方案
    整合junit和spring框架

    1. 導入相關的jar包
    2. 使用juint提供的一個註解將原來的main方法替換掉,替換層spring提供的 @Runwith
    3. 告知spring運行器,spring和IOC的創建是基於xml還是註解,並說明位置 @ContextConfiguration(),屬性有:
      1. locations:指定xml的文件位置,加上classpath關鍵字,表示在類路徑下
      2. classes:指定註解所在的位置,這裏要注意:註解配置類無論是否使用@Import的父配置類,都要加上@Configuration表明它是一個配置類,否則無法獲取容器中的bean對象
        @RunWith(SpringJUnit4ClassRunner.class)
        @ContextConfiguration(classes = SpringConfig.class)
        public class AnnotationTest {
        
            @Autowired
            private IAccountService ias;
        
            @Test
            public void testFindAll(){
        
                List<Account> accounts = ias.findAllAccount();
                for (Account account : accounts) {
                    System.out.println(account);
                }
        
            }
        
        }
        
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章