javascript
Spring web应用最大的败笔
第一篇 介紹下IOC DI
Spring主要是業務層框架,現在已經發展成為一個完整JavaEE開發框架,它的主要特點是IoC DI和AOP等概念的融合,強項在面向切面AOP。推出之初因為Ioc/AOP等新設計理念值得框架設計者學習,現在已經成為Java世界主流框架,從其2.0引入auto-wired自動配對以后,開發效率大大提高,SpringMVC以簡化和REST風格著稱。Struts + Spring + Hibernate 號稱SSH框架是JavaEE經典開發組合。Spring是于2003 年興起的一個輕量級的Java開源框架框架,由Rod Johnson 在其著作Expert One-On-One J2EE Development and Design中闡述的部分理念和原型衍生而來。Rod Johnson 對Java EE正統框架臃腫、低效、脫離現實的種種現狀提出了質疑,并積極尋求探索革新之道。Spring相比EJB,重新重視和定義了POJO.
每次執行invoke方法時,都要生成一個A對象,如果A對象代碼較長,這是費時的事
情。于是有如下寫法:
public class B{
A a = new A();
public void invoke(){
a.myMethod();
}
}
將A對象變成B的類屬性。 如果我們不想在B中實現A的實例,也就是不想立即new A(),而是想通過外界傳入, 注意,如果你想知道為什么,這里涉及到設計模式以及解耦等因素,可詳細參考 GoF 23 種設計模式。
如果想讓A的實例從外界傳入,有兩種寫法:
public class B{
A a;
public void setA(A a){
this.a = a;
}
public A getA(){
return a;
}
public void invoke(){
a.myMethod();
}
}
這種寫法,A并沒有被實例化,需要通過外界調用setA方法,將A的對象實例賦入B中. 或者通過B的構造函數傳入,如下:
public class B{
A a;
public B(A a){
this.a = a;
}
public void invoke(){
a.myMethod();
}
}
上述兩種寫法在編程中是經常發生的,B作為調用者,A是被調用者,A的實例化不在調用者B內部中完成,而是通過構造函數或setXXX方法賦值進來,這種方式我們稱為依賴 性注射(IoC 模式),B 和A 的依賴聯系是通過構造函數或setXXX 方法賦值進來,這樣, 最大程度解耦了調用者B和被調用者A之間的耦合聯系。Spring?IOC三種注入方式(接口注入、setter注入、構造器注入)
Spring如何實現依賴注射:上文提到,A的實例化不在調用者B內部中完成,而是通過構造函數或setXXX 方法賦 值進來,Spring實際就是完成這個賦值的過程。 為了讓Spring自動完成B代碼中的A的實例化,需要通過配置文件告訴Spring有關A 的類的屬性,這個配置是applicationContext.xml文件。 在 applicationContext.xml中,我們先定義JavaBeans為B的配置:
在 applicationContext.xml中,我們先定義JavaBeans為B的配置: <beans><bean id="b" class="springsimple.B"/> </beans> 這是最常用的JavaBeans的定義,id相當于對象名,當前文件應該是唯一,如果你有Jsp 編程經驗,這非常類似于Jsp里調用JavaBeans語句: <jsp:userBeans id="b" class="springsimple.B"/>再在applicationContext.xml定義A的配置如下: <beans><bean id="b" class="springsimple.B"/><bean id="a" class="springsimple.A"/> </beans> 這樣我們告訴Spring我們有兩個JavaBeans,現在解決關鍵問題,B代碼中還調用了A, 那么如何讓Spring將A的實例注射到B中?使用Spring配置的property語法。具體配置如下: <beans><bean id="b" class="springsimple.B"><property name="a"><ref local="a" /></property> <!— 增加這一行--> </bean><bean id="a" class="springsimple.A" /> </beans>增加一行說明:B 的屬性a 指向了a,這樣,Spring 會知道B 中屬性a 的實例就是 springsimple.A,在B實例化時將會將B中的a 實現實例化,這是通過setA方法注射進入。 注意,property name="a"中的a 是setA字符中去掉set 后的字符串,這個字符串第一個必須是小寫,例如,如果B中有setOneA方法,那么,配置文件應該是property name="oneA"。
在程序中調用 Spring 前面我們寫了兩個程序: B類和A類,并且寫了配置文件applicationContext.xml,下面我們就可以運行B類的invoke方法。
1. 首先,將Spring配置到Web容器中。Spring框架是運行在Web容器中,所以,我們需要配置Web容器的配置文件web.xml,告訴Web容器有關Spring的配置文件名,這里取為applicationContext.xml,當然可以取其他名稱,如下:
<web-app>
<display-name>WebModule1</display-name>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
</web-app>
除了配置/WEB-INF/applicationContext.xml 文件, 還需要加入一個ContextLoaderListener,用于加載Spring配置文件,并實現初始化工作。將Spring注冊(寄 存)到Web 容器,這樣,在一個Web 容器中,只有一個Spring 應用框架,我們可以借助 Spring框架實現Singleton全局。
在 Web 層應用程序中使用Spring。前面已經配置成功后,就可以在Web 層的Servlet或Jsp中調用訪問Spring了,如果你編制的是一個Servlet/Jsp 程序,那么在你的Servlet/Jsp 使用下面的代碼通過Spring 創建B 的實例:
如果你編制的是一個Servlet/Jsp 程序,那么在你的Servlet/Jsp 使用下面的代碼通過Spring 創建B 的實例:ServletContext servletContext =this.getServletContext(); WebApplicationContext appContext = WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext); B b = (B) appContext.getBean("b");
上面基本是兩句,首先獲得WebApplicationContext實例,然后從WebApplicationContext
實例中獲得B的實例。 如果你使用的是Web 層應用框架,如Struts或Tapestry,除了Spring說明提供特殊方便的調用方式,你需要從這個框架中獲得當前Servlet容器ServletContext實例,再調用上面代碼即可。
第二篇 回答幾個概念
????
先來看看Spring的框架結構。Spring是一個IOC和AOP的開發框架和平臺。Spring的特點:輕量,Spring?在大小和透明度上是輕量的,Spring基本核心版本大概只有1M,處理開銷也非常小。IoC:?Spring使用IOC實現松耦合,對象不是自己主動去尋找依賴而是依賴主動推給了自己。AOP :?Spring支持面向方面編程,從業務邏輯中分離關注,能夠實現聚焦開發。容器:?Spring?容器包含和管理應用對象的生命周期。框架 :?Spring提供最內核的功能,其余留給開發者自己開發。?
Spring的核心容器模塊:它提供基本功能的Spring框架。在此模塊中的BeanFactory是任何基于Spring的應用程序的心臟。整個框架是建立在本模塊的基礎之上。該模塊生成Spring容器。
applicationContext說明,應用程序上下文模塊使得Spring成為了一個框架。該模塊擴展了BeanFactory的概念,國際化(I18N)消息,應用程序生命周期事件,和驗證提供支持。此模塊還提供了許多的企業服務,例如JNDI訪問,EJB集成,遠程和調度。它還提供了其他框架的支持。?
Aop模塊:該AOP模塊用于Spring的應用程序開發方面。提供了大部分AOP聯盟的支持,以確保Spring和其它AOP框架之間的互操作性。此模塊還引入元數據編程。使用Spring的元數據支持我們的源代碼,通過引入元注釋,指導我們具體在哪里實現方面編程。
JDBC和DAO抽象模塊:使用這個模塊,我們可以保持干凈和簡單的數據庫的代碼,并防止失敗關閉數據庫資源等問題。基于多個數據庫服務器的出錯信息之上建立的一個新的有意義的異常層。此外,該模塊使用Spring的AOP模塊實現事務管理。
對象關系數據庫映射整合模塊:Spring支持使用一個對象/關系映射(ORM)工具,基于JDBC提供ORM模塊。配合一些流行的ORM框架,包括Hibernate,JDO和iBATIS SQL映射。 Spring的事務管理也支持這些ORM框架以及JDBC。
Web模塊:該模塊是建立在應用上下文模塊之上,提供了一個基于Web應用的上下文。此模塊還包含幾個面向Web的任務,比如多個文件上傳和將請求參數透明地綁定到你的業務對象。它也包含與Jakarta Struts的集成支持。
SpringMVC:Spring提供了一個全功能的構建Web應用程序的MVC框架。雖然Spring可以很容易地集成到其他的MVC框架,如Struts,Spring的MVC框架使用IOC將控制器邏輯從業務對象中干凈分離。它還允許您以聲明方式綁定請求參數到你的業務對象。它還可以利用Spring的其他服務,如本地化消息和驗證。
BeanFactory:一個BeanFactory是工廠模式的實現,使用反轉控制將從應用程序的配置和依賴性從應用程序代碼中分離。?Spring配置文件:Spring的配置文件是一個XML文件。此文件中包含的類信息,并描述這些類是如何配置,并實現相互的依賴導入。
Bean的生命周期:Spring容器從XML中發現一個Bean的定義,然后初始化創建它們 使用依賴注入,Spring將這個Bean中需要的屬性進行導入。 如果這個Bean實現 BeanNameAware 接口, 工廠將調用setBeanName() 傳遞Bean的ID。 如果bean實現BeanFactoryAware,工廠將調用 setBeanFactory(), 將自己的實例傳給它。 如果這個bean有 BeanPostProcessors 關聯,他們的post- ProcessBeforeInitialization()方法將被調用。 如果這個Bean有一個初始init方法,它將被調用。 最后,如果有關聯對象 BeanPostProcessors ,postProcessAfterInitialization()方法將被調用。一個簡單的Spring應用程序包括:這些應用程序像任何Java應用程序。它們是由多個類組成,每個類執行應用程序內的一個特定的功能。這些類的配置是通過一個XML文件向對方導入依賴。此XML文件描述如何配置類,稱為Spring配置文件。
第三篇 web應用的敗筆
開發人員在使用Spring應用是非常擅長談論依賴注入的好處。不幸的是,他們不是那么真的利用它的好處,如單一職責原則,分離關注原則。如果我們一起來看看大部分Spring的Web應用程序,常見的錯誤的設計如下:
1.領域模型對象用來存儲應用的數據(當作DTO使用),領域模型是貧血模型這樣的反模式。
2.服務層每個實體有一個服務。
問題是這樣很普遍,錯誤在哪里呢?
Spring的web應用程序之所以這樣是因為他們做事物的方式一直都是這樣做的,老習慣難改,特別是如果他們是高級開發人員或軟件架構師,這些人捍衛這樣做的論據之一是:我們的應用程序遵循關注分離的原則,因為它已經被分為若干層,每個層有自己的特定職責。
1. Web層負責處理用戶輸入,并返回正確的響應返回給用戶。 web層與服務層通信。
2.服務層作為一個事務邊界。它也負責授權和包含我們的應用程序的業務邏輯。服務層管理的域模型對象,并與其他服務和存儲庫層進行通信。
3.存儲庫/數據訪問層負責與所使用的數據的存儲進行通信。
分離關注(Soc)是分離計算機程序為不同的部分,每個部分有一個關注聚焦,一個典型的Spring Web應用在一定程度上遵循這一原則,但現實是,該應用程序有一個整體的服務層,它有太多的責任。更具體地,服務層有兩個主要問題:
1.在服務層發現業務邏輯。業務邏輯被分散在各個服務層。如果我們需要檢查一個業務規則是如何實現的,我們必須先找到它。這可能并不容易。此外,如果相同的業務規則需要在多個服務類,問題是,規則需要從一個服務到另一個簡單地復制。這將導致維護的噩夢。
2.每個領域模型一個服務。這完全違反了單一職責原則,它被定義為如下:單一職責原則指出,每一個類都應該有一個責任,責任應該由類完全封裝。其所有的服務應該狹義與責任相一致。(不應將原屬于領域模型的行為方法等劃放在服務中實現,對象不但有屬性還有行為)。
服務類有很多依賴,以及大量的循環依賴。更像網絡緊密耦合和單片服務。這使得很難理解,維護和重用。這聽起來有點苛刻,但一個Spring的web應用的服務層往往是最容易出問題的部分。幸運的是,所有的希望都不會丟失。
1. 我們必須將我們的應用程序的業務邏輯從服務層遷移到領域模型類中。
舉個例子:假設我是一個服務類,你是一個域模型對象。如果我讓你從屋頂上跳下來,你會喜歡我這樣的決定嗎?(跳下來會摔傷,自己沒有腦子或被洗腦,變成僵尸,只聽從執行,不思考自己的安全,這就是貧血模型的問題)
將業務邏輯從服務層遷移到域模型類有下面三個優勢:
(1)我們的代碼將以邏輯方式切割,服務層只要關注應用邏輯,而我們的領域模型關注業務邏輯。
(2)業務邏輯只存在一個地方,容易發現修改。
(3)服務層的源代碼是清潔的,不包含任何復制粘貼代碼
2. 將每個實體服務切割為單一目標的更小的服務。
比如,有一個單一服務類,提供對人員和用戶賬戶的CRUD操作,我們應該將它分為兩個獨立的服務類:
第一個是對人員的提供CRUD操作
第二個是提供與用戶賬戶相關的操作。
好處:每個服務類中有一個邏輯組職責。每個服務類的依賴較少,這意味著他們不再是緊耦合的源頭。他們是較小的和松耦合的組件。服務類更容易理解,維護和重用。
這兩個簡單的步驟將幫助我們使得我們的應用程序架構更干凈,有助于同行開發商提高生產力和幸福。
第四篇 日志
日志是一種簡單的不能再簡單的存儲抽象。它是一個只能增加的(?append),完全按照時間排序的一系列記錄。日志看起來如下:
?
只能給日志的末尾添加記錄(append 類似隊列),日志記錄是從左到右讀取的。每一條日志記錄都有一個唯一的序列編號(一般我們使用時間戳)。?日志記錄的排序是由"時間"決定,處于左邊的記錄比右邊的要早些。記錄編號可以看作是這條記錄的"時間戳"。當然剛開始我們就把這種排序說成是按時間排序顯得有點多余,不過,與任何一個具體的物理時鐘相比,時間屬性是非常便于使用的屬性。在多個分布式系統中,時間會非常重要。?日志在存儲空間完全耗盡的情況下,就不可能再給日志添加記錄。稍后我們將會提到這個問題。?日志只是按照時間順序存儲記錄的?一種數據表或者文件,可以表現為日志文件或日志數據庫。日志重要特點是:它記錄了在某個什么時間發生了什么事情。 而這對分布式數據系統而言才是問題的真正核心。不過,在我們更加深入的討論之前,先澄清有些讓人混淆的概念。每個編程人員都熟悉另一種日志:使用syslog或者log4j寫入到本地文件里的、沒有結構的、跟蹤信息或錯誤信息。為了將兩者區分開來,我們把這種日志稱為"應用日志"。應用日志是我所說日志中的一種低級變種。兩者最大的區別是:這些文本日志意味著主要用來方便人們閱讀,而我所說的"日志"或者"數據日志"是為了方便程序訪問。(實際上,如果你對它進行深入的思考,那么人們自己讀取某個機器上的日志這種理念有些不順應時代潮流。當涉及到許多服務和服務器的時候,這種方法很快就變成一個難于管理的。)
數據庫日志:日志在數據庫里的用法是:當數據庫崩潰的時候用日志來同步各種數據結構和索引。為了保證操作的原子性和持久性,在對數據庫進行維護,也就是所有各種數據結構做出更改之前,數據庫會把即將修改的信息備份追加到日志里。日志記錄了已經發生了什么,每個表或者索引都是其歷史投影。由于日志是即刻持久化的,所以在宕機時可以用來作為恢復的可信數據源。
隨著時間推移,日志用途從實現ACID發展為數據庫之間復制數據的一種方法。發生在數據庫上的操作動作順序與遠端備份數據庫上的操作順序通過日志保持完全同步。Oracle,MySQL 和PostgreSQL都是使用復制日志同步實現主從同步。Oracle還把日志產品化為一個通用的數據訂閱機制,MySQL和PostgreSQL有類似的實現則,日志已經成為許多數據結構的關鍵組件。正是由于這樣的起源,機器可識別的日志這個概念大部分時候還是都被局限在數據庫內部。日志用做數據訂閱的機制似乎是偶然出現,不過要把這種數據抽象用于支持所有類型的消息傳輸、數據流和實時數據處理也是可行的。
分布式系統日志:日志解決了兩個問題:操作動作的順序化和數據的分發,這兩個問題在分布式數據系統里顯得尤為重要。保持一致的操作動作的順序是分布式系統設計的核心問題之一。以日志為中心實現分布式系統是受到了一個簡單經驗常識的啟發,它稱為狀態機復制原理:如果兩個相同的確定的處理過程從同一狀態開始,以相同的順序輸入相同的(數據或事件),那么這兩個處理過程必然會產生相同的輸出(結果),并且在最后相同的狀態結束。這也許有點難以理解,讓我們更加深入的探討,弄懂它的真正含義。確定性意味著處理過程是與時間無關,而且任何其他"外部的"輸入不會影響到其處理結果。例如,如果一個程序的輸出會受到線程執行的具體順序影響,或者受到getTimeOfDay調用、或者其他一些非重復性事件的影響,那么這樣的程序一般被認為是非確定性的。處理狀態是處理過程保存在計算機上的任何數據(狀態),在處理過程結束后,這些狀態數據要么保存在內存里,要么保存在磁盤上。“以相同的順序獲得相同輸入”這個地方應當引起注意的是:這里就是引入日志的地方。這兒有一個重要的常識:如果給兩段確定性代碼相同的日志輸入,那么它們就會生成相同的輸出。分布式計算在這方面的應用格外明顯。你可以把用多臺機器一起執行同一件事情縮減為為這些進程輸入分布式一致性的日志數據。這里使用日志的目的是把所有非確定性的東西排除在輸入流之外,這樣來確保每個復制都能夠同步地處理輸入。當你理解了以后,狀態機復制原理就不再復雜或者說不再深奧了:它或多或少的意味著"確定性的處理過程就是確定性的"。不管怎樣,我都認為它是分布式系統設計里較常用的設計工具之一。這種方式的一個美妙之處就在于作為日志的索引的時間戳就像時鐘狀態的一個副本。你可以用一個單獨的數字描述每一個副本,這就是經過處理的日志的時間戳。時間戳與日志一一對應著整個副本的狀態。由于寫進日志的內容不同,也就會有許多在系統中應用這個原則的不同方式。舉個例子,我們可以記錄一個服務的請求,或者也可以記錄服務從請求到響應的狀態變化,或者它執行命令的轉換。理論上來說,我們甚至可以為每一個要執行的機器指令或者調用的方法名和參數實現一系列副本記錄。只要兩個處理過程用相同的方式處理這些輸入,這些處理過程就會保持副本的一致性。一千個人眼中有一千種日志的用法。數據庫工作者通常區分物理日志和邏輯日志。物理日志就是記錄每一行被改變的內容。邏輯日志記錄的不是改變的行,而是那些引起行的內容被改變的SQL語句(insert,update和delete語句)。分布式系統通常可以寬泛劃分為兩種方法來進行數據處理和復制。"狀態機器模型"通常使用一個active-active模型,在這個模型中我們保存了請求和該請求的復制處理。我們可以對"狀態機器模型"進行細微的更改,稱之為"預備份primary-backup模型",也就是選出一個副本做為leader,并允許它按照請求到達的時間來進行處理請求,并將該請求導致狀態的改變輸出到日志。其他的副本按照leader狀態改變的順序而應用執行這些改變,這樣他們之間就能達到同步,并能夠在leader失敗的時候接替leader的工作。
第五篇 HTTP 2.0說明
今年春節的時候,我們發現之前訪問www.baidu.com會跳轉到http://www.baidu.com。但是從這天起,訪問都跳轉到了https://www.baidu.com。http是基于http1.0協議的。https是基于http2.0協議的。使用了https之后,我們看看發生了些什么變化。
百度實現全站https這一技術的實現,有效避免網絡信息劫持。百度這次的全站https就是一種加密協議,即使在傳輸的過程中被截取,也是加密的形式,所以被截取的信息都是無用的數據。
?
百度的這次全站https以后發展下來,是不是百度要對收錄的網站進行更高的安全級別。當使用http的時候,你訪問網站的所有數據都是明文的,只要中間有人抓到數據包就能知道你要搜索的東西。換成https的,至少別人抓到你的數據包也不知道你搜索的是啥。(https起到了保護隱私的作用)
那么在性能方面:https多幾次握手,網絡耗時變長,用戶從http跳轉到https還要一點時間。機器性能:機器性能,https要多做一次RSA校驗。CDN:全國所有節點要支持https才行,另外,如果面對DDOS,https的解決方案也會復雜得多。
對周邊系統的影響:頁面里所有嵌入的資源都要改成https的,這些資源可能會來自不同的部門甚至不同的公司,包括:圖片、js、form表單等等,否則瀏覽器就會報警。手機百度這類使用了百度搜索服務的客戶端產品,也可能要修改。解決第三方網站看不到refer的問題。所有的開發、測試環境都要做https的升級。
使用https的收益:?最惡心的是泄露用戶數據,經常在網上看到說「我用百度搜了一個黃金,馬上就有人聯系我了」「我在百度上搜了一種病,馬上醫院就來電話了」。其實在搜索HTTPS很久之前,百度就做了搜索結果url加密。用戶搜索安全,被運營商強插廣告,甚至在正常結果前面插一條廣告。?
?
?
總結
以上是生活随笔為你收集整理的Spring web应用最大的败笔的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Spring Boot 乐观锁加锁失败
- 下一篇: C++11标准库 - array