10.劍指JavaOffer-Spring

IOC:控制反轉

依賴注入DI:

image

一層層依賴,底盤根據輪子的大小來設計,如果輪子更改,變大了,都要改,災難

舉例說明:
1.一開始呢,構造方法是無參的


初始

2.后來需求中想要,更改輪子的size,用上可變的構造參數,那么所有的函數都需要改


image

什么是依賴注入?
image

3.把輪子作為構造函數的參數注入到底盤中,一步步注入,則如果改輪子大小只需要更改輪子就行了,其他的類不需要更改。


image

以上是構造函數的方式實現依賴注入

除此之外還有如下注入方式:
setter注入

public class Bottom{
private Tire tire;
public void setTire(Tire tire) {
 this.tire = tire;
 } }

接口注入
注解注入(@Autowire)
構造器注入(上面的例子)

IOC容器的優勢:

避免在各處使用new來創建類并且可以統一維護(IOC Container)

創建實例的時候不需要知曉其中細節(下圖中藍色部分)

image
springIOC容器

1.讀取bean的配置信息
2.生成一份bean定義的注冊表
3.根據注冊表去實例化bean
4.裝配好bean的依賴關系放入緩存池
5.依據java的反射和依賴關系使用bean

image.png

最核心的就是依賴注入和自動裝配

先了解一下幾個核心接口和類:


image.png

會把xml中定義的類或者注解里的類轉化為BeanDefinition

image.png

如圖中源碼


源碼

兩個核心接口BeanFactory和ApplicationContext(升級了的BeanFactory)

BeanFactory

image

比較核心的方法getbean

ApplicationContext

ApplicationContext的功能(繼承了多個接口)

BeanFactory:能夠管理、裝配bean

ResourcePatternResolver:能夠加載資源文件

MessageSource:能夠實現國際化等功能

ApplicationEventPublisher:能夠注冊監聽器,實現監聽機制

超級全的:


image.png

實戰

在config里面這樣定義一個bean:

package com.imooc.framework.ioc.config;

import com.imooc.framework.ioc.entity.Person;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;
// Configuration標簽會把類注入到容器里面 Bean標簽是把initPerson這個方法返回的實體類裝配到IOc容器當中

@Configuration

public class ApplicationConfig{

    @Bean(name="person")

    public Person initPerson(){

        Person user = new Person();

        user.setId(1L);

        user.setName("Jack");

        return user;

    }

}
image.png

上面的方法不推薦,springboot是以下方式裝配bean的,就能被掃描到,不過掃描的是當前package和子package

@Component("person")

public class Person{

@Value("1")

private Long id;

@Value("Jack")

private String name;
//get set 方法省略

如果person類實現pet接口類的方法,將pet注入到person類(馴獸師)中

person類增加這幾行,就可以調用pet的方法類

@Autowired

private Pet pet;

public void call(){

        pet.move();

    }

//pet接口省略,只有一個方法move

@Component

@Primary(當有多個實現類的時候需要加入這個標簽)

public class Dog implements Pet{

@Override

public void move(){

System.out.println("running");

    }

}
Spring容器的refresh()
image

Spring Bean的作用域

  • singleton
    Spring的默認作用域,容器里擁有唯一的Bean實例(適合無狀態的bean)
    在Spring的IoC容器中只存在一個實例,所有對該對象的引用將共享這個實例。該實例從容器啟動,并因為第一次被請求而初始化之后,將一直存活到容器退出,
    也就是說,它與IoC容器“幾乎”擁有相同的“壽命”。

  • prototype
    針對每個getBean請求,容器都會創建一個Bean實例(適合有狀態的bean)
    對聲明為擁有prototype scope的bean定義,容器在接到該類型對象的請求的時候,會每次都重新生成一個新的對象實例給請求方。雖然這種類型的對象的實例化以及屬性設置等工作都是由容器負責的,但是只要準備完畢,并且對象實例返回給請求方之后,容器就不再擁有當前返回對象的引用,請求方需要自己負責當前返回對象的后繼生命周期的管理工作,包括該對象的銷毀。也就是說,容器每
    次返回給請求方一個新的對象實例之后,就任由這個對象實例“自生自滅”了。

web容器支持一下三種作用域:

  • request
    會為每個http請求創建一個bean實例,該bean僅在當前HTTP request內有效,當請求結束后,該對象的生命周期即告結束

  • session
    會為每個session創建一個bean實例,該bean僅在當前HTTP session內有效

  • globalSession
    會為每個全局的http session創建一個bean實例,該作用域僅對Portlet有效


Spring Bean 的生命周期

image
  • 實例化bean并設置bean屬性「如前面例子中Person類的@Component("Person")+ 屬性name上面的@Value("Jack") 」

  • 檢查Aware 接口(對SpringIOC容器的感知)并設置相關依賴


    image.png
  • BeanPostProcessor前置處理,在bean完成實例化之和完成一些自定義的處理邏輯(比如替換當前對象實例或者字節碼增強當前對象實例等。Spring的AOP則更多地使用BeanPostProcessor來為對象生成相應的代理對象)。

  • 檢查是否實現接口InitiallizingBean以決定是否調用afterPropertiesSet,做一些屬性被設置之后一些自定義的事情(比如,在有些情況下,某個業務對象實例化完成后,還不能處于可以使用狀態。這個時候就可以讓該業務對象實現該接口,并在方法afterPropertiesSet()中完成對該業務對象的后續處理。)

  • 檢查是否有自定義的init-method,做一些初始化的工作

  • BeanPostProcessor后置處理,bean初始化之后的自定義的工作
    銷毀的過程


    image.png
image
總流程

Spring AOP

關注點分離:不同問題交給不同的部分去解決,

  • 面向切面編程AOP是這種技術的體現

  • 通用化功能代碼的實現,對應的就是所謂的切面(Aspect)

  • 業務功能代碼和切面代碼分開后,架構將變得高內聚低耦合

  • 確保功能的完整性:切面最終需要被合并到業務中(織入)

實現AOP的技術,主要分為兩大類:一是采用動態代理技術,利用截取消息的方式,對該消息進行裝飾,以取代原有對象行為的執行;二是采用靜態織入的方式,引入特定的語法創建“方面”,從而使得編譯器可以在編譯期間織入有關“方面”的代碼。

AspectJ是idea可以new的

控制層:

@RestController

public class HelloController{

@RequestMapping(value="/hello", method = RequestMethod.GET)

@ResponseBody

public String hello(){

String sentence ="Hello World";

        System.out.println(sentence);

return sentence;

    }

@RequestMapping(value="/hi", method = RequestMethod.GET)

@ResponseBody

public String hi(){

String sentence ="Hi World";

        System.out.println(sentence);

return sentence;

    }

}

可能會有許多的controller和不同的方法,每個都寫記錄日志的邏輯,成本很高
日志提醒切面

@Aspect

@Component

public class RequestLogAspect{

private static final Logger logger = LoggerFactory.getLogger(RequestLogAspect.class);

@Pointcut("execution(public * com.imooc.framework.web..*.*(..))")//這個下面的任何參數任意方法都是切入點,都打日志。

public void webLog(){}

@Before("webLog()")

public void doBefore(JoinPoint joinPoint){

// 接收到請求,記錄請求內容

        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();

        HttpServletRequest request = attributes.getRequest();

// 記錄下請求內容

logger.info("URL : "+ request.getRequestURL().toString());

logger.info("IP : "+ request.getRemoteAddr());

    }

@AfterReturning(returning ="ret", pointcut ="webLog()")

public void doAfterReturning(Object ret){

// 處理完請求,返回內容

logger.info("RESPONSE : "+ ret);

    }

}

Aspect:RequestLogAspect,

Target:HelloController

Join Point :hi方法和hello方法

Pointcut @Pointcut("execution(public * com.imooc.framework.web...(..))")

Advice :Before、AfterReturning 異常通知等

image

AOP的實現

image

JDKProxy:通過java的內部反射機制實現的,利用攔截器(攔截器必須實現InvocationHanlder)加上反射機制

Cglib:借助ASM(能夠操作java字節碼)實現的,對代理對象類的class文件加載進來,通過修改其字節碼生成子類來處理。實現CGLIB動態代理必須實現MethodInterceptor(方法攔截器)接口
總的來說反射機制生成類的過程中比較高效,ASM在生成類之后的執行過程比較高效

何時使用JDK還是CGLIB?
1)如果目標對象實現了接口,默認情況下會采用JDK的動態代理實現AOP。

2)如果目標對象實現了接口,可以強制使用CGLIB實現AOP。

3)如果目標對象沒有實現了接口,必須采用CGLIB庫,Spring會自動在JDK動態代理和CGLIB之間轉換。

代理模式:接口+真實實現類+代理類(里面有真實實現類)

例子:Payment 接口類,方法付錢,AliPay代理類,RealPayment 真實實現類

Payment接口

public interface Payment{
    void pay();
}
public class RealPayment implements Payment{

@Override

public void pay(){

System.out.println("作為用戶,我只關心支付功能");

    }

}

代理類

public class AliPay implements Payment{//代理類

private Payment payment;

public AliPay(Payment payment){

this.payment = payment;

    }

public void beforePay(){

System.out.println("從招行取款");

    }

@Override

public void pay(){

        beforePay();

        payment.pay();

        afterPay();

    }

public void afterPay(){

System.out.println("支付給慕課網");

    }

}
public class ProxyDemo{

public static void main(String[] args){

Payment proxy =newAliPay(newRealPayment());

        proxy.pay();

    }

}

Spring里的代理模式的實現

  • 真實實現類的邏輯包含在getBean方法里
  • getBean方法返回的實際上是Proxy的實例
  • Proxy實例是Spring采用JDK Proxy或CGLIB動態生成的

doGetBean方法中有個initializeBean方法,它調用了BeanPostProcessor,它調用了AbstractProxyCreator下面的wrapIfNecessary方法,里面有createProxy-> createAopProxy->DefaultAopProxyFactory.createAopProxy()

Spring事務管理

Spring并不直接管理事務,而是提供了多種事務管理器,他們將事務管理的職責委托給hibernate或者JTA等持久化機制所提供的相關平臺框架的事務來實現。

  • JDBC事務

如果應用程序中直接使用JDBC來進行持久化,DataSourceTransactionManager會為你處理事務邊界。為了使用DataSourceTransactionManager,你需要使用如下的XML將其裝配到應用程序的上下文定義中:

<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>

實際上,DataSourceTransactionManager是通過調用Java.sql.Connection來管理事務,而后者是通過DataSource獲取到的。通過調用連接的commit()方法來提交事務,同樣,事務失敗則通過調用rollback()方法進行回滾。

  • Hibernate事務

如果應用程序的持久化是通過Hibernate實現的,那么你需要使用HibernateTransactionManager。對于Hibernate3,需要在Spring上下文定義中添加如下的<bean>聲明:

<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">  
      <property name="sessionFactory" ref="sessionFactory" />    
</bean>

sessionFactory屬性需要裝配一個Hibernate的session工廠,HibernateTransactionManager的實現細節是它將事務管理的職責委托給org.hibernate.Transaction對象,而后者是從Hibernate Session中獲取到的。當事務成功完成時,HibernateTransactionManager將會調用Transaction對象的commit()方法,反之,將會調用rollback()方法。

  • Java持久化API事務(JPA)

Hibernate多年來一直是事實上的Java持久化標準,但是現在Java持久化API作為真正的Java持久化標準進入大家的視野。如果你計劃使用JPA的話,那你需要使用Spring的JpaTransactionManager來處理事務。你需要在Spring中這樣配置JpaTransactionManager:

<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">        
    <property name="sessionFactory" ref="sessionFactory" />    
</bean>

JpaTransactionManager只需要裝配一個JPA實體管理工廠(javax.persistence.EntityManagerFactory接口的任意實現)。JpaTransactionManager將與由工廠所產生的JPA EntityManager合作來構建事務。

  • Java原生API事務

如果你沒有使用以上所述的事務管理,或者是跨越了多個事務管理源(比如兩個或者是多個不同的數據源),你就需要使用JtaTransactionManager:

<bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">        
    <property name="transactionManagerName" value="java:/TransactionManager" />   
</bean>

JtaTransactionManager將事務管理的責任委托給javax.transaction.UserTransaction和javax.transaction.TransactionManager對象,其中事務成功完成通過UserTransaction.commit()方法提交,事務失敗通過UserTransaction.rollback()方法回滾。

Spring 事務中的隔離級別有哪幾種?

TransactionDefinition 接口中定義了五個表示隔離級別的常量:

  • TransactionDefinition.ISOLATION_DEFAULT: 使用后端數據庫默認的隔離級別,Mysql 默認采用的 REPEATABLE_READ隔離級別 Oracle 默認采用的 READ_COMMITTED隔離級別.
  • TransactionDefinition.ISOLATION_READ_UNCOMMITTED: 最低的隔離級別,允許讀取尚未提交的數據變更,可能會導致臟讀、幻讀或不可重復讀
  • TransactionDefinition.ISOLATION_READ_COMMITTED: 允許讀取并發事務已經提交的數據,可以阻止臟讀,但是幻讀或不可重復讀仍有可能發生
  • TransactionDefinition.ISOLATION_REPEATABLE_READ: 對同一字段的多次讀取結果都是一致的,除非數據是被本身事務自己所修改,可以阻止臟讀和不可重復讀,但幻讀仍有可能發生。
  • TransactionDefinition.ISOLATION_SERIALIZABLE: 最高的隔離級別,完全服從ACID的隔離級別。所有的事務依次逐個執行,這樣事務之間就完全不可能產生干擾,也就是說,該級別可以防止臟讀、不可重復讀以及幻讀。但是這將嚴重影響程序的性能。通常情況下也不會用到該級別。

Spring 事務中哪幾種事務傳播行為?

支持當前事務的情況:

  • TransactionDefinition.PROPAGATION_REQUIRED: 如果當前存在事務,則加入該事務;如果當前沒有事務,則創建一個新的事務。
  • TransactionDefinition.PROPAGATION_SUPPORTS: 如果當前存在事務,則加入該事務;如果當前沒有事務,則以非事務的方式繼續運行。
  • TransactionDefinition.PROPAGATION_MANDATORY: 如果當前存在事務,則加入該事務;如果當前沒有事務,則拋出異常。(mandatory:強制性)

不支持當前事務的情況:

  • TransactionDefinition.PROPAGATION_REQUIRES_NEW: 創建一個新的事務,如果當前存在事務,則把當前事務掛起。
  • TransactionDefinition.PROPAGATION_NOT_SUPPORTED: 以非事務方式運行,如果當前存在事務,則把當前事務掛起。
  • TransactionDefinition.PROPAGATION_NEVER: 以非事務方式運行,如果當前存在事務,則拋出異常。

其他情況:

  • TransactionDefinition.PROPAGATION_NESTED: 如果當前存在事務,則創建一個事務作為當前事務的嵌套事務來運行;如果當前沒有事務,則該取值等價于TransactionDefinition.PROPAGATION_REQUIRED。

各類注解

https://blog.csdn.net/lipinganq/article/details/79155072

SpingMVC 流程

(1)客戶端(瀏覽器)發送請求,直接請求到 DispatcherServlet。
(2)DispatcherServlet 根據請求信息調用 HandlerMapping,解析請求對應的 Handler。
(3)解析到對應的 Handler(也就是我們平常說的 Controller 控制器)后,開始由 HandlerAdapter 適配器處理。
(4)HandlerAdapter 會根據 Handler 來調用真正的處理器開處理請求,并處理相應的業務邏輯。
(5)處理器處理完業務后,會返回一個 ModelAndView 對象,Model 是返回的數據對象,View 是個邏輯上的 View。
(6)ViewResolver 會根據邏輯 View 查找實際的 View。
(7)DispaterServlet 把返回的 Model 傳給 View(視圖渲染)。
(8)把 View 返回給請求者(瀏覽器)

SpringMVC 重要組件說明
1、前端控制器DispatcherServlet(不需要工程師開發),由框架提供(重要)

作用:Spring MVC 的入口函數。接收請求,響應結果,相當于轉發器,中央處理器。有了 DispatcherServlet 減少了其它組件之間的耦合度。用戶請求到達前端控制器,它就相當于mvc模式中的c,DispatcherServlet是整個流程控制的中心,由它調用其它組件處理用戶的請求,DispatcherServlet的存在降低了組件之間的耦合性。

2、處理器映射器HandlerMapping(不需要工程師開發),由框架提供

作用:根據請求的url查找Handler。HandlerMapping負責根據用戶請求找到Handler即處理器(Controller),SpringMVC提供了不同的映射器實現不同的映射方式,例如:配置文件方式,實現接口方式,注解方式等。

3、處理器適配器HandlerAdapter

作用:按照特定規則(HandlerAdapter要求的規則)去執行Handler 通過HandlerAdapter對處理器進行執行,這是適配器模式的應用,通過擴展適配器可以對更多類型的處理器進行執行。

4、處理器Handler(需要工程師開發)

注意:編寫Handler時按照HandlerAdapter的要求去做,這樣適配器才可以去正確執行Handler,Handler 是繼DispatcherServlet前端控制器的后端控制器,在DispatcherServlet的控制下Handler對具體的用戶請求進行處理。 由于Handler涉及到具體的用戶業務請求,所以一般情況需要工程師根據業務需求開發Handler。

5、視圖解析器View resolver(不需要工程師開發),由框架提供

作用:進行視圖解析,根據邏輯視圖名解析成真正的視圖(view) View Resolver負責將處理結果生成View視圖,View Resolver首先根據邏輯視圖名解析成物理視圖名即具體的頁面地址,再生成View視圖對象,最后對View進行渲染將處理結果通過頁面展示給用戶。 springmvc框架提供了很多的View視圖類型,包括:jstlView、freemarkerView、pdfView等。 一般情況下需要通過頁面標簽或頁面模版技術將模型數據通過頁面展示給用戶,需要由工程師根據業務需求開發具體的頁面。

6、視圖View(需要工程師開發)

View是一個接口,實現類支持不同的View類型(jsp、freemarker、pdf...)

注意:處理器Handler(也就是我們平常說的Controller控制器)以及視圖層view都是需要我們自己手動開發的。其他的一些組件比如:前端控制器DispatcherServlet、處理器映射器HandlerMapping、處理器適配器HandlerAdapter等等都是框架提供給我們的,不需要自己手動開發。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,882評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,208評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,746評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,666評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,477評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,960評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,047評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,200評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,726評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,617評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,807評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,327評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,049評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,425評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,674評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,432評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,769評論 2 372

推薦閱讀更多精彩內容

  • Spring入門使用Spring容器Spring容器使用ApplicationContextApplication...
    漸丶忘閱讀 1,417評論 0 4
  • 2.1 我們的理念是:讓別人為你服務 IoC是隨著近年來輕量級容器(Lightweight Container)的...
    好好學習Sun閱讀 2,722評論 0 11
  • JAVA面試題 1、作用域public,private,protected,以及不寫時的區別答:區別如下:作用域 ...
    JA尐白閱讀 1,171評論 1 0
  • 一. Java基礎部分.................................................
    wy_sure閱讀 3,826評論 0 11
  • Part III. Core Technologies github 地址 https://github.com/...
    天幕_bc1a閱讀 483評論 2 0