Inversion of Control 將創建對象的權利交給框架,包括DI(Dependency Injection,依賴註入)和DL(Dependency Lookup,依賴查找),能削減電腦程式的耦合,即解除代碼中的依賴關係 應用 xml 1. 建立maven工程 2. 導入jar包,pom ...
Inversion of Control
將創建對象的權利交給框架,包括DI(Dependency Injection,依賴註入)和DL(Dependency Lookup,依賴查找),能削減電腦程式的耦合,即解除代碼中的依賴關係
應用
xml
建立maven工程
導入jar包,pom.xml中加入spring-context依賴
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.0.2.RELEASE</version> </dependency>
創建配置文件,在resources目錄下新建bean.xml文件,並導入約束
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/beans" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> </beans>
配置bean,在bean.xml中的beans標簽中加入bean標簽配置bean
- 使用預設構造函數,如果類中無預設構造函數則無法創建
<bean id="bean的唯一標識(名字)" class="全限定類名"></bean>
- 使用某個類(工廠)的某個方法創建對象
<bean id="beanId" factory-bean="工廠類的全限定類名" factory-method="方法名"></bean>
- 使用某個類(靜態工廠)的某個靜態方法創建對象
<bean id="beanId" class="全限定類名" factory-method="方法名"></bean>
bean標簽還有兩個屬性init-method和destroy-method指定構造方法和銷毀方法
依賴註入
在bean中需要的數據或其他類的對象都由spring提供,只需配置,但不適合註入常變的數據
能註入的數據有三類:基本類型和String,其他bean,複雜類型/集合類型
註入方式有三種,在bean標簽內用一個標簽代表一個參數:
構造函數註入
<constructor-arg type(不常)="類型的全限定名" index(不常)="參數序號,從0開始" name(常用)="參數名" value="基本類型值" ref="引用其他bean"></constructor-arg>
優:獲取bean對象時註入數據是必須的,否則無法創建成功
弊:改變了bean的實例化方式,在創建對象時如果用不到這些數據也必須提供
set方法註入(常用)
<property name="set方法對應名" value="基本類型值" ref="其他bean"></property>
優:創建對象時沒有明確的限制,可以直接使用預設構造函數
弊:set方法可能不執行,如果某個成員必須有值,此方法不能保證
複雜類型的註入需要在參數標簽中再加標簽array,list,set,map,props
<property name="listPropName"> <array> <value>...</value> ... </array> </property> <property name="mapPropName"> <map> <entry key="keyName" value="值"></entry> <entry key="keyName"> <value>值</value> </entry> </map> </property>
List結構的用list,array,set都一樣,Map結構的用map,props都一樣
獲取bean,在java代碼中
//獲取核心容器對象,也可以用BeanFactory代替ApplicationContext ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml"); //根據id獲取Bean對象 ClassOfBean bean = (ClassOfBean)ac.getBean("beanId");
- ApplicationContext的三個常用實現類
ClassPathXmlApplicationContext: 可以載入類路徑下的配置文件
FileSystemXmlApplicationContext: 可以載入磁碟任意路徑(有訪問許可權)下的配置文件
AnnotationConfigApplicationContext: 用於讀取註解創建容器
- ApplicationContext和BeanFactory的區別
前者在構建容器時採用立即載入的方式,一讀取完配置文件就馬上創建bean
後者在構建視窗時採用延遲載入的方式,當獲取bean時才真正創建對象
註解
用xml文件 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"
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">
<!-- 告知spring在創建容器時要掃描的包 -->
<context:component-scan base-package="包路徑"></context:component-scan>
</beans>
不用xml文件
創建一個配置類,類上加
@Configuration指定當前類是一個配置類,獲取容器時作為參數傳入的配置類可以不加
@ComponentScan(basePackages="包路徑")指定要掃描的包,basePackages可以換成value
獲取容器時用new AnnotationConfigApplicationContext(配置類.class)
@Import 導入其他配置類,被導入的類不用再加@Configuration
@PropertySource("classpath:路徑/文件名") 用指定properties文件的位置,屬性value
@PropertySources 有多個properties文件的時候用
導入配置文件後可以用@Value(${...})讀取
與bean有關的註解
@Bean 用於將當前方法的返回值作為bean存入ioc容器中,屬性name指定beanId預設值為當前方法的名稱,如果方法有參數,spring框架會去容器中查找 有沒有可用的bean對象,查找方式同Autowired
用於創建對象的,相當於bean標簽
@Component 作用:把當前對象存入spring容器中;屬性:value:指定bean的id,預設是當前類名且首字母小寫;@Controller @Service @Repository與@Component完全一樣,體現三層架構
用於註入數據的,相當於property標簽
@Autowired 作用:自動按照類型註入,如果容器中有唯一一個類匹配該數據的類型則註入,若沒有匹配類型則報錯,如果有多個則通過變數名尋找beanId對應的那個bean註入;位置:類上,方法上,變數上
@Qualifier 作用:在按照類型註入的基礎上再按照名稱註入,在給類成員註入時不能單獨使用必須和@Autowired一起,但在給方法參數註入時可以;屬性:value用於指定beanId
以上三種只能註入bean,不基本類型和String,複雜類型必須用xml配置
@Value 作用:用於註入基本類型和String;屬性:value用於指定數據的值,可以使用SpEL(spring的EL表達式:${表達式})
@Resource 作用:直接按照bean的id註入,可以獨立使用;屬性:name指定beanId
用於改變作用範圍的,相當於bean標簽的scope屬性
@Scope 作用:指定bean的作用範圍;屬性:value一般取singleton(預設)或prototype
和生命周期相關的,相當於bean標簽的init-method和destroy-method屬性(不常用)
@PostConstruct
@PreDestroy
與Junit整合
添加spring-test和junit依賴,spring5.x要求junit 4.12及以上
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.0.2.RELEASE</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency>
使用Junit提供的一個註解@RunWith把原有的main方法替換成spring提供的
@RunWith(SpringJUnit4ClassRunner.class)
告知spring的運行器,是基於xml還是註解,並說明位置
@ContextConfiguration
locations: 指定xml文件位置
classes: 指定註解類所在位置
常見問題
bean的作用範圍
bean標簽和@Component註解的scope屬性:用於指定bean的作用範圍
取值:singleton 單例的(常用)
prototype 多例的(常用)
request 作用於web應用的請求範圍
session 作用於web應用的會話範圍
global-session 集群環境的會話範圍(全局會話範圍),若不是集群環境,則為session
bean的生命周期
singleton: 容器創建時對象創建(ApplicationContext),容器銷毀時對象銷毀
prototype: 使用對象時創建對象,當對象長時間不用且長時間沒有別的對象引用時,由java的垃圾回收器回收
原理
ioc只需要導入spring-context依賴
閱讀源碼:github下載源碼,導入idea,運行調試加註釋
spring項目啟動後,會掃描所有類,找到符合bean標準(加註釋或xml配置)的類,通過一個描述類(BeanDefinition)存放bean的信息,isSingleton屬性預設為true,再將描述類存入一個map(BeanDefinationMap)中
掃描之後bean的獲取:通過BeanDefinationMap里的key(bean名)找到value(BeanDefination)的class屬性進行反射,找到bean對應的類。與class名沒有關係,可以通過自己實現BeanFactory介面,從beanFactory中獲取描述類BeanDefinition,setBeanClass(another.class)將bean和class的對應關係更改
掃描前BeanDefinationMap中已有7個bean,包括AppConfig
main() --> AnnotationConfigApplicationContext(AppConfig.class) --> refresh() --> invokeBeanFactoryPostProcessors(beanFactory)(掃描並存入bean)
後置處理器BeanPostProcessors的子類會被自動掃描並對bean進行多層代理,預設有九次(進入依賴註入等操作),實現此介面可以對bean的創建過程進行干預
Spring上下文或環境:為了實現IOC或AOP所需要的各種Spring組件的集合,如BeanDefination,BeanPostProcessor(後置處理器),DefaultListableBeanFactory,BeanDefinitionMap,單例池等等
掃描只是將bean對應的類的信息存入map中,並不會實例化類,即lazy(懶載入)
factoryBean是一個特殊的bean,beanFactory是bean工廠
單例bean第一次被實例化後就會存入名為singleObjects的map(單例池)中,再次getBean()時就不會new,(保證單例)
Spring容器:不止單例池,還包括其他IOC組件
迴圈依賴:
單例bean的迴圈依賴
isSingletonCurrentlyInCreation判斷這個bean是否在創建過程中
單例池只存放完整的bean
get(A) --> new A --> auto B --> get(B) --> new B --> auto A --> get(A)
獲取A,新建A,發現A需要註入B且單例池中沒有B且B不在創建過程中,獲取B,發現B需要註入A且單例池中沒有A且A正在創建過程中,獲取A並註入B,將B註入A,將A放入單例池