一苇经、本文目錄
- spring的簡單使用方法
- spring初級(jí)容器XmlBeanFactory簡單介紹
- spring初級(jí)容器XmlBeanFactory初始化
二捎迫、spring的簡單使用
- 我們首先新建一個(gè)Student類,作為我們的示例bean
- spring的初衷,就是裝載一個(gè)一個(gè)的bean腻贰,這些bean迅腔,其實(shí)就是簡單的Java對(duì)象
public class Student {
private String name="JHXY";
private int age;
// 省略getter牍帚、setter银伟、toString方法
- spring對(duì)應(yīng)的applicationContext.xml文件中配置student實(shí)例bean
<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"
xmlns:aop="http://www.springframework.org/schema/aop"
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 https://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="student" class="com.jhxy.common.Student"/>
</beans>
- 測試代碼
public static void main(String[] args) {
XmlBeanFactory xmlBeanFactory =
new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));
// 從XmlBeanFactory即:spring容器中,獲取student bean
Student student = (Student) xmlBeanFactory.getBean("student");
System.out.println(student.getName());
}
-
測試結(jié)果
XmlBeanFactory基本工作原理
- spring最基本的容器:XmlBeanFactory,我們熟知的ApplicationContent相當(dāng)于spring的高級(jí)容器
- ApplicationContenxt高級(jí)容器在XmlBeanFactory基礎(chǔ)上并徘,添加了很多擴(kuò)展功能和特性
- ClassPathResource封裝了applicationContext.xml文件,作為XmlBeanFactory構(gòu)造方法參數(shù)遣钳,創(chuàng)建XmlBeanFactory
三、spring初級(jí)容器XmlBeanFactory簡單介紹
- 通過下載Spring源碼,使用Intellij進(jìn)行代碼調(diào)試,我們已經(jīng)將spring的源碼下載到了本地麦乞,通過Intellij進(jìn)行源碼閱讀蕴茴,通過上面的簡單使用,我們知道,spring的初級(jí)容器XmlBeanFactory在初始化的時(shí)候姐直,其實(shí)就是通過簡單的new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));創(chuàng)建spring的初級(jí)容器
- 首先倦淀,我們來看下構(gòu)造函數(shù)的參數(shù),ClassPathResource對(duì)應(yīng)的Resource是什么
-
我們先來看下ClassPathResource對(duì)應(yīng)的類繼承關(guān)系
- 我們可以看到
- ClassPathResource實(shí)現(xiàn)了Resource接口
- Resource接口繼承了InputStreamSource
- 和ClassPathResource相似的還有InputStreamResource,ByteArrayResource,FileSystemResource
- 其實(shí)spring將所有的資源都抽象成一個(gè)InputStreamSource,這樣声畏,不同的來源使用不同的實(shí)現(xiàn)類撞叽。
-
Resource 接口中的方法
- exists():對(duì)資源狀態(tài)的判斷,資源是否存在
- isReadable():對(duì)資源狀態(tài)的判斷插龄,是否是可讀狀態(tài)
- isOpen():對(duì)資源狀態(tài)的判斷愿棋,資源是否打開狀態(tài)
- isFile():對(duì)資源狀態(tài)的判斷,判斷是否是文件類型
- 通過類繼承圖可以知道均牢,Resource接口繼承了InputStreamSource糠雨,這就意味著所有的資源只要封裝了Resource接口,就可以通過調(diào)用InputStreamSource的getInputStream方法來獲取資源對(duì)應(yīng)的InputStream輸入流了
- 而資源是多種多樣的膨处,我們平時(shí)項(xiàng)目中的applicationContext.xml其實(shí)就是項(xiàng)目的classpath下的xml,ClassPathResource就是用來加載classpath路徑下的資源文件
- 所以见秤,各種Resource是如何加載資源的,我們通過示例中的ClassPathResource的getInputStream方法可以看出,ClasspathResource就是通過class或者classLoader的底層方法來加載的
/**
* This implementation opens an InputStream for the given class path resource.
* @see java.lang.ClassLoader#getResourceAsStream(String)
* @see java.lang.Class#getResourceAsStream(String)
*/
@Override
public InputStream getInputStream() throws IOException {
InputStream is;
if (this.clazz != null) {
is = this.clazz.getResourceAsStream(this.path);
}
else if (this.classLoader != null) {
is = this.classLoader.getResourceAsStream(this.path);
}
else {
is = ClassLoader.getSystemResourceAsStream(this.path);
}
if (is == null) {
throw new FileNotFoundException(getDescription() + " cannot be opened because it does not exist");
}
return is;
}
- 綜上真椿,我們可以得出結(jié)論:Resource就是spring內(nèi)部資源的一個(gè)抽象,而InputStreamResource的接口實(shí)現(xiàn)乎澄,使我們對(duì)各種來源的資源都可以輕松的獲取對(duì)應(yīng)的輸入流InputStream
四突硝、spring初級(jí)容器XmlBeanFactory的初始化
- 我們知道,創(chuàng)建spring初級(jí)容器XmlBeanFactory置济,通過XmlBeanFactory構(gòu)造函數(shù)直接創(chuàng)建解恰,先來看下XmlBeanFactory的構(gòu)造函數(shù)
public class XmlBeanFactory extends DefaultListableBeanFactory {
//XmlBeanDefinitionReader用于讀取資源的reader組件
private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);
/**
* Create a new XmlBeanFactory with the given resource,
* which must be parsable using DOM.
* @param resource the XML resource to load bean definitions from
* @throws BeansException in case of loading or parsing errors
*/
public XmlBeanFactory(Resource resource) throws BeansException {
this(resource, null);
}
/**
* Create a new XmlBeanFactory with the given input stream,
* which must be parsable using DOM.
* @param resource the XML resource to load bean definitions from
* @param parentBeanFactory parent bean factory
* @throws BeansException in case of loading or parsing errors
*/
public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
//我們再去父類的構(gòu)造方法中看看锋八,構(gòu)造方法中實(shí)現(xiàn)了哪些功能
super(parentBeanFactory);
//通過 XmlBeanDefinitionReader進(jìn)行加載資源
this.reader.loadBeanDefinitions(resource);
}
}
- XmlBeanFactory構(gòu)造函數(shù)中,首先要調(diào)用父類的構(gòu)造方法护盈,我們一路走下去挟纱,最終走到AbstractAutowireCapableBeanFactory中
- 在AbstractAutowireCapableBeanFactory中,ignoreDependencyInterface方法設(shè)置了一些類腐宋,分別是BeanNameAware紊服、BeanFactoryAware、BeanClassLoaderAware
2.ignoreDependencyInterface方法胸竞,就是將參數(shù)中的類添加到集合ignoredDependencyInterfaces中
public AbstractAutowireCapableBeanFactory() {
super();
ignoreDependencyInterface(BeanNameAware.class);
ignoreDependencyInterface(BeanFactoryAware.class);
ignoreDependencyInterface(BeanClassLoaderAware.class);
}
-
我們再來看下這三個(gè)感知接口的類繼承關(guān)系圖
- 通過接口名字Aware欺嗤,應(yīng)該能猜到,這些都是感知相關(guān)的接口卫枝,當(dāng)bean實(shí)現(xiàn)了這些接口煎饼,在spring實(shí)例化bean的時(shí)候,就可以通過感知接口中的方法注入相應(yīng)的數(shù)據(jù)
- 我們首先通過一個(gè)例子來看下校赤,BeanNameAware的作用
public class Student implements BeanNameAware{
private String name="JHXY";
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public void setBeanName(String name) {
System.out.println("beanName:" + name);
}
}
applicationContext和之前的配置一樣吆玖,測試代碼
public static void main(String[] args) {
XmlBeanFactory xmlBeanFactory =
new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));
// 從XmlBeanFactory即:spring容器中,獲取student bean
Student student = (Student) xmlBeanFactory.getBean("student");
System.out.println(student.getName());
}
測試結(jié)果
- 通過測試結(jié)果可以看到马篮,BeanNameAware中的setBeanName方法被調(diào)用衰伯,并且bean name就是我們在zpplicationContext中配置的Student 的id的屬性值,student
- 那現(xiàn)在有個(gè)疑問积蔚,為什么Student實(shí)現(xiàn)了BeanNameAware接口之后意鲸,setBeanName方法就會(huì)被調(diào)用?setBeanName什么時(shí)候被調(diào)用尽爆,調(diào)用之后為什么就能拿到beanName怎顾?不著急,我們繼續(xù)看
- ignoreDependencyInterface方法是干什么的漱贱?通過注釋我們可以知道,在自動(dòng)裝配的時(shí)候槐雾,忽略指定的接口依賴
/**
* Ignore the given dependency interface for autowiring.
* <p>This will typically be used by application contexts to register
* dependencies that are resolved in other ways, like BeanFactory through
* BeanFactoryAware or ApplicationContext through ApplicationContextAware.
* <p>By default, only the BeanFactoryAware interface is ignored.
* For further types to ignore, invoke this method for each type.
* @see org.springframework.beans.factory.BeanFactoryAware
* @see org.springframework.context.ApplicationContextAware
*/
public void ignoreDependencyInterface(Class<?> ifc) {
this.ignoredDependencyInterfaces.add(ifc);
}
- 我們順藤摸瓜,看看ignoredDependencyInterfaces集合在哪里被調(diào)用幅狮,如下代碼
根據(jù)注釋募强,一個(gè)bean的屬性,是否要從依賴排查中剔除崇摄,這句話什么意思呢擎值?
也就是說,我們一個(gè)bean的某個(gè)屬性逐抑,是否要被注入對(duì)應(yīng)的依賴鸠儿,還要看一下你這個(gè)屬性對(duì)應(yīng)的類是否實(shí)現(xiàn)了BeanNameAware、BeanFactoryAware、BeanClassLoaderAware這些接口
/**
* Determine whether the given bean property is excluded from dependency checks.
* <p>This implementation excludes properties defined by CGLIB and
* properties whose type matches an ignored dependency type or which
* are defined by an ignored dependency interface.
* @param pd the PropertyDescriptor of the bean property
* @return whether the bean property is excluded
* @see #ignoreDependencyType(Class)
* @see #ignoreDependencyInterface(Class)
*/
protected boolean isExcludedFromDependencyCheck(PropertyDescriptor pd) {
return (AutowireUtils.isExcludedFromDependencyCheck(pd) ||
this.ignoredDependencyTypes.contains(pd.getPropertyType()) ||
AutowireUtils.isSetterDefinedInInterface(pd, this.ignoredDependencyInterfaces));
}
我們繼續(xù)向下看AutowireUtils.isSetterDefinedInInterface()
在isSetterDefinedInInterface方法中
1.bean對(duì)應(yīng)的屬性进每,是否實(shí)現(xiàn)了BeanNameAware汹粤、BeanFactoryAware、BeanClassLoaderAware
2.bean屬性對(duì)應(yīng)的setter方法田晚,在這三個(gè)感知接口中是否也有相同的方法
如果滿足以上兩種情況嘱兼,isSetterDefinedInInterface就會(huì)返回true,spring在自動(dòng)裝配這個(gè)bean時(shí),就不會(huì)為這個(gè)屬性注入值
- 我們還是通過一個(gè)案例來看下
public class BeanNameAwareTest implements BeanNameAware {
private String beanName;
public String getBeanName() {
return beanName;
}
@Override
public void setBeanName(String beanName) {
this.beanName = beanName;
}
}
我們將屬性設(shè)置為beanName,且該類實(shí)現(xiàn)了BeanNameAware接口贤徒,這樣芹壕,setBeanName方法即是beanName的setter方法,且在感知接口中也有setBeanName方法
滿足了這兩個(gè)條件之后泞莉,spring就不會(huì)為bean beanNameAwareTest的屬性beanName注入任何值
<?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"
xmlns:aop="http://www.springframework.org/schema/aop"
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 https://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="beanNameAwareTest" class="org.springframework.jhxy.BeanNameAwareTest">
<property name="beanName" value="beanName"/>
</bean>
</beans>
我們?yōu)閷傩詁eanName設(shè)置了屬性值“beanName”,我們通過測試結(jié)果來看下“beanName”這個(gè)值能否注入到bean beanNameAwareTest對(duì)應(yīng)的beanName屬性中
public static void main(String[] args) {
XmlBeanFactory xmlBeanFactory =
new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));
BeanNameAwareTest beanNameAwareTest = (BeanNameAwareTest) xmlBeanFactory.getBean("beanNameAwareTest");
System.out.println(beanNameAwareTest.getBeanName());
}
測試結(jié)果
可以看到哪雕,我們設(shè)置的beanName的屬性并沒有變成"beanName",拿到的依舊是beanNameAwareTest鲫趁,所以說斯嚎,如果一個(gè)bean實(shí)現(xiàn)了BeanNameAware、BeanFactoryAware挨厚、BeanClassLoaderAware的話堡僻,并且想通過spring自動(dòng)裝配給屬性賦值,那么屬性的setter方法疫剃,就不能和感知接口中的setter方法相同钉疫。
spring這樣設(shè)計(jì),主要是想巢价,如果實(shí)現(xiàn)了BeanNameAware,對(duì)應(yīng)的beanName屬性值,就應(yīng)該是這個(gè)bean在spring容器中的名字牲阁,此時(shí),如果我們從外部xml或者注解中壤躲,注入一個(gè)新的bean的名稱城菊,spring默認(rèn)就會(huì)忽略掉外部注入的名稱,確保bean的名稱唯一
OK
我們接著向下看初始化spring容器的邏輯
- XmlBeanDefinitionReader如何加載資源,這里我們可以看到碉克,會(huì)將Resource封裝成EncodedResource
/**
* Load bean definitions from the specified XML file.
* @param resource the resource descriptor for the XML file
* @return the number of bean definitions found
* @throws BeanDefinitionStoreException in case of loading or parsing errors
*/
@Override
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
//將Resource封裝太EncodedResource
return loadBeanDefinitions(new EncodedResource(resource));
}
- 我們看下EncodedResource部分關(guān)鍵源碼
public class EncodedResource implements InputStreamSource {
//這里的resource是跟資源有關(guān)的
private final Resource resource;
//編碼
@Nullable
private final String encoding;
//字符集
@Nullable
private final Charset charset;
/**
* Create a new {@code EncodedResource} for the given {@code Resource},
* not specifying an explicit encoding or {@code Charset}.
* @param resource the {@code Resource} to hold (never {@code null})
*/
//構(gòu)造方法中凌唬,除了資源resource不為空,編碼和字符集都為空
public EncodedResource(Resource resource) {
this(resource, null, null);
}
/**
* Open a {@code java.io.Reader} for the specified resource, using the specified
* {@link #getCharset() Charset} or {@linkplain #getEncoding() encoding}
* (if any).
* @throws IOException if opening the Reader failed
* @see #requiresReader()
* @see #getInputStream()
*/
//通過上面的構(gòu)造方法可知漏麦,默認(rèn)charset和encoding為null客税,所以getReader(),根據(jù)資源獲取resource的輸入流
public Reader getReader() throws IOException {
if (this.charset != null) {
return new InputStreamReader(this.resource.getInputStream(), this.charset);
}
else if (this.encoding != null) {
return new InputStreamReader(this.resource.getInputStream(), this.encoding);
}
else {
return new InputStreamReader(this.resource.getInputStream());
}
}
// 省略部分代碼.....
- 接著loadBeanDefinitions繼續(xù)向下看
/**
* Load bean definitions from the specified XML file.
* @param encodedResource the resource descriptor for the XML file,
* allowing to specify an encoding to use for parsing the file
* @return the number of bean definitions found
* @throws BeanDefinitionStoreException in case of loading or parsing errors
*/
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
Assert.notNull(encodedResource, "EncodedResource must not be null");
if (logger.isTraceEnabled()) {
logger.trace("Loading XML bean definitions from " + encodedResource);
}
Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
if (!currentResources.add(encodedResource)) {
throw new BeanDefinitionStoreException(
"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
}
//獲取EncodedResource的輸入流
try (InputStream inputStream = encodedResource.getResource().getInputStream()) {
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
//正式開始加載資源
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"IOException parsing XML document from " + encodedResource.getResource(), ex);
}
finally {
currentResources.remove(encodedResource);
if (currentResources.isEmpty()) {
this.resourcesCurrentlyBeingLoaded.remove();
}
}
}
/**
* Actually load bean definitions from the specified XML file.
* @param inputSource the SAX InputSource to read from
* @param resource the resource descriptor for the XML file
* @return the number of bean definitions found
* @throws BeanDefinitionStoreException in case of loading or parsing errors
* @see #doLoadDocument
* @see #registerBeanDefinitions
*/
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
//將傳進(jìn)來的inputSource和resource,封裝成Document對(duì)象
Document doc = doLoadDocument(inputSource, resource);
//解析document對(duì)象撕贞,并將解析的bean注入到spring中
int count = registerBeanDefinitions(doc, resource);
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + count + " bean definitions from " + resource);
}
return count;
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (SAXParseException ex) {
throw new XmlBeanDefinitionStoreException(resource.getDescription(),
"Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex);
}
catch (SAXException ex) {
throw new XmlBeanDefinitionStoreException(resource.getDescription(),
"XML document from " + resource + " is invalid", ex);
}
catch (ParserConfigurationException ex) {
throw new BeanDefinitionStoreException(resource.getDescription(),
"Parser configuration exception parsing XML from " + resource, ex);
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(resource.getDescription(),
"IOException parsing XML document from " + resource, ex);
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(resource.getDescription(),
"Unexpected exception parsing XML document from " + resource, ex);
}
}
/**
* Actually load the specified document using the configured DocumentLoader.
* @param inputSource the SAX InputSource to read from
* @param resource the resource descriptor for the XML file
* @return the DOM Document
* @throws Exception when thrown from the DocumentLoader
* @see #setDocumentLoader
* @see DocumentLoader#loadDocument
*/
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
getValidationModeForResource(resource), isNamespaceAware());
}
spring在加載的過程中更耻,將XmlBeanDefinitionReader委托給DocumentLoader進(jìn)行加載
加載xml時(shí),將會(huì)按照xml規(guī)范和格式進(jìn)行加載xml中 的bean
結(jié)尾
-
受限于篇幅問題麻掸,我們下一篇筆記接著學(xué)習(xí)spring加載XmlBeadFactory