spring的三大組件:node
1.bean:bean的定義,bean的建立已及對bean的解析web
2.context:給 spring 提供一個運行的環境(鏈接上下文)正則表達式
3.core:相似於utility類,定義了資源的訪問方式spring
接下來直接從代碼來看:express
1.啓動:app
(1)web.XML中配置spring(開發環境通常都是這種)jvm
(2) ApplicationContext手動加載(測試)ide
由於是我的實驗,因此我選擇第(2)種方式.性能
2.總流程:測試
讀取ClassPathXmlApplicationContext.xml 文件-->按照<bean>標籤經過bean工廠建立bean倉庫-->按照key值返回bean對象
這裏要提一點:倉庫是final修飾,只在第一次賦值,以後爲只讀狀態.可是StringBuffer和list能夠進行修改,由於綁定的是地址而不是值.
3.言歸正傳,咱們先來看一下他是怎麼讀取xml文件的:
(1)先來一個總的:
解讀:調用構造方法建立XmlBeanDefinitReader對象,調用方法加載資源獲取bean倉庫對象
接下來看他是怎麼加載資源的:
public XmlBeanDefinitionReader(ResourceLoader resourceLoader) { super(resourceLoader); }
XmlBeanDefinitionReader 實例化類的時候,由於繼承了抽象類AbstractBeanDefinitionReader
public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {}
因此他會在初始化的時候先初始化父類,建立一個Map做爲bean 倉庫.
protected AbstractBeanDefinitionReader(ResourceLoader resourceLoader) { this.registry = new HashMap<String, BeanDefinition>(); this.resourceLoader = resourceLoader; }
//獲取外部資源,把xml文件轉成輸入流
@Override public void loadBeanDefinitions(String location) throws Exception { InputStream inputStream = getResourceLoader().getResource(location).getInputStream(); doLoadBeanDefinitions(inputStream); }
protected void doLoadBeanDefinitions(InputStream inputStream) throws Exception { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder docBuilder = factory.newDocumentBuilder(); Document doc = docBuilder.parse(inputStream);//把輸入流包裝到 Document 類對象 // 註冊bean registerBeanDefinitions(doc); inputStream.close(); }
這裏提一下newInstance()方法,主要是與new的比較
new關鍵字是強類型的,效率相對較高。
newInstance()是弱類型的,效率相對較低。只能調用無參構造方法
例子: 既然使用newInstance()構造對象的地方經過new關鍵字也能夠建立對象,爲何又會使用newInstance()來建立對象呢?
假設定義了一個接口Door,開始的時候是用木門的,定義爲一個類WoodenDoor,在程序裏就要這樣寫 Door door = new WoodenDoor() 。假設後來生活條件提升,換爲自動門了,定義一個類AutoDoor,這時程序就要改寫爲 Door door = new AutoDoor() 。雖然只是改個標識符,若是這樣的語句特別多,改動仍是挺大的。因而出現了工廠模式,全部Door的實例都由DoorFactory提供,這時換一種門的時候,只須要把工廠的生產模式改一下,仍是要改一點代碼。
而若是使用newInstance(),則能夠在不改變代碼的狀況下,換爲另一種Door。具體方法是把Door的具體實現類的類名放到配置文件中,經過newInstance()生成實例。這樣,改變另一種Door的時候,只改配置文件就能夠了。示例代碼以下:
String className = 從配置文件讀取Door的具體實現類的類名;
Door door = (Door) Class.forName(className).newInstance();
再配合依賴注入的方法,就提升了軟件的可伸縮性、可擴展性。
public void registerBeanDefinitions(Document doc) { Element root = doc.getDocumentElement(); parseBeanDefinitions(root); }
//把bean轉換成beanDefinition對象
protected void parseBeanDefinitions(Element root) { NodeList nl = root.getChildNodes(); for (int i = 0; i < nl.getLength(); i++) { Node node = nl.item(i); if (node instanceof Element) { Element ele = (Element) node; processBeanDefinition(ele); } } }
//裝配bean,而且存庫裏面
protected void processBeanDefinition(Element ele) { String name = ele.getAttribute("id"); String className = ele.getAttribute("class"); BeanDefinition beanDefinition = new BeanDefinition(); processProperty(ele, beanDefinition); //把類實例化,而後放到beanDefinition裏面 beanDefinition.setBeanClassName(className); getRegistry().put(name, beanDefinition); }
//把屬性加到propertyValues,若是有ref(依賴其餘類),就把依賴類對象加到propertyValues
private void processProperty(Element ele, BeanDefinition beanDefinition) { NodeList propertyNode = ele.getElementsByTagName("property"); for (int i = 0; i < propertyNode.getLength(); i++) { Node node = propertyNode.item(i); if (node instanceof Element) { Element propertyEle = (Element) node; String name = propertyEle.getAttribute("name"); String value = propertyEle.getAttribute("value"); if (value != null && value.length() > 0) { beanDefinition.getPropertyValues().addPropertyValue(new PropertyValue(name, value)); } else { String ref = propertyEle.getAttribute("ref"); if (ref == null || ref.length() == 0) { throw new IllegalArgumentException("Configuration problem: <property> element for property '" + name + "' must specify a ref or value"); } BeanReference beanReference = new BeanReference(ref); beanDefinition.getPropertyValues().addPropertyValue(new PropertyValue(name, beanReference)); } } } }
這樣就建立了ioc容器了,他的主要思想就是服務剛啓動的時候把xml文件裏面的bean所對應的類加載(注意是加載,而不是實例化類對象)到一個類庫,類庫是一個map<String(類名),BeanDefinition(類定義).
4.按照類名獲取實例化類對象
先上一段測試代碼:
@Test
public void test() throws Exception {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("tinyioc.xml");
HelloWorldService helloWorldService = (HelloWorldService) applicationContext.getBean("helloWorldService");
helloWorldService.helloWorld();
}
咱們着重來看下getBean方法
@Override
public Object getBean(String name) throws Exception {
return beanFactory.getBean(name);
}
繼續往下走
public Object getBean(String name) throws Exception {
BeanDefinition beanDefinition = beanDefinitionMap.get(name);
if (beanDefinition == null) {
throw new IllegalArgumentException("No bean named " + name + " is defined");
}
Object bean = beanDefinition.getBean();
if (bean == null) {
bean = doCreateBean(beanDefinition);
bean = initializeBean(bean, name);
beanDefinition.setBean(bean);
}
return bean;
}
從上述代碼來看,spring 採用的是延遲加載,只要當類用到的時候纔會去調用doCreateBean(beanDefinition)方法去建立實例化類。
由於以前只是加載的類並沒實例化,因此調用方法實例化而且保存在beanDefinition 對象中
這就實現了根據類名返回實現類
AOP實現:
首先,在讀取xml文件的時候,會加載一些靜態常量參數,以下圖:
而後咱們來講aop攔截器的實現:
先上一段代碼:
從上面咱們看到,他說先會去匹配expression,而後再按照正則表達式去攔截相應的方法(代碼太長不貼了).
也就是說咱們已經找到切入點(joinPoint)了.
接下來咱們來看aop的織入器實現:
aop的實現是生成代理類,而後在將織入器織入,
aop 分爲兩種:靜態AOP和動態AOP
靜態織入:
a、原理:在編譯期,切面直接以字節碼形式編譯到目標字節碼文件中 ;
b、優勢:對系統性能無影響;
c、缺點:不夠靈活;
動態代理 :
a、原理:在運行期,目標類加載後,爲接口動態生成代理類。將切面織入到代理類中;
b、優勢:更靈活;
c、缺點:切入的關注點要實現接口;
Joinpoint:攔截點,如某個業務方法;
Pointcut:Jointpoint的表達式,表示攔截哪些方法。一個Pointcut對應多個Joinpoint;
Advice:要切入的邏輯。
Before Advice:在方法前切入;
After Advice:在方法後切入,拋出異常時也會切入;
After Returning Advice:在方法返回後切入,拋出異常不會切入;
After Throwing Advice:在方法拋出異常時切入;
Around Advice:在方法執行先後切入,能夠中斷或忽略原有流程的執行;
aop動態代理:
1. 在使用動態代理類時,咱們必須實現InvocationHandler接口
2.從代碼看
//生成代理類
public Object getProxy() {
return Proxy.newProxyInstance(getClass().getClassLoader(), advised.getTargetSource().getInterfaces(), this);
}
首先普及一下基礎知識:
getClassLoader():類加載器,實現了把.class文件從內存加載到jvm,xx. getClassLoader().getInstance()纔是返回的是類對象
而後對生成方法的參數進行分析:
這裏爲何要是用接口:
當調用代理類的方法時與調用被代理類的方法時在寫法上是沒有任何區別的,只有接口才能保證這種一致性。(這裏跟動態字節碼有關係,稍後回來更新)
3.當咱們執行被代理類的方法的時候,會由代理類去執行invoke()方法,而後能夠在方法執行前和執行後加入advice(織入邏輯).