【已轉移】【Java架構:基礎技術】一篇文章搞掂:Spring

本文篇幅較長,建議合理利用右上角目錄進行查看(若是沒有目錄請刷新)。前端

本文是對《SPRING實戰第4版》的總結,你們也能夠去仔細研讀該書java

【------------------------Spring 核心------------------------】

1、Spring的由來和簡介

1.一、Spring的使命:簡化Java開發

幾個概念:

  • POJO:Plain Old Java Object,普通的Java對象。指只有屬性、get、set等方法,不包含複雜邏輯的Java類。
  • JavaBean:一種可重用組件,是指符合JavaBean規範的類。
    • JavaBean規範主要有如下幾條(僅從網絡信息摘取過來,有待商榷)
      • 類必須是具體的和公共的,而且具備無參數的構造器。
      • 提供符合一致性設計模式的公共方法將內部域暴露成員屬性,set和get方法獲取
      • 屬性名稱符合這種模式,其餘Java 類能夠經過自省機制(反射機制)發現和操做這些JavaBean 的屬性。
  • EJB:Enterprice JavaBean,是指符合Java企業級應用規範的類
    • 爲了快速開發企業級應用,Java提出了EJB,可是過重量級,並很差用;Spring技術則提供了一種方案,僅基於POJO又能實現EJB或其它企業級規範JAVA對象纔有的功能,使企業級開發變得更加輕量級,簡單;另外Java也看到了Spring技術的優點和好處,也使用Spring技術的原理,改進了原來的EJB,使其更加簡單好用;目前爲止,Spring技術仍是比EJB技術更加好用。
    • 另外,Spring還在不斷進步中,移動開發、社交API集成、NoSQL數據庫,雲計算以及大數據等領域,Spring都一直創新,用更加高效簡單的方式來提供這些領域的解決方案
  • Spring中把應用組件(類)也稱爲Bean或者JavaBean,可是Spring指的是任何POJO,因此文章中全部Bean或JavaBean不必定符合JavaBean規範,可能只是一個POJO

Spring的幾個關鍵策略

爲了簡化Java開發,Spring使用瞭如下幾個關鍵策略web

  • 一、基於POJO的輕量級和最小侵入編程正則表達式

    • 一般一個類應用Spring技術,不須要引用Spring;即便是最壞的狀況,也只須要用到Spring註解,不會有Spring的代碼出如今一個類中。從而保證應用中的類還是POJO
  • 二、基於依賴注入和麪向接口實現鬆耦合redis

    • DI:Dependency Injection 依賴注入

  在傳統編程中,不免會有如下這種編程情景(多個類交互)spring

public class DamselRescuingKnight implements Knight {
  private RescueDamselQuest quest;
  public DamselRescuingKnight() {
    this.quest = new RescueDamselQuest();
  }
  public void embarkOnQuest() {
    quest.embark();
  }
}

  DamselRescuingKnight中,經過new 建立了一個RescueDamselQuest實例,2個類造成了緊耦合。sql

  其中的依賴關係是DamselRescuingKnight依賴RescueDamselQuest,並且限制了embarkOnQuest方法的實現數據庫

  並且,沒法對DamselRescuingKnight進行單元測試,由於embarkOnQuest方法須要調用RescueDamselQuest的embark方法,而僅在這個方法內,並無這個方法的實現編程

  耦合的兩面性:必須的(複雜邏輯必然有多個類進行交互)、過於耦合將致使難以測試、複用、難以理解json

  依賴注入:將對象的依賴關係交給第三方來進行建立和管理

public class BraveKnight implements Knight {
  private Quest quest;
  public BraveKnight(Quest quest) {
    this.quest = quest;
  }
  public void embarkOnQuest() {
    quest.embark();
  }
}

  上面使用了依賴注入的一種方式:構造器注入;並且傳入的是一個Quest接口,對象和依賴對象的具體實現沒有耦合關係,就造成了鬆耦合。

  並且,此狀況下,可使用mock(一種測試方式,具體自行學習)實現單元測試。

  Spring能夠做爲一個依賴注入容器,經過不一樣方式注入,也有相應的配置,裝配策略,留到後面講解。

  • 三、基於切面和慣例進行聲明式編程

    • AOP:Aspect Oriented Programming,面向切面編程

  DI負責讓互相協做的組件實現鬆耦合,而AOP則容許把遍及程序各處的功能分離出來造成可重用組件。

  能夠把切面想象成不少組件上的一個外殼,藉助AOP,可使用各類功能層包裹核心業務層,以聲明的方式靈活應用到系統中,核心應用中與這些功能層沒有耦合,保證了POJO的簡單性

  例如,你的代碼中可能會出現如下情景

public class BraveKnight implements Knight {
    private Quest quest;
    private Minstrel minstrel;
    public BraveKnight(Quest quest, Minstrel minstrel) {
        this.quest = quest;
        this.minstrel = minstrel;
    }
    public void embarkOnQuest() {
        minstrel.singBeforeQuest();
        quest.embark();
        minstrel.singAfterQuest();
    }
}

  minstrel類能夠看做一個日誌功能,在某個其它類中,須要調用這個日誌類來記錄日誌,致使這個功能代碼與業務代碼混淆在一塊兒

  而Spring提供的AOP方案,能夠經過配置文件等方式,將這個日誌類的相關代碼從這個業務類中去除,從而實現解耦;具體方式後面介紹

  • 四、經過切面和模板減小樣板式代碼

  例如:以往使用JDBC進行操做數據庫時,每次操做,都有不少鏈接數據庫,斷開鏈接等代碼和業務代碼交織在一齊

  而Spring則提供瞭如jdbcTemplate等類,對這些樣板式代碼進行簡化

1.二、使用Spring管理Bean

Spring框架中對象是由Spring建立和管理的,本節討論Spring如何管理這些Bean

Spring提供一個或多個Spring容器,Spring容器負責建立、裝配、配置、管理對象的整個生命週期

Spring有多個容器實現,能夠分爲2種類型:

  • bean工廠:是最簡單的容器,提供基本的DI支持(因爲bean工廠對於大部分程序來講過低級,因此只討論應用上下文)
  • 應用上下文:基於BeanFactory構建,提供應用框架級別的服務

使用應用上下文建立Spring容器:

Spring自帶多種應用上下文:

  • AnnotationConfigApplicationContext:從一個或多個Java配置類加載Spring應用上下文
  • AnnotationConfigWebApplicationContext:從一個或多個Java配置類加載Spring Web應用上下文
  • ClassPathXmlApplicationContext:從類路徑下的一個或多個XML配置文件中加載上下文定義,把應用上下文的定義文件做爲類資源
  • FileSystemXmlapplicationcontext:從文件系統下的一個或多個XML配置文件中加載上下文定義
  • XmlWebApplicationContext:從Web應用下的以惡搞或多個XML配置文件加載上下文定義

建立Spring容器例子:ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("META-INF/spring/knight.xml");

經過context.getBean()方法便可獲取容器內的Bean

bean生命週期:

普通Java應用程序中,bean的生命週期很簡單,就是經過new實例化,就可使用了,當bean再也不被使用,就會被自動回收。

而Spring中的bean的生命週期就很複雜,並且很是重要,由於有時候對這個生命週期的擴展點進行自定義

1.三、Spring框架包含的內容

由前面的內容知道,Spring框架致力於經過DI、AOP和消除模板式代碼來簡化企業級JAVA開發

而在整個Spring框架範疇內,Spring不只提供了多種簡化開發的方式,還構建了一個在覈心框架上的龐大生態圈,將Spring擴展到不一樣領域,如Web服務、REST、移動開發,NoSQL等

Spring框架中的模塊:

如Spring4.0中,Spring框架發佈版本中包含20個不一樣模塊,每一個模塊有3個JAR文件(二進制類庫、源碼、JavaDoc)

Spring發佈版本中lib目錄下的JAR文件

Spring Portfolio:

Spring Portfolio包含構建於核心Spring框架之上的框架和類庫,歸納地說,Spring Portfolio爲每個領域的Java開發都提供了Spring編程模型

  • Spring Web Flow:爲基於流程的會話式Web應用提供支持,如購物車或嚮導系統
  • Spring Web Service:提供契約優先的Web Service模型,服務的實現都是爲了知足服務的契約而編寫的
  • Spring Security:爲Spring應用提供聲明式安全機制
  • Spring Integration:提供多種通用應用集成模式的聲明式風格實現
  • Spring Batch:對數據進行大量操做
  • Spring Data:不管是關係型數據庫仍是NoSQL、圖形數據庫等數據模式,Spring Data都爲這些持久化提供了一種簡單的編程模型
  • Spring Social:一個社交網絡擴展模塊
  • Spring Mobile:支持移動Web應用開發
  • Spring for Android:旨在經過Spring框架爲開發基於Android設備的本地應用提供某些簡單的支持
  • Spring Boot:依賴自動配置技術,消除大部分Spring配置,減少Spring工程構建文件大小

2、裝配Bean

2.一、裝配Bean的可行方式

Spring框架支持如下3種主要裝配Bean的方式:

  • 一、XML中進行顯式配置
  • 二、Java中進行顯式配置
  • 三、隱式的bean發現機制和自動裝配

最佳實踐:建議是儘量使用自動配置,減小顯式配置;當須要顯式配置,推薦使用類型安全的JavaConfig;最後再使用XML配置

2.二、自動化裝配bean

Spring從3個步驟完成自動化裝配:

  • 建立組件(@Component):在類上面進行標註,告知Spring這個類須要建立Bean
  • 組件掃描(@ComponentScan):爲Spring指定組件掃描範圍,告知Spring從哪些地方發現Bean
  • 自動裝配(@Autowiring):在屬性、set方法、構造函數上指定,告知Spring這個屬性或方法的參數,須要Spring來裝配對應的Bean

@Component:

標明這個類是一個組建類,告知Spring要爲這個類建立bean

也能夠用@Named註解(Java依賴規範/Java Dependency Injection 提供)代替,但通常不推薦

public interface CompactDisc {
  void play();
}
@Component
public class SgtPeppers implements CompactDisc {
  private String title = "Sgt. Pepper's Lonely Hearts Club Band";  
  private String artist = "The Beatles";
  public void play() {
    System.out.println("Playing " + title + " by " + artist);
  }
}

能夠爲bean命名id(默認自動生成id,是類名首字母小寫)

@Component("myName")

@ComponentScan:

Spring組件掃描默認是關閉的,能夠經過Java設置或者是XML設置來開啓

會搜索配置類所在的包以及子包

Java配置形式:

@Configuration
@ComponentScan
public class CDPlayerConfig { 
}

XML配置形式:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:context="http://www.springframework.org/schema/context"
  xmlns:c="http://www.springframework.org/schema/c"
  xmlns:p="http://www.springframework.org/schema/p"
  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

  <context:component-scan base-package="soundsystem" />

</beans>

指定組件掃描的基礎包,能夠經過類名,或者包中的類或接口:

@ComponentScan("soundsystem")
@ComponentScan(basePackages = "soundsystem")
@ComponentScan(basePackages = { "soundsystem", "video" })
@ComponentScan(basePackages = { CDPlayer.class, MediaPlayer.class })

推薦在包中建立標識接口,這樣在重構系統的時候,不會對這個配置形成影響

@Autowired:

能夠用在類中任何方法內(包括構造方法、setter)

這樣,調用這個方法的時候,Spring就會去查找適合方法參數的bean並裝配到方法中

若是找不到,或者有多個符合,則會報錯

能夠經過@Autowired(required=false)使找不到時不報錯,那麼傳入的參數值爲null,須要本身手動處理代碼

可使用@Inject(Java依賴規範/Java Dependency Injection 提供)代替,但通常不推薦

public class CDPlayer implements MediaPlayer {
  private CompactDisc cd;
  @Autowired
  public CDPlayer(CompactDisc cd) {
    this.cd = cd;
  }
  public void play() {
    cd.play();
  }
}

2.三、經過Java代碼裝配bean

例如,須要將第三方庫的組件加載到你的應用中,此時沒法給他的類上添加@Component和@Autowired註解,此時不能使用自動化裝配了。

這種狀況下,就必須使用顯式裝配的形式,能夠選擇Java代碼裝配或Xml裝配

建議:顯式配置是優先使用JavaConfig裝配,由於他強大、類型安全且對重構友好;由於他和業務代碼無關,應放到單獨的包中

@Configuration:

告訴Spring,這是一個Spring配置類,用來配置Spring應用上下文如何配置bean

@Bean:

建立一個方法,用來產生類的實例,並告訴Spring,這個實例要註冊爲Spring應用上下文中的bean

@Configuration
public class CDPlayerConfig {
    @Bean
    public CompactDisc sgtPeppers() {
        return new SgtPeppers();
    }
    @Bean
    public CDPlayer cdPlayer1() {
        return new CDPlayer(sgtPeppers());
    }
    @Bean
    public CDPlayer cdPlayer2(CompactDisc compactDisc) {
        return new CDPlayer(compactDisc);
    }
}
  • 首先,使用new SgtPeppers()的實例,爲CompactDisc類型建立一個bean
  • cdPlayer1中,經過直接調用SgtPeppers()方法獲取實例,看似每次調用cdPlayer1,都會產生一個新的CompactDisc實例,但實際上,由於SgtPeppers()方法添加了@bean備註,Spring會攔截其調用,若是已建立bean則直接返回bean,不會從新建立。
  • cdPlayer2中,給這個方法添加了@Bean註解,Spring會在調用的時候爲參數找到對應類型的實例,自動注入
  • 其中cdPlayer2的方式更符合實際代碼的運行,建議使用這種方式,方便理解。

2.四、經過XML裝配bean

Spring剛出現時,用XML描述配置是主要方式;如今有了強大的自動化配置和Java配置,XML配置再也不是首選;可是以往的項目仍然存在大量的XML配置,因此有必要掌握這種方式。

建立XML配置規範:

Java配置須要@Configuration註解,而XML配置的配置規範以下

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
      http://www.springframework.org/schema/beans/spring-beans.xsd
      http://www.springframework.org/schema/context">

    <!-- 配置內容 -->

</beans>

<bean>:

用來聲明一個簡單的bean

spring檢測到這個設置,會調用該類的默認構造器來建立實例,並註冊爲bean

使用字符串做爲類名設置,會出現寫錯等問題;可使用有Spring感知的IDE來確保XML配置的正確性,如Spring Tool Suite

<bean class="soundsystem.BlankDisc" />
<bean id="compactDisc" class="soundsystem.BlankDisc" />

若是不設置id,會自動給予id:class+#0,#1,#2...

爲了減少XML文件的繁瑣性,建議只對須要按名字引用的bean添加id屬性

經過構造器注入初始化bean:

2種方式:使用<constructor-arg>元素、使用Spring3.0引入的c-命名空間

區別:<constructor-arg>元素冗長繁瑣;但有些事情<constructor-arg>元素能作到,c-命名空間作不到

使用<constructor-arg>元素:

引用bean注入:

<bean id="compactDisc" class="soundsystem.SgtPeppers" />
      
<bean id="cdPlayer" class="soundsystem.CDPlayer">
  <constructor-arg ref="compactDisc" />
</bean>
  • 第一個bean:使用soundsystem.SgtPeppers的默認構造器建立實例做爲bean,id爲compactDisc
  • 第二個bean:把id爲compactDisc的bean,傳遞到soundsystem.CDPlayer的帶參數構造器中,使用這個bean做爲參數,建立實例後做爲bean,id爲cdPlayer

用字面量注入:

<bean id="compactDisc" class="soundsystem.BlankDisc">
    <constructor-arg value="Sgt. Pepper's Lonely Hearts Club Band" />
    <constructor-arg value="The Beatles" />
</bean>

裝配集合:

傳入空值:

<bean id="compactDisc" class="soundsystem.collections.BlankDisc">
    <constructor-arg value="Sgt. Pepper's Lonely Hearts Club Band" />
    <constructor-arg value="The Beatles" />
    <constructor-arg><null/></constructor-arg>
</bean>

使用List傳入字面量:

<bean id="compactDisc" class="soundsystem.collections.BlankDisc">
    <constructor-arg value="Sgt. Pepper's Lonely Hearts Club Band" />
    <constructor-arg value="The Beatles" />
    <constructor-arg>
        <list>
            <value>Sgt. Pepper's Lonely Hearts Club Band</value>
            <value>With a Little Help from My Friends</value>
        </list>
    </constructor-arg>
</bean>

使用List傳入引用bean:

<bean id="compactDisc" class="soundsystem.collections.BlankDisc">
    <constructor-arg value="Sgt. Pepper's Lonely Hearts Club Band" />
    <constructor-arg value="The Beatles" />
    <constructor-arg>
        <list>
            <ref bean="cd1"/>
            <ref bean="cd2"/>
        </list>
    </constructor-arg>
</bean>

List能夠替換成Set,區別是所建立的是List仍是Set

使用c-命名空間:

聲明c-模式:

首先,使用這個命名控件要在beans標籤增長聲明此模式:xmlns:c="http://www.springframework.org/schema/c"

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:c="http://www.springframework.org/schema/c"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
      http://www.springframework.org/schema/beans/spring-beans.xsd
      http://www.springframework.org/schema/context">

    <!-- 配置內容 -->

</beans>

引用bean注入:

<bean id="compactDisc" class="soundsystem.SgtPeppers" />

<bean id="cdPlayer" class="soundsystem.CDPlayer" c:cd-ref="compactDisc" />

其中,cd是指對應構造方法的參數名,按照這種寫法,若是修改了構造方法,可能就會出錯

替代方案:

<bean id="cdPlayer" class="soundsystem.CDPlayer" c:_0-ref="compactDisc" />

<bean id="cdPlayer" class="soundsystem.CDPlayer" c:_-ref="compactDisc" />

_0,_1:使用參數順序來注入

_:若是隻有一個參數,能夠不用標示參數,用下劃線代替

用字面量注入:

<bean id="compactDisc" class="soundsystem.BlankDisc" c:_0="Sgt. Pepper's Lonely Hearts Club Band" c:_1="The Beatles" />

裝配集合:

c-的方式沒法實現裝配集合

使用屬性注入

怎麼選擇構造器注入和屬性注入?建議對強依賴使用構造器注入,對可選依賴使用屬性注入。

假設有一個類CDPlayer:

public class CDPlayer implements MediaPlayer {
    private CompactDisc compactDisc;
    @Autowired
    public void setCompactDisc(CompactDisc compactDisc) {
        this.compactDisc = compactDisc;
    }
    public void play() {
        compactDisc.play();
    }
}

使用<property>元素:

引用bean注入:

<bean id="cdPlayer" class="soundsystem.properties.CDPlayer">
    <property name="compactDisc" ref="compactDisc" />
</bean>

表明經過setCompactDisc方法把id爲compactDisc的bean注入到compactDisc屬性中

用字面量注入以及裝配集合:

<bean id="compactDisc" class="soundsystem.properties.BlankDisc">
    <property name="title" value="Sgt. Pepper's Lonely Hearts Club Band" />
    <property name="artist" value="The Beatles" />
    <property name="tracks">
        <list>
            <value>Sgt. Pepper's Lonely Hearts Club Band</value>
            <value>With a Little Help from My Friends</value>
        </list>
    </property>
</bean>

使用p-命名空間

聲明p-模式:

首先,使用這個命名控件要在beans標籤增長聲明此模式:xmlns:p="http://www.springframework.org/schema/p"

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
      http://www.springframework.org/schema/beans/spring-beans.xsd
      http://www.springframework.org/schema/context">

    <!-- 配置內容 -->

</beans>

而後使用p-命名空間來裝配屬性

引用bean注入:

<bean id="cdPlayer" class="soundsystem.properties.CDPlayer" p:compactDisc-ref="compactDisc" />

用字面量注入以及裝配集合:

<bean id="compactDisc" class="soundsystem.properties.BlankDisc" p:title="Sgt. Pepper's Lonely Hearts Club Band" p:artist="The Beatles">
    <property name="tracks">
        <list>
            <value>Sgt. Pepper's Lonely Hearts Club Band</value>
            <value>With a Little Help from My Friends</value>
        </list>
    </property>
</bean>

使用util-命名空間進行簡化:

聲明util-模式:

首先,使用這個命名控件要在beans標籤增長聲明此模式:xmlns:util="http://www.springframework.org/schema/util"和2個http

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:util="http://www.springframework.org/schema/util"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/util 
    http://www.springframework.org/schema/util/spring-util.xsd
    http://www.springframework.org/schema/context">

    <!-- 配置內容 -->

</beans>

使用util:list簡化:

<bean id="compactDisc" class="soundsystem.properties.BlankDisc"
    p:title="Sgt. Pepper's Lonely Hearts Club Band" 
    p:artist="The Beatles"
    p:tracks-ref="trackList" />

<util:list id="trackList">
    <value>Sgt. Pepper's Lonely Hearts Club Band</value>
    <value>With a Little Help from My Friends</value>
</util:list>

<bean id="cdPlayer" class="soundsystem.properties.CDPlayer"
    p:compactDisc-ref="compactDisc" />

util-命名控件的所有元素:

2.五、導入和混合配置

一、拆分JavaConfig:

其中一個JavaConfig:

@Configuration
public class CDConfig {
    @Bean
    public CompactDisc compactDisc() {
        return new SgtPeppers();
    }
}

使用@Import註解引入另一個JavaConfig:

@Configuration
@Import(CDConfig.class)
public class CDPlayerConfig {
    @Bean
    public CDPlayer cdPlayer(CompactDisc compactDisc) {
        return new CDPlayer(compactDisc);
    }
}

或使用一個更高級別的JavaConfig引用2個JavaConfig

@Configuration
@Import({ CDPlayerConfig.class, CDConfig.class })
public class SoundSystemConfig {
}

二、JavaConfig中引用XML配置

1中把CDPlayer和CompactDisc分開了,假設出於某些緣由,須要把CompactDisc用XML來配置

<bean id="compactDisc" class="soundsystem.BlankDisc"
    c:_0="Sgt. Pepper's Lonely Hearts Club Band" c:_1="The Beatles">
    <constructor-arg>
        <list>
            <value>Sgt. Pepper's Lonely Hearts Club Band</value>
            <value>With a Little Help from My Friends</value>
            <value>Lucy in the Sky with Diamonds</value>
            <value>Getting Better</value>
            <value>Fixing a Hole</value>
            <!-- ...other tracks omitted for brevity... -->
        </list>
    </constructor-arg>
</bean>

JavaConfig引用XML配置

@Configuration
@Import(CDPlayerConfig.class)
@ImportResource("classpath:cd-config.xml")
public class SoundSystemConfig {
}

這樣,CDPlayer和BlankDisc都會做爲bean被加載到Spring容器中;而CDPlayer添加了@Bean註解,所需參數CompactDisc也會把BlanDisc加載進來

三、拆分XML配置

<import resource="cd-config.xml" />
<bean id="cdPlayer" class="soundsystem.CDPlayer" c:cd-ref="compactDisc" />

四、XML配置中引用JavaConfig

<bean class="soundsystem.CDConfig" />
<bean id="cdPlayer" class="soundsystem.CDPlayer" c:cd-ref="compactDisc" />

推薦不管使用JavaConfig仍是XML配置,都加入一個更高層次的配置文件,負責組合這些配置文件

<bean class="soundsystem.CDConfig" />
<import resource="cdplayer-config.xml" />

3、高級裝配

3.一、環境與profile

程序運行有多個環境,好比開發環境、測試環境、生產環境等。

在每一個環境中,不少東西有不一樣的作法,如使用的數據庫等。

爲了不在切換運行環境是須要對程序進行大量修改,Spring提供了profile設置,使程序能對不一樣環境作出對應的處理。

使用profile,可使一個部署單元(如war包)能夠用於不一樣的環境,內部的bean會根據環境所需而建立。

配置profile bean:

Java配置profile bean:

使用@Profile註解指定bean屬於哪一個profile

針對類,代表這個類下全部的bean都在對應profile激活時才建立:

@Configuration
@Profile("dev")
public class DataSourceConfig {
  @Bean(destroyMethod = "shutdown")
  public DataSource embeddedDataSource() {
    return new EmbeddedDatabaseBuilder().build();
  }
}

針對方法,代表只有註解的方法纔會根據profile來建立bean

@Configuration
public class DataSourceConfig {
  @Bean(destroyMethod = "shutdown")
  @Profile("dev")
  public DataSource embeddedDataSource() {
    return new EmbeddedDatabaseBuilder().build();
  }
  @Bean
  @Profile("prod")
  public DataSource jndiDataSource() {
    JndiObjectFactoryBean jndiObjectFactoryBean = new JndiObjectFactoryBean();return (DataSource) jndiObjectFactoryBean.getObject();
  }
}

 XML配置profile:

設置整個XML文件屬於某個profile, 每個環境建立一個XML文件,把這些XML文件都放在部署環境中,根據環境自動調用

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jdbc="http://www.springframework.org/schema/jdbc"
  xmlns:jee="http://www.springframework.org/schema/jee" xmlns:p="http://www.springframework.org/schema/p"
  xsi:schemaLocation="
    http://www.springframework.org/schema/jee
    http://www.springframework.org/schema/jee/spring-jee.xsd
    http://www.springframework.org/schema/jdbc
    http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
    http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd"
    profile="dev">
    
    <jdbc:embedded-database id="dataSource" type="H2">
      <jdbc:script location="classpath:schema.sql" />
      <jdbc:script location="classpath:test-data.sql" />
    </jdbc:embedded-database>
    
</beans>

 也能夠經過嵌套<beans>元素,在同一個XML文件下建立不一樣profile bean

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jdbc="http://www.springframework.org/schema/jdbc"
  xmlns:jee="http://www.springframework.org/schema/jee" xmlns:p="http://www.springframework.org/schema/p"
  xsi:schemaLocation="
    http://www.springframework.org/schema/jee
    http://www.springframework.org/schema/jee/spring-jee.xsd
    http://www.springframework.org/schema/jdbc
    http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
    http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd">

  <beans profile="dev">
    <jdbc:embedded-database id="dataSource" type="H2">
      <jdbc:script location="classpath:schema.sql" />
      <jdbc:script location="classpath:test-data.sql" />
    </jdbc:embedded-database>
  </beans>
  
  <beans profile="prod">
    <jee:jndi-lookup id="dataSource"
      lazy-init="true"
      jndi-name="jdbc/myDatabase"
      resource-ref="true"
      proxy-interface="javax.sql.DataSource" />
  </beans>
</beans>

激活profile:

bean根據profile的建立步驟:

  • 一、沒有指定profile的bean任什麼時候候都會建立
  • 二、根據spring.profiles.active屬性,找到當前激活的profile
  • 三、若是active屬性沒有設置,則找spring.profiles.default屬性,找到默認的profile
  • 四、若是2個值都沒有設置,則不會建立任何定義在profile裏面的bean

spring.profiles.active,spring.profiles.default2個參數的設置方式:

  • 一、做爲DispatcherServlet的初始化參數;
  • 二、做爲Web應用的上下文參數;
  • 三、做爲JNDI條目;
  • 四、做爲環境變量;
  • 五、做爲JVM的系統屬性;
  • 六、在集成測試類上,使用@ActiveProfiles註解設置

具體寫法,後面的例子會講到

使用profile進行測試:

使用@ActiveProfiles註解:

  @RunWith(SpringJUnit4ClassRunner.class)
  @ContextConfiguration(classes=DataSourceConfig.class)
  @ActiveProfiles("prod")
  public static class ProductionDataSourceTest {
    @Autowired
    private DataSource dataSource;
    
    @Test
    public void shouldBeEmbeddedDatasource() {
      // should be null, because there isn't a datasource configured in JNDI
      assertNull(dataSource);
    }
  }

3.二、條件化Bean

使用@Conditional註解:

@Conditional註解中給定一個類,這個類實現Condition接口,其中實現的matches方法的值表明改bean是否生成。

@Configuration
public class MagicConfig {

  @Bean
  @Conditional(MagicExistsCondition.class)
  public MagicBean magicBean() {
    return new MagicBean();
  }
  
}
public class MagicExistsCondition implements Condition {

  @Override
  public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
    Environment env = context.getEnvironment();
    return env.containsProperty("magic");
  }
  
}

ConditionContext接口:

AnnotatedTypeMetadata接口:

3.三、處理自動裝配的歧義性

出現歧義的情景:

使用自動裝配時,若是有多個bean能匹配上,會產生錯誤

例如:

//某方法
@Autowired
public void setDessert(Dessert dessert){
    this.dessert = dessert;
}

//有3個類實現了該接口
@Component
public class Cake implements Dessert{...}
@Component
public class Cookies implements Dessert{...}
@Component
public class IceCream implements Dessert{...}

Spring沒法從3者中作出選擇,拋出NoUniqueBeanDefinitionException異常。

使用@Primary註解標註首選bean:

//使用@Component配置bean時,可使用@Primary註解
@Component
@Primary
public class Cake implements Dessert{...}

//使用@Bean配置bean時,可使用@Primary註解
@Bean
@Primary
public Dessert iceCream{
    return new IceCream();
}
<!-- 使用XML配置時,設置primary屬性 -->
<bean id="iceCream" class="com.desserteater.IceCream" primary="true" />

 使用@Primary時,也會出現多個匹配的bean都標註了primary屬性,一樣會讓Spring出現沒法選擇的狀況,致使錯誤

 使用@Qualifier註解限定裝配的bean:

一、使用默認限定符:

@Autowired
@Qualifier("iceCream")
public void setDessert(Dessert dessert){
    this.dessert = dessert;
}

@Qualifier註解設置的參數是要注入的bean的ID,若是沒有爲bean設定ID,則爲首字母小寫的類名(也稱這個bean的默認限定符)

二、爲bean設置自定義限定符:

@Component
@Qualifier("soft")
public class Cake implements Dessert{...}

@Bean
@Qualifier("cold")
public Dessert iceCream{
    return new IceCream();
}

在bean上使用@Qualifier註解,表示爲bean設置自定義的限定符。那麼自動裝載時,就可使用自定義的限定符進行限定

@Autowired
@Qualifier("cold")
public void setDessert(Dessert dessert){
    this.dessert = dessert;
}

三、使用自定義的限定符註解:

假設已經有一個bean使用了限定符cold,結果另一個bean也須要使用限定符cold,這樣也出現了多個匹配的bean也會報錯。

解決這個問題的思路是,再爲這2個bean增長限定符,繼續細化;可是@Qualifier註解並不支持重複註解,不能在一個bean上使用多個@Qualifier註解。

爲了解決這個問題,可使用自定義的限定符註解:

//代替@Qualifier("cold")
@Target({ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Cold{}

//代替@Qualifier("creamy")
@Target({ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Creamy{}

 這樣,如下代碼,自動裝配式使用了2個自定義限定符註解,也能夠找到惟一匹配的bean。

@Bean
@Cold
@Creamy
public Dessert iceCream{
    return new IceCream();
}

@Bean
@Cold
public Dessert ice{
    return new Ice();
}

@Autowired
@Cold
@Creamy
public void setDessert(Dessert dessert){
    this.dessert = dessert;
}

3.四、bean的做用域

默認狀況下,bean是單例的形式建立的,既整個應用程序使用的bean是同一個實例。

有些狀況,若是想重用這個bean,結果這個bean被以前的操做污染了,會使程序發生錯誤。

Spring支持爲bean設置做用域,提供瞭如下幾種做用域:

  • 單例(Singleton):整個應用中,只建立bean的一個實例。
  • 原型(Prototype):每次注入或者經過Spring應用上下文獲取的時候,都會建立一個新的bean實例。
  • 會話(Session):Web應用中,爲每一個會話建立一個bean實例。
  • 請求(Request):Web應用中,爲每一個請求建立一個bean實例。
//可使用組件掃描時,聲明做用域;做用域可使用ConfigurableBeanFactory表示
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class Cake implements Dessert{...}

//能夠在Java配置Bean時,聲明做用域;做用域也可使用字符串表示,不過建議使用ConfigurableBeanFactory更不容易出錯
@Bean
@Scope("prototype")
public Dessert iceCream{
    return new IceCream();
}
<!-- 使用XML配置時,設置bean做用域 -->
<bean id="iceCream" class="com.desserteater.IceCream" scope="prototype" />

使用會話和請求做用域

 某些情景下,某個bean(例如Web應用中的購物車bean),不該該是對於程序單例的,而是對於每個用戶有對應一個bean。

這種狀況下,適合使用會話做用域的bean

@Component
@Scope(
    value=WebApplicationContext.SCOPE_SESSION,
    proxyMode=ScopedProxyMode.INTERFACES)
public ShopingCart cart(){...}
  • WebApplicationContext:SCOPE_SESSION屬性代表Spring爲每個Web會話建立一個實例
  • ScopedProxyMode:解決會話做用域的bean注入到單例bean的問題。當會話做用域的bean注入到單例bean的時候,Spring改成注入一個bean代理,當單例bean須要調用會話做用域的bean時,該代理才針對當前會話,找到對應的bean,進行調用。
    • ScopedProxyMode.INTERFACES:當該bean是一個接口的時候,設置值爲INTERFACES,表示該代理要實現該接口,並講調用委託給實現的bean。
    • ScopedProxyMode.TARGET_CLASS:當該bean是一個類的時候,設置值爲TARGET_CLASS,由於SPring沒法建立基於接口的代理,須要使用CGLib來生成基於類的代理,經過生成目標類擴展的方式來進行代理。

一樣,請求做用域也是有同樣的問題,一樣處理便可。

在XML中聲明做用域代理

引用aop命名空間

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="
      http://www.springframework.org/schema/aop
      http://www.springframework.org/schema/aop/spring-beans.xsd
      http://www.springframework.org/schema/beans 
      http://www.springframework.org/schema/beans/spring-beans.xsd
      http://www.springframework.org/schema/context">

    <!-- 配置內容 -->

</beans>

使用aop命名控件聲明做用域代理:

<aop:scoped-proxy />是和@Scope註解的proxyMode屬性相同的XML元素

它告訴Spring爲bean建立一個做用域代理,默認狀態下,會使用CGLib建立目標類代理。

<bean id="cart"
    class="com.myapp.ShoppingCart"
    scope="session">
    <aop:scoped-proxy />
</bean>

設置爲生成基於接口的代理

<bean id="cart"
    class="com.myapp.ShoppingCart"
    scope="session">
    <aop:scoped-proxy proxy-target-class="false" />
</bean>

3.五、運行時值注入

以前討論的依賴注入,主要關注將一個bean引用注入到另外一個bean的屬性或構造器參數中。

依賴注入還有另一方面:將值注入到bean的屬性或構造器參數中。

固然可使用以前說過的硬編碼,可是咱們這裏但願這些值在運行時肯定。

Spring提供2種運行時求值方式:屬性佔位符(Property placeholder)、Spring表達式語言(SpEL)

一、注入外部的值

使用@PropertySource註解和Environment:

@Configuration
@PropertySource("classpath:/com/soundsystem/app.properties")
public class EnvironmentConfig {

  @Autowired
  Environment env;
  
  @Bean
  public BlankDisc blankDisc() {
    return new BlankDisc(
        env.getProperty("disc.title"),
        env.getProperty("disc.artist"));
  }
  
}

經過@PropertySource註解引用一個名爲app.properties的文件,文件內容以下

disc.title=Sgt. Peppers Lonely Hearts Club Band
disc.artist=The Beatles

而後經過Spring的Environment,使用getProperty()方法,進行檢索屬性。

深刻Spring的Environment:

getProperty()方法的重載

String getProperty(String key)
String getProperty(String key, String defaultValue)
T getProperty(String key, class<T> type)
T getProperty(String key, class<T> type,T defaultValue)
  • getProperty()方法若是沒有找到對應的屬性,會返回null值
  • getRequiredProperty()方法,若是沒有找到屬性,會拋出IllegalStateException異常
  • containsProperty()方法:檢查某個屬性是否存在
  • getPropertyAsClass()方法:將屬性解析爲類

檢查profile的激活狀態

  • String[] getActiveProfiles():返回激活profile名稱的數組;
  • String[] getDefaultProfiles():返回默認profile名稱的數組;
  • boolean acceptsProfiles(String... profiles):若是environment支持給定profile的話,返回true

解析屬性佔位符「${...}」以及註解@Value:

使用Environment檢索屬性很方便,Spring同時也提供了佔位符裝配屬性的方法

a、配置PropertyPlaceholderConfigurer bean或PropertySourcesPlaceholderConfigurer bean(Spring3.1開始推薦用這個) ,這個bean能夠基於Spring Environment以及其屬性源來解析佔位符

Java配置

@Bean
public static PropertySourcesPlaceholderConfigurer placeholderConfigurer(){
    return new PropertySourcesPlaceholderConfigurer();
}

XML配置,引用Spring context命名空間中的<context:property-placeholder />便可自動生成PropertySourcesPlaceholderConfigurer bean

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="
      http://www.springframework.org/schema/beans 
      http://www.springframework.org/schema/beans/spring-beans.xsd
      http://www.springframework.org/schema/context
      http://www.springframework.org/schema/context/spring-beans.xsd">

    <context:property-placeholder />

</beans>

b、注入

Java注入

public BlandKisc(
    @Value("${disc.title}") String title,
    @Value("${disc.artist}") String artist){
    this.title = title;
    this.artist = artist;
}

XML注入

<bean id="sgtPeppers"
      class="soundsystem.BlandDisc"
      c:_title="${disc.title}"
      c:_artist="${disc.artist}" />

二、使用Spring表達式語言進行裝配

Spring 3引入了Spring表達式語言(Spring Expression Language,SpEL),可以強大和簡潔地把值裝配到bean屬性和構造器參數中。

SpEL的一些特性:

  • 使用bean的ID引用bean;
  • 調用方法和訪問對象的屬性;
  • 對值進行算術、關係和邏輯運算;
  • 正則表達式匹配;
  • 集合操做;

2.一、表示字面值:

#{1}//整型
#{3.14159}//浮點數
#{9.87E4}//科學計數法
#{'Hello'}//String類型字面值
#{false}//Boolean類型

2.二、引用bean、屬性和方法:

#{sgtPeppers}//引用ID爲sgtPeppers的bean
#{sgtPeppers.artist}//引用bean的屬性
#{sgtPeppers.selectArtist()}//調用bean的方法
#{sgtPeppers.selectArtist().toUpperCase()}//調用bean的方法的值的方法,例如方法值是String類型,則能夠調用String對象的toUpperCase方法
#{sgtPeppers.selectArtist()?.toUpperCase()}//類型安全的運算符「?.」,當值爲null時返回null,不然猜執行toUpperCase方法

2.三、表達式中使用類型:

主要做用是調用類的靜態方法和變量。使用T()運算符,得到Class對象。

#{T(java.lang.Math)}//獲得一個class對象
#{T(java.lang.Math).PI}//類的常量
#{T(java.lang.Math).random()}//類的靜態方法

2.四、SpEL運算符:

用於在SpEL表達式上作運算

#{2 * T(java.lang.Math).PI * circle.radius}//計算周長
#{T(java.lang.Math).PI * circle.radius ^ 2}//計算面積
#{disc.title + ' by ' + disc.artist}//拼接字符串
#{counter.total == 100}//比較運算符 結果爲布爾值
#{counter.total eq 100}//比較運算符 結果爲布爾值
#{scoreboard.score ? 1000 ? "Winner!" : "Loser"}//三元運算符
#{disc.title ?: 'Rattle and Hum'}//Elvis運算符(Elvis是貓王的名字,?:符號像貓王的頭髮),判斷是否爲null,是null則給默認值

2.五、計算正則表達式:

使用matches運算符,返回Boolean類型值

#{admin.email matches '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.com'}//驗證有效郵件

 2.六、計算集合:

#{jukebox.songs[4].title}//songs集合第5個元素的title屬性
#{'this is a test'[3]}//String中的一個字符
#{jukebox.songs.?[artist eq 'Jay']}//使用查詢運算符(.?[])過濾子集
#{jukebox.songs.^[artist eq 'Jay']}//使用第一個匹配符(.^[])找到第一個匹配的項
#{jukebox.songs.$[artist eq 'Jay']}//使用最後一個匹配符(.$[])找到最後一個匹配的項
#{jukebox.songs.![title]}//投影運算符(.![])選擇屬性投影到一個新的集合

2.七、這裏介紹的SpEL只是冰山一角,有須要的去查閱相關資料

4、面向切面的Spring

4.一、什麼是面向切面編程

什麼是AOP:

爲何須要面向切面編程(AOP)技術:

  • 在軟件開發中,有一些需求須要散步在應用中的多處,稱爲橫切關注點。
  • 例如但願每一次操做,都記錄下日誌;固然咱們能夠在每一次操做都加上記錄日誌的代碼,可是這樣變得十分複雜和繁瑣。
  • 面向切面編程(AOP)的目的就是把這些橫切關注點和業務邏輯相分離。
  • 依賴注入(DI)實現了應用對象之間的解耦;而面向切面編程(AOP)實現了橫切關注點和它們影響的對象之間的解耦。

什麼是面向切面編程(AOP)技術:

如上所述,切面能夠幫助咱們模塊化橫切關注點。

通常若是咱們須要重用通用功能的話,常見的面向對象技術是繼承或委託。

可是若是整個應用中都使用一樣的基類,繼承每每會致使一個脆弱的對象體系;

而使用委託可能須要對委託對象進行復雜的調用。

而切面提供了另外一種可選方案:在一個獨立的地方定義通用功能,經過聲明的方式定義此功能用何種方式在何處應用,而無需修改受影響的類。橫切關注點能夠被模塊化特殊的類,這些類稱爲切面。

這樣作有2個好處:

  • a、每一個關注點集中在一個地方,而不是分散到多出代碼中;
  • b、服務模塊更簡潔,由於它們只關注核心代碼,次要代碼被轉移到切面中。

AOP術語:

通知(Advice):

通知定義了切面是什麼以及什麼時候使用。除了描述切面要完成的工做,通知還解決了什麼時候執行這個工做的問題。

Spring切面有5種類型的通知:

  • a、前置通知(Before):在目標方法被調用以前調用通知功能;
  • b、後置通知(After):在目標方法完成以後調用通知,此事不會關心方法的輸出是什麼;
  • c、返回通知(After-returning):在目標方法成功執行以後調用通知;
  • d、異常通知(After-throwing):在目標方法拋出異常後調用通知;
  • e、環繞通知(Around):通知包裹了被通知的方法,在被通知的方法調用以前和調用以後執行自定義的行爲。

鏈接點(Join point):

鏈接點是咱們的程序能夠應用通知的實際,是應用執行過程當中可以插入切面的一個點。

切面代碼能夠利用這些點插入到應用的正常流程之中,並添加新的行爲。

切點(Poincut):

切點指切面所通知的鏈接點的範圍。

一個切面不須要通知整個程序的鏈接點,經過切點,定義了切面所應用的範圍。

通知定義了切面是「什麼」以及「什麼時候」執行,切點定義了「何處」須要執行切面。

切面(Aspect):

 切面是通知和切點的結合,定義了須要作什麼,在什麼時候,何處完成該功能。

引入(Introduction):

引入容許咱們想現有的類添加新的方法或屬性。

能夠在不修改現有類的狀況下,將新的方法和實例變量引入到該類中。

織入(Weaving):

織入是把切面應用到目標對象並建立新的代理對象的過程。

切面在指定的鏈接點被織入到目標對象中。

在目標對象的聲明週期有幾個點能夠進行織入:

  • a、編譯期:切面在目標類編譯時被織入。這種方式須要特殊的編譯期。AspectJ的織入編譯器就是用這種方式織入切面。
  • b、類加載期:切面在目標類加載到JVM時被織入。這種方式須要特殊的類加載器(ClassLoader),它能夠在目標類被引入應用以前加強該目標類的字節碼。
  •       AspectJ 5的加載時織入(load-time weaving,LTW)就支持以這種方式織入切面。
  • c、運行期:切面在應用運行的某個時刻被織入。通常狀況下,在織入切面時,AOP容器會以目標對象動態建立一個代理對象。Spring AOP就是以這種方式織入切面的。

Spring對AOP的支持:

  • 不是全部的AOP框架都相同,好比在鏈接點模型上可能有強弱之分,有些容許在字段修飾符級別應用通知,而另外一些只支持方法調用相關的鏈接點;織入切面的方式和時機也不一樣。
  • 可是,AOP框架的基本功能,就是建立切點來定義切面所織入的鏈接點。
  • 本書關注Spring AOP,同時Spring和AspectJ項目之間有大量協做,Spring對AOP支持有不少方面借鑑了AspectJ項目

Spring支持4種類型的AOP支持:

  • a、基於代理的經典Spring AOP
    • 經典Spring AOP過於笨重和複雜,不做介紹。
  • b、純POJO切面
    • 藉助Spring的aop命名空間,能夠將純POJO轉換爲切面。
    • 實際上這些POJO知識提供了知足切點條件時所調用的方法。
    • 但這種技術須要XML配置。
  • c、@AspectJ註解驅動的切面
    • 提供以註解驅動的AOP。本質上仍是Spring基於代理的AOP,可是編程模型修改成和AspectJ註解一致。
    • 這種方式好處在於不用使用XML。
  • d、注入式AspectJ切面(適用於Spring各版本)
    • 若是AOP需求超過了簡單的方法調用(如構造器或屬性攔截),則須要使用AspectJ來實現切面。
  • 前3種都是Spring AOP實現的變體,基於動態代理,因此Spring對AOP的支持侷限於方法攔截

Spring通知是Java編寫的:

Spring所建立的通知是標準的Java類編寫的,咱們可使用Java開發IDE來開發切面。

並且定義通知所應用的切點一般會用註解或Spring XML編寫,Java開發者都很是熟悉。

AspectJ與之相反,AspectJ最初是以Java語言擴展的方式實現的。

優勢是經過特有的AOP語言,咱們能夠得到更強大和細粒度的控制,以及更豐富的AOP工具集。

缺點是須要額外學習新的工具和語法。

Spring在運行時通知對象:

經過在代理類中包裹切面,Spring在運行期把切面織入到Spring管理的bean中。

代理類封裝目標類,並攔截被通知方法的調用,再把調用轉發給真正的目標bean。

代理攔截到方法調用時,會在調用目標bean方法以前執行切面邏輯。

知道應用須要被代理的bean時,Spring纔會建立代理對象。若是使用的是ApplicationContext的話,在ApplicationContext從BeanFactory中加載全部bean的時候,Spring纔會建立被代理的對象。

由於Spring運行時才建立代理對象,因此咱們不須要特殊的編譯器來織入Spring AOP的切面。

Spring只支持方法級別的鏈接點:

由於Spring是基於動態代理,因此只支持方法鏈接點;但也足以知足絕大部分需求。

若是須要攔截字段和構造器,可使用AspectJ和JBoss

4.二、經過切點來選擇鏈接點

Spring AOP中,要使用AspectJ的切點表達式語言來定義切點。

Spring僅支持AspectJ切點指示器的一個子集。

Spring是基於代理的,而某些切點表達式是與基於代理的AOP無關的。

下面是Spring AOP所支持的AspectJ切點指示器:

其中,只有execution指示器是實際執行匹配的,其它指示器都是用來限制匹配的。

Spring中嘗試使用AspectJ其它指示器時,會拋出IllegalArgument-Exception異常

編寫切點:

假設有一個接口

public interface Performance{
    public void perform();
}

 使用切點表達式設置當perform()方法執行時觸發通知的調用:

使用within()指示器限制僅匹配concert包

支持的關係操做符有且(&&),或(||),非(!)

若是用XML配置,由於&在XML中有特殊含義,因此可使用and,or,not來做爲關係操做符

切點中選擇bean:

Spring引入了一個新的bean()指示器,經過bean的ID來限制bean

 限制id爲woodstock的bean才應用通知

 
 限制id非woodstock的bean才應用通知

4.三、使用註解建立切面

AspectJ5以前,編寫AspectJ切面須要學習一種Java語言的擴展。

AspectJ5引入了使用註解來建立切面的關鍵特性,AspectJ面向註解的模型能夠很是簡便地經過註解把任意類轉變爲切面。

一、定義切面:

@Aspect
public class Audience{
    //表演前
    @Before("execution(** concert.Performance.perform(..))")
    public void silenceCellPhones(){
        System.out.println("Silencing cell phones");
    }
    //表演前
    @Before("execution(** concert.Performance.perform(..))")
    public void takeSeats(){
        System.out.println("Taking seats");
    }
    //表演後
    @AfterReturning("execution(** concert.Performance.perform(..))")
    public void applause(){
        System.out.println("Clap!!");
    }
    //表演失敗後
    @AfterThrowing("execution(** concert.Performance.perform(..))")
    public void demandRefund(){
        System.out.println("Refund!!");
    }

}

@Aspect註解表示Audience不只是一個POJO,仍是一個切面。

@Before,@AfterReturning等註解,用來聲明通知方法。

使用@Pointcut註解定義可重用切點:

@Aspect
public class Audience{
    //可重用切點
    @Pointcut("excution(** concert.performance.perform(..))")
    public void performance(){}
    //表演前
    @Before("performance()")
    public void silenceCellPhones(){
        System.out.println("Silencing cell phones");
    }
    //表演前
    @Before("performance()")
    public void takeSeats(){
        System.out.println("Taking seats");
}

使用一個空的方法,做爲標記,使用@Pointcut註解定義成一個可重用的節點,而後經過「方法名()」來進行引用

啓用自動代理:

以上僅僅是定義了切面,要啓動切面功能,須要啓動切面的代理

Java配置方法:

@Configuration
@EnableAspectJAutoProxy//啓動AspectJ自動代理
@ComponentScan
public class ConcertConfig{
    @Bean
    Public Audience audience(){//聲明Audience bean
        return new Audience();
    }
}

 XML配置方法(使用aop命名空間)

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="
      http://www.springframework.org/schema/aop
      http://www.springframework.org/schema/aop/spring-aop.xsd
      http://www.springframework.org/schema/beans 
      http://www.springframework.org/schema/beans/spring-beans.xsd
      http://www.springframework.org/schema/context
      http://www.springframework.org/schema/context/spring-beans.xsd">

    <context:conponent-scan base-package="concert" />

    <!-- 啓用AspectJ自動代理 -->
    <aop:aspectj-autoproxy />
    
    <!-- 聲明Audience bean -->
    <bean class="concert.Audience" />
    
</beans>

代理的做用:

使用上述2種方法,會給Concert bean建立一個代理,Audence類中的通知方法會在perform()調用先後執行。

注意!Spring的AspectJ自動代理僅僅使用@AspectJ做爲建立切面的知道,切面本質上仍是Spring基於代理的切面,僅侷限於代理方法的調用。

若是想要使用AspectJ的全部能力,必須運行時使用AspectJ而且不依賴Spring。

二、建立環繞通知

@Aspect
public class Audience{
    //可重用切點
    @Pointcut("excution(** concert.performance.perform(..))")
    public void performance(){}

    //環繞通知方法
    @Around("performance()")
    public void watchPerformance(ProceedingJoinPoint jp){
        try{
            System.out.println("Silencing cell phones");
            System.out.println("Taking seats");
            jp.proceed();
            System.out.println("CLAP!!");
        }catch(Throwable e){
            System.out.println("refund!!");
        }
    }
}

 能夠將前置和後置通知寫在一個方法中,並使用ProceedingJoinPoint對象來進行對目標方法的調用。

三、處理通知中的參數

@Aspect
public class TrackCounter{
    private Map<Interger,Integer> trackCounts = new HashMap<Integer,Integer>();

    @Pointcut(
        "execution(* soundsystem.CompactDisc.playTrack(int))" +
        "&& args(trackNumber)")
    public  void trackPlayed(int trackNumber){}

    @Before("trackPlayed(trackNumber)")
    public void countTrrack(int trackNumber){
        int currentCount = getPlayCount(trackNumber);
        trackCounts.put(trackNumber,currentCount + 1);
    }

    public int getPlayCount(int trackNumber){
        return trackCounts.containsKey(trackNumber)
                ? trackCounts.get(trackNumber) : 0;
    }
}

四、經過註解引入新功能

假設情景:有一個類,但願讓其以及其實例實現某個接口,但這個類是不能夠修改的(如沒有源碼)。咱們能夠經過AOP爲這個類引入新的方法,實現該接口。

例如

咱們有一個類concert.Performance

但願能經過AOP實現接口

public interface Encoreable{
    void performEncore();
}

切面

@Aspect
public class EncoreableIntroducer{
    @DeclareParents(value="concert.performance+",defaultImpl=DefaultEncoreable.class)
    public static Encoreable encoreable;
}

經過聲明一個切面,使用@DeclareParents註解,講Encoreable接口引入到Performance bean中。

@DeclareParents的組成部分

一、Value屬性指定哪一種類型bean要引入該接口。本例中,指全部實現Performance的類型。(加號表示是Performance的全部子類型,而不是Performance自己。)

二、defaultImpl屬性指定爲引入功能提供實現的類。在這裏咱們指定的是DefaultEncoreable提供實現。

三、@DeclareParents註解所標註的驚天屬性知名了要引入的接口。在這裏咱們引入的是Encoreable接口。

【------------------------Spring Web------------------------】

5、構建Spring Web應用程序

5.一、Spring MVC起步

在基於HTTP協議的Web應用中,Spring MVC將用戶請求在調度Servlet、處理器映射、控制器以及視圖解析器之間移動,再將用戶結果返回給用戶。

咱們將介紹請求如何從客戶端發起,通過Spring MVC中的組件,最終回到客戶端。

一、跟蹤Spring MVC的請求

請求:請求離開瀏覽器時,帶有用戶請求內容的信息。通常包含請求的URL、用戶提交的表單信息等。

  • 一、前端控制器:前端控制器是一種經常使用的Web應用程序模式。服務器使用一個單例的Servlet(Spring的DispatcherServlet)做爲前端控制器,將請求委託給應用程序的其它組件來執行實際的處理。
  • 二、查詢映射並分發請求:DispatcherServlet的任務是將請求發送給Spring MVC控制器。DispatcherServlet收到請求後,根據請求攜帶的URL信息,查詢處理器映射,肯定請求對應的控制器,併發送給控制器。
  • 三、控制器處理請求:控制器獲得請求後,會獲取請求中用戶提交的信息,並等待控制器處理這些信息。
  • 四、模型和視圖名:控制器處理完數據後,會產生一些信息(稱爲模型),爲了顯示,這些模型須要發送給一個視圖。控制器把模型數據打包,並標示出用於渲染輸出的視圖名,而後將請求、模型、視圖邏輯名發送回給DispatcherServlet
  • 五、視圖解析器:DispatcherServlet根據邏輯視圖名找到真正的視圖實現。
  • 六、視圖渲染:根據找到的視圖實現,並將模型數據交付給視圖實現,便可將數據渲染輸入。
  • 七、傳遞到客戶端:這個輸出會經過響應對象傳遞給客戶端。

二、搭建Spring MVC

2.一、配置DispatcherServlet:

傳統方式:配置在web.xml中,這個文件放到應用的WAR包中。

如今方法:使用Java將DispatcherServlet配置到Servlet容器中。

public class SpitterWebInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
  
  @Override
  protected Class<?>[] getRootConfigClasses() {
    return new Class<?>[] { RootConfig.class };
  }

  @Override
  protected Class<?>[] getServletConfigClasses() {
    return new Class<?>[] { WebConfig.class };//指定配置類
  }

  @Override
  protected String[] getServletMappings() {
    return new String[] { "/" };//將DispatcherServlet映射到「/」
  }

}

擴展AbstractAnnotationConfigDispatcherServletInitializer的任意類都會自動配置DipatcherServlet和Spring應用上下文,Spring的應用上下文會位於應用程序的Servlet上下文之中。

getServletMappings()方法將一個或多個路徑映射到DispatcherServlet上。本例中「/」,表明是應用的默認Servlet,會處理進入應用的全部請求。

要理解另外2個方法,須要先理解DispatcherServlet和一個Servlet監聽器(ContextLoaderListener)的關係。

2.二、兩個應用上下文之間的關係

DispatcherServlet啓動時,建立Spring應用上下文,加載配置文件或配置類中聲明的bean。

getServletConfigClasses()方法要求DispatcherServlet加載應用上下文時,使用定義在WebConfig配置類中的bean。

ContextLoaderListener會建立另一個應用上下文

咱們但願DispatcherServlet記載包含Web組件的bean,如控制器、視圖解析器以及處理器映射;而ContextLoaderListener要加載應用中其它bean,一般是驅動應用後端的中間層和數據層組件。

AbstractAnnotationConfigDispatcherServletInitializer 會同時建立DispatcherServlet和ContextLoaderListener。

getServletConfigClasses()方法返回的帶有@Configuration註解的類會用來定義DispatcherServlet應用上下文的bean。

getRootConfigClasses()方法返回的帶有@Configuration註解的類將會用來配置ContextLoaderListener建立的應用上下文中的bean。

Servlet3.0以前的服務器,只能支持web.xml的配置方式;Servlet3.0及之後版本才支持AbstractAnnotationConfigDispatcherServletInitializer 的配置方式。

2.三、啓動Spring MVC

傳統方法:使用XML配置Spring MVC組件

如今方法:使用Java配置Spring MVC組件

最簡單的Spring MVC配置

@Configuration
@EnableWebMvc
public class WebConfig {
}

這樣就能啓動Spring MVC,可是有如下問題:

  • 一、沒有配置視圖解析器,Spring使用默認的BeanNameViewResolver,這個視圖解析器會查找ID與視圖名稱匹配的bean,而且查找的bean要實現View接口,以這樣的方式解析視圖。
  • 二、沒有啓動組件掃描。致使Spring只能找到顯式聲明在配置類中的控制器。
  • 三、DispatcherServlet會映射爲默認的Servlet,因此會處理全部的請求,包括對靜態資源的請求,如圖片和樣式表(通常不但願這樣)。

如下配置便可解決上述問題

@Configuration
@EnableWebMvc//啓用Spring MVC
@ComponentScan("spittr.web")//啓用組件掃描
public class WebConfig extends WebMvcConfigurerAdapter {
  @Bean
  public ViewResolver viewResolver() {//配置JSP視圖解釋器
    InternalResourceViewResolver resolver = new InternalResourceViewResolver();
    resolver.setPrefix("/WEB-INF/views/");
    resolver.setSuffix(".jsp");
    return resolver;
  }
  @Override
  public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
    configurer.enable();
  }
  @Override
  public void addResourceHandlers(ResourceHandlerRegistry registry) {//配置靜態資源處理
    // TODO Auto-generated method stub
    super.addResourceHandlers(registry);
  }
}
  • 一、@ComponentScan註解,會掃描spittr.web包查找組件;一般咱們編寫的控制器會使用@Controller註解,使其成文組件掃描時的houxuanbean。因此咱們不須要在配置類顯式聲明任何控制器。
  • 二、ViewResolver bean。後面會詳細討論,這個的做用是查找JSP文件。例如home的視圖會被解析爲/WEB-INF/views/home.jsp。
  • 三、新的WebConfig類擴展了WebMvcConfigurerAdapter並重寫了其configureDefaultServletHandling()方法。經過調用enable()方法,要求DispatcherServlet將對靜態資源的請求轉發到Servlet容器中默認的Servlet上,而不是使用DispatcherServlet自己來處理此類請求。

RootConfig的配置

@Configration
@ComponentScan(basePackages={"spitter"},
    excludeFilters={
        @filter{type=FilterType.ANNOTATION, value=EnableWebMvc.class)
    })
public class RootConfig{
}

 使用@ComponentScan註解,後期可使用不少非Web組件來完善RootConfig。

5.二、編寫基本的控制器

一、控制器的基本形式

@RequestMapping註解:

聲明這個控制器所要處理的請求

@Controller
public class HomeController{
    @RequestMapping(value="/", method=GET)
    public String home(){
        return "home";
    }
}

1.一、聲明控制器

2種方式讓控制器類能被掃描稱爲組件:

  • a、在類上使用@Controller聲明這是一個控制器
  • b、在類上使用@Component聲明這是一個組件,而且類名使用Controller做爲結束

1.二、指定處理請求路徑

@RequestMapping(value="/",method=GET)

value表明要處理的請求路徑,method屬性指定所處理的HTTP方法

1.三、返回視圖名稱

return "home";

返回一個字符串,表明須要渲染的視圖名稱。DispatcherServlet會要求視圖解析器將這個邏輯名稱解析爲實際的視圖。

基於咱們在InternalResourceViewResolver的配置,視圖名「home」將被解析爲「/WEB-INF/views/home.jsp」路徑的JSP。

二、測試控制器

控制器自己也是一個POJO,可用普通POJO的測試方法測試,可是沒有太大的意義。

Public class HomeControllerTest{
    @Test
    public void testHomePage() throws Exception{
        HomeController controller = new HomeController();
        assertEquals("home", controller.home());
    }
}

這個測試只是測試home()方法的返回值,沒有站在SpringMVC控制器的角度進行測試。

Spring 3.2開始能夠按照控制器的方式來測試控制器。

Spring 3.2開始包含一種mock Spring MVC並針對控制器執行HTTP請求的機制,這樣測試控制器就不用啓動Web服務器和Web瀏覽器了。

Public class HomeControllerTest{
    @Test
    public void testHomePage() throws Exception{
        HomeController controller = new HomeController();
        MockMvc mockMvc = standaloneSetup(controller).build();//搭建MockMvc
        mockMvc.perform(get("/"))//對"/"執行GET請求
                      .andExpect(view().name("home"));//預期獲得home視圖
    }
}

先傳遞一個HomeController實例到standaloneSetup()並調用build()來構建MockMvc實例,而後用這個實例來執行鍼對「/」的GET請求並設置指望獲得的視圖名稱。

三、定義類級別的請求處理

對類使用@RequestMapping註解,那麼這個註解會應用到全部處理器方法中。

@Controller
@RequestMapping("/")
public class HomeController{
    @RequestMapping( method=GET)
    public String home(){
        return "home";
    }
}

路徑還能夠是一個數組,下面例子表明home()方法能夠映射到對「/」和「homepage」的GET請求。

@Controller
@RequestMapping({"/", "/homepage"})
public class HomeController{
    @RequestMapping( method=GET)
    public String home(){
        return "home";
    }
}

四、傳遞模型數據到視圖中

使用Model傳遞數據

@RequestMapping(method=RequestMethod.GET)
public String spittles(Model model){
    model.addAttribute(spittleRepository.findSpittles(Long.MAX_VALUE,20));
    return "spittles";
}

經過Model參數,能夠將控制器裏面的值,傳遞到視圖中,渲染到客戶端。

Model其實是一個Map(Key-Value對集合)

使用addAttribute而且不指定key的時候,Model會根據類型自動生成key,例如上面是一個List<Spittle>,那麼key值就是spittleList

最後控制器返回視圖邏輯名,標明須要渲染的視圖。

改用Map傳遞數據

若是不想使用Spring類型,把Model改爲Map類型也是能夠的

@RequestMapping(method=RequestMethod.GET)
public String spittles(Map model){
    model.put("spittleList",spittleRepository.findSpittles(Long.MAX_VALUE,20));
    return "spittles";
}

直接返回數據

@RequestMapping(method=RequestMethod.GET)
public List<Spittle> spittles(){
    return spittleRepository.findSpittles(Long.MAX_VALUE,20);
}

這種寫法,沒有返回視圖名稱,也沒有顯式設定模型。

模型:當處理器方法直接返回對象或集合時,這個值會放進模型中,模型的key由類型推斷出來。

視圖:而視圖的邏輯名稱會根據請求路徑推斷得出,如/spittles的GET請求,邏輯視圖名稱就是spittles(去掉開頭斜線)。

視圖的渲染

不管使用哪一種方法,結果是同樣的:

在控制器中,將數據定義爲模型,併發送到指定的視圖,根據視圖的邏輯名稱,按照咱們配置的InternalResourceViewResolver視圖解析器,找到對應的視圖文件(如"/WEB-INF/views/spittles.jsp")。

當視圖是JSP的時候,模型數據會做爲請求屬性放到請求(request)之中。

所以,在jsp文件中可使用JSTL(JavaServer Pages Standard Tag Library)的<c:forEach>標籤進行渲染。

測試控制器視圖名以及傳遞的模型數據

@Test
public void houldShowRecentSpittles() throws Exception {
List<Spittle> expectedSpittles = createSpittleList(20);
SpittleRepository mockRepository = mock(SpittleRepository.class);//使用mock,利用接口建立一個實現,並建立一個實例對象
when(mockRepository.findSpittles(Long.MAX_VALUE, 20))
    .thenReturn(expectedSpittles);//調用mock實現,建立20個Spittle對象

SpittleController controller = new SpittleController(mockRepository);
MockMvc mockMvc = standaloneSetup(controller)//搭建MockMvc
    .setSingleView(new InternalResourceView("/WEB-INF/views/spittles.jsp"))
    .build();

mockMvc.perform(get("/spittles"))//對/spittles發起GET請求
   .andExpect(view().name("spittles"))//斷言視圖名爲spittles
   .andExpect(model().attributeExists("spittleList"))
   .andExpect(model().attribute("spittleList", 
              hasItems(expectedSpittles.toArray())));
}

 

5.三、接受請求的輸入

一、處理查詢參數

場景:咱們須要分頁查找Spittle列表,但願傳入2個參數:上一頁最後項的id、每頁的數據數量,從而找到下一頁應該讀取的數據。

獲取參數

使用@RequestParam註解獲取參數

@RequestMapping(method=RequestMethod.GET)
public List<Spittle> spittles(
        @RequestParam("max") long max,
        @RequestParam("count") int count){
    return spittleRepository.findSpittles(max,count);
}

給定默認值

private static final String MAX_LONG_AS_STRING = Long.toString(Long.MAX_VALUE);
@RequestMapping(method=RequestMethod.GET)
public List<Spittle> spittles(
        @RequestParam(value="max",defaultValue=MAX_LONG_AS_STRING) long max,
        @RequestParam(value="count",defaultValue="20") int count){
    return spittleRepository.findSpittles(max,count);
}

注意,查詢參數都是String類型,因此須要把Long.MAX_VALUE轉換爲String類型才能制定默認值。

當綁定到方法的參數時,纔會轉換爲對應的參數的類型。

測試方法

@Test
public void shouldShowPagedSpittles() throws Exception {
    List<Spittle> expectedSpittles = createSpittleList(50);
    SpittleRepository mockRepository = mock(SpittleRepository.class);
    when(mockRepository.findSpittles(238900, 50)).thenReturn(expectedSpittles);//預期的max和count參數

    SpittleController controller = new SpittleController(mockRepository);
    MockMvc mockMvc = standaloneSetup(controller)
            .setSingleView(new InternalResourceView("/WEB-INF/views/spittles.jsp")).build();

    mockMvc.perform(get("/spittles?max=238900&count=50")).andExpect(view().name("spittles"))//傳入max和count參數
            .andExpect(model().attributeExists("spittleList"))
            .andExpect(model().attribute("spittleList", hasItems(expectedSpittles.toArray())));
}

二、經過路徑接收參數

按上面講解的寫法:

@RequestMapping(value = "/show", method = RequestMethod.GET)
public String spittle(@RequestParam("spittleId") long spittleId, Model model) {
    model.addAttribute(spittleRepository.findOne(spittleId));
    return "spittle";
}

這個控制器,能夠處理請求是這樣的:"/spittle/show/?spittle_id=12345"

可是如今流行面向資源的方式,咱們查找一個spittle的行爲,至關於獲取一個spittle資源,更但願URL的方式是:對"/spittle/12345"這樣的URL進行GET請求。

使用@RequestMapping的佔位符與@PathVariable註解

@RequestMapping(value = "/{spittleId}", method = RequestMethod.GET)
public String spittle(@PathVariable("spittleId") long spittleId, Model model) {
    model.addAttribute(spittleRepository.findOne(spittleId));
    return "spittle";
}

這樣在"/spittle/"後面的參數將會傳遞到spittleId變量中

若是變量名和佔位符名稱相同,能夠省掉@PathVariable中的變量

@RequestMapping(value = "/{spittleId}", method = RequestMethod.GET)
public String spittle(@PathVariable("spittleId") long spittleId, Model model) {
    model.addAttribute(spittleRepository.findOne(spittleId));
    return "spittle";
}

測試方法

@Test
public void testSpittle() throws Exception {
    Spittle expectedSpittle = new Spittle("Hello", new Date());
    SpittleRepository mockRepository = mock(SpittleRepository.class);
    when(mockRepository.findOne(12345)).thenReturn(expectedSpittle);

    SpittleController controller = new SpittleController(mockRepository);
    MockMvc mockMvc = standaloneSetup(controller).build();

    mockMvc.perform(get("/spittles/12345")).andExpect(view().name("spittle"))//經過路徑請求資源
            .andExpect(model().attributeExists("spittle")).andExpect(model().attribute("spittle", expectedSpittle));
}

使用路徑傳參只使用於少許參數的狀況,若是須要傳遞大量數據,則須要使用表單。

5.四、處理表單(待補充)

【暫略】6、渲染Web視圖

【暫略】7、Spring MVC的高級技術

【暫略】8、使用Spring Web Flow

9、Spring Security保護Web應用

Spring Security是一種基於Spring AOP和Servlet規範中filter實現的安全框架

  • 提供聲明式安全保護
  • 提供了完整的安全解決方案

Spring Security從2個角度解決安全問題

  • 使用Servlet規範中的filter保護Web請求並限制URL級別的訪問(本章)
  • 使用Spring的AOP,藉助對象代理和使用通知,確保有適當權限的用戶才能訪問受保護的方法(14章)

Spring Security分爲11個模塊

要使用Spring Security,至少要引入Core和Configuration2個模塊

9.一、Spring Security簡介

開發方式的演進:

  • Spring Security基於Servlet Filter,在web.xml或webApplicationInitializer中配置多個filter

簡化:使用一個特殊的filter:DelegatingFilterProxy,這個filter是一個代理類,會把工做交給Spring上下文中的javax.servlet.filter實現類

 

  • Spring 3.2帶來了新的Java配置方式

在實現了WebSecurityConfigurer的bean中使用註解@EnableWebSecurity

更簡化:在擴展類WebSecurityConfigurerAdapter中使用註解@EnableWebSecurity;若是使用Spring MVC,則須要使用註解@EnableWebMvcSecurity

Spring Security的配置

能夠經過重寫如下3個方法,對Spring Security的行爲進行配置

9.二、配置用戶信息服務

Spring Security須要配置用戶信息服務,代表哪些用戶能夠進行訪問

有如下幾種方式,暫時先不詳細介紹

  • 基於內存設置
  • 基於數據庫表設置
  • 基於LDAP設置
  • 配置自定義用戶服務,實現UserDetailsService接口

9.三、攔截請求

 

 

 

 

【---------------------後端中的Spring----------------------】

【暫略】10、經過Spring和JDBC征服數據庫

【暫略】11、使用對象-關係映射持久化數據庫

12、Spring中使用NoSQL數據庫

12.一、使用MongoDB持久化文檔數據

待補充

12.二、使用Noe4j操做圖數據

待補充

12.三、使用Redis操做key-value數據

Redis是一種基於key-value存儲的數據庫,Spring Data沒有把Repository生成功能應用到Redis中,而是使用面向模版的數據訪問的方式來支持Redis的訪問。

Spring Data Redis經過提供一個鏈接工廠來建立模版

12.3.一、鏈接到Redis

建立鏈接工廠:

  爲了鏈接和訪問Redis,有不少Redis客戶端,如Jedis、JRedis等。

  Spring Data Redis爲4種Redis客戶端實現提供了鏈接工廠:

    • JedisConnectionFactory
    • JredisConnectionFactory
    • LettuceConnectionFactory
    • SrpConnectionFactory

  選擇哪一個客戶端實現取決於你,對於Spring Data Redis,這些鏈接工廠的適用性是同樣的

  咱們使用一個bean來建立這個工廠

@Bean
public RedisConnectionFactory redisCF() {
    JedisConnectionFactory cf = new JedisConnectionFactory();
    cf.setHostName("redis-server");
    cf.setPort(7379);
    cf.setPassword("123456");
    return cf;
}

  對於不一樣的客戶端實現,他的工廠的方法(如setHostName)也是相同的,因此他們在配置方面是相同的操做。

12.3.二、Redis模版RedisTemplate

獲取RedisTemplate

  其中一種訪問Redis的方式是,從鏈接工廠中獲取鏈接RedisConnection,使用字節碼存取數據(不推薦):

RedisConnectionFactory cf = ...;
RedisConnection conn = cf.getConnection();
conn.set("greeting".getBytes(),"Hello World".getBytes());
byte[] greetingBytes = conn.get("greeting".getBytes());
String greeting = new String(greetingBytes);

  Spring Date Redis提供了基於模版的較高等級的數據訪問方案,其中提供了2個模版:

    • RedisTemplate:直接持久化各類類型的key和value,而不是隻侷限於字節數組
    • StringRedisTemplate:擴展了RedisTemplate,只關注String類型
RedisConnectionFactory cf = ..;
RedisTemplate<String, Product> redis = new RedisTemplate<String, Product>();
redis.setConnectionFactory(cf);

RedisConnectionFactory cf = ..;
StringRedisTemplate redis = new StringRedisTemplate(cf);

//設置爲bean
@Bean
public RedisTemplate<String, Product> redisTemplate(RedisConnectionFactory cf) {
    RedisTemplate<String, Product> redis = new RedisTemplate<String, Product>();
    redis.setConnectionFactory(cf);
    return redis;
}

@Bean
public StringRedisTemplate redisTemplate(RedisConnectionFactory cf) {
    return new StringRedisTemplate(cf);
}

使用RedisTemplate存取數據

  

//redis是一個RedisTemplate<String,Product>類型的bean

//【使用簡單的值】
//經過product的sku進行存儲和讀取product對象
redis.opsForValue().set(product.getSku(),product);
Product product = redis.opsForValue().get("123456");

//【使用List類型的值】
//在List類型條目尾部/頭部添加一個值,若是沒有cart這個key的列表,則會建立一個
redis.opsForList().rightPush("cart",product);
redis.opsForList().leftPush("cart",product);
//彈出一個元素,會移除該元素
Product product = redis.opsForList().rightPop("cart");
Product product = redis.opsForList().leftPop("cart");
//只是想獲取值
//獲取索引2到12的元素;若是超出範圍,則只返回範圍內的;若是範圍內沒有,則返回空值
List<Product> products = redis.opsForList().range("cart",2,12);

//在Set上執行操做
//添加一個元素
redis.opsForSet().add("cart", product);
//求差別、交集、並集
Set<Product> diff = redis.opsForSet().difference("cart1", "cart2");
Set<Product> union = redis.opsForSet().union("cart1", "cart2");
Set<Product> isect = redis.opsForSet().intersect("cart1", "cart2");
//移除元素
redis.opsForSet().remove(product);
//隨機元素
Product random = redis.opsForSet().randomMember("cart");

//綁定到某個key上,至關於建立一個變量,引用這個變量時都是針對這個key來進行的
BoundListOperations<String, Product> cart = redis.boundListOps("cart");
Product popped = cart.rightPop(); 
cart.rightPush(product);
cart.rightPush(product2);
cart.rightPush(product3);

12.3.三、使用key和value的序列化器

當某個條目保存到Redis key-value存儲的時候,key和value都會使用Redis的序列化器進行序列化。

Spring Data Redis提供了多個這樣的序列化器,包括:

  • GenericToStringSerializer:使用Spring轉換服務進行序列化
  • JacksonJsonRedisSerializer:使用Jackson1,講對象序列化爲JSON
  • Jackson2JsonRedisSerializer:使用Jackson2,講對象序列化爲JSON
  • JdkSerializationRedisSerializer:使用Java序列化
  • OxmSerializer:使用Spring O/X映射的編排器和解排器實現序列化,用於XML薛麗華
  • StringRedisSerializer:序列化String類型的key和value

這些序列化器都實現了RedisSerializer接口,若是其中沒有符合需求的序列化器,能夠自行建立。

RedisTemplate默認使用JdkSerializationRedisSerializer,那麼key和value都會經過Java進行序列化。

StringRedisTemplate默認使用StringRedisSerializer,實際就是實現String和byte數組之間的轉化。

例子:

   使用RedisTemplate,key是String類型,咱們但願使用StringRedisSerializer進行序列化;value是Product類型,咱們但願使用JacksonJsonRedisSerializer序列化爲JSON

@Bean
public void RedisTemplate<String,Product> redisTemplate(RedisConnectionFactory cf) {
    RedisTemplate<String, Product> redis = new RedisTemplate<String, Product>();
    redis.setConnectionFactory(cf);
    redis.setKeySerializer(new StringRedisSerializer());
    redis.setValueSerializer(new Jackson2JsonRedisSerializer<Product>(Product.class));
    return redis;
}

十3、使用Spring緩存技術

什麼叫緩存:

  一些變更不頻繁或者不變更的數據,當每次獲取的時候,都須要從數據庫中提取或者計算,每次都須要消耗資源。

  咱們能夠把這些計算後的結果,在某個地方存放起來,當下次訪問的時候直接返回,就能夠避免了屢次的資源消耗,這就叫緩存技術。

13.一、啓用緩存支持

13.1.一、2種方式啓用Spring對註解驅動緩存的支持:

  • 在一個配置類上使用@EnableCaching註解
  • 使用XML進行配置

@EnableCaching註解方式:

@Configuration
@EnableCaching
public class CachingConfig {
    @Bean
    public CacheManager cacheManager() {
        return new ConcurrentMapCacheManager();
    }
}

XML方式:

  使用Spring cache命名空間中的<cache:annotation-driven>元素啓動註解驅動的緩存

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:cache="http://www.springframework.org/schema/cache"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/cache
        http://www.springframework.org/schema/cache/spring-cache.xsd">
    
    <!-- 啓用緩存-->
    <cache:annotation-driven />

    <!-- 聲明緩存管理器-->
    <bean id="cacheManager" class="org.springframework.cache.concurrent.ConcurrentMapCacheManager" />

<beans>

 代碼解析:

  2種方式本質上是同樣的,都建立一個切面並觸發Spring緩存註解的切點

  根據所使用的註解和緩存狀態,切面會從緩存中獲取數據,並將數據添加到緩存中或者從緩存刪除某個值。

  其中不只啓用了註解驅動的緩存,還聲明瞭一個緩存管理器(cache manager)的bean。

  緩存管理器是SPring緩存抽象的狠心,能夠和不一樣的緩存實現進行集成。

  2個例子都是用ConcurrentHashMap做爲緩存存儲,是基於內存的,用在開發或者測試能夠,可是在大型企業級應用程序就有其餘更好的選擇了。

13.1.二、 配置緩存管理器

Spring3.1內置五個緩存管理器實現:

  • SimpleCacheManager
  • NoOpCacheManager
  • ConcurrentMapCacheManager
  • CompositeCacheManager
  • EhCacheCacheManager

Spring3.2引入了另一個緩存管理器實現

  • 這個管理器能夠用於基於JCache(JSR-107)的緩存提供商之中

除了核心Spring框架,Spring Data提供了2個緩存管理器實現

  • RedisCacheManager(來自於Spring Data Redis項目)
  • GemfireCacheManager(來自於Spring Data GemFire項目)

咱們選擇一個緩存管理器,而後在Spring應用上下文中,以bean的形式進行設置。使用不一樣的緩存管理器會影響數據如何存儲,可是不會影響Spring如何聲明緩存。

例子——配置EhCache緩存管理器:

  此例子暫時省略。。。

例子——配置Redis緩存管理器

  使用RedisCacheManager,它會與一個Redis服務器協做,並經過RedisTemplate存取條目

  RedisCacheManager要求:

  • 一個RedisConnectFactory實現類的bean
  • 一個RedisTemplate的bean
@Configuration
@EnableCaching
public class CachingConfig {

    //Redis緩存管理器bean
    @Bean
    public CacheManager cacheManager(RedisTemplate redisTemplate) {
        return new RedisCacheManager(redisTemplate);
    }

    //Redis鏈接工廠bean
    @Bean
    public JedisConnectionFactory redisConnectionFactory() {
        JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory();
        jedisConnectionFactory.afterPropertiesSet();
        return jedisConnectionFactory;
    }

    //RedisTemplate bean
    @Bean
    public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory redisCF) {
        RedisTemplate<String, String> redisTemplate = new RedisTemplate<String, String>();
        redisTemplate.setConnectionFactory(redisCF);
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }
}

例子——使用多個緩存管理器

使用Spring的CompositeCacheManager

如下例子建立一個CompositeCacheManager的bean,裏面配置了多個緩存管理器,會按順序迭代查找這些緩存管理器裏面是否存在值

@Bean
public CacheManager cacheManager(net.sf.ehcache.CacheManager cm,javax.cache.CacheManager jcm){
    CompositeCacheManager cacheManager = new CompositeCacheManager();
    List<CacheManager> managers = new ArrayList<CacheManager>();
    managers.add(new JCacheCacheManager(jcm));
    managers.add(new EhcacheCacheManager(cm));
    manager.add(new RedisCacheManager(reisTemplate()));
    cacheManager.setCacheManagers(managers);
    return cacheManager;
}

13.二、爲方法添加註解以支持緩存

設置好了鏈接工廠和緩存管理器,咱們就可使用註解來設置緩存了。

Spring的緩存抽象很大程度是基於切面構建的,啓用了緩存,就會建立一個切面,觸發Spring的緩存註解。

這些註解能夠用在方法或類上,用在類上,則類的全部方法都會應用該緩存行爲。

13.2.一、填充緩存

@Cacheable和@CachePut

最簡單的狀況下只須要使用value屬性便可

@Cacheable("spittleCache")
public Spittle findOne(long id){
    return new Spittle ();//能夠是具體的邏輯
}

緩存流程:

  • 調用findOne方法
  • 緩存切面攔截
  • 緩存切面在緩存中名爲spittleCache的緩存數據中,key爲id的值
    • 若是有值,則直接返回緩存值,再也不調用方法。
    • 若是無值,則調用方法,並將結果放到緩存中。

緩存註解也能夠寫在接口上,那麼它的全部實現類都會應用這個緩存規則。

將值放到緩存中:

一個使用情景:咱們保存一個數據,而後前臺刷新或者其餘用戶獲取,咱們能夠保存時直接更新緩存

@CachePut("spittleCache")
Spittle save(Spittle spittle);

自定義緩存key:

上面例子中,默認把Spittle參數做爲緩存的key,這樣並不合適,咱們但願用Spittle的ID做爲key值

可是Spittle未保存狀況下,又未有ID,這樣咱們能夠經過SpEL表達式解決這個問題

@CachePut(value="spittleCache",key="#result.id")
Spittle save(Spittle spittle);

條件化緩存:

使用unless和condition屬性,接受一個SpEL表達式

unless:若是爲false,調用方法時依然會在緩存中查找,只是阻止結果放進緩存;

condition:若是是false,則禁用整個緩存,包括方法調用前的緩存查找和方法調用後把結果放進緩存都被禁用

@Cacheable(value="spittleCache" 
unless="#result.message.contains('NoCache')") Spittle findOne(long id);
@Cacheable(value="spittleCache" 
unless="#result.message.contains('NoCache')"
condition="#id >= 10") Spittle findOne(long id);

13.2.二、移除緩存條目

@CacheEvict註解

調用方法時,會刪除該緩存記錄,

@CacheEvict("spittleCache")
void remove(long spittleId);

 

13.三、使用XML聲明緩存

有時候沒法是註解,或者不想使用註解,可使用XML聲明,這裏暫不介紹。

【暫略】十4、保護方法應用

【------------------------Spring集成------------------------】

【暫略】十5、使用遠程服務

十6、使用Spring MVC建立REST API

16.一、REST介紹

一、什麼是REST

REST的名稱解釋:

SOAP:簡單對象訪問協議(英文:Simple Object Access Protocol,簡稱SOAP)。

REST:表述性狀態傳遞(英文:Representational State Transfer,簡稱REST)。

REST是比SOAP更簡單的一個Web應用可選方案。

REST是一種面向資源的架構風格,強調描述應用程序的事物和名詞。

  • Representational :表述性,REST資源可使用各類不一樣的形式進行表述,如XML,JSON,HTML;
  • State:狀態,使用REST的時候,咱們關注的是資源的狀態,而不是行爲;
  • Transfer:轉移,REST的資源,經過某種形式的表述,在應用之間傳遞轉移。

簡潔地說,REST就是將資源的狀態,以最合適客戶端或服務器的表述方式,在服務器與客戶端之間轉移。

REST與HTTP方法:

URL:REST中,資源經過URL定位和識別。雖然沒有嚴格的URL格式定義,可是一個URL應該能識別資源,而不是簡單的一個命令。由於REST的核心是資源,而不是行爲。

行爲:REST中也有行爲,可是不是在URL中體現,通常經過HTTP行爲來定義,例如CRUD

  • Creat:POST
  • Read:GET
  • Update:PUT/PATCH
  • Delete:Delete

二、Spring對REST的支持

  • a、控制器支持全部HTTP方法,包含POST/GET/PUT/DELETE,Spring3.2及以上版本還包含PATCH。
  • b、@PathVariable註解使控制器能夠處理參數化URL
  • c、Spring的視圖和視圖解析器,資源能夠以多種方式表述,包括將模型數據渲染成XML/JSON/Atom/RSS的View實現。
  • d、可使用ContentNegotiatingViewResolver來選擇客戶端最適合的表述。
  • e、使用@Response註解的各類HttpMethodConverter實現,可以替換基於視圖的渲染方式。
  • f、使用@Response註解的各類HttpMethodConverter能夠將傳入的HTTP數據轉化爲控制器處理方法的Java對象。
  • g、藉助RestTemplate,Spring應用可以方便地使用REST資源。

16.二、建立REST端點

須要實現RESTful功能的Spring MVC控制器

@Controller
@RequestMapping("/spittle")
public class SpittleApiController {
    private static final String MAX_LONG_AS_STRING = "9223372036854775807";
    private SpittleRepository spittleRepository;
    @Autowired
    public SpittleApiController(SpittleRepository spittleRepository) {
        this.spittleRepository = spittleRepository;
    }
    @RequestMapping(method = RequestMethod.GET)
    public List<Spittle> spittles(@RequestParam(value = "max", defaultValue = MAX_LONG_AS_STRING) long max,
            @RequestParam(value = "count", defaultValue = "20") int count) {
        return spittleRepository.findSpittles(max, count);
    }
}

一、Spring提供了2種將Java表述形式轉換爲發給客戶端的表述形式的方式

內容協商

選擇一個視圖,可以將模型渲染爲呈現給客戶端的表述形式

這種方式通常不用

消息轉換器

使用HTTP信息轉換器

這是一種直接的方式,將控制器的數據轉換爲服務於客戶端的表述形式。

當使用消息轉換功能時, DispatcherServlet不用麻煩地將模型傳遞給視圖中。

這裏沒有視圖,甚至沒有模型,只有控制器產生的數據,而後通過消息轉換器,產生資源表述,傳到客戶端。

Spring中自帶了各類各樣的轉換器,可以知足常見的對象轉換爲表述的需求。

例如,客戶端經過請求的Accept頭信息代表它能接受"application/json",而且Jackson JSON在類路徑下,那麼處理方法返回的對象將交給MappingJacksonHttpMessageConverter,由他轉換爲返回客戶端的JSON表述形式。

或者,若是請求的頭信息代表客戶端想要"text/xml"格式,那麼Jaxb2RootElementHttpMessageConverter將會爲客戶端產生XML表述形式。

除了其中5個外,其它都是自動註冊的,不須要Spring配置;可是爲了支持他們,須要把對應的庫添加到類路徑中。

二、消息轉換器的具體用法

在響應體中返回資源狀態

正常狀況,若是控制器方法返回Java對象,這個對象會放到模型中,並在視圖中渲染。

爲了使用消息轉換功能,咱們須要告訴Spring跳過正常的模型/視圖流程,並使用消息轉換器。

最簡單的方式:使用@ResponseBody

@RequestMapping(method = RequestMethod.GET, produces = "application/json")
public @ResponseBody List<Spittle> spittles(@RequestParam(value = "max", defaultValue = MAX_LONG_AS_STRING) long max,
        @RequestParam(value = "count", defaultValue = "20") int count) {
    return spittleRepository.findSpittles(max, count);
}

@ResponseBody會告訴Spring,咱們要將返回的對象做爲資源發送給客戶端,並轉換爲客戶端要求的表述形式。

DispatcherServlet會根據請求中Accept頭部信息,找到對應的消息轉換器,而後把Java對象轉換爲客戶端須要的表述形式。

例如客戶端請求的Accept頭部信息代表它接收"application/json",且Jackson JSON庫位於應用的類路徑下,那麼將選擇MappingJacksonHttpMessageConverter或MappingJackson2HttpMessageConverter(取決於類路徑下是哪一個版本的Jackson)做爲消息轉換器,並將Java對象轉換爲JSON文檔,寫入到相應體中。

@ RequestMapping中produces屬性,表明該控制器只處理預期輸出爲JSON的請求,也就是Accept頭信息包含"application/json"的請求。

其它類型請求,即便URL匹配且爲GET請求,也不會被處理。

這樣的請求會被其它的方法進行處理(有適當方法的狀況下),或者返回HTTP 406(Not Acceptable)響應。

請求體中接收資源狀態

上面只討論瞭如何將一個REST資源轉換爲客戶端所須要的表述,這裏討論如何將客戶端發送過來的資源狀態表述轉換爲JAVA對象。

使用@RequestBody註解

@RequestMapping(method = RequestMethod.POST, consumes = "application/json")
public @ResponseBody Spittle saveSpittle(@RequestBody Spittle spittle) {
    return spittleRepository.save(spittle);
}

@RequestBody代表

  • a、這個控制器方法,只能處理/spittles(定義在類級別上了)的POST請求,並且請求中預期要包含一個Spittle的資源表述
  • b、Spring會根據請求的Content-Type頭信息,查找對應的消息轉換器,把客戶端發送的資源的表述形式(JSON,或HTMl)轉化爲Java對象

爲控制器默認設置消息轉換

@RestController註解(Spring 4.0及以上)

使用@RestController代替@Controller標註控制器,Spring會爲全部方法應用消息轉換功能,咱們就不用每一個方法添加@ResponseBody,固然@RequestBody若是須要使用到,是不能省略的

16.三、提供資源以外的其它內容

一、發送錯誤信息到客戶端

@RequestMapping(value = "/{id}", method = RequestMethod.GET)
public @ResponseBody Spittle spittleById(@PathVariable Long id) {
    return spittleRepository.findOne(id);
}

上述方法是查找一個Spittle對象。

假設找不到,則返回null,這時候,返回給客戶端的HTTP狀態碼是200(OK),表明事情正常運行,可是數據是空的。

而咱們但願這種狀況下或許可使狀態碼未404(Not Found),這樣客戶端就知道發生了什麼錯誤了。

要實現這種功能,Spring提供了一下幾種方式:

  • a、使用@ResponseStatus註解指定狀態碼
  • b、控制器方法能夠返回ResponseEntity對象,改對象可以包含更多響應相關元數據
  • c、異常處理器可以應對錯誤場景,這樣處理器方法就能關注於正常的情況

使用ResponseEntity

使用ResponseEntity對象替代@ResponseBody

@RequestMapping(value = "/{id}", method = RequestMethod.GET)
public ResponseEntity<Spittle> spittleById(@PathVariable Long id) {
    return spittleRepository.findOne(id);
    HttpStatus status = spittle != null ? HttpStatus.OK : HttpStatus.NOT_FOUND;
    return new ResponseEntity<Spittle>(spittle, status);
}

使用ResponseEntity,能夠指定HTTP狀態碼,並且自己也包含@ResponseBody的定義,至關於使用了@ResponseBody。

接下來,咱們但願若是找不到Spittle對象時,返回錯誤信息

能夠建立一個Error類,而後使用泛型,在找不到Spittle對象時,返回一個Error對象

@RequestMapping(value = "/{id}", method = RequestMethod.GET)
public ResponseEntity<?> spittleById(@PathVariable Long id) {
    return spittleRepository.findOne(id);
    if (spittle == null){
        Error error = new Error(4, "Spittle [" + id + "] not found");
        return new ResponseEntity<Error>(error, HttpStatus.NOT_FOUND);
    }
    return new ResponseEntity<Spittle>(spittle, HttpStatus.OK);
}

可是,這種寫法貌似使代碼變得有點複雜,咱們能夠考慮使用錯誤處理器。

處理錯誤

步驟1:建立一個異常類

public class SpittleNotFoundException extends RuntimeException {
    private static final long serialVersionUID = 1L;
    private long spittleId;
    public SpittleNotFoundException(long spittleId) {
        this.spittleId = spittleId;
    }
    public long getSpittleId() {
        return spittleId;
    }
}

步驟2:在控制器下建立一個錯誤處理器的處理方法

@ExceptionHandler(SpittleNotFoundException.class)
public ResopnseEntity<Error> spittleNotFound(SpittleNotFoundException e) {
    long spittleId = e.getSpittleId();
    Error error =  new Error(4, "Spittle [" + spittleId + "] not found");
    return new ResponseEntity<Error>(error, HttpStatus.NOT_FOUND);
}

@ExceptionHandler註解加到控制器方法中,能夠處理對應的異常。

若是請求A發生異常,被這個方法捕獲到該異常, 那麼請求的返回值就是這個異常處理器的返回值。

步驟3:原來的業務控制器方法能夠獲得簡化

@RequestMapping(value = "/{id}", method = RequestMethod.GET)
public ResponseEntity<Spittle> spittleById(@PathVariable Long id) {
    return spittleRepository.findOne(id);
    if (spittle == null) { throw new SpittleNotFoundException(id); }
    return new ResponseEntity<Spittle>(spittle, HttpStatus.OK);
}

又由於此時任什麼時候候,這個控制方法都有數據返回,因此HTTP狀態碼始終是200,因此能夠不使用ResponseEntity,二使用@ResponseBody;若是控制器上面還使用了@RestController,咱們又能夠把@ResponseBody省掉, 最後獲得代碼

@RequestMapping(value = "/{id}", method = RequestMethod.GET)
public Spittle spittleById(@PathVariable Long id) {
    return spittleRepository.findOne(id);
    if (spittle == null) { throw new SpittleNotFoundException(id); }
    return Spittle;
}

步驟4:同理,能夠簡化一下錯誤處理器

@ExceptionHandler(SpittleNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public Error spittleNotFound(SpittleNotFoundException e) {
    long spittleId = e.getSpittleId();
    return new Error(4, "Spittle [" + spittleId + "] not found");
}

在簡化過程當中,咱們都但願避免使用ResponseEntity,由於他會致使代碼看上來很複雜。

可是有的狀況必須使用ResponseEntity才能實現某些功能。

在響應中設置頭部信息  

要使用到ResponseEntity,好比說我新建一個Spittle後,系統經過HTTP的Location頭部信息,返回這個Spittle的URL資源地址。

這裏暫時不討論

【暫略】16.四、編寫REST客戶端

【暫略】十7、Spring消息

【暫略】十8、使用WebSocket和STMOP實現消息功能

【暫略】十9、使用Spring發送Email

【暫略】二10、使用JMX管理Spring Bean

【暫略】二11、使用Spring Boot簡化Spring開發

--

實例

實例01:@ResponseBody使用實體接收數據,發送實體內沒有實體時,報錯

SpringMVC中默認的序列化器只支持子集傳入,能夠注入bean,使用fastJson做爲MVC使用的序列化和反序列化器

@Bean
public HttpMessageConverters fastJsonHttpMessageConverters() {
    FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
    FastJsonConfig fastJsonConfig = new FastJsonConfig();
    fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat);
    fastConverter.setFastJsonConfig(fastJsonConfig);
    HttpMessageConverter<?> converter = fastConverter;
    return new HttpMessageConverters(converter);
}
相關文章
相關標籤/搜索