Spring知識(shí)點(diǎn)回顧 IOC、AOP、Bean實(shí)例化、生命周期、攔截器、事務(wù)、Spring MVC流程-《Java EE 企業(yè)級(jí)應(yīng)用開(kāi)發(fā)教程》讀書(shū)筆記

博客遷移至:https://blog.csdn.net/wangshihuidev

前言

本文主要內(nèi)容包含如下:


image.png

內(nèi)容來(lái)自于《Java EE 企業(yè)級(jí)應(yīng)用開(kāi)發(fā)教程》這本書(shū)的翻讀筆記,內(nèi)容相對(duì)簡(jiǎn)單,用于基礎(chǔ)知識(shí)的復(fù)習(xí)鞏固。

Spring的核心容器

Spring框架提供了兩種核心容器,分別為BeanFactory和ApplicationContext。

知識(shí)點(diǎn):BeanFactory和ApplicationContext的區(qū)別是什么?

BeanFactory

BeanFactoryBeanFactory由org.springframework.beans.facytory.BeanFactory接口定義,是基礎(chǔ)類(lèi)型的IoC容器,它提供了完整的IoC服務(wù)支持。簡(jiǎn)單來(lái)說(shuō),BeanFactory就是一個(gè)管理Bean的工廠(chǎng),它主要負(fù)責(zé)初始化各種Bean,并調(diào)用它們的生命周期方法。BeanFactory接口提供了幾個(gè)實(shí)現(xiàn)類(lèi),其中最常用的是org.springframework.beans. factory.xml.XmlBeanFactory,該類(lèi)會(huì)根據(jù)XML配置文件中的定義來(lái)裝配Bean。創(chuàng)建BeanFactory實(shí)例時(shí),需要提供Spring所管理容器的詳細(xì)配置信息,這些信息通常采用XML文件形式來(lái)管理,其加載配置信息的語(yǔ)法如下。


BeanFactory beanFactory =new XmlBeanFactory(new FileSystemResource("F: /applicationContext.xml"));

ApplicationContext

ApplicationContext是BeanFactory的子接口,也被稱(chēng)為應(yīng)用上下文,是另一種常用的Spring核心容器。它由org.springframework.context. ApplicationContext接口定義,不僅包含了BeanFactory的所有功能,還添加了對(duì)國(guó)際化、資源訪(fǎng)問(wèn)、事件傳播等方面的支持。創(chuàng)建ApplicationContext接口實(shí)例,通常采用兩種方法,具體如下。
1.通過(guò)ClassPathXmlApplicationContext創(chuàng)建
2.通過(guò)FileSystemXmlApplicationContext創(chuàng)建

在使用Spring框架時(shí),可以通過(guò)實(shí)例化其中任何一個(gè)類(lèi)來(lái)創(chuàng)建ApplicationContext容器。通常在Java項(xiàng)目中,會(huì)采用通過(guò)ClassPathXmlApplicationContext類(lèi)來(lái)實(shí)例化ApplicationContext容器的方式,而在Web項(xiàng)目中,ApplicationContext容器的實(shí)例化工作會(huì)交由Web服務(wù)器來(lái)完成。Web服務(wù)器實(shí)例化ApplicationContext容器時(shí),通常會(huì)使用基于ContextLoaderListener實(shí)現(xiàn)的方式,此種方式只需要在web.xml中添加如下代碼。

<! --  指定Spring配置文件的位置,多個(gè)配置文件時(shí),以逗號(hào)分隔-->
<context-param>    
  <param-name>contextConfigLocation</param-name>    
  <! --  Spring將加載spring目錄下的applicationContext.xml文件 -->    
  <param-value>classpath:spring/applicationContext.xml    
  </param-value>
</context-param>

BeanFactory和ApplicationContext兩種容器都是通過(guò)XML配置文件加載Bean的。二者的主要區(qū)別在于,如果Bean的某一個(gè)屬性沒(méi)有注入,使用BeanFacotry加載后,在第一次調(diào)用getBean()方法時(shí)會(huì)拋出異常,而ApplicationContext則在初始化時(shí)自檢,這樣有利于檢查所依賴(lài)屬性是否注入。因此,在實(shí)際開(kāi)發(fā)中,通常都優(yōu)先選擇使用ApplicationContext,而只有在系統(tǒng)資源較少時(shí),才考慮使用BeanFactory。

控制翻轉(zhuǎn)和依賴(lài)注入

控制翻轉(zhuǎn)概念

在使用Spring框架之后,對(duì)象的實(shí)例不再由調(diào)用者來(lái)創(chuàng)建,而是由Spring容器來(lái)創(chuàng)建,Spring容器會(huì)負(fù)責(zé)控制程序之間的關(guān)系,而不是由調(diào)用者的程序代碼直接控制。這樣,控制權(quán)由應(yīng)用代碼轉(zhuǎn)移到了Spring容器,控制權(quán)發(fā)生了反轉(zhuǎn),這就是Spring的控制反轉(zhuǎn)。


image.png

依賴(lài)注入方式

依賴(lài)注入的實(shí)現(xiàn)方式依賴(lài)注入的作用就是在使用Spring框架創(chuàng)建對(duì)象時(shí),動(dòng)態(tài)地將其所依賴(lài)的對(duì)象注入Bean組件中,其實(shí)現(xiàn)方式通常有兩種,一種是屬性setter方法注入,另一種是構(gòu)造方法注入,具體介紹如下。

  • 屬性setter方法注入:指IoC容器使用setter方法注入被依賴(lài)的實(shí)例。通過(guò)調(diào)用無(wú)參構(gòu)造器或無(wú)參靜態(tài)工廠(chǎng)方法實(shí)例化Bean后,調(diào)用該Bean的setter方法,即可實(shí)現(xiàn)基于setter方法的依賴(lài)注入。
  • 構(gòu)造方法注入:指IoC容器使用構(gòu)造方法注入被依賴(lài)的實(shí)例?;跇?gòu)造方法的依賴(lài)注入通過(guò)調(diào)用帶參數(shù)的構(gòu)造方法來(lái)實(shí)現(xiàn),每個(gè)參數(shù)代表著一個(gè)依賴(lài)。

Bean的實(shí)例化

Bean的實(shí)例化在面向?qū)ο蟮某绦蛑校胍褂媚硞€(gè)對(duì)象,就需要先實(shí)例化這個(gè)對(duì)象。同樣,在Spring中,要想使用容器中的Bean,也需要實(shí)例化Bean。實(shí)例化Bean有三種方式,分別為:

  1. 構(gòu)造器實(shí)例化(最常用)
  2. 靜態(tài)工廠(chǎng)方式實(shí)例化
  3. 實(shí)例工廠(chǎng)方式實(shí)例化。
構(gòu)造器實(shí)例化
 <bean id="bean1" class="com.itheima.instance.constructor.Bean1" />
靜態(tài)工廠(chǎng)方式實(shí)例化

靜態(tài)工廠(chǎng)方式實(shí)例化使用靜態(tài)工廠(chǎng)是實(shí)例化Bean的另一種方式。該方式要求開(kāi)發(fā)者創(chuàng)建一個(gè)靜態(tài)工廠(chǎng)的方法來(lái)創(chuàng)建Bean的實(shí)例,其Bean配置中的class屬性所指定的不再是Bean實(shí)例的實(shí)現(xiàn)類(lèi),而是靜態(tài)工廠(chǎng)類(lèi),同時(shí)還需要使用factory-method屬性來(lái)指定所創(chuàng)建的靜態(tài)工廠(chǎng)方法。

<bean id="bean2"            
class="com.itheima.instance.static_factory.MyBean2Factory"           
factory-method="createBean" />
</beans>

在上述配置文件中,首先通過(guò)<bean>元素的id屬性定義了一個(gè)名稱(chēng)為bean2的Bean,然后由于使用的是靜態(tài)工廠(chǎng)方法,所以需要通過(guò)class屬性指定其對(duì)應(yīng)的工廠(chǎng)實(shí)現(xiàn)類(lèi)為MyBean2Factory。由于這種方式配置Bean后,Spring容器不知道哪個(gè)是所需要的工廠(chǎng)方法,所以增加了factory-method屬性來(lái)告訴Spring容器,其方法名稱(chēng)為createBean。

實(shí)例工廠(chǎng)方式實(shí)例化

實(shí)例工廠(chǎng)方式實(shí)例化還有一種實(shí)例化Bean的方式就是采用實(shí)例工廠(chǎng)。此種方式的工廠(chǎng)類(lèi)中,不再使用靜態(tài)方法創(chuàng)建Bean實(shí)例,而是采用直接創(chuàng)建Bean實(shí)例的方式。同時(shí),在配置文件中,需要實(shí)例化的Bean也不是通過(guò)class屬性直接指向的實(shí)例化類(lèi),而是通過(guò)factory-bean屬性指向配置的實(shí)例工廠(chǎng),然后使用factory-method屬性確定使用工廠(chǎng)中的哪個(gè)方法。

<! -- 配置工廠(chǎng) -->
<bean id="myBean3Factory"8            
class="com.itheima.instance.factory.MyBean3Factory" />
<! -- 使用factory-bean屬性指向配置的實(shí)例工廠(chǎng),
使用factory-method屬性確定使用工廠(chǎng)中的哪個(gè)方法-->
<bean id="bean3" factory-bean="myBean3Factory"
factory-method="createBean" />

在上述配置文件中,首先配置了一個(gè)工廠(chǎng)Bean,然后配置了需要實(shí)例化的Bean。在id為bean3的Bean中,使用factory-bean屬性指向配置的實(shí)例工廠(chǎng),該屬性值就是工廠(chǎng)Bean的id。使用factory-method屬性來(lái)確定使用工廠(chǎng)中的createBean()方法。

Bean的生命周期

Spring容器可以管理singleton作用域的Bean的生命周期,在此作用域下,Spring能夠精確地知道該Bean何時(shí)被創(chuàng)建,何時(shí)初始化完成以及何時(shí)被銷(xiāo)毀。對(duì)于prototype作用域的Bean, Spring只負(fù)責(zé)創(chuàng)建,當(dāng)容器創(chuàng)建了Bean實(shí)例后,Bean的實(shí)例就交給客戶(hù)端代碼來(lái)管理,Spring容器將不再跟蹤其生命周期。每次客戶(hù)端請(qǐng)求prototype作用域的Bean時(shí),Spring容器都會(huì)創(chuàng)建一個(gè)新的實(shí)例,并且不會(huì)管那些被配置成prototype作用域的Bean的生命周期。

了解Bean的生命周期的意義就在于,可以在某個(gè)Bean生命周期的某些指定時(shí)刻完成一些相關(guān)操作。這種時(shí)刻可能有很多,但在一般情況下,常會(huì)在Bean的postinitiation(初始化后)和predestruction(銷(xiāo)毀前)執(zhí)行一些相關(guān)操作。

image.png

Bean的生命周期的整個(gè)執(zhí)行過(guò)程描述如下。
(1)根據(jù)配置情況調(diào)用Bean構(gòu)造方法或工廠(chǎng)方法實(shí)例化Bean。
(2)利用依賴(lài)注入完成Bean中所有屬性值的配置注入。
(3)如果Bean實(shí)現(xiàn)了BeanNameAware接口,則Spring調(diào)用Bean的setBeanName()方法傳入當(dāng)前Bean的id值。
(4)如果Bean實(shí)現(xiàn)了BeanFactoryAware接口,則Spring調(diào)用setBeanFactory()方法傳入當(dāng)前工廠(chǎng)實(shí)例的引用。
(5)如果Bean實(shí)現(xiàn)了ApplicationContextAware接口,則Spring調(diào)用setApplicationContext()方法傳入當(dāng)前ApplicationContext實(shí)例的引用。
(6)如果BeanPostProcessor和Bean關(guān)聯(lián),則Spring將調(diào)用該接口的預(yù)初始化方法postProcessBeforeInitialzation()對(duì)Bean進(jìn)行加工操作,這個(gè)非常重要,Spring的AOP就是用它實(shí)現(xiàn)的。
(7)如果Bean實(shí)現(xiàn)了InitializingBean接口,則Spring將調(diào)用afterPropertiesSet()方法。
(8)如果在配置文件中通過(guò)init-method屬性指定了初始化方法,則調(diào)用該初始化方法。
(9)如果有BeanPostProcessor和Bean關(guān)聯(lián),則Spring將調(diào)用該接口的初始化方法post ProcessAfterInitialization()。此時(shí),Bean已經(jīng)可以被應(yīng)用系統(tǒng)使用了。
(10)如果在<bean> 中指定了該Bean的作用范圍為scope="singleton",則將該Bean放入Spring IoC的緩存池中,將觸發(fā)Spring對(duì)該Bean的生命周期管理;如果在<bean>中指定了該Bean的作用范圍為scope="prototype",則將該Bean交給調(diào)用者,調(diào)用者管理該Bean的生命周期,Spring不再管理該Bean。
(11)如果Bean實(shí)現(xiàn)了DisposableBean接口,則Spring會(huì)調(diào)用destory()方法將Spring中的Bean銷(xiāo)毀;如果在配置文件中通過(guò)destory-method屬性指定了Bean的銷(xiāo)毀方法,則Spring將調(diào)用該方法進(jìn)行銷(xiāo)毀。Spring為Bean提供了細(xì)致全面的生命周期過(guò)程,通過(guò)實(shí)現(xiàn)特定的接口或通過(guò)<bean>的屬性設(shè)置,都可以對(duì)Bean的生命周期過(guò)程產(chǎn)生影響。我們可以隨意地配置<bean>的屬性,但是在這里建議不要過(guò)多地使用Bean實(shí)現(xiàn)接口,因?yàn)檫@樣會(huì)使代碼和Spring聚合比較緊密。

Bean的裝配方式

Bean的裝配方式Bean的裝配可以理解為依賴(lài)關(guān)系注入,Bean的裝配方式即Bean依賴(lài)注入的方式。Spring容器支持多種形式的Bean的裝配方式,如

  1. 基于XML的裝配
  2. 基于注解(Annotation)的裝配
  3. 自動(dòng)裝配
基于XML的裝配

基于XML的裝配Spring提供了兩種基于XML的裝配方式:

  • 設(shè)值注入(Setter Injection)
  • 構(gòu)造注入(Constructor Injection)

在Spring實(shí)例化Bean的過(guò)程中,Spring首先會(huì)調(diào)用Bean的默認(rèn)構(gòu)造方法來(lái)實(shí)例化Bean對(duì)象,然后通過(guò)反射的方式調(diào)用setter方法來(lái)注入屬性值。因此,設(shè)值注入要求一個(gè)Bean必須滿(mǎn)足以下兩點(diǎn)要求。

  • Bean類(lèi)必須提供一個(gè)默認(rèn)的無(wú)參構(gòu)造方法。
  • Bean類(lèi)必須為需要注入的屬性提供對(duì)應(yīng)的setter方法。
    使用設(shè)值注入時(shí),在Spring配置文件中,需要使用<bean>元素的子元素<property>來(lái)為每個(gè)屬性注入值;而使用構(gòu)造注入時(shí),在配置文件里,需要使用<bean>元素的子元素<constructor-arg>來(lái)定義構(gòu)造方法的參數(shù),可以使用其value屬性(或子元素)來(lái)設(shè)置該參數(shù)的值。
基于注解(Annotation)的裝配

@Autowired:用于對(duì)Bean的屬性變量、屬性的setter方法及構(gòu)造方法進(jìn)行標(biāo)注,配合對(duì)應(yīng)的注解處理器完成Bean的自動(dòng)配置工作。默認(rèn)按照Bean的類(lèi)型進(jìn)行裝配。
@Resource:其作用與Autowired一樣。其區(qū)別在于@Autowired默認(rèn)按照Bean類(lèi)型裝配,而@Resource默認(rèn)按照Bean實(shí)例名稱(chēng)進(jìn)行裝配。
@Resource中有兩個(gè)重要屬性:name和type。Spring將name屬性解析為Bean實(shí)例名稱(chēng),type屬性解析為Bean實(shí)例類(lèi)型。如果指定name屬性,則按實(shí)例名稱(chēng)進(jìn)行裝配;如果指定type屬性,則按Bean類(lèi)型進(jìn)行裝配;如果都不指定,則先按Bean實(shí)例名稱(chēng)裝配,如果不能匹配,再按照Bean類(lèi)型進(jìn)行裝配;如果都無(wú)法匹配,則拋出NoSuchBeanDefinitionException異常?!?@Qualifier:與@Autowired注解配合使用,會(huì)將默認(rèn)的按Bean類(lèi)型裝配修改為按Bean的實(shí)例名稱(chēng)裝配,Bean的實(shí)例名稱(chēng)由@Qualifier注解的參數(shù)指定。

自動(dòng)裝配

自動(dòng)裝配雖然使用注解的方式裝配Bean,在一定程度上減少了配置文件中的代碼量,但是也有企業(yè)項(xiàng)目中,是沒(méi)有使用注解方式開(kāi)發(fā)的,那么有沒(méi)有什么辦法既可以減少代碼量,又能夠?qū)崿F(xiàn)Bean的裝配呢?答案是肯定的,Spring的<bean>元素中包含一個(gè)autowire屬性,我們可以通過(guò)設(shè)置autowire的屬性值來(lái)自動(dòng)裝配Bean。所謂自動(dòng)裝配,就是將一個(gè)Bean自動(dòng)地注入到其他Bean的Property中。

image.png

在默認(rèn)情況下,配置文件中需要通過(guò)ref來(lái)裝配Bean,但設(shè)置了autowire="byName"后,Spring會(huì)自動(dòng)尋找userService Bean中的屬性,并將其屬性名稱(chēng)與配置文件中定義的Bean做匹配。由于UserServiceImpl中定義了userDao屬性及其setter方法,這與配置文件中id為userDao的Bean相匹配,所以Spring會(huì)自動(dòng)地將id為userDao的Bean裝配到id為userService的Bean中。

Spring AOP

目前最流行的AOP框架有兩個(gè),分別為Spring AOP和AspectJ。Spring AOP使用純Java實(shí)現(xiàn),不需要專(zhuān)門(mén)的編譯過(guò)程和類(lèi)加載器,在運(yùn)行期間通過(guò)代理方式向目標(biāo)類(lèi)織入增強(qiáng)的代碼。AspectJ是一個(gè)基于Java語(yǔ)言的AOP框架,從Spring 2.0開(kāi)始,Spring AOP引入了對(duì)AspectJ的支持,AspectJ擴(kuò)展了Java語(yǔ)言,提供了一個(gè)專(zhuān)門(mén)的編譯器,在編譯時(shí)提供橫向代碼的織入。

動(dòng)態(tài)代理

Spring中的AOP代理,可以是JDK動(dòng)態(tài)代理,也可以是CGLIB動(dòng)態(tài)代理。

知識(shí)點(diǎn): JDK動(dòng)態(tài)代理和CGLIB動(dòng)態(tài)代理的區(qū)別是什么?

JDK動(dòng)態(tài)代理

JDK動(dòng)態(tài)代理JDK動(dòng)態(tài)代理是通過(guò)java.lang.reflect.Proxy類(lèi)來(lái)實(shí)現(xiàn)的,我們可以調(diào)用Proxy類(lèi)的newProxyInstance()方法來(lái)創(chuàng)建代理對(duì)象。對(duì)于使用業(yè)務(wù)接口的類(lèi),Spring默認(rèn)會(huì)使用JDK動(dòng)態(tài)代理來(lái)實(shí)現(xiàn)AOP。
基本流程:
JdkProxy類(lèi)實(shí)現(xiàn)了InvocationHandler接口,并實(shí)現(xiàn)了接口中的invoke()方法,所有動(dòng)態(tài)代理類(lèi)所調(diào)用的方法都會(huì)交由該方法處理。在創(chuàng)建的代理方法createProxy()中,使用了Proxy類(lèi)的newProxyInstance()方法來(lái)創(chuàng)建代理對(duì)象。newProxyInstance()方法中包含3個(gè)參數(shù),其中第1個(gè)參數(shù)是當(dāng)前類(lèi)的類(lèi)加載器,第2個(gè)參數(shù)表示的是被代理對(duì)象實(shí)現(xiàn)的所有接口,第3個(gè)參數(shù)this代表的就是代理類(lèi)JdkProxy本身。在invoke()方法中,目標(biāo)類(lèi)方法執(zhí)行的前后,會(huì)分別執(zhí)行切面類(lèi)中的check_Permissions()方法和log()方法。

CGLIB代理

CGLIB代理JDK動(dòng)態(tài)代理的使用非常簡(jiǎn)單,但它還有一定的局限性——使用動(dòng)態(tài)代理的對(duì)象必須實(shí)現(xiàn)一個(gè)或多個(gè)接口。如果要對(duì)沒(méi)有實(shí)現(xiàn)接口的類(lèi)進(jìn)行代理,那么可以使用CGLIB代理。CGLIB(Code Generation Library)是一個(gè)高性能開(kāi)源的代碼生成包,它采用非常底層的字節(jié)碼技術(shù),對(duì)指定的目標(biāo)類(lèi)生成一個(gè)子類(lèi),并對(duì)子類(lèi)進(jìn)行增強(qiáng)。
基本流程:
首先創(chuàng)建了一個(gè)動(dòng)態(tài)類(lèi)對(duì)象Enhancer,它是CGLIB的核心類(lèi);然后調(diào)用了Enhancer類(lèi)的setSuperclass()方法來(lái)確定目標(biāo)對(duì)象;接下來(lái)調(diào)用了setCallback()方法添加回調(diào)函數(shù),其中的this代表的就是代理類(lèi)CglibProxy本身;最后通過(guò)return語(yǔ)句將創(chuàng)建的代理類(lèi)對(duì)象返回。intercept()方法會(huì)在程序執(zhí)行目標(biāo)方法時(shí)被調(diào)用,方法運(yùn)行時(shí)將會(huì)執(zhí)行切面類(lèi)中的增強(qiáng)方法。

基于代理類(lèi)的AOP實(shí)現(xiàn)

在Spring中,使用Proxy FactoryBean是創(chuàng)建AOP代理的最基本方式。
Spring中的通知按照在目標(biāo)類(lèi)方法的連接點(diǎn)位置,可以分為以下5種類(lèi)型。

  • MethodInterceptor(環(huán)繞通知)在目標(biāo)方法執(zhí)行前后實(shí)施增強(qiáng),可以應(yīng)用于日志、事務(wù)管理等功能。
  • MethodBeforeAdvice(前置通知)在目標(biāo)方法執(zhí)行前實(shí)施增強(qiáng),可以應(yīng)用于權(quán)限管理等功能。
  • AfterReturningAdvice(后置通知)在目標(biāo)方法執(zhí)行后實(shí)施增強(qiáng),可以應(yīng)用于關(guān)閉流、上傳文件、刪除臨時(shí)文件等功能。
  • ThrowsAdvice(異常通知)在方法拋出異常后實(shí)施增強(qiáng),可以應(yīng)用于處理異常記錄日志等功能。
  • IntroductionInterceptor(引介通知)在目標(biāo)類(lèi)中添加一些新的方法和屬性,可以應(yīng)用于修改老版本程序(增強(qiáng)類(lèi))。

ProxyFactoryBeanProxyFactoryBean是FactoryBean接口的實(shí)現(xiàn)類(lèi),F(xiàn)actoryBean負(fù)責(zé)實(shí)例化一個(gè)Bean,而ProxyFactoryBean負(fù)責(zé)為其他Bean創(chuàng)建代理實(shí)例。

image.png

首先通過(guò)<bean>元素定義了目標(biāo)類(lèi)和切面,然后使用ProxyFactoryBean類(lèi)定義了代理對(duì)象。在定義的代理對(duì)象中,分別通過(guò)<property>子元素指定了代理實(shí)現(xiàn)的接口、代理的目標(biāo)對(duì)象、需要織入目標(biāo)類(lèi)的通知以及代理方式。

AspectJ開(kāi)發(fā)

AspectJ開(kāi)發(fā)AspectJ是一個(gè)基于Java語(yǔ)言的AOP框架,它提供了強(qiáng)大的AOP功能。Spring 2.0以后,Spring AOP引入了對(duì)AspectJ的支持,并允許直接使用AspectJ進(jìn)行編程,而Spring自身的AOP API也盡量與AspectJ保持一致。新版本的Spring框架,也建議使用AspectJ來(lái)開(kāi)發(fā)AOP。使用AspectJ實(shí)現(xiàn)AOP有兩種方式:

  • 基于XML的聲明式AspectJ
  • 基于注解的聲明式AspectJ
基于XML的聲明式AspectJ

基于XML的聲明式AspectJ基于XML的聲明式AspectJ是指通過(guò)XML文件來(lái)定義切面、切入點(diǎn)及通知,所有的切面、切入點(diǎn)和通知都必須定義在<aop:config>元素內(nèi)。<aop:config>元素及其子元素如圖所示。

image.png

Spring配置文件中的<beans>元素下可以包含多個(gè)<aop:config>元素,一個(gè)<aop:config>元素中又可以包含屬性和子元素,其子元素包括<aop:pointcut>、<aop:advisor>和<aop:aspect>。在配置時(shí),這3個(gè)子元素必須按照此順序來(lái)定義。在<aop:aspect>元素下,同樣包含了屬性和多個(gè)子元素,通過(guò)使用<aop:aspect>元素及其子元素就可以在XML文件中配置切面、切入點(diǎn)和通知。圖中灰色部分標(biāo)注的元素即為常用的配置元素,這些常用元素的配置代碼如下所示。

>> <! -- 定義切面Bean --><bean id="myAspect" class="com.itheima.aspectj.xml.MyAspect" />
<aop:config>    
<! --1.配置切面 --> 
  <aop:aspect  id="aspect"  ref="myAspect">  
   <! --  2.配置切入點(diǎn) -->   
  <aop:pointcut expression="execution(* com.itheima.jdk.*.*(..))"                                            id="myPointCut" />  
   <! --3.配置通知 -->  
   <! -- 前置通知 -->    
 <aop:before method="myBefore" pointcut-ref="myPointCut" />    
 <! -- 后置通知 -->   
  <aop:after-returning method="myAfterReturning"         
    pointcut-ref="myPointCut" returning="returnVal" />    
 <! -- 環(huán)繞通知 -->   
  <aop:around method="myAround" pointcut-ref="myPointCut" />  
   <! -- 異常通知 -->   
  <aop:after-throwing method="myAfterThrowing"        
     pointcut-ref="myPointCut" throwing="e" />     
  <! -- 最終通知 -->     
  <aop:after method="myAfter" pointcut-ref="myPointCut" />  
 </aop:aspect>
</aop:config>

配置切面在Spring的配置文件中,配置切面使用的是<aop:aspect>元素,該元素會(huì)將一個(gè)已定義好的Spring Bean轉(zhuǎn)換成切面Bean,所以要在配置文件中先定義一個(gè)普通的Spring Bean(如上述代碼中定義的myAspect)。定義完成后,通過(guò)<aop:aspect>元素的ref屬性即可引用該Bean。

在上述配置代碼片段中,execution(* com.itheima.jdk..(..))就是定義的切入點(diǎn)表達(dá)式,該切入點(diǎn)表達(dá)式的意思是匹配com.itheima.jdk包中任意類(lèi)的任意方法的執(zhí)行。其中execution()是表達(dá)式的主體,第1個(gè)表示的是返回類(lèi)型,使用代表所有類(lèi)型;com.itheima.jdk表示的是需要攔截的包名,后面第2個(gè)表示的是類(lèi)名,使用代表所有的類(lèi);第3個(gè)表示的是方法名,使用表示所有方法;后面(..)表示方法的參數(shù),其中的“..”表示任意參數(shù)。需要注意的是,第1個(gè)*與包名之間有一個(gè)空格。

在AOP的配置信息中,使用<aop:after-returning>配置的后置通知和使用<aop:after>配置的最終通知雖然都是在目標(biāo)方法執(zhí)行之后執(zhí)行,但它們也是有所區(qū)別的。后置通知只有在目標(biāo)方法成功執(zhí)行后才會(huì)被織入,而最終通知不論目標(biāo)方法如何結(jié)束(包括成功執(zhí)行和異常中止兩種情況),它都會(huì)被織入。

基于注解的聲明式AspectJ

基于注解的聲明式AspectJ與基于代理類(lèi)的AOP實(shí)現(xiàn)相比,基于XML的聲明式ApectJ要便捷得多,但是它也存在著一些缺點(diǎn),那就是要在Spring文件中配置大量的代碼信息。為了解決這個(gè)問(wèn)題,AspectJ框架為AOP的實(shí)現(xiàn)提供了一套注解,用以取代Spring配置文件中為實(shí)現(xiàn)AOP功能所配置的臃腫代碼。

image.png

使用<aop:aspectj-autoproxy />來(lái)啟動(dòng)Spring對(duì)基于注解的聲明式AspectJ的支持。

Spring事務(wù)管理概述

事務(wù)管理的方式Spring中的事務(wù)管理分為兩種方式:

  • 編程式事務(wù)管理:是通過(guò)編寫(xiě)代碼實(shí)現(xiàn)的事務(wù)管理,包括定義事務(wù)的開(kāi)始、正常執(zhí)行后的事務(wù)提交和異常時(shí)的事務(wù)回滾。
  • 聲明式事務(wù)管理:是通過(guò)AOP技術(shù)實(shí)現(xiàn)的事務(wù)管理,其主要思想是將事務(wù)管理作為一個(gè)“切面”代碼單獨(dú)編寫(xiě),然后通過(guò)AOP技術(shù)將事務(wù)管理的“切面”代碼織入到業(yè)務(wù)目標(biāo)類(lèi)中。
聲明式事務(wù)管理

聲明式事務(wù)管理Spring的聲明式事務(wù)管理可以通過(guò)兩種方式來(lái)實(shí)現(xiàn):

  • 基于XML的方式
  • 基于Annotation的方式。
基于XML的方式

基于XML方式的聲明式事務(wù)基于XML方式的聲明式事務(wù)管理是通過(guò)在配置文件中配置事務(wù)規(guī)則的相關(guān)聲明來(lái)實(shí)現(xiàn)的。Spring 2.0以后,提供了tx命名空間來(lái)配置事務(wù),tx命名空間下提供了<tx:advice>元素來(lái)配置事務(wù)的通知(增強(qiáng)處理)。當(dāng)使用<tx:advice>元素配置了事務(wù)的增強(qiáng)處理后,就可以通過(guò)編寫(xiě)的AOP配置,讓Spring自動(dòng)對(duì)目標(biāo)生成代理。配置<tx:advice>元素時(shí),通常需要指定id和transaction-manager屬性,其中id屬性是配置文件中的唯一標(biāo)識(shí),transaction-manager屬性用于指定事務(wù)管理器。除此之外,還需要配置一個(gè)<tx:attributes>子元素,該子元素可通過(guò)配置多個(gè)<tx:method>子元素來(lái)配置執(zhí)行事務(wù)的細(xì)節(jié)。<tx:advice>元素及其子元素如圖所示。

image.png
基于Annotation的方式

基于Annotation方式的聲明式事務(wù)Spring的聲明式事務(wù)管理還可以通過(guò)Annotation(注解)的方式來(lái)實(shí)現(xiàn)。這種方式的使用非常簡(jiǎn)單,開(kāi)發(fā)者只需做兩件事情:
① 在Spring容器中注冊(cè)事務(wù)注解驅(qū)動(dòng),其代碼如下。

<tx:annotation-driven transaction-manager="transactionManager"/>

② 在需要使用事務(wù)的Spring Bean類(lèi)或者Bean類(lèi)的方法上添加注解@Transactional。如果將注解添加在Bean類(lèi)上,則表示事務(wù)的設(shè)置對(duì)整個(gè)Bean類(lèi)的所有方法都起作用;如果將注解添加在Bean類(lèi)中的某個(gè)方法上,則表示事務(wù)的設(shè)置只對(duì)該方法有效。使用@Transactional注解時(shí),可以通過(guò)其參數(shù)配置事務(wù)詳情。@Transactional注解可配置的參數(shù)信息如表。

image.png

Spring MVC的工作流程

image.png

Spring MVC程序的完整執(zhí)行流程如下。
(1)用戶(hù)通過(guò)瀏覽器向服務(wù)器發(fā)送請(qǐng)求,請(qǐng)求會(huì)被Spring MVC的前端控制器DispatcherServlet所攔截。
(2)DispatcherServlet攔截到請(qǐng)求后,會(huì)調(diào)用HandlerMapping處理器映射器。
(3)處理器映射器根據(jù)請(qǐng)求URL找到具體的處理器,生成處理器對(duì)象及處理器攔截器(如果有則生成)一并返回給DispatcherServlet。
(4)DispatcherServlet會(huì)通過(guò)返回信息選擇合適的HandlerAdapter(處理器適配器)。
(5)HandlerAdapter會(huì)調(diào)用并執(zhí)行Handler(處理器),這里的處理器指的就是程序中編寫(xiě)的Controller類(lèi),也被稱(chēng)之為后端控制器。
(6)Controller執(zhí)行完成后,會(huì)返回一個(gè)ModelAndView對(duì)象,該對(duì)象中會(huì)包含視圖名或包含模型和視圖名。
(7)HandlerAdapter將ModelAndView對(duì)象返回給DispatcherServlet。
(8)DispatcherServlet會(huì)根據(jù)ModelAndView對(duì)象選擇一個(gè)合適的ViewReslover(視圖解析器)。
(9)ViewReslover解析后,會(huì)向DispatcherServlet中返回具體的View(視圖)。
(10)DispatcherServlet對(duì)View進(jìn)行渲染(即將模型數(shù)據(jù)填充至視圖中)。
(11)視圖渲染結(jié)果會(huì)返回給客戶(hù)端瀏覽器顯示。

Spring數(shù)據(jù)綁定流程

數(shù)據(jù)綁定介紹在執(zhí)行程序時(shí),Spring MVC會(huì)根據(jù)客戶(hù)端請(qǐng)求參數(shù)的不同,將請(qǐng)求消息中的信息以一定的方式轉(zhuǎn)換并綁定到控制器類(lèi)的方法參數(shù)中。這種將請(qǐng)求消息數(shù)據(jù)與后臺(tái)方法參數(shù)建立連接的過(guò)程就是Spring MVC中的數(shù)據(jù)綁定。在數(shù)據(jù)綁定過(guò)程中,Spring MVC框架會(huì)通過(guò)數(shù)據(jù)綁定組件(DataBinder)將請(qǐng)求參數(shù)串的內(nèi)容進(jìn)行類(lèi)型轉(zhuǎn)換,然后將轉(zhuǎn)換后的值賦給控制器類(lèi)中方法的形參,這樣后臺(tái)方法就可以正確綁定并獲取客戶(hù)端請(qǐng)求攜帶的參數(shù)了。

image.png

(1)Spring MVC將ServletRequest對(duì)象傳遞給DataBinder。
(2)將處理方法的入?yún)?duì)象傳遞給DataBinder。
(3)DataBinder調(diào)用ConversionService組件進(jìn)行數(shù)據(jù)類(lèi)型轉(zhuǎn)換、數(shù)據(jù)格式化等工作,并將ServletRequest對(duì)象中的消息填充到參數(shù)對(duì)象中。
(4)調(diào)用Validator組件對(duì)已經(jīng)綁定了請(qǐng)求消息數(shù)據(jù)的參數(shù)對(duì)象進(jìn)行數(shù)據(jù)合法性校驗(yàn)。
(5)校驗(yàn)完成后會(huì)生成數(shù)據(jù)綁定結(jié)果BindingResult對(duì)象,Spring MVC會(huì)將BindingResult對(duì)象中的內(nèi)容賦給處理方法的相應(yīng)參數(shù)。

Spring JSON數(shù)據(jù)交互

JSON數(shù)據(jù)轉(zhuǎn)換為了實(shí)現(xiàn)瀏覽器與控制器類(lèi)(Controller)之間的數(shù)據(jù)交互,Spring提供了一個(gè)HttpMessageConverter<T>接口來(lái)完成此項(xiàng)工作。該接口主要用于將請(qǐng)求信息中的數(shù)據(jù)轉(zhuǎn)換為一個(gè)類(lèi)型為T(mén)的對(duì)象,并將類(lèi)型為T(mén)的對(duì)象綁定到請(qǐng)求方法的參數(shù)中,或者將對(duì)象轉(zhuǎn)換為響應(yīng)信息傳遞給瀏覽器顯示。Spring為HttpMessageConverter<T>接口提供了很多實(shí)現(xiàn)類(lèi),這些實(shí)現(xiàn)類(lèi)可以對(duì)不同類(lèi)型的數(shù)據(jù)進(jìn)行信息轉(zhuǎn)換。其中MappingJackson2HttpMessageConverter是Spring MVC默認(rèn)處理JSON格式請(qǐng)求響應(yīng)的實(shí)現(xiàn)類(lèi)。該實(shí)現(xiàn)類(lèi)利用Jackson開(kāi)源包讀寫(xiě)JSON數(shù)據(jù),將Java對(duì)象轉(zhuǎn)換為JSON對(duì)象和XML文檔,同時(shí)也可以將JSON對(duì)象和XML文檔轉(zhuǎn)換為Java對(duì)象。


image.png

在使用注解式開(kāi)發(fā)時(shí),需要用到兩個(gè)重要的JSON格式轉(zhuǎn)換注解,分別為@RequestBody和@ResponseBody,關(guān)于這兩個(gè)注解的說(shuō)明如表所示。

Spring RESTful支持

什么是RESTful?RESTful也稱(chēng)之為REST(Representational State Transfer),可以將它理解為一種軟件架構(gòu)風(fēng)格或設(shè)計(jì)風(fēng)格,而不是一個(gè)標(biāo)準(zhǔn)。簡(jiǎn)單來(lái)說(shuō),RESTful風(fēng)格就是把請(qǐng)求參數(shù)變成請(qǐng)求路徑的一種風(fēng)格。例如,傳統(tǒng)的URL請(qǐng)求格式為:http://.../queryItems? id=1而采用RESTful風(fēng)格后,其URL請(qǐng)求為:http://.../items/1從上述兩個(gè)請(qǐng)求中可以看出,RESTful風(fēng)格中的URL將請(qǐng)求參數(shù)id=1變成了請(qǐng)求路徑的一部分,并且URL中的queryItems也變成了items(RESTful風(fēng)格中的URL不存在動(dòng)詞形式的路徑,如queryItems表示查詢(xún)訂單,是一個(gè)動(dòng)詞,而items表示訂單,為名詞)。

/** *接收RESTful風(fēng)格的請(qǐng)求,其接收方式為GET */
@RequestMapping(value="/user/{id}", 
method=RequestMethod.GET)
@ResponseBodypublic User selectUser(@PathVariable("id") String id){       
User user=new User();    
...
//返回JSON格式的數(shù)據(jù)    
return user;
}

在上述代碼中,@RequestMapping(value="/user/{id}", method=RequestMethod.GET)注解用于匹配請(qǐng)求路徑(包括參數(shù))和方式。其中value="/user/{id}"表示可以匹配以“/user/{id}”結(jié)尾的請(qǐng)求,id為請(qǐng)求中的動(dòng)態(tài)參數(shù);method=RequestMethod.GET表示只接收GET方式的請(qǐng)求。方法中的@PathVariable("id")注解則用于接收并綁定請(qǐng)求參數(shù),它可以將請(qǐng)求URL中的變量映射到方法的形參上,如果請(qǐng)求路徑為“/user/{id}”,即請(qǐng)求參數(shù)中的id和方法形參名稱(chēng)id一樣,則@PathVariable后面的“("id")”可以省略。

Spring 攔截器

攔截器概述Spring MVC中的攔截器(Interceptor)類(lèi)似于Servlet中的過(guò)濾器(Filter),它主要用于攔截用戶(hù)請(qǐng)求并做相應(yīng)的處理。例如通過(guò)攔截器可以進(jìn)行權(quán)限驗(yàn)證、記錄請(qǐng)求信息的日志、判斷用戶(hù)是否登錄等。
攔截器的定義要使用Spring MVC中的攔截器,就需要對(duì)攔截器類(lèi)進(jìn)行定義和配置。通常攔截器類(lèi)可以通過(guò)兩種方式來(lái)定義。一種是通過(guò)實(shí)現(xiàn)HandlerInterceptor接口,或繼承HandlerInterceptor接口的實(shí)現(xiàn)類(lèi)(如HandlerInterceptorAdapter)來(lái)定義;另一種是通過(guò)實(shí)現(xiàn)WebRequestInterceptor接口,或繼承WebRequestInterceptor接口的實(shí)現(xiàn)類(lèi)來(lái)定義。

HandlerInterceptor三個(gè)方法的具體描述如下:

  • preHandler()方法:該方法會(huì)在控制器方法前執(zhí)行,其返回值表示是否中斷后續(xù)操作。當(dāng)其返回值為true時(shí),表示繼續(xù)向下執(zhí)行;當(dāng)其返回值為false時(shí),會(huì)中斷后續(xù)的所有操作(包括調(diào)用下一個(gè)攔截器和控制器類(lèi)中的方法執(zhí)行等)。
  • postHandle()方法:該方法會(huì)在控制器方法調(diào)用之后,且解析視圖之前執(zhí)行。可以通過(guò)此方法對(duì)請(qǐng)求域中的模型和視圖做出進(jìn)一步的修改。
  • afterCompletion()方法:該方法會(huì)在整個(gè)請(qǐng)求完成,即視圖渲染結(jié)束之后執(zhí)行??梢酝ㄟ^(guò)此方法實(shí)現(xiàn)一些資源清理、記錄日志信息等工作。
單個(gè)攔截器的執(zhí)行流程

單個(gè)攔截器的執(zhí)行流程在運(yùn)行程序時(shí),攔截器的執(zhí)行是有一定順序的,該順序與配置文件中所定義的攔截器的順序相關(guān)。如果在項(xiàng)目中只定義了一個(gè)攔截器,那么該攔截器在程序中的執(zhí)行流程如圖所示。

image.png

程序首先會(huì)執(zhí)行攔截器類(lèi)中的preHandle()方法,如果該方法的返回值為true,則程序會(huì)繼續(xù)向下執(zhí)行處理器中的方法,否則將不再向下執(zhí)行;在業(yè)務(wù)處理器(即控制器Controller類(lèi))處理完請(qǐng)求后,會(huì)執(zhí)行postHandle()方法,然后會(huì)通過(guò)DispatcherServlet向客戶(hù)端返回響應(yīng);在DispatcherServlet處理完請(qǐng)求后,才會(huì)執(zhí)行afterCompletion()方法。

多個(gè)攔截器的執(zhí)行流程

在大型的企業(yè)級(jí)項(xiàng)目中,通常不會(huì)只有一個(gè)攔截器,開(kāi)發(fā)人員可能會(huì)定義很多攔截器來(lái)實(shí)現(xiàn)不同的功能。那么多個(gè)攔截器的執(zhí)行順序又是怎樣的呢?下面通過(guò)一張圖來(lái)描述多個(gè)攔截器的執(zhí)行流程(假設(shè)有兩個(gè)攔截器Interceptor1和Interceptor2,并且在配置文件中,Interceptor1攔截器配置在前)。

image.png

當(dāng)有多個(gè)攔截器同時(shí)工作時(shí),它們的preHandle()方法會(huì)按照配置文件中攔截器的配置順序執(zhí)行,而它們的postHandle()方法和afterCompletion()方法則會(huì)按照配置順序的反序執(zhí)行。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,619評(píng)論 6 539
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,155評(píng)論 3 425
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 177,635評(píng)論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀(guān)的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 63,539評(píng)論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,255評(píng)論 6 410
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 55,646評(píng)論 1 326
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,655評(píng)論 3 444
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 42,838評(píng)論 0 289
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,399評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,146評(píng)論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,338評(píng)論 1 372
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,893評(píng)論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,565評(píng)論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 34,983評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 36,257評(píng)論 1 292
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,059評(píng)論 3 397
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,296評(píng)論 2 376

推薦閱讀更多精彩內(nèi)容