Spring系列教程六: Spring基於註解的Ioc以及Ioc案例
學習基於註解的Ioc配置,我們腦海中需要有一個認知,就是註解配置和xml配置實現的功能都是一樣的,都是要降低程式間的耦合,只是配置的形式不一樣
在實際開發中到底是使用xml還是註解,每個公司有不同的使用習慣,所有這兩種配置方式我們都需要掌握
我們在講解註解配置時,採用上一章的案例,把Spring的xml配置內容改為使用註解逐步實現
1、Spirng中Ioc的常用註解按照作用分類
用於建立物件:作用是和xml配置檔案中編寫一個bean標籤建立物件的功能是一樣的
@Component(以下三個是Spring框架為我們明確提供的三層使用註解,使我們的三層物件更加清晰,以下是Component的衍生類)
Controller(這個物件一般用在表現層)
Service(這個物件一遍用於業務層)
Repository(這個業務一般用於持久層)
作用:把當前物件存入Spring容器中
屬性:value 用於指定bean的id,當我們不寫的時候,它的預設是當前類名,且首字母小寫,也可以指定一個名稱
①在介面中實現@Component的程式碼改寫
@Component("accountServiceImpl4") public class AccountServiceImpl4 implements IAccountService { private String name; private Integer age; private Date brithday; AccountServiceImpl4(String name, Integer age, Date brithday){ this.name=name; this.age=age; this.brithday=brithday; } public void saveAccount(){ System.out.println("service中的saveAccount方法執行了...."+name+age+brithday); } }
②配置檔案中改寫(需要新新增引入context的約束)
<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 http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx"> <!--新新增一個約束:告訴Spring在建立容器時要掃描的包,配置所需要的標籤不是在bean的約束中,而是一個名稱為 context的名稱和空間中,這個時候就會掃描base-pack類上或者介面上的註解--> <context:component-scan base-package="com.ithema.jdbc"></context:component-scan> </beans>
③測試類
package com.ithema.jdbc.ui;
import com.ithema.jdbc.service.IAccountService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* 模擬一個表現層,用於呼叫業務層
*/
public class Client2 {
public static void main(String[] args) {
ApplicationContext ac=new ClassPathXmlApplicationContext("ApplicationContext2.xml");
IAccountService as= (IAccountService) ac.getBean("accountServiceImpl4");
System.out.println(as);
as.saveAccount();
}
}
顯示結果如下
用於注入資料:作用是和xml配置檔案中編寫一個property標籤建立物件的功能是一樣的
Autowired
作用:自動按照型別注入,主要容器中有唯一一個bean物件和要注入的變數型別匹配,就可以注入成功,如果Ioc容器中沒有任何bean型別和要注入的變數型別匹配,則報錯
如果有Ioc容器中有多個匹配型別:首先按照型別圈定出來匹配的物件,使用變數名稱作為bean的id,在圈定出來的兩個裡面繼續查詢,如果bean id有一樣也可以注入成功
出現位置:可以是變數上,也可以是方法上
細節:在使用註解注入時候,set方法就不用了
但是這種情況不是我們想看到的,那麼有新的方法嗎??如下
Qualifier(不能獨立使用,需要和Autowired配合使用,有解決的方案嗎?如下一個屬性)
作用:在按照類中注入的基礎上再按照名稱注入,它在給類成員注入時,不能單獨使用,但在給方法引數注入時候可以
屬性:value 用於指定bean的id
Resource:直接按照bean的id注入,平時用的比較多,它可以獨立使用
@Resource(name="accountDao")
以上三個註解只能注入其他bean型別的資料,而基本資料型別和String型別無法使用上述註解實現,另外,集合型別的注入只能使用xml來實現,怎麼解決如下
Value:
作用:湧入注入基本型別和String型別的資料
屬性 value:用於指定資料的值,它可以使用Spring中spel(也就是Spring中的el表示式),spel寫法$(表示式)
①介面上修改程式碼
②啟動測試類顯示結果如下,成功注入資料
存在一bean物件時候
存在多個bean物件時候,要注入的變數名稱和某個bean的id保持一致也可以注入成功
用於改變作用範圍:他們的作用是和xml配置檔案中編寫一個scope標籤的功能是一樣的
作用:用於指定bean的作用範圍,屬性:value 指定範圍取值,常用取值singleton(預設值)/prototype
測試如下,作用範圍預設是單例的
那麼如果我們把作用範圍改成prototype的啦?結果當然是false
和生命週期相關:他們的作用是和xml配置檔案中編寫一個init-method和destory-method標籤的功能是一樣的
PreDestory 作用:用於指定銷燬方法
PostConstruct 作用:初始化方法
2、使用xml方式和註解方式實現單表的CURD操作,持久技術層選用dbutils(案例)
3、改造基於註解的Ioc案例,使用註解的方式實現Spring的一些新註解的使用
如何實現在不是我們自己寫類上的註解,比如我們引入QueryRunner類
基於註解的 IoC 配置已經完成,但是大家都發現了一個問題:我們依然離不開 spring 的 xml 配置檔案,那麼能不能不寫這個 bean.xml,所有配置都用註解來實現呢?當然,我們們也需要注意一下,我們選擇哪種配置的原則是簡化開發和配置方便,而非追求某種技術。
①先建立一個config檔案,裡面建立一個Springconfiguration的類
package com.it.config;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.apache.commons.dbutils.QueryRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import javax.sql.DataSource;
@Configuration
@ComponentScan("com.it")
public class SpringConfiguration {
/**
*建立一個QueryRunner物件
*/
@Bean("runner")
@Scope("prototype")
public QueryRunner createQueryRunner(DataSource dataSource) {
return new QueryRunner(dataSource);
}
/**
* 建立資料來源物件
*/
@Bean("dataSource")
public DataSource createDataSource() {
try {
ComboPooledDataSource ds = new ComboPooledDataSource();
ds.setDriverClass("com.mysql.jdbc.Driver");
ds.setJdbcUrl("jdbc:mysql://localhost:3306/fresh?characterEncoding=UTF-8");
ds.setUser("root");
ds.setPassword("root");
return ds;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
Configuration
* 作用:指定當前類是一個配置類
* 細節:當配置類作為AnnotationConfigApplicationContext物件建立的引數時,該註解可以不寫
ComponentScan
* 作用:用於通過註解指定spring在建立容器時要掃描的包
* 屬性:
* value:它和basePackages的作用是一樣的,都是用於指定建立容器時要掃描的包。
* 我們使用此註解就等同於在xml中配置了:
* <context:component-scan base-package="com.itheima"></context:component-scan>
Bean
* 作用:用於把當前方法的返回值作為bean物件存入spring的ioc容器中,該註解只能寫在方法上
* 屬性:
* name:用於指定bean的id,當不寫時,預設值是當前方法的名稱
* 細節:
* 當我們使用註解配置方法時,如果方法有引數,spring框架會去容器中查詢有沒有可用的bean物件。
* 查詢的方式和Autowired註解的作用是一樣的
②測試類檔案中的改寫
package com.it;
import com.it.config.SpringConfiguration;
import com.it.entity.Student;
import com.it.service.StudentService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.util.List;
public class TestClient {
@Test
public void findAll(){
//ApplicationContext ac= new ClassPathXmlApplicationContext("ApplicationContext.xml");
ApplicationContext ac= new AnnotationConfigApplicationContext(SpringConfiguration.class);
StudentService as = ac.getBean("studentService",StudentService.class);
List<Student> studentList =as.findAllStudent();
for (Student student:studentList) {
System.out.println(student);
}
}
}
測試結果如下:
問題來了,上面劃線的部分兩個的作用是一樣的嘛?效果是不一樣的,下面劃線部分,當把物件建立好了後,會把物件給你丟到Spring Ioc容器中,上面劃線的部分當然不會那麼做,它只是會給你返回一個QueryRunner的物件,這個時候就需要我們手動把物件丟到容器裡面去,就需要採用@Bean的方法
QueryRunner物件是不是一個單例物件
線面還有一個問題就是我們的QueryRunner物件是不是一個單例物件啦?這個問題關係到我們執行緒的問題
那怎麼改變bean的作用範圍啦?這個是時候寄需要用的scope屬性啦,結果如下
父配置類匯入子配置類用@Import
對於配置類我們想要把SpringConfiguration中的jbdc配置專門提取到一個配置類中去?這樣的話也方便後期的管理和修改
單元測試findAll的測試結果如下
匯入配置perproties配置檔案@PropertySource
* 作用:用於指定properties檔案的位置
* 屬性:
* value:指定檔案的名稱和路徑。
* 關鍵字:classpath,表示類路徑下
*
在之前JdbcConfig配置類中dataSource的所有的屬性都是寫死的後來我們進行了如下改造
package com.it.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 org.springframework.context.annotation.Scope;
import javax.sql.DataSource;
public class JbdcConfig {
/**
* 建立dataSource物件
* @param dataSource
* @return
*/
@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("runner")
@Scope("prototype")
public QueryRunner createQueryRunner(DataSource dataSource) {
return new QueryRunner(dataSource);
}
/**
* 建立資料來源物件
*/
@Bean("dataSource")
public DataSource createDataSource() {
try {
ComboPooledDataSource ds = new ComboPooledDataSource();
ds.setDriverClass(driver);
ds.setJdbcUrl(url);
ds.setUser(username);
ds.setPassword(password);
return ds;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
對於新建立的private String driver屬性,我們使用@Valeue("${jdbc.diver}")來注入perproties中的值,在SpringConfigruation配置父類中使用@PropertySource來注入配置檔案
測試結果如下,一樣能查詢到結果
那麼我們一般適合用那種配置方式?用註解還是,xml啦?
用到已經寫好的jar的,一般情況下我們用xml來配置,比較省事,要是這個類是我們自己寫的話,用註解來配置比較方便
4、Spring和Junit的整合
在我們的測試類中存在很多的重複的方法,那麼有沒方法解決啦!!我先採用init的方式先載入建立容器的方式
結果顯示沒有任何問題,那麼這個問題被徹底解決掉了嘛?
根據分析得到,junit執行原理
使用Junit單元測試:測試我們的配置,Spring整合junit的配置
1、匯入spring整合junit的jar:spring-test(座標)
2、使用Junit提供的一個註解把原有的main方法替換了,替換成spring提供的@Runwith
3、告知spring的執行器,spring和ioc建立是基於xml還是註解的,並且說明位置
@ContextConfiguration
屬性:locations:指定xml檔案的位置,加上classpath關鍵字,表示在類路徑下
classes:指定註解類所在地位置
細節:當我們使用spring 5.x版本的時候,要求junit的jar必須是4.12及以上
package com.it;
import com.it.config.SpringConfiguration;
import com.it.entity.Student;
import com.it.service.StudentService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.List;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfiguration.class)
public class TestClient {
@Autowired
private StudentService as;
@Test
public void findAll(){
List<Student> studentList =as.findAllStudent();
for (Student student:studentList) {
System.out.println(student);
}
}
@Test
public void findbyidtest() {
//ApplicationContext ac = new ClassPathXmlApplicationContext("ApplicationContext.xml");
Student student = as.findByid(4);
System.out.println(student);
}
@Test
public void saveTest() {
Student student=new Student();
student.setId(7);
student.setStuno("10007");
student.setName("陳多糖");
student.setClassid(2);
as.saveStudent(student);
}
@Test
public void updatetest() {
Student student = as.findByid(4);
student.setName("陳柳柳");
as.updateStudent(student);
}
@Test
public void deletetest() {
ApplicationContext ac = new ClassPathXmlApplicationContext("ApplicationContext.xml");
StudentService as = ac.getBean("studentService", StudentService.class);
as.deleteStudent(7);
}
}
測試結果如下:
為什麼 不把測試類配到 xml
在解釋這個問題之前,先解除大家的疑慮,配到 XML 中能不能用呢?
答案是肯定的,沒問題,可以使用。
那麼為什麼不採用配置到 xml 中的方式呢?
這個原因是這樣的:
第一:當我們在 xml 中配置了一個 bean,spring 載入配置檔案建立容器時,就會建立物件。
第二:測試類只是我們在測試功能時使用,而在專案中它並不參與程式邏輯,也不會解決需求上的問
題,所以建立完了,並沒有使用。那麼存在容器中就會造成資源的浪費。
所以,基於以上兩點,我們不應該把測試配置到 xml 檔案