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>
上述配置可以用於創建對象,用於注入數據,用於改變作用範圍和生命週期
換做註解實現:
-
用於創建對象
- dao層實現類:
@Component("accountDao") public class AccountDaoImpl implements AccountDao{ public void save() { System.out.println("保存了數據庫"); } }
- 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(); } }
- 測試函數
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框架爲我們提供三層架構使用的註解,使我們三次架構更加清晰。 - dao層實現類:
-
用於注入數據
- 在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 對象。
- 如果此時遇到有多個AccountDao的實現類,那麼Autowired第一次尋找就找到了兩個,此時如何區分呢?該使用哪一個接口對象呢?退而求其次,Autowired會再尋找注入的變量名稱和spring的IOC容器中bean對象的被Component標記的vlaue值,也就是bean對象的id值是否相等,如果不等則報錯,如果相等創建其對象。
- 爲什麼解決上面出現的兩個dao層實現類實現同一接口使用,@Qualifier指定特定的key值
缺陷就是必須配置@Autowired使用,如何解決這一個捆綁的煩惱呢? - 爲了解決上述捆綁的煩惱,可以使用爲Resource的name屬性配置
- 在1的基礎上,修改service實現類獲取AccountDao 對象的方法,使用注入數據的方式
-
用於改變作用範圍和生命週期(瞭解)
- 單例和多例的配置
- 生命週期的問題
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操作
-
準備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); } } }
-
準備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); } }
-
準備實體類
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 + '}'; } }
-
準備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>
-
準備測試函數
/** * 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(); //測試 } }
- 依賴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案例,使用註解和注入的方式實現
- 在2的基礎上修改使用註解的方式:
-
修改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>
-
持久層
-
業務層
-
- 在上面1的基礎上繼續優化,bean.xml使用註解替換
- bean.xml哪些東西需要使用註解替換掉
- 如上圖,需要使用註解替換的地方有1,2,3處
- 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處是根據構造函數來注入數據,3處是根據setXXX來注入數據所以我們也可以自己定義構造出函數來注入數據
@Bean註解:
作用:把當前方法的返回值作爲bean對象存入到spring的IOC容器中。
屬性:name用於指定bean的id,如果不寫默認爲當前方法的名稱
細節:
當我們使用註解配置方法是,如果方法有參數,spring框架會去容器中尋找有沒有可用的bean對象,查找的方式和Autowired的方式一樣,自動按照類型注入,如果有唯一匹配,則注入,否則報錯。如果出現多個類型匹配,在進而匹配它的變量名稱。 - 可以將QueryRunner改爲多例
只需要在QueryRunner注入處添加註解@Scope(“prototype”)即可/** * 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 :單例 } }
- @Import註解的使用
-
發現一個問題,配置類缺少@Configuration程序依然可以運行
這是因爲在測試類中加載類配置文件的字節碼ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfig.class);
-
如果出現多個配置文件,並且將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的類就是父配置類,而導入的就是子配置類
-
- @PropertySource的使用
- 準備配置文件
- 刪除創建DataSource方法中的jdbc連接信息
- 添加JDBCConfig的屬性,通過@Value屬性將外部的配置文件信息加載到屬性值中
DataSource方法中的jdbc連接信息@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")
- 但是現在配置文件中的信息就會被加載進來嗎,不可以,需要在父配類上加上註解@PropertySource
因爲放到resource資源文件下的配置文件部署後都會在類路徑下,這樣就可以從@Value獲取到值@ComponentScan("com.liuzeyu") @Import(JDBCConfig.class) @PropertySource("classpath:jdbc.properties") public class SpringConfig { }
- @Qualifier註解在方法上的使用
解決方案:
- 1處的替換使用一個配置類
- bean.xml哪些東西需要使用註解替換掉
4. spring和JUnit的整合
-
發現測試方法存在的問題
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函數裏面出現問題,則測試人員將無法作報告分析,因爲這是開發人員留下的坑,一般的測試人員只對測試方法進行分析。
-
進行分析問題
可以使用註解的方式注入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容器了,因此會注入失敗,出現空指針。 -
解決方案
整合junit和spring框架- 導入相關的jar包
- 使用juint提供的一個註解將原來的main方法替換掉,替換層spring提供的 @Runwith
- 告知spring運行器,spring和IOC的創建是基於xml還是註解,並說明位置 @ContextConfiguration(),屬性有:
- locations:指定xml的文件位置,加上classpath關鍵字,表示在類路徑下
- 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); } } }