Java EE
Spring
Spring的模塊大概分為6個。分別是:
- Core Container(Spring的核心)【重要】
- AOP(面向切面變成)【重要】
- Messaging(消息發送的支持)
- Data Access/Integration(數據訪問和集成)
- Web(主要是SpringWeb內容,包括MVC)【重要】
- Test(Spring測試支持,包含JUint等測試單元的支持)
- Instrumentation(設備支持,比如Tomcat的支持)
spring框架的優點
Spring是一個輕量級的DI和AOP容器框架,它的優點主要有以下幾點:
- Spring是一個非侵入式框架,其目標是使應用程序代碼對框架的依賴最小化,應用代碼可以在沒有Spring或者其他容器的情況下運行。
- Spring提供了一個一致的編程模型,使應用直接使用POJO(Plain Ordinary Java Object)開發,從而可以使運行環境隔離開來。
- Spring推動應用的設計風格向面向對象及面向接口編程轉變,提高了代碼的重用性和可測試性。
- Spring改進了結構體系的選擇,雖然作為應用平臺,Spring可以幫助我們選擇不同的技術實現,比如從Hibernate切換到其他的ORM工具,從Struts切換到Spring MVC,盡管我們通常不會這么做,但是我們在技術方案上選擇使用Spring作為應用平臺,Spring至少為我們提供了這種可能性的選擇,從而降低了平臺鎖定風險。
Spring的啟動過程
Spring用到的設計模式
- 單例模式:bean默認為單例模式
- 工廠模式:beanfactory就是簡單工廠模式的體現,用來創建對象實例
- 代理模式:AOP用到的jdk動態代理和CGLIB字節碼生成技術
- 模板方法:用來解決代碼重復問題,如RestTemplate,JmsTemplate,JpaTemplate。
- 觀察者模式:定義對象間一種一對多的依賴關系,當一個對象發生改變時,所有依賴他的對象都會被通知更新,如listener的實現
SpringMVC處理請求流程
springmvc和spring-boot區別
Spring MVC是基于Servlet的一個MVC框架,主要解決WEB開發的問題,但關于Spring的配置比較繁瑣;而SpringBoot是在Spring的基礎上做得一個擴展,Springboot的原則是:約定優于配置,可以極大地簡化了spring的配置流程。SpringBoot對比Spring的一些優點包括:
- 提供嵌入式容器支持
- 使用命令java -jar獨立運行jar
- 在外部容器中部署時,可以選擇排除依賴關系以避免潛在的jar沖突
- 部署時靈活指定配置文件的選項
- 用于集成測試的隨機端口生成
Spring 就像一個大家族,有眾多衍生產品例如 Boot,Security,JPA等等。但他們的基礎都是Spring 的 IOC 和 AOP,IOC提供了依賴注入的容器,而AOP解決了面向切面的編程,然后在此兩者的基礎上實現了其他衍生產品的高級功能;因為 Spring 的配置非常復雜,各種xml,properties處理起來比較繁瑣。于是為了簡化開發者的使用,Spring社區創造性地推出了Spring Boot,它遵循約定優于配置,極大降低了Spring使用門檻,但又不失Spring原本靈活強大的功能。
Springboot
- 定義:SpringBoot是由Pivotal團隊在2013年開始研發、2014年4月發布第一個版本的全新開源的輕量級框架。它基于Spring4.0設計,不僅繼承了Spring框架原有的優秀特性,而且還通過簡化配置來進一步簡化了Spring應用的整個搭建和開發過程。另外SpringBoot通過集成大量的框架使得依賴包的版本沖突,以及引用的不穩定性等問題得到了很好的解決。
- 特征
(1)可以創建獨立的Spring應用程序,并且基于其Maven或Gradle插件,可以創建可執行的JARs和WARs;
(2)內嵌Tomcat或Jetty等Servlet容器;
(3)提供自動配置的"starter"項目對象模型(POMS)以簡化Maven配置;
(4)盡可能自動配置Spring容器;
(5)提供準備好的特性,如指標、健康檢查和外部化配置;
(6)不需要XML配置。
- 策略:開箱即用和約定優于配置
- 開箱即用,Outofbox,是指在開發過程中,通過在MAVEN項目的pom文件中添加相關依賴包,然后使用對應注解來代替繁瑣的XML配置文件以管理對象的生命周期。這個特點使得開發人員擺脫了復雜的配置工作以及依賴的管理工作,更加專注于業務邏輯。
- 約定優于配置,Convention over configuration,是一種由SpringBoot本身來配置目標結構,由開發者在結構中添加信息的軟件設計范式。這一特點雖降低了部分靈活性,增加了BUG定位的復雜性,但減少了開發人員需要做出決定的數量,同時減少了大量的XML配置,并且可以將代碼編譯、測試和打包等工作自動化。
Spring中自動裝配的方式
- no:不進行自動裝配,手動設置Bean的依賴關系。
- byName:根據Bean的名字進行自動裝配。
- byType:根據Bean的類型進行自動裝配。
- constructor:類似于byType,不過是應用于構造器的參數,如果正好有一個Bean與構造器的參數類型相同則可以自動裝配,否則會導致錯誤。
- autodetect:如果有默認的構造器,則通過constructor的方式進行自動裝配,否則使用byType的方式進行自動裝配。
@Autowired 和@Resource
- 共同點:兩者都可以寫在字段和setter方法上。兩者如果都寫在字段上,那么就不需要再寫setter方法。
- 不同點
- @Autowired為Spring提供的注解,只按照byType注入。@Autowired注解是按照類型(byType)裝配依賴對象,默認情況下它要求依賴對象必須存在,如果允許null值,可以設置它的required屬性為false。如果我們想使用按照名稱(byName)來裝配,可以結合@Qualifier注解一起使用。
- @Resource默認按照byName自動注入。@Resource有兩個重要的屬性:name和type,而Spring將@Resource注解的name屬性解析為bean的名字,而type屬性則解析為bean的類型。所以,如果使用name屬性,則使用byName的自動注入策略,而使用type屬性時則使用byType自動注入策略。如果既不制定name也不制定type屬性,這時將通過反射機制使用byName自動注入策略。
Spring中Bean的作用域
Bean的生命周期
如上圖所示,Bean 的生命周期還是比較復雜的,下面來對上圖每一個步驟做文字描述:
- Spring啟動,查找并加載需要被Spring管理的bean,進行Bean的實例化
- Bean實例化后對將Bean的引入和值注入到Bean的屬性中
- 如果Bean實現了BeanNameAware接口的話,Spring將Bean的Id傳遞給setBeanName()方法
- 如果Bean實現了BeanFactoryAware接口的話,Spring將調用setBeanFactory()方法,將BeanFactory容器實例傳入
- 如果Bean實現了ApplicationContextAware接口的話,Spring將調用Bean的setApplicationContext()方法,將bean所在應用上下文引用傳入進來。
- 如果Bean實現了BeanPostProcessor接口,Spring就將調用他們的postProcessBeforeInitialization()方法。
- 如果Bean 實現了InitializingBean接口,Spring將調用他們的afterPropertiesSet()方法。類似的,如果bean使用init-method聲明了初始化方法,該方法也會被調用
- 如果Bean 實現了BeanPostProcessor接口,Spring就將調用他們的postProcessAfterInitialization()方法。
- 此時,Bean已經準備就緒,可以被應用程序使用了。他們將一直駐留在應用上下文中,直到應用上下文被銷毀。
- 如果bean實現了DisposableBean接口,Spring將調用它的destory()接口方法,同樣,如果bean使用了destory-method 聲明銷毀方法,該方法也會被調用。
Spring Bean的加載過程
- 獲取 BeanName,對傳入的 name 進行解析,轉化為可以從 Map 中獲取到 BeanDefinition 的 bean name。
- 合并 Bean 定義,對父類的定義進行合并和覆蓋,如果父類還有父類,會進行遞歸合并,以獲取完整的 Bean 定義信息。
- 實例化,使用構造或者工廠方法創建 Bean 實例。
- 屬性填充,尋找并且注入依賴,依賴的 Bean 還會遞歸調用 getBean 方法獲取。
- 初始化,調用自定義的初始化方法。
- 獲取最終的 Bean,如果是 FactoryBean 需要調用 getObject 方法,如果需要類型轉換調用 TypeConverter 進行轉化。
IOC和DI
IoC叫控制反轉,是Inversion of Control的縮寫,DI(Dependency Injection)叫依賴注入,是對IoC更簡單的詮釋。控制反轉是把傳統上由程序代碼直接操控的對象的調用權交給容器,通過容器來實現對象組件的裝配和管理。所謂的"控制反轉"就是對組件對象控制權的轉移,從程序代碼本身轉移到了外部容器,由容器來創建對象并管理對象之間的依賴關系。
依賴注入的基本原則是應用組件不應該負責查找資源或者其他依賴的協作對象。配置對象的工作應該由容器負責,查找資源的邏輯應該從應用組件的代碼中抽取出來,交給容器來完成。DI是對IoC更準確的描述,即組件之間的依賴關系由容器在運行期決定,形象的來說,即由容器動態的將某種依賴關系注入到組件之中。
一個類A需要用到接口B中的方法,那么就需要為類A和接口B建立關聯或依賴關系,最原始的方法是在類A中創建一個接口B的實現類C的實例,但這種方法需要開發人員自行維護二者的依賴關系,也就是說當依賴關系發生變動的時候需要修改代碼并重新構建整個系統。如果通過一個容器來管理這些對象以及對象的依賴關系,則只需要在類A中定義好用于關聯接口B的方法(構造器或setter方法),將類A和接口B的實現類C放入容器中,通過對容器的配置來實現二者的關聯。
依賴注入可以通過setter方法注入(設值注入)、構造器注入和接口注入三種方式來實現,Spring支持setter注入和構造器注入,通常使用構造器注入來注入必須的依賴關系,對于可選的依賴關系,則setter注入是更好的選擇,setter注入需要類提供無參構造器或者無參的靜態工廠方法來創建對象。
- 實現IOC的步驟
- 定義用來描述bean的配置的Java類
- 解析bean的配置,將bean的配置信息轉換為BeanDefinition對象保存在內存中,spring中采用HashMap進行對象存儲,其中會用到一些xml解析技術
- 遍歷存放BeanDefinition的HashMap對象,逐條取出BeanDefinition對象,獲取bean的配置信息,利用Java的反射機制實例化對象,將實例化后的對象保存在另外一個Map中即可。
AOP
AOP(Aspect Oriented Programming),即面向切面編程,可以說是OOP(Object Oriented Programming,面向對象編程)的補充和完善。OOP引入封裝、繼承、多態等概念來建立一種對象層次結構,用于模擬公共行為的一個集合。不過OOP允許開發者定義縱向的關系,但并不適合定義橫向的關系,例如日志功能。日志代碼往往橫向地散布在所有對象層次中,而與它對應的對象的核心功能毫無關系對于其他類型的代碼,如安全性、異常處理和透明的持續性也都是如此,這種散布在各處的無關的代碼被稱為橫切(cross cutting),在OOP設計中,它導致了大量代碼的重復,而不利于各個模塊的重用。
AOP技術恰恰相反,它利用一種稱為"橫切"的技術,剖解開封裝的對象內部,并將那些影響了多個類的公共行為封裝到一個可重用模塊,并將其命名為"Aspect",即切面。所謂"切面",簡單說就是那些與業務無關,卻為業務模塊所共同調用的邏輯或責任封裝起來,便于減少系統的重復代碼,降低模塊之間的耦合度,并有利于未來的可操作性和可維護性。使用"橫切"技術,AOP把軟件系統分為兩個部分:核心關注點和橫切關注點。業務處理的主要流程是核心關注點,與之關系不大的部分是橫切關注點。橫切關注點的一個特點是,他們經常發生在核心關注點的多處,而各處基本相似,比如權限認證、日志、事務。AOP的作用在于分離系統中的各種關注點,將核心關注點和橫切關注點分離開來。
Spring AOP中的動態代理主要有兩種方式,JDK動態代理和CGLIB字節碼生成技術。JDK動態代理通過反射來接收被代理的類,并且要求被代理的類必須實現一個接口。JDK動態代理的核心是InvocationHandler接口和Proxy類。CGLIB(Code Generation Library),是一個代碼生成的類庫,可以在運行時動態的生成某個類的子類,注意,CGLIB是通過繼承的方式做的動態代理,因此如果某個類被標記為final,那么它是無法使用CGLIB做動態代理的。
- 動態代理應用場景:
- 統計每個 api 的請求耗時
- 統一的日志輸出
- 校驗被調用的 api 是否已經登錄和權限鑒定
- Spring的 AOP 功能模塊就是采用動態代理的機制來實現切面編程
- JDK是基于反射機制,生成一個實現代理接口的匿名類,然后重寫方法,實現方法的增強。它生成類的速度很快,但是運行時因為是基于反射,調用后續的類操作會很慢,而且他是只能針對接口編程的。
- CGLIB是基于繼承機制,繼承被代理類,所以方法不要聲明為final,然后重寫父類方法達到增強了類的作用。它底層是基于asm第三方框架,是對代理對象類的class文件加載進來,通過修改其字節碼生成子類來處理。生成類的速度慢,但是后續執行類的操作時候很快。可以針對類和接口。
- 因為jdk是基于反射,CGLIB是基于字節碼。所以性能上會有差異。在老版本CGLIB的速度是JDK速度的10倍左右,但是CGLIB啟動類比JDK慢8倍左右,但是實際上JDK的速度在版本升級的時候每次都提高很多性能,而CGLIB仍止步不前。在對JDK動態代理與CGlib動態代理的代碼實驗中看,1W次執行下,JDK7及8的動態代理性能比CGlib要好20%左右。
- 具體應用:
- 如果目標對象實現了接口,默認情況下是采用JDK動態代理實現AOP。
- 如果目標對象沒有實現接口,必須采用CGLIB庫。
動態代理和靜態代理的區別
代理模式是常用的Java設計模式,它的特征是代理類與委托類有同樣的接口,代理類主要負責為委托類預處理消息、過濾消息、把消息轉發給委托類,以及事后處理消息等。
- 代理模式的好處:
- 可以使真實角色的操作更加純粹,不用去關心一些公共業務
- 公共業務交給了代理角色,實現了業務的分工
- 公共業務發生擴展的時候方便集中管理
- 缺點:一個真實角色就會產生一個代理角色,代碼量翻倍
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy;
public class ProxyInvocationHandler implements InvocationHandler { //被代理的接口 private Object target;
public void setTarget(Object target) { this.target = target; }
public Object getProxy(){ return Proxy.newProxyInstance(this.getClass().getClassLoader(), this.target.getClass().getInterfaces(), this); } //處理代理實例,并返回結果 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { log(method.getName()); Object res= method.invoke(target,args); //fare(); return res; }
public void log(String msg){ System.out.println("執行了"+msg+"方法"); } } |
Spring中BeanFactory和ApplicationContext的區別
- BeanFactory是spring中比較原始,比較古老的Factory,所以BeanFactory無法支持spring插件,例如:AOP、Web應用等功能。
- ApplicationContext是BeanFactory的子類,因為BeanFactory無法滿足不斷更新的spring的需求,于是ApplicationContext就基本上代替了BeanFactory的工作,以一種更面向框架的工作方式以及對上下文進行分層和實現繼承
- 區別:
- 如果使用ApplicationContext,如果配置的bean是singleton,那么不管你有沒有或想不想用它,它都會被實例化。好處是可以預先加載,壞處是浪費內存。
- BeanFactory,當使用BeanFactory實例化對象時,配置的bean不會馬上被實例化,而是等到你使用該bean的時候(getBean)才會被實例化。好處是節約內存,壞處是速度比較慢。多用于移動設備的開發。
- 沒有特殊要求的情況下,應該使用ApplicationContext完成。因為BeanFactory能完成的事情,ApplicationContext都能完成,并且提供了更多接近現在開發的功能。
Spring中BeanFactory和FactoryBean的區別
BeanFactory是個Factory,也就是IOC容器或對象工廠,FactoryBean是個Bean。在Spring中,所有的Bean都是由BeanFactory(也就是IOC容器)來進行管理的。但對FactoryBean而言,這個Bean不是簡單的Bean,而是一個能生產或者修飾對象生成的工廠Bean,它的實現與設計模式中的工廠模式和修飾器模式類似
Spring支持的事務管理類型
Spring支持編程式事務管理和聲明式事務管理。許多Spring框架的用戶選擇聲明式事務管理,因為這種方式和應用程序的關聯較少,因此更加符合輕量級容器的概念。聲明式事務管理要優于編程式事務管理,盡管在靈活性方面它弱于編程式事務管理,因為編程式事務允許你通過代碼控制業務。
事務分為全局事務和局部事務。全局事務由應用服務器管理,需要底層服務器JTA支持(如WebLogic、WildFly等)。局部事務和底層采用的持久化方案有關,例如使用JDBC進行持久化時,需要使用Connetion對象來操作事務;而采用Hibernate進行持久化時,需要使用Session對象來操作事務。
這些事務的父接口都是PlatformTransactionManager。Spring的事務管理機制是一種典型的策略模式,PlatformTransactionManager代表事務管理接口,該接口定義了三個方法,該接口并不知道底層如何管理事務,但是它的實現類必須提供getTransaction()方法(開啟事務)、commit()方法(提交事務)、rollback()方法(回滾事務)的多態實現,這樣就可以用不同的實現類代表不同的事務管理策略。使用JTA全局事務策略時,需要底層應用服務器支持,而不同的應用服務器所提供的JTA全局事務可能存在細節上的差異,因此實際配置全局事務管理器是可能需要使用JtaTransactionManager的子類,如:WebLogicJtaTransactionManager(Oracle的WebLogic服務器提供)、UowJtaTransactionManager(IBM的WebSphere服務器提供)等。
Spring事務的傳播級別
Spring MVC注解的優點
- XML配置起來有時候冗長,此時注解可能是更好的選擇,如jpa的實體映射;注解在處理一些不變的元數據時有時候比XML方便的多,比如springmvc的數據綁定,如果用xml寫的代碼會多的多;
- 注解最大的好處就是簡化了XML配置;其實大部分注解一旦確定后很少會改變,所以在一些中小項目中使用注解反而提供了開發效率;
- 注解相對于XML的另一個好處是類型安全的,XML只能在運行期才能發現問題。
Java注解實現
Java EE下用Spring boot框架后,開始面向注解編程,可以說Spring boot就是建立在注解之上。
- Java 注解(Annotation)又稱 Java 標注,是JDK5.0引入的一種注釋機制。 注解是元數據的一種形式,提供有關于程序但不屬于程序本身的數據。注解對它們注解的代碼的操作沒有直接影響。
- Annotation(注解)就是Java提供了一種元程序中的元素關聯任何信息和著任何元數據(metadata)的途徑和方法。Annotion(注解)是一個接口,程序可以通過反射來獲取指定程序元素的Annotion對象,然后通過Annotion對象來獲取注解里面的元數據。
注解可以實現以下功能:
- 生成文檔。這是最常見的,也是java 最早提供的注解。常用的有@param @return 等
- 跟蹤代碼依賴性,實現替代配置文件功能。比如Dagger 2依賴注入,未來java開發,將大量注解配置,具有很大用處;
- 在編譯時進行格式檢查。如@override 放在方法前,如果你這個方法并不是覆蓋了超類方法,則編譯時就能檢查出。
/*
*定義一個注解
*/
@Target({ElementType.TYPE,ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Length {
int max();
int min();
String errorMsg();
}
/*
*使用上面的注解
*/
class Teacher {
private Long id;
private String name;
@Length(min =11, max =11, errorMsg ="傳入的手機號長度有誤,必須為11位")
private String mobilephone;
}
Spring典型注解
@Controller,@Service,@Repository都有帶@Component父注解,四個注解都可以說成是Component級別的注解,Spring框架自動掃描的注解也是檢測是否有Component注解標記。把普通pojo實例化到spring容器中,相當于配置文件中的<bean id="" class=""/>。
- @Component泛指組件,當組件不好歸類的時候,我們可以使用這個注解進行標注。
- @Repository 用于持久層,數據庫訪問層。當Service需要使用Spring創建的名字叫"userDao"的UserDaoImpl實例時,就可以使用@Resource(name = "userDao")注解告訴Spring,Spring把創建好的userDao注入給Service即可。
- @Service 用于服務層,處理業務邏輯。@Service("userService")注解是告訴Spring,當Spring要創建UserServiceImpl的的實例時,bean的名字必須叫做"userService",這樣當Action需要使用UserServiceImpl的的實例時,就可以由Spring創建好的"userService",然后注入給Action。
- @Controller 用于控制層,用于標記在一個類上,使用它標記的類就是一個SpringMVC Controller 對象。分發處理器將會掃描使用了該注解的類的方法。通俗來說,被Controller標記的類就是一個控制器,這個類中的方法,就是相應的動作。
@Controller和@RestController
@RestController注解相當于@ResponseBody + @Controller合在一起的作用
1) 如果只是使用@RestController注解Controller,則Controller中的方法無法返回jsp頁面,或者html,配置的視圖解析器 InternalResourceViewResolver不起作用,返回的內容就是Return 里的內容。
2) 如果需要返回到指定頁面,則需要用 @Controller配合視圖解析器InternalResourceViewResolver才行。
如果需要返回JSON,XML或自定義mediaType內容到頁面,則需要在對應的方法上加上@ResponseBody注解。
SpringMVC的Controller是單例還是多例,有沒有并發安全問題,如何解決?
- Controller默認是單例,如果定義了成員變量就有并發問題,單例是不安全的,會導致屬性重復使用。可以通過@Scope("prototype")設置為多例模式。
- 解決方案
- 不要在controller中定義成員變量。
- 萬一必須要定義一個非靜態成員變量時候,則通過注解@Scope("prototype"),將其設置為多例模式。
- 在Controller中使用ThreadLocal變量
Bean在什么時候被實例化?
- 對于Prototype:在第一次請求的時候才被實例化的
- 對于Singleton:
- 一般在IoC容器啟動的時候就被實例化,然后被緩存在內存中
- 如果bean標簽中有設置lazy-init=true,則會在第一次請求時才會被實例化,而不是在容器啟動的時候就被實例化
- 但是,當一個懶實例化的Bean依賴了一個非懶實例化的Bean,那么IOC容器在啟動的時候也會實例化這個Bean,因為它必須滿足單例的依賴性
浙公網安備 33010602011771號