Spring4.x 基礎

把如下 jar 包加入到工程的 classpath 下:javascript

搭建Spring開發環境


Spring 的配置文件: 一個典型的 Spring 項目須要建立一個或多個 Bean 配置文件, 這些配置文件用於在 Spring IOC 容器裏配置 Bean. Bean 的配置文件能夠放在 classpath 下, 也能夠放在其它目錄下.java

(1).Spring_HelloWorld

Helloworld.classmysql

public class Helloworld {
        private String name;

    public void setName(String name) {
        System.out.println("setName:"+name);
        this.name = name;
    }

    public void  hello(){
        System.out.println("hello:"+name);
    }
}

輸出正則表達式

public static void main(String[] args) {
//        Helloworld helloworld = new Helloworld();
//        helloworld.setName("tangsan");
//        helloworld.hello();

        //1.建立Spring的IOC容器對象
        ApplicationContext context =
                new ClassPathXmlApplicationContext("applicationContext.xml");
        //2.從IOC獲取 Bean 實例
        Helloworld hello = (Helloworld)context.getBean("helloWorld");
         hello.hello();
    }

(2).IOC與DI

  • IOC(Inversion of Control):其思想是反轉資源獲取的方向. 傳統的資源查找方式要求組件向容器發起請求查找資源. 做爲迴應, 容器適時的返回資源. 而應用了 IOC 以後, 則是容器主動地將資源推送給它所管理的組件, 組件所要作的僅是選擇一種合適的方式來接受資源. 這種行爲也被稱爲查找的被動形式
  • DI(Dependency Injection) — IOC 的另外一種表述方式:即組件以一些預先定義好的方式(例如: setter 方法)接受來自如容器的資源注入. 相對於 IOC 而言,這種表述更直接

(3).Spring 中的 Bean 配置

配置形式:spring

  • 基於 XML 文件的方式;
  • 基於註解的方式

配置方式:sql

  • 經過全類名(反射)
  • 經過工廠方法(靜態工廠方法 & 實例工廠方法)
  • FactoryBean

IOC 容器數據庫

  • BeanFactory
  • ApplicationContext

依賴注入的方式:express

  • 屬性注入;
  • 構造器注入;

(3.1).在 Spring 的 IOC 容器裏配置 Bean

<!--
      配置 bean:
      class: bean 全類名,經過反射的方式在 IOC 容器中建立 Bean,因此要求 Bean 中必須有無參數的構造器
      id : 惟一,bean名稱
    -->
    <bean id="NameSetValue" class="com.cnblogs.tangge.spring.HelloWorld">
      <property name="name" value="tangsansan"></property>
    </bean>

(3.2).Spring容器

  • 在 Spring IOC 容器讀取 Bean 配置建立 Bean 實例以前, 必須對它進行實例化. 只有在容器實例化後, 才能夠從 IOC 容器裏獲取 Bean 實例並使用.
  • Spring 提供了兩種類型的 IOC 容器實現.
    • BeanFactory: IOC 容器的基本實現.
    • ApplicationContext: 提供了更多的高級特性. 是 BeanFactory 的子接口.

區別:apache

  • BeanFactory 是 Spring 框架的基礎設施,面向 Spring 自己;
  • ApplicationContext 面向使用 Spring 框架的開發者,幾乎全部的應用場合都直接使用 ApplicationContext 而非底層的 BeanFactory。
  • 不管使用何種方式, 配置文件時相同的。

(3.2.1).ApplicationContext

  • ApplicationContext 的主要實現類:
    • ClassPathXmlApplicationContext:從 類路徑下加載配置文件
    • FileSystemXmlApplicationContext: 從文件系統中加載配置文件
  • ConfigurableApplicationContext 擴展於 ApplicationContext,新增長兩個主要方法:refresh() 和 close(), 讓 ApplicationContext 具備啓動、刷新和關閉上下文的能力
  • ApplicationContext 在初始化上下文時就實例化全部單例的 Bean。
  • WebApplicationContext 是專門爲 WEB 應用而準備的,它容許從相對於 WEB 根目錄的路徑中完成初始化工做

(3.2.2).從IOC獲取Bean

調用 ApplicationContext 的 getBean() 方法
編程

(3.3).依賴注入的方式

  • 屬性注入
  • 構造器注入
  • 工廠方法注入(不多使用,不推薦)

(3.3.1).屬性注入

  • 屬性注入即經過 setter 方法注入Bean 的屬性值或依賴的對象
  • 屬性注入使用 <property> 元素, 使用 name 屬性指定 Bean 的屬性名稱,value 屬性或 <value> 子節點指定屬性值
  • 屬性注入是實際應用中最經常使用的注入方式
<bean id="NameSetValue" class="com.cnblogs.tangge.spring.HelloWorld">
      <property name="name" value="tangsansan"></property>
    </bean>

(3.3.2).構造器注入

  • 經過構造方法注入Bean 的屬性值或依賴的對象,它保證了 Bean 實例在實例化後就可使用。
  • 構造器注入在 <constructor-arg> 元素裏聲明屬性, <constructor-arg> 中沒有 name 屬性
<bean id="carAndprice" class="Models.Car">
    <constructor-arg value="Audi"></constructor-arg>
    <constructor-arg value="Shanghia"></constructor-arg>
    <constructor-arg value="300000"></constructor-arg>
  </bean>

  <!--
    構造方法注入配置 bean 的屬性
    能夠指定參數的位置(index)和參數的類型(type)
  -->
  <bean id="carAndSpeed" class="Models.Car">
    <constructor-arg value="BMW"  type="java.lang.String"></constructor-arg>
    <constructor-arg value="Shanghia" index="1" type="java.lang.String"></constructor-arg>
    <constructor-arg value="240" type="int"></constructor-arg>
  </bean>

(3.3.3).屬性值細節

  • 字面值:可用字符串表示的值,能夠經過 元素標籤或 value 屬性進行注入。
  • 基本數據類型及其封裝類、String 等類型均可以採起字面值注入的方式
  • 若字面值中包含特殊字符,可使用 <![CDATA[]]> 把字面值包裹起來。
<bean id="carAndSpeed" class="Models.Car">
    <constructor-arg value="BMW"  type="java.lang.String"></constructor-arg>
    <constructor-arg index="1" type="java.lang.String">
      <value><![CDATA[<Shanghia>^]]></value>
    </constructor-arg>
    <constructor-arg value="240" type="int"></constructor-arg>
  </bean>

結果:

Car{brand='BMW', corp='<Shanghia>^', price=0.0, maxSpeed=240}

(3.4).Bean之間引用關係

  • 組成應用程序的 Bean 常常須要相互協做以完成應用程序的功能. 要使 Bean 可以相互訪問, 就必須在 Bean 配置文件中指定對 Bean 的引用
  • 在 Bean 的配置文件中, 能夠經過 <ref> 元素或 ref 屬性爲 Bean 的屬性或構造器參數指定對 Bean 的引用.
  • 也能夠在屬性或構造器裏包含 Bean 的聲明, 這樣的 Bean 稱爲內部 Bean

(3.4.1).外部 Bean

建立 Person 類

package Models;

public class Person {
    private String name;
    private int age;
    private Car car;

    public Person() {
    }

    /**
     * 若是不建立3個構造函數,會報錯。Could not resolve matching constructor
     * @param name
     * @param age
     * @param car
     */
    public Person(String name, int age, Car car) {
        this.name = name;
        this.age = age;
        this.car = car;
    }


    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", car=" + car +
                '}';
    }
}

XML
注意 ref

<bean id="Person" class="Models.Person">
    <constructor-arg value="10" type="int"></constructor-arg>
    <constructor-arg value="是男是女" type="java.lang.String"></constructor-arg>
    <constructor-arg name="car" ref="carAndSpeed"></constructor-arg>
  </bean>

執行

ApplicationContext context =
                new ClassPathXmlApplicationContext("applicationContext.xml");
Person person = (Person)context.getBean("Person");
        System.out.println(person);
//  Person{name='是男是女', age=10, car=Car{brand='BMW', corp='<Shanghia>^', price=0.0, maxSpeed=240}}

(3.4.2).內部 Bean

  • 當 Bean 實例僅僅給一個特定的屬性使用時, 能夠將其聲明爲內部 Bean. 內部 Bean 聲明直接包含在 <property><constructor-arg> 元素裏, 不須要設置任何 id 或 name 屬性
  • 內部 Bean 不能使用在任何其餘地方
<!--property 必須經過 setter 注入,因此必須在Person類添加 set方法-->
  <bean id="PersonIn" class="Models.Person">
    <property name="name" value="內部Bean"></property>
    <property name="age" value="20"></property>
    <property name="car">
      <bean id="carAndSpeed" class="Models.Car">
        <constructor-arg value="benz" type="java.lang.String"></constructor-arg>
        <constructor-arg index="1" type="java.lang.String">
          <value><![CDATA[<Shanghia>^]]></value>
        </constructor-arg>
        <constructor-arg value="300" type="int"></constructor-arg>
      </bean>
    </property>
  </bean>

(3.4.3).注入參數詳解:null 空值和級聯屬性

  • 可使用專用的 <null/>元素標籤爲 Bean 的字符串或其它對象類型的屬性注入 null 值 ,或者<null></null>
  • 和 Struts、Hiberante 等框架同樣,Spring 支持級聯屬性的配置。

null空值

<constructor-arg name="car"><null/></constructor-arg>

級聯屬性

<bean id="PersonOut" class="Models.Person">
    <constructor-arg value="10" type="int"></constructor-arg>
    <constructor-arg value="是男是女" type="java.lang.String"></constructor-arg>
    <constructor-arg name="car" ref="carAndSpeed"></constructor-arg>
    <!--Spring 支持級聯屬性的配置。property須要setter 注意:屬性須要先初始化才能夠爲級聯屬性賦值,
    不然有異常,和 Structs2 不一樣-->
    <property name="car.maxSpeed" value="218"></property>
  </bean>

(3.4.4).集合屬性

1).List、數組、Set

  • 在 Spring中能夠經過一組內置的 xml 標籤(例如: <list>, <set><map>) 來配置集合屬性.
  • 配置 java.util.List 類型的屬性, 須要指定 <list> 標籤, 在標籤裏包含一些元素. 這些標籤能夠經過 <value> 指定簡單的常量值, 經過 <ref> 指定對其餘 Bean 的引用. 經過<bean> 指定內置 Bean 定義. 經過 <null/> 指定空元素. 甚至能夠內嵌其餘集合.
  • 數組的定義和 List 同樣, 都使用 <list>
  • 配置 java.util.Set 須要使用 <set> 標籤, 定義元素的方法與 List 同樣.
<bean id="personlist" class="Models.PersonList">
    <constructor-arg value="周星星"></constructor-arg>
    <constructor-arg value="50"></constructor-arg>
    <constructor-arg name="car">
      <list>
        <ref bean="carAndSpeed" />
        <ref bean="carAndprice" />
      </list>
    </constructor-arg>
  </bean>

2).Map

  • Java.util.Map 經過 <map> 標籤訂義, <map> 標籤裏可使用多個 <entry> 做爲子標籤. 每一個條目包含一個鍵和一個值.
  • 必須在 <key> 標籤裏定義鍵
  • 由於鍵和值的類型沒有限制, 因此能夠自由地爲它們指定 <value>, <ref>, <bean><null> 元素.
  • 能夠將 Map 的鍵和值做爲 <entry> 的屬性定義: 簡單常量使用 key 和 value 來定義; Bean 引用經過 key-ref 和 value-ref 屬性定義
  • 使用 <props> 定義 java.util.Properties, 該標籤使用多個 <prop> 做爲子標籤. 每一個 <prop> 標籤必須定義 key 屬性.
<bean id="personmap" class="Models.PersonMap">
    <constructor-arg value="周星星"></constructor-arg>
    <constructor-arg value="50"></constructor-arg>
    <constructor-arg name="car">
    <!--使用map節點及map的entry子節點配置 Map類型的成員變量-->
      <map>
        <entry key="一號車" value-ref="carAndSpeed"></entry>
        <entry key="二號車" value-ref="carAndprice"></entry>
      </map>
    </constructor-arg>
  </bean>
PersonMap person = (PersonMap)context.getBean("personmap");
        System.out.println(person.toString());
        //Person{name='周星星', age=50, car={一號車=Car{brand='BMW', corp='<Shanghia>^--', price=0.0, maxSpeed=218},
        // 二號車=Car{brand='Audi', corp='Shanghia', price=0.0, maxSpeed=300000}}}

3).Properties

建立一個 DataSource 類

public class DataSource {
    private Properties properties;

    public Properties getProperties() {
        return properties;
    }

    public void setProperties(Properties properties) {
        this.properties = properties;
    }
}

配置Bean

<bean id="datasource" class="Models.DataSource">
    <property name="properties">
      <!--使用 props 和 prop 子節點爲 properties 賦值-->
      <props>
        <prop key="user">root</prop>
        <prop key="pass">123</prop>
        <prop key="jdbcurl">jdbc:mysql:///test</prop>
        <prop key="driverClass">com.mysql.jdbc.Driver</prop>
      </props>
    </property>
  </bean>

調用

DataSource source = context.getBean(DataSource.class);
        System.out.println(source.getProperties());
        //{jdbcurl=jdbc:mysql:///test, driverClass=com.mysql.jdbc.Driver, user=root, pass=123}

4).使用 util scheme 定義集合

可使用 util schema 裏的集合標籤訂義獨立的集合 Bean. 須要注意的是, 必須在 根元素裏添加 util schema 定義

<!--導入 util 命名空間  http://www.springframework.org/schema/util-->
  <util:list id="utilcars">
    <ref bean="carAndSpeed" />
    <ref bean="carAndprice" />
  </util:list>

  <util:map id="utilmap">
    <entry key="一號車" value-ref="carAndSpeed"></entry>
    <entry key="二號車" value-ref="carAndprice"></entry>
  </util:map>

  <bean id="personmap" class="Models.PersonMap">
    <constructor-arg value="周星星"></constructor-arg>
    <constructor-arg value="50"></constructor-arg>
    <constructor-arg name="car" ref="utilmap">
      <!--使用map節點及map的entry子節點配置 Map類型的成員變量-->
      <!--<map>-->
      <!--<entry key="一號車" value-ref="carAndSpeed"></entry>-->
      <!--<entry key="二號車" value-ref="carAndprice"></entry>-->
      <!--</map>-->
    </constructor-arg>
  </bean>

(3.4.5).P 命名空間

爲了簡化 XML 文件的配置,愈來愈多的 XML 文件採用屬性而非子元素配置信息。
Spring 從 2.5 版本開始引入了一個新的 p 命名空間,能夠經過 <bean> 元素屬性的方式配置 Bean 的屬性。
使用 p 命名空間後,基於 XML 的配置方式將進一步簡化

<bean id="PersonWithP" class="Models.Person" p:name="孔明" p:age="33"></bean>

(3.5).XML 配置裏的 Bean 自動裝配

  • Spring IOC 容器能夠自動裝配 Bean. 須要作的僅僅是<bean>autowire 屬性裏指定自動裝配的模式
  • byType(根據類型自動裝配): 若 IOC 容器中有多個與目標 Bean 類型一致的 Bean. 在這種狀況下, Spring 將沒法斷定哪一個 Bean 最合適該屬性, 因此不能執行自動裝配.
  • byName(根據名稱自動裝配): 必須將目標 Bean 的名稱和屬性名設置的徹底相同.
  • constructor(經過構造器自動裝配): 當 Bean 中存在多個構造器時, 此種自動裝配方式將會很複雜. 不推薦使用

缺點:

  • 在 Bean 配置文件裏設置 autowire 屬性進行自動裝配將會裝配 Bean 的全部屬性. 然而, 若只但願裝配個別屬性時, autowire 屬性就不夠靈活了.
  • autowire 屬性要麼根據類型自動裝配, 要麼根據名稱自動裝配, 不能二者兼而有之.

通常狀況下,在實際的項目中不多使用自動裝配功能,由於和自動裝配功能所帶來的好處比起來,明確清晰的配置文檔更有說服力一些

<bean id="address" class="com.cnblogs.tangge.autowire.Address" p:city="重慶" p:street="小龍坎"></bean>
  <bean id="car" class="com.cnblogs.tangge.autowire.Car" p:brand="Audi" p:price="300000"></bean>
  <!--
    使用autowire屬性指定自動裝配方式:
    byName:根據 bean 的名字和當前 bean 的setter 風格的屬性名進行自動裝配。
    byType:根據 bean 的屬性類型自動裝配。
  -->
  <bean id="person" class="com.cnblogs.tangge.autowire.Person" p:name="Tom"
    autowire="byName"></bean>
</beans>

3.6.bean 之間的關係:繼承;依賴

1).繼承

  • Spring 容許繼承 bean 的配置, 被繼承的 bean 稱爲父 bean. 繼承這個父 Bean 的 Bean 稱爲子 Bean
  • 子 Bean 從父 Bean 中繼承配置, 包括 Bean 的屬性配置
  • 子 Bean 也能夠覆蓋從父 Bean 繼承過來的配置
  • 父 Bean 能夠做爲配置模板, 也能夠做爲 Bean 實例. 若只想把父 Bean 做爲模板, 能夠設置 abstract 屬性爲 true, 這樣 Spring 將不會實例化這個 Bean
  • 並非 <bean> 元素裏的全部屬性都會被繼承. 好比: autowire, abstract 等.
  • 也能夠忽略父 Bean 的 class 屬性, 讓子 Bean 指定本身的類, 而共享相同的屬性配置. 但此時 abstract 必須設爲 true

parent 繼承

<bean id="address" class="com.cnblogs.tangge.autowire.Address" p:city="重慶" p:street="小龍坎"></bean>
  <!--bean 配置的繼承:使用 parent 屬性指定繼承哪一個 bean 的配置,這裏繼承了 class與 p:street-->
  <bean id="address2"  p:city="重慶1" parent="address"></bean>

結果

Address address  =(Address) context.getBean("address");
        System.out.println(address);
        address  =(Address) context.getBean("address2");
        System.out.println(address);
        /*
        Address{city='重慶', street='小龍坎'}
        Address{city='重慶1', street='小龍坎'}
         */

abstract 抽象,做爲模板

<!--
  1.若只想把父 Bean 做爲模板, 能夠設置 <bean> 的abstract 屬性爲 true, 這樣 Spring 將不會實例化這個 Bean,只能繼承
  2.若是沒有 class 屬性,則必須是一個抽象 bean
   -->
  <bean id="address" class="com.cnblogs.tangge.autowire.Address" p:city="重慶" p:street="小龍坎" abstract="true"></bean>
  <!--bean 配置的繼承:使用 parent 屬性指定繼承哪一個 bean 的配置,這裏繼承了 class與 p:street-->
  <bean id="address2"  p:city="重慶1" parent="address"></bean>

2).依賴
做用:depends-on用來指定Bean初始化及銷燬時的順序。

  • Spring 容許用戶經過 depends-on 屬性設定 Bean 前置依賴的Bean,前置依賴的 Bean 會在本 Bean 實例化以前建立好
  • 若是前置依賴於多個 Bean,則能夠經過逗號,空格或的方式配置 Bean 的名稱
<bean id="car" class="com.cnblogs.tangge.autowire.Car" p:brand="Audi" p:price="300000"></bean>
  <!--要求再配置person時,必須有一個關聯car. id="personDependsOn" 這個bean依賴於 id="car" 這個bean-->
  <bean id="personDependsOn" class="com.cnblogs.tangge.autowire.Person" p:name="www" p:address-ref="address2"
    depends-on="car"></bean>

3.7.Bean 的做用域

  • 在 Spring 中, 能夠在 <bean> 元素的 scope 屬性裏設置 Bean 的做用域.
  • 默認狀況下, Spring 只爲每一個在 IOC 容器裏聲明的 Bean 建立惟一一個實例, 整個 IOC 容器範圍內都能共享該實例:全部後續的 getBean() 調用和 Bean 引用都將返回這個惟一的 Bean 實例.該做用域被稱爲 singleton, 它是全部 Bean 的默認做用域.
類別 說明
singleton 在 SpringIOC 容器中只存在一個實例,Bean以單實例的形式存在
prototype 每次調用 getBean() 返回一個新實例
request 每次HTTP請求,返回一個新Bean,該做用域適用 WebApplicationContext環境
session 同一個HTTP Session共享一個Bean,不一樣HTTP Session使用不一樣的Bean。該做用域適用 WebApplicationContext環境
<bean id="car" class="com.cnblogs.tangge.autowire.Car" p:brand="Audi" p:price="300000"></bean>

代碼

ApplicationContext ctx = new ClassPathXmlApplicationContext("beans-scope.xml");
        Car car1= (Car) ctx.getBean("car");
        Car car2= (Car) ctx.getBean("car");
        System.out.println(car1.equals(car2)); //true

添加 scope="prototype"

<bean id="car" class="com.cnblogs.tangge.autowire.Car" p:brand="Audi" p:price="300000" scope="prototype"></bean>

返回結果爲: System.out.println(car1.equals(car2)); //false

3.8.使用外部屬性文件

  • 在配置文件裏配置 Bean 時, 有時須要在 Bean 的配置裏混入系統部署的細節信息(例如: 文件路徑, 數據源配置信息等). 而這些部署細節實際上須要和 Bean 配置相分離
  • Spring 提供了一個 PropertyPlaceholderConfigurer 的 BeanFactory 後置處理器, 這個處理器容許用戶將 Bean 配置的部份內容外移到屬性文件中. 能夠在 Bean 配置文件裏使用形式爲 ${var} 的變量, PropertyPlaceholderConfigurer 從屬性文件里加載屬性, 並使用這些屬性來替換變量.
  • Spring 還容許在屬性文件中使用 ${propName},以實現屬性之間的相互引用。

示例:
導入mysql和jdbc的jar包

<bean id="datasource" class="org.apache.commons.dbcp2.BasicDataSource" >
    <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"></property>
    <property name="url" value="jdbc:mysql://localhost:3306/day22_jdbc?serverTimezone = GMT"></property>
    <property name="username" value="root"></property>
    <property name="password" value="123"></property>
  </bean>

java

DataSource dataSource = (DataSource) ctx.getBean("datasource");
        System.out.println(dataSource.getConnection());
        //1763344271, URL=jdbc:mysql://localhost:3306/day22_jdbc?serverTimezone = GMT, UserName=root@localhost, MySQL Connector/J

註冊PropertyPlaceholderConfigurer


db.properties

drivername=com.mysql.cj.jdbc.Driver
url = jdbc:mysql://localhost:3306/day22_jdbc?serverTimezone = GMT
user = root
pass = 123

bean設置

<context:property-placeholder location="classpath:db.properties"/>
  <!--使用外部配置文件-->
  <bean id="datasource2" class="org.apache.commons.dbcp2.BasicDataSource" >
    <property name="driverClassName" value="${drivername}"></property>
    <property name="url" value="${url}"></property>
    <property name="username" value="${user}"></property>
    <property name="password" value="${pass}"></property>
  </bean>

3.9.SpEL

  • Spring 表達式語言(簡稱SpEL):是一個支持運行時查詢和操做對象圖的強大的表達式語言。
  • 語法相似於 EL:SpEL 使用 #{…} 做爲定界符,全部在大框號中的字符都將被認爲是 SpEL
  • SpEL 爲 bean 的屬性進行動態賦值提供了便利
  • 經過 SpEL 能夠實現:
    • 經過 bean 的 id 對 bean 進行引用
    • 調用方法以及引用對象中的屬性
    • 計算表達式的值
    • 正則表達式的匹配




address.class

package com.cnblogs.tangge.spel;
public class Address {
        private String  city;
        private String street;

    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }

    public String getStreet() {
        return street;
    }

    public void setStreet(String street) {
        this.street = street;
    }

    @Override
    public String toString() {
        return "Address{" +
                "city='" + city + '\'' +
                ", street='" + street + '\'' +
                '}';
    }
}

car.class

package com.cnblogs.tangge.spel;

public class Car {

    public Car() {
        System.out.println("default constuct...");
    }

    //品牌
    private String brand;

    private double price;
    //輪胎周長
    private double tyrePerimeter;

    public void setBrand(String brand) {
        this.brand = brand;
    }
    //#{car.price > 300000 ?'金領':'白領'}的時候,price 這裏必須有getter方法。
   public double getPrice() {
        return price;
    }
    
    public void setPrice(double price) {
        this.price = price;
    }

    @Override
    public String toString() {
        return "Car{" +
                "brand='" + brand + '\'' +
                ", price=" + price +
                ", tyrePerimeter=" + tyrePerimeter +
                '}';
    }

    public void setTyrePerimeter(double tyrePerimeter) {
        this.tyrePerimeter = tyrePerimeter;
    }
}

person.class

package com.cnblogs.tangge.spel;

/**
 * @Description:
 * @Package: com.cnblogs.tangge.autowire
 * @ClassName: Person
 * @Author: tangge
 * @CreateDate: 2018年08月14 16:39
 * @Version: 1.0
 **/
public class Person {

    private String name;
    private Car car;
    //引用 address bean 的 city 屬性
    private String city;
    //根據car的 price 決定 info:car 的 price >=300000
    private String info;

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", car=" + car +
                ", city='" + city + '\'' +
                ", info='" + info + '\'' +
                '}';
    }

    public void setName(String name) {
        this.name = name;
    }


    public void setCar(Car car) {
        this.car = car;
    }

    public void setCity(String city) {
        this.city = city;
    }

    public void setInfo(String info) {
        this.info = info;
    }
}
<?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">
<bean id="address" class="com.cnblogs.tangge.spel.Address">
  <!--spel 字面量-->
  <property name="city" value="#{'Beijing'}"></property>
  <property name="street" value="Wudaokou"></property>
</bean>

  <bean id="car" class="com.cnblogs.tangge.spel.Car">
  <!--spel 字面量-->
  <property name="brand" value="#{'audi'}"></property>
  <property name="price" value="#{300000}"></property>
  <!--spel 調用類的靜態屬性 T()-->
  <property name="tyrePerimeter" value="#{T(java.lang.Math).PI * 80}"></property>
</bean>

  <bean id="person" class="com.cnblogs.tangge.spel.Person">
    <property name="name" value="#{'tangsansan'}"></property>
    <!--spel 引用其餘對象的屬性-->
    <property name="city" value="#{address.city}"></property>
    <!--spel 引用其餘對象-->
    <property name="car" value="#{car}"></property>
    <!--spel if esle變體-->
    <property name="info" value="#{car.price > 300000 ?'金領':'白領'}"></property>
  </bean>
</beans>

<!--
Address{city='Beijing', street='Wudaokou'}
Car{brand='audi', price=300000.0, tyrePerimeter=251.32741228718345}
Person{name='tangsansan', car=Car{brand='audi', price=300000.0, tyrePerimeter=251.32741228718345}, city='Beijing', info='白領'}
-->

3.9 Bean 生命週期

3.9.1 init-method 與 destroy-method

  • Spring IOC 容器能夠管理 Bean 的生命週期, Spring 容許在 Bean 生命週期的特定點執行定製的任務.
  • Spring IOC 容器對 Bean 的生命週期進行管理的過程:
    1.經過構造器或工廠方法建立 Bean 實例
    2.爲 Bean 的屬性設置值和對其餘 Bean 的引用
    3.調用 Bean 的初始化方法
    4.Bean 可使用了
    5.當容器關閉時, 調用 Bean 的銷燬方法
  • 在 Bean 的聲明裏設置 init-method 和 destroy-method 屬性, 爲 Bean 指定初始化和銷燬方法.
package com.cnblogs.tangge.cycle;

public class Car {
private String brand;

    public Car() {
        System.out.println("Car constructor...");
    }

    public void setBrand(String brand) {
        System.out.println("setBrand..");
        this.brand = brand;
    }

    public void  init(){
        System.out.println("init....");
    }
    public void  destroy(){
        System.out.println("destroy....");
    }

    @Override
    public String toString() {
        return "Car{" +
                "brand='" + brand + '\'' +
                '}';
    }
}

Bean配置

<bean id="car" class="com.cnblogs.tangge.cycle.Car" init-method="init" destroy-method="destroy">
    <property name="brand" value="audi"></property>
  </bean>

測試

public static void main(String[] args) {
        //ApplicationContext 擴展類,支持close()
        ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext("bean-cycle.xml");
        Car car = (Car) ctx.getBean("car");
        System.out.println(car);
        ctx.close();
        /*
        Car constructor...  //構造器
        setBrand..          //setter
        init....            //init()
        Car{brand='audi'}   //toString()
        destroy....         //destroy()
         */
    }

3.9.2 後置處理器

BeanPostProcessor

  • Bean 後置處理器容許在調用初始化方法先後對 Bean 進行額外的處理.
  • Bean 後置處理器對 IOC 容器裏的全部 Bean 實例逐一處理, 而非單一實例. 其典型應用是: 檢查 Bean 屬性的正確性或根據特定的標準更改 Bean 的屬性.
  • 對Bean 後置處理器而言, 須要實現 BeanPostProcessor 接口. 在初始化方法被調用先後, Spring 將把每一個 Bean 實例分別傳遞給上述接口的如下兩個方法:
    • Object postProcessBeforeInitialization(Object bean, String beanName)
    • Object postProcessAfterInitialization(Object bean, String beanName)
  • Spring IOC 容器對 Bean 的生命週期進行管理的過程:
    • 經過構造器或工廠方法建立 Bean 實例
    • 爲 Bean 的屬性設置值和對其餘 Bean 的引用
    • 將 Bean 實例傳遞給 Bean 後置處理器的 postProcessBeforeInitialization 方法
    • 調用 Bean 的初始化方法
    • 將 Bean 實例傳遞給 Bean 後置處理器的 postProcessAfterInitialization方法
    • Bean 可使用了
    • 當容器關閉時, 調用 Bean 的銷燬方法

新建一個MyBeanPostprocessor.class

package com.cnblogs.tangge.cycle;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;

public class MyBeanPostprocessor implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.printf("postProcessBeforeInitialization:%s,%s %n",bean,beanName);
        if ("car".equals(beanName)){
        Car car = new Car();
            car.setBrand("BMW");
            return car;
        }
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.printf("postProcessAfterInitialization:%s,%s %n",bean,beanName);
        return bean;
    }
}

配置bean

<!--
    實現BeanPostProcessor接口,提供具體接口
    Object postProcessBeforeInitialization(Object bean, String beanName) init-method 以前被調用
    Object postProcessAfterInitialization(Object bean, String beanName) init-method 以後被調用

    bean:bean 實例自己
    beanName: IOC 容器配置的 bean 的名字
    返回值:是實際上返回給用戶的那個 bean,注意:以上兩個方法修改 bean,甚至返回一個新的 bean
  -->
  <!--配置 bean 後置處理器:不須要id,IOC容器自動識別是一個 BeanPostProcessor-->
  <bean class="com.cnblogs.tangge.cycle.MyBeanPostprocessor"></bean>

運行

public static void main(String[] args) {
        //ApplicationContext 擴展類,支持close()
        ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext("bean-cycle.xml");
        Car car = (Car) ctx.getBean("car");
        System.out.println(car);
        ctx.close();
        /**
         Car constructor...                                     //1.構造器
         setBrand..                                             //2.setter
         postProcessBeforeInitialization:Car{brand='audi'},car  //3.postProcessBeforeInitialization
         Car constructor...                                      //postProcessBeforeInitialization 先new個car 在stter
         setBrand..
         init....                                                //4.init
         postProcessAfterInitialization:Car{brand='BMW'},car    //5.postProcessAfterInitialization
         Car{brand='BMW'}                                        //toString()
         destroy....                                             //6.destroy()
         */
    }

4.其餘配置方式(除了反射)

4.1 工廠方法

4.1.1.靜態方法 factory-method

調用靜態工廠方法建立 Bean是將對象建立的過程封裝到靜態方法中. 當客戶端須要對象時, 只須要簡單地調用靜態方法, 而不一樣關心建立對象的細節.
要聲明經過靜態方法建立的 Bean, 須要在 Bean 的 class 屬性裏指定擁有該工廠的方法的類, 同時在 factory-method 屬性裏指定工廠方法的名稱. 最後, 使用 <constrctor-arg> 元素爲該方法傳遞方法參數.

package com.cnblogs.tangge.factory;
import java.util.HashMap;
import java.util.Map;

public class staticCarFactory {
    private static Map<String,Car> cars = new HashMap<>();
    static {
        cars.put("audi",new Car("audi",300000));
        cars.put("ford",new Car("ford",230000));
    }

 //靜態方法
    public static Car getCar(String name){
        return cars.get(name) ;
    }
}

Bean
constructor-arg:若是工廠方法須要傳入參數,則使用constructor-arg來配置參數

<!--靜態方法配置bean,不是配置工廠方法實例,而是bean實例-->
  <!--
  class:指向靜態方法全類名
  factory-method:指向靜態工廠方法的名字
  constructor-arg:若是工廠方法須要傳入參數,則使用constructor-arg來配置參數
  -->
  <bean id="car" class="com.cnblogs.tangge.factory.staticCarFactory" factory-method="getCar">
    <constructor-arg value="ford"></constructor-arg>
  </bean>

這裏測試的結果爲:Car{brand='ford', price=230000.0}

4.1.2.實例方法 factory-bean

要聲明經過實例工廠方法建立的 Bean

  • 在 bean 的 factory-bean 屬性裏指定擁有該工廠方法的 Bean
  • factory-method 屬性裏指定該工廠方法的名稱
  • 使用 construtor-arg 元素爲工廠方法傳遞方法參數
    InstanceCarFactory.class
package com.cnblogs.tangge.factory;

import java.util.HashMap;
import java.util.Map;

/**
 * @Description:實例工廠的方法
 *
 **/
public class InstanceCarFactory {
    private static Map<String,Car> cars = null;

    public InstanceCarFactory() {
        cars = new HashMap<>();
        cars.put("audi",new Car("audi",300000));
        cars.put("ford",new Car("ford",230000));
    }

    public Car getInstanceCar(String name){
        return cars.get(name) ;
    }
}

Bean

<!--實例工廠方法-->
  <bean id="carFactory" class="com.cnblogs.tangge.factory.InstanceCarFactory"/>
  <bean id="car2" factory-bean="carFactory" factory-method="getInstanceCar">
    <constructor-arg value="audi"></constructor-arg>
  </bean>

調用結果:Car{brand='audi', price=300000.0}

4.2 FactoryBean

  • Spring 中有兩種類型的 Bean, 一種是普通Bean, 另外一種是工廠Bean, 即FactoryBean.
  • 工廠 Bean 跟普通Bean不一樣, 其返回的對象不是指定類的一個實例, 其返回的是該工廠 Bean 的 getObject 方法所返回的對象

    FactoryBean接口定義
package org.springframework.beans.factory;

public interface FactoryBean<T> {
    T getObject() throws Exception;

    Class<?> getObjectType();

    boolean isSingleton();
}

FactoryBean 一般是用來建立比較複雜的bean,通常的bean 直接用xml配置便可,但若是一個bean的建立過程當中涉及到不少其餘的bean 和複雜的邏輯,用xml配置比較困難,這時能夠考慮用FactoryBean。
建立個人FactoryBean

package com.cnblogs.tangge.factoryBean;

import org.springframework.beans.factory.FactoryBean;

public class MyFactoryBean implements FactoryBean<Car> {
    
    private String brand;

    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }
    /**
     *
     * @return 返回bean的對象
     * @throws Exception
     */
    @Override
    public Car getObject() throws Exception {
        return new Car(brand,500000);
    }

    /**
     *
     * @return bean的類型
     */
    @Override
    public Class<?> getObjectType() {
        return Car.class;
    }

    /**
     *
     * @return 是否爲單例
     */
    @Override
    public boolean isSingleton() {
        return false;
    }


}

Bean

<!--
    經過FactoryBean 配置 Bean實例
    class: 指向 FactoryBean 的全類名
    property: 配置 FactoryBean 屬性
    但實際返回的實例倒是 FactoryBean 的 getObject() 方法返回的實例
  -->
  <bean id="car" class="com.cnblogs.tangge.factoryBean.MyFactoryBean">
    <property name="brand" value="BMW"></property>
  </bean>

測試結果:Car{brand='BMW', price=500000.0}

5.其餘配置形式(除了基於XML)

5.1.註解

  • 在classPath中掃描組件,偵測和實例化具備特定註解的組件
  • 特定組件包括:
    • @Component: 基本註解, 標識了一個受 Spring 管理的組件
    • @Respository: 標識持久層組件
    • @Service: 標識服務層(業務層)組件
    • @Controller: 標識表現層組件
      Spring 有默認的命名策略: 使用非限定類名, 第一個字母小寫. 也能夠在註解中經過 value 屬性值標識組件的名稱
  • 當在組件類上使用了特定的註解以後, 還須要在 Spring 的配置文件中聲明 <context:component-scan>
    • base-package 屬性指定一個須要掃描的基類包,Spring 容器將會掃描這個基類包裏及其子包中的全部類.
    • 當須要掃描多個包時, 可使用逗號分隔.
    • 若是僅但願掃描特定的類而非基包下的全部類,可以使用 resource-pattern 屬性過濾特定的類,示例:
    • <context:include-filter> 子節點表示要包含的目標類
    • <context:exclude-filter> 子節點表示要排除在外的目標類
    • context:component-scan 下能夠擁有若干個 context:include-filter 和 context:exclude-filter 子節點

context:include-filter 和 context:exclude-filter 子節點支持多種類型的過濾表達式:

Filter Type 示例 描述
annotation org.example.SomeAnnotation 符合SomeAnnoation的target class
assignable org.example.SomeClass 指定class或interface的全名
aspectj org.example..*Service+ AspectJ語法
regex org.example.Default.* Regelar Expression
custom org.example.MyTypeFilter Spring3新增自訂Type,實做org.springframework.core.type.TypeFilter


TestObject.class

package com.cnblogs.tangge.annoncation;

import org.springframework.stereotype.Component;

@Component
public class TestObject {

}

UserController.class

package com.cnblogs.tangge.annoncation.Controller;

import org.springframework.stereotype.Controller;


@Controller
public class UserController {
        public void execute(){
            System.out.println("UserController execute...");
        }
}

UserService.class

package com.cnblogs.tangge.annoncation.Service;

import org.springframework.stereotype.Service;

@Service
public class UserService {
    public void  add(){
        System.out.println("UserService add...");
    }
}

接口 UserRepository.class

package com.cnblogs.tangge.annoncation.Repository;

public interface UserRepository {

    void save();
}

UserRepositoryImp.class

package com.cnblogs.tangge.annoncation.Repository;

import org.springframework.stereotype.Repository;

@Repository("userRepository")
public class UserRepositoryImp implements UserRepository {

    @Override
    public void save() {
        System.out.println("UserRepository save..");
    }
}

bean-annocation.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"
  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">
  <!--指定Spring掃描的包-->
  <context:component-scan base-package="com.cnblogs.tangge.annoncation"></context:component-scan>
</beans>

測試

public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("bean-annocation.xml");

        TestObject test = (TestObject) ctx.getBean("testObject");
        System.out.println(test);
        UserController controller = (UserController) ctx.getBean("userController");
        System.out.println(controller);
        UserService service = (UserService) ctx.getBean("userService");
        System.out.println(service);
        UserRepository repository = (UserRepository) ctx.getBean("userRepository");
        System.out.println(repository);
    }
        /*
    com.cnblogs.tangge.annoncation.TestObject@1990a65e
    com.cnblogs.tangge.annoncation.Controller.UserController@64485a47
    com.cnblogs.tangge.annoncation.Service.UserService@25bbf683
    com.cnblogs.tangge.annoncation.Repository.UserRepositoryImp@6ec8211c
     */

5.1.1 resource-pattern

若是僅但願掃描特定的類而非基包下的全部類,可以使用 resource-pattern 屬性過濾特定的類

<context:component-scan base-package="com.cnblogs.tangge.annoncation"
    resource-pattern="repository/*.class"></context:component-scan>

報錯:No bean named 'testObject' available
只能掃描 UserRepository

//        TestObject test = (TestObject) ctx.getBean("testObject");
//        System.out.println(test);
//        UserController controller = (UserController) ctx.getBean("userController");
//        System.out.println(controller);
//        UserService service = (UserService) ctx.getBean("userService");
//        System.out.println(service);
        UserRepository repository = (UserRepository) ctx.getBean("userRepository");
        System.out.println(repository);

5.1.2 <context:exclude-filter>

<context:component-scan base-package="com.cnblogs.tangge.annoncation">
    <!--不包含Repository類-->
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Repository" />
  </context:component-scan>

執行

public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("bean-annocation.xml");

        TestObject test = (TestObject) ctx.getBean("testObject");
        System.out.println(test);
        UserController controller = (UserController) ctx.getBean("userController");
        System.out.println(controller);
        UserService service = (UserService) ctx.getBean("userService");
        System.out.println(service);
        UserRepository repository = (UserRepository) ctx.getBean("userRepository");
        System.out.println(repository);
    }
    /*
    com.cnblogs.tangge.annoncation.TestObject@1990a65e
    com.cnblogs.tangge.annoncation.Controller.UserController@64485a47
    com.cnblogs.tangge.annoncation.Service.UserService@25bbf683
    Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'userRepository' available
    'userRepository' 不可用
     */

5.1.3 <context:include-filter>

<!--
    context:include-filter:指定包含哪些表達式的組件,必須與use-default-filters配合使用
    use-default-filters:設置爲false
  -->
  <context:component-scan base-package="com.cnblogs.tangge.annoncation" use-default-filters="false">
    <!--只包含Repository類-->
    <context:include-filter  type="annotation" expression="org.springframework.stereotype.Repository" />
  </context:component-scan>

這裏執行,直接失敗,由於只能實例 userRepository,其餘都失敗。

5.1.4 type="assignable"

指定class或interface的全名

<context:component-scan base-package="com.cnblogs.tangge.annoncation" use-default-filters="false">
    <!--只包含Repository類,
    type=assignable :  指定class或interface的全名
    expression : class全名
    -->
    <context:include-filter  type="assignable" expression="com.cnblogs.tangge.annoncation.Repository.UserRepository" />
  </context:component-scan>

和上面效果同樣,只能實例化 userRepository。

5.1.5 組件裝配

<context:component-scan> 元素還會自動註冊 AutowiredAnnotationBeanPostProcessor 實例, 該實例能夠自動裝配具備 @Autowired@Resource@Inject註解的屬性.

@Autowired

  • 構造器, 普通字段(即便是非 public), 一切具備參數的方法均可以應用@Authwired 註解
    Bean設置
<context:component-scan base-package="com.cnblogs.tangge.annoncation">
  </context:component-scan>

UserController.class
調用 userService.add();

package com.cnblogs.tangge.annoncation.Controller;

import com.cnblogs.tangge.annoncation.Service.UserService;
import org.springframework.stereotype.Controller;


@Controller
public class UserController {

    private UserService userService;
        public void execute(){
            System.out.println("UserController execute...");
            userService.add();
        }
}

UserService.class
調用 userRepository.save();

package com.cnblogs.tangge.annoncation.Service;

import com.cnblogs.tangge.annoncation.Repository.UserRepository;
import org.springframework.stereotype.Service;

@Service
public class UserService {
    private UserRepository userRepository;
    public void  add(){
        System.out.println("UserService add...");
        userRepository.save();
    }
}

調用

public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("bean-annocation.xml");
        UserController controller = (UserController) ctx.getBean("userController");
        controller.execute();
    }

執行結果:

UserController execute...
    at com.cnblogs.tangge.annoncation.Controller.UserController.execute(UserController.java:13)
    at com.cnblogs.tangge.annoncation.annocationMain.main(annocationMain.java:29)

咱們UserController在添加@Autowired

@Controller
public class UserController {
    @Autowired
    private UserService userService;
        public void execute(){
            System.out.println("UserController execute...");
            userService.add();
        }
}

也在UserService添加@Autowired

@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;
    public void  add(){
        System.out.println("UserService add...");
        userRepository.save();
    }
}

執行結果:
UserController execute...
UserService add...
UserRepository save..

  • 默認狀況下, 全部使用 @Authwired 註解的屬性都須要被設置. 當 Spring 找不到匹配的 Bean 裝配屬性時, 會拋出異常, 若某一屬性容許不被設置, 能夠設置 @Authwired 註解的 required 屬性爲 false
    如今修改UserRepositoryImp
package com.cnblogs.tangge.annoncation.Repository;

import com.cnblogs.tangge.annoncation.TestObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

@Repository("userRepository")
public class UserRepositoryImp implements UserRepository {
    @Autowired
    TestObject testObject;
    @Override
    public void save() {
        System.out.println("UserRepository save..");
        System.out.println(testObject);
    }
}

而後把TestObject的@Component 去掉

package com.cnblogs.tangge.annoncation;

public class TestObject {

}

報錯:

DependencyException: Error creating bean with name 'userRepository': Unsatisfied dependency expressed through field 'testObject';

修改 @Autowired(required = false) 不檢查 TestObject

@Repository("userRepository")
public class UserRepositoryImp implements UserRepository {
    @Autowired(required = false)
    TestObject testObject;
    @Override
    public void save() {
        System.out.println("UserRepository save..");
        System.out.println(testObject);
    }
}

執行結果:
UserController execute...
UserService add...
UserRepository save..
null

  • 默認狀況下, 當 IOC 容器裏存在多個類型兼容的 Bean 時, 經過類型的自動裝配將沒法工做. 此時能夠在 @Qualifier 註解裏提供 Bean 的名稱. Spring 容許對方法的入參標註 @Qualifiter 已指定注入 Bean 的名稱

UserRepositoryImp修改,去掉命名@Repository,使用默認

/@Repository("userRepository")
@Repository
public class UserRepositoryImp implements UserRepository {
    @Autowired(required = false)
    TestObject testObject;
    @Override
    public void save() {
        System.out.println("UserRepository save..");
        System.out.println(testObject);
    }
}

建立第2個Repository,UserjdbcRrpository.class

package com.cnblogs.tangge.annoncation.Repository;

import org.springframework.stereotype.Repository;

@Repository
public class UserjdbcRrpository implements UserRepository {

    @Override
    public void save() {
        System.out.println("UserjdbcRrpository save...");
    }
}

執行結果:
報錯:expected single matching bean but found 2: userjdbcRrpository,userRepositoryImp

下面,修改UserService,添加 @Qualifier,指定注入的Bean名稱

@Service
public class UserService {

    @Autowired
    @Qualifier("userjdbcRrpository")
    private UserRepository userRepository;
    public void  add(){
        System.out.println("UserService add...");
        userRepository.save();
    }
}

執行結果:
UserController execute...
UserService add...
UserjdbcRrpository save...

實際上,@Qualifier("userjdbcRrpository")還能夠寫到形參前面,

@Service
public class UserService {

    private UserRepository userRepository;
    
    @Autowired
    public void setUserRepository(@Qualifier("userjdbcRrpository") UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public void  add(){
        System.out.println("UserService add...");
        userRepository.save();
    }
}
  • @Authwired 註解也能夠應用在數組類型的屬性上, 此時 Spring 將會把全部匹配的 Bean 進行自動裝配.
  • @Authwired 註解也能夠應用在集合屬性上, 此時 Spring 讀取該集合的類型信息, 而後自動裝配全部與之兼容的 Bean.
  • @Authwired 註解用在 java.util.Map 上時, 若該 Map 的鍵值爲 String, 那麼 Spring 將自動裝配與之 Map 值類型兼容的 Bean, 此時 Bean 的名稱做爲鍵值

6.Spring4.x:泛型依賴注入

Spring 4.x 中能夠爲子類注入子類對應的泛型類型的成員變量的引用

7.Spring AOP

  • AOP(Aspect-Oriented Programming, 面向切面編程): 是一種新的方法論, 是對傳統 OOP(Object-Oriented - Programming, 面向對象編程) 的補充.
  • AOP 的主要編程對象是切面(aspect), 而切面模塊化橫切關注點.
  • 在應用 AOP 編程時, 仍然須要定義公共功能, 但能夠明確的定義這個功能在哪裏, 以什麼方式應用, 而且沒必要修改受影響的類. 這樣一來橫切關注點就被模塊化到特殊的對象(切面)裏.
  • AOP 的好處:
    • 每一個事物邏輯位於一個位置, 代碼不分散, 便於維護和升級
    • 業務模塊更簡潔, 只包含核心業務代碼.
  • 切面(Aspect): 橫切關注點(跨越應用程序多個模塊的功能)被模塊化的特殊對象
  • 通知(Advice): 切面必需要完成的工做
  • 目標(Target): 被通知的對象
  • 代理(Proxy): 向目標對象應用通知以後建立的對象
  • 鏈接點(Joinpoint):程序執行的某個特定位置:如類某個方法調用前、調用後、方法拋出異常後等。鏈接點由兩個信息肯定:方法表示的程序執行點;相對點表示的方位。例如 ArithmethicCalculator#add() 方法執行前的鏈接點,執行點爲 ArithmethicCalculator#add(); 方位爲該方法執行前的位置
  • 切點(pointcut):每一個類都擁有多個鏈接點:例如 ArithmethicCalculator 的全部方法實際上都是鏈接點,即鏈接點是程序類中客觀存在的事務。AOP 經過切點定位到特定的鏈接點。類比:鏈接點至關於數據庫中的記錄,切點至關於查詢條件。切點和鏈接點不是一對一的關係,一個切點匹配多個鏈接點,切點經過 org.springframework.aop.Pointcut 接口進行描述,它使用類和方法做爲鏈接點的查詢條件。

7.1 AspectJ 註解

AspectJ:Java 社區裏最完整最流行的 AOP 框架.
在 Spring2.0 以上版本中, 可使用基於 AspectJ 註解或基於 XML 配置的 AOP

一、aspectjweaver.jar 下載地址:aspectjweaver.jar
二、下載
com.springsource.org.aopalliance-1.0.0.jar
com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
spring-aop-4.3.18.RELEASE.jar
spring-aspects-4.3.18.RELEASE.jar

  • 要在 Spring 應用中使用 AspectJ 註解, 必須在 classpath 下包含 AspectJ 類庫: aopalliance.jar、aspectj.weaver.jar 和 spring-aspects.jar
  • 將 aop Schema 添加到 <beans> 根元素中.
  • 要在 Spring IOC 容器中啓用 AspectJ 註解支持, 只要在 Bean 配置文件中定義一個空的 XML 元素 <aop:aspectj-autoproxy>
  • 當 Spring IOC 容器偵測到 Bean 配置文件中的 <aop:aspectj-autoproxy> 元素時, 會自動爲與 AspectJ 切面匹配的 Bean 建立代理.
  • 用 AspectJ 註解聲明切面

  • 要在 Spring 中聲明 AspectJ 切面, 只須要在 IOC 容器中將切面聲明爲 Bean 實例. 當在 Spring IOC 容器中初始化 AspectJ 切面以後, Spring IOC 容器就會爲那些與 AspectJ 切面相匹配的 Bean 建立代理.
  • 在 AspectJ 註解中, 切面只是一個帶有 @Aspect 註解的 Java 類.
  • 通知是標註有某種註解的簡單的 Java 方法.
  • AspectJ 支持 5 種類型的通知註解:
    • @Before: 前置通知, 在方法執行以前執行
    • @After: 後置通知, 在方法執行以後執行
    • @AfterRunning: 返回通知, 在方法返回結果以後執行
    • @AfterThrowing: 異常通知, 在方法拋出異常以後
    • @Around: 環繞通知, 圍繞着方法執行

7.1.1.前置通知

  1. <aop:aspectj-autoproxy/>
  2. 橫向關注點抽象到切面類中
    • 加入@Component
    • @Aspect 註解
  3. 各類通知

bean

<context:component-scan base-package="com.cnblogs.tangge"></context:component-scan>
  <!--使用 AspjectJ 註解起做用:自動爲匹配的類生成代理對象-->
  <aop:aspectj-autoproxy/>

接口ArithmeticCalculator

package com.cnblogs.tangge.spring.aop.impl;

public interface ArithmeticCalculator {
        int add(int i, int j);
        int sub(int i, int j);

        int mul(int i, int j);
        int div(int i, int j);
}

實現類ArithmeticCalculatorImpl

package com.cnblogs.tangge.spring.aop.impl;

import org.springframework.stereotype.Component;

@Component
public class ArithmeticCalculatorImpl implements ArithmeticCalculator {

    @Override
    public int add(int i, int j) {
        int result = i+j;
        return result;
    }

    @Override
    public int sub(int i, int j) {
        int result = i-j;
        return result;
    }

    @Override
    public int mul(int i, int j) {
        int result = i*j;
        return result;
    }

    @Override
    public int div(int i, int j) {
        int result = i/j;
        return result;
    }
}

建立一個AOP

package com.cnblogs.tangge.spring.aop.impl;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

//把這個類聲明爲一個切面:須要把該類放入IOC容器中。再聲明爲一個切面
@Aspect
@Component
public class LogAspect {

    //聲明一個前置通知:在目標方法開始以前
    @Before("execution(public int com.cnblogs.tangge.spring.aop.impl.ArithmeticCalculator.add(int,int))")
    public void beforeMethod() {
        System.out.println("the method add begins");
    }

    public void afterMethod() {
        System.out.println("sub result:");
    }
}

測試

public class demo {

    public static void main(String[] args) {
        //1.建立Spring 的IOC容器
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");

        //2.從IOC 容器獲取 bean
        ArithmeticCalculator arithmeticCalculator = ctx.getBean(ArithmeticCalculator.class);

        //3.使用bean
        int result = arithmeticCalculator.add(1,3);
        System.out.println("result:"+result);
        /*
         the mothod add begins
         result:4
         */
    }
}

7.1.2 後置通知,返回通知,異常通知

package com.cnblogs.tangge.spring.aop.impl;

import java.util.Arrays;
import java.util.List;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

//把這個類聲明爲一個切面:須要把該類放入IOC容器中。再聲明爲一個切面
@Aspect
@Component
public class LogAspect {

    //聲明一個前置通知:在目標方法開始以前
    @Before("execution(public int com.cnblogs.tangge.spring.aop.impl.ArithmeticCalculator.*(int,int))")
    public void beforeMethod(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        //Arrays.asList(T... a) 返回由指定數組支持的固定大小的列表。
        List<Object> args  = Arrays.asList(joinPoint.getArgs());
        System.out.println("前置通知@Before:the method "+methodName+"  begins"+args);
    }

    /**
     * 方法執行後,執行的代碼
     * @param joinPoint
     */
    @After("execution(public int com.cnblogs.tangge.spring.aop.impl.ArithmeticCalculator.*(..))")
    public void afterMethod(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("後置通知@After:the method "+methodName+"  end..");
    }

    /**
     * 返回通知, 在方法返回結果以後執行
     * @param joinPoint
     * @param result1 能夠訪問方法返回值
     */
    @AfterReturning(value = "execution(public int com.cnblogs.tangge.spring.aop.impl.ArithmeticCalculator.*(..))",returning = "result1")
    public void afterReturningMethod(JoinPoint joinPoint,Object result1) {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("返回通知@AfterReturning:the method "+methodName+"  AfterReturning..The result with:"+result1);
    }

    /**
     * 異常通知,能夠訪問異常對象,且能夠指定出現特定異常時在執行通知代碼
     * @param joinPoint
     * @param exception
     */
    @AfterThrowing(value = "execution(public int com.cnblogs.tangge.spring.aop.impl.ArithmeticCalculator.*(..))",
            throwing = "exception")
    public void afterThrowingMethod(JoinPoint joinPoint,Exception exception) {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("異常通知@AfterReturning:the method "+methodName+"  throwing exception:"+exception);
    }
}

執行結果:

前置通知@Before:the method add begins[1, 3]
後置通知@After:the method add end..
返回通知@AfterReturning:the method add AfterReturning..The result with:4
result:4
前置通知@Before:the method div begins[10, 2]
後置通知@After:the method div end..
返回通知@AfterReturning:the method div AfterReturning..The result with:5
result:5

7.1.3 環繞通知

/**
     * 環繞通知
     * @param joinPoint
     */
    @Around(value = "execution(public int com.cnblogs.tangge.spring.aop.impl.ArithmeticCalculator.*(..))")
    public Object AroundMethod(ProceedingJoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("-----進入環繞通知-----");
        //執行目標方法
        Object result = null;
        try {
            System.out.println("前置通知@Before:the method "+methodName+"  begins"+ Arrays.asList(joinPoint.getArgs()));
            //執行目標方法
            result = joinPoint.proceed();
            System.out.println("返回通知@AfterReturning:the method "+methodName+"  AfterReturning..The result with:"+result);
        } catch (Throwable throwable) {
            System.out.println("異常通知@AfterReturning:the method "+methodName+"  throwing exception:"+throwable.toString());
            throwable.printStackTrace();
        }
        System.out.println("後置通知@After:the method "+methodName+"  end..");

        return result;
    }

執行結果:

-----進入環繞通知-----
前置通知@Before:the method add begins[1, 3]
返回通知@AfterReturning:the method add AfterReturning..The result with:4
後置通知@After:the method add end..
result:4
-----進入環繞通知-----
前置通知@Before:the method div begins[10, 2]
返回通知@AfterReturning:the method div AfterReturning..The result with:5
後置通知@After:the method div end..
result:5

7.1.3 優先級 @Order

切面的優先級能夠經過實現 Ordered 接口或利用 @Order 註解指定.
實現 Ordered 接口, getOrder() 方法的返回值越小, 優先級越高.
若使用 @Order 註解, 序號出如今註解中

7.1.4 重用切入點 @Pointcut

/**
         * @Pointcut 聲明切入點表達式
         * 其餘通知直接使用方法名引用當前的切入點
      */
    @Pointcut("execution(public int com.cnblogs.tangge.spring.aop.impl.ArithmeticCalculator.*(..))")
        public void declarexecution() {}

其餘方法經過 @Pointcut 註解將一個切入點聲明成簡單的方法. 切入點的方法體一般是空的, 由於將切入點定義與應用程序邏輯混在一塊兒是不合理的.

/**
     * 環繞通知
     * @param joinPoint
     */
    @Around(value = "declarexecution()")
    public Object AroundMethod(ProceedingJoinPoint joinPoint) {
    ...
    }

7.2 在 XML 配置的 AOP

  • 除了使用 AspectJ 註解聲明切面, Spring 也支持在 Bean 配置文件中聲明切面. 這種聲明是經過 aop schema 中的 XML 元素完成的.
  • 正常狀況下, 基於註解的聲明要優先於基於 XML 的聲明. 經過 AspectJ 註解, 切面能夠與 AspectJ 兼容, 而基於 XML 的配置則是 Spring 專有的. 因爲 AspectJ 獲得愈來愈多的 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/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 http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

  <!--1.配置bean-->
  <bean id="arithmeticCalculator" class="com.cnblogs.tangge.spring.aop.impl.ArithmeticCalculatorImpl"></bean>

  <!--2.配置切面的bean-->
  <bean id="logAspect" class="com.cnblogs.tangge.spring.aop.impl.LogAspect"></bean>

  <!--3.配置AOP-->
  <aop:config>
    <!--配置切點表達式-->
    <aop:pointcut id="pointcut" expression="execution(public int com.cnblogs.tangge.spring.aop.impl.ArithmeticCalculator.*(..))"></aop:pointcut>
    <!--配置切面及通知-->
    <aop:aspect ref="logAspect" order="2">
      <aop:around method="AroundMethod" pointcut-ref="pointcut"></aop:around>
    </aop:aspect>
  </aop:config>
</beans>

8 JdbcTemplate

  • 每次使用都建立一個 JdbcTemplate 的新實例, 這種作法效率很低下.
  • JdbcTemplate 類被設計成爲線程安全的, 因此能夠再 IOC 容器中聲明它的單個實例, 並將這個實例注入到全部的 DAO 實例中.
  • JdbcTemplate 也利用了 Java 1.5 的特定(自動裝箱, 泛型, 可變長度等)來簡化開發
  • Spring JDBC 框架還提供了一個 JdbcDaoSupport 類來簡化 DAO 實現. 該類聲明瞭 jdbcTemplate 屬性, 它能夠從 IOC 容器中注入, 或者自動從數據源中建立.

db.properties

drivername=com.mysql.cj.jdbc.Driver
url = jdbc:mysql://localhost:4040/day22_jdbc?serverTimezone = GMT
user = root
pass = 123

Bean配置 applicationContext.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"
  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">

  <!--引用外部文件db.properties-->
  <context:property-placeholder location="classpath:db.properties"/>

  <!--配置jdbc-->
  <bean id="datasource" class="org.apache.commons.dbcp2.BasicDataSource" >
    <property name="driverClassName" value="${drivername}"></property>
    <property name="url" value="${url}"></property>
    <property name="username" value="${user}"></property>
    <property name="password" value="${pass}"></property>
  </bean>

  <!--配置Spring的JdbcTemplate-->
  <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <property name="dataSource" ref="datasource"></property>
  </bean>

  <!--指定Spring掃描的包
  <context:component-scan> 元素還會自動註冊 AutowiredAnnotationBeanPostProcessor 實例,
  該實例能夠自動裝配具備 @Autowired 和 @Resource 、@Inject註解的屬性.
  -->
  <context:component-scan base-package="com.cnblogs.tangge.jdbc">
  </context:component-scan>
</beans>

下面進行測試

package com.cnblogs.tangge.jdbc;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import javax.sql.DataSource;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;

public class JDBCTest {

    ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
    JdbcTemplate jdbcTemplate = (JdbcTemplate) ctx.getBean("jdbcTemplate");

    public static void main(String[] args) {
        JDBCTest test = new JDBCTest();
        try {
            test.UpdateTemplate();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 更新:insert,update,delete
     */
    public void UpdateTemplate() {
        String sql = "UPDATE `day22_jdbc`.`sort` SET  `sname`=? WHERE (`sid`=?);\n ";
        jdbcTemplate.update(sql, "什麼球", 5);
    }

    /**
     * 批量更新:insert,update,delete
     */
    public void InsertTemplate() {
        String sql = "insert into sort(`sname`, `sprice`, `sdesc`) VALUES (?,?,?) ";
        List<Object[]> batchList = new ArrayList<>();
        batchList.add(new Object[]{"棒球", 36.22, "體育用品"});
        batchList.add(new Object[]{"冰箱", 1466.99, "加點用品"});
        jdbcTemplate.batchUpdate(sql, batchList);
    }

    /**
     * 讀取一條數據,獲得對應的對象
     * 注意不是調用 queryForObject(String sql, Class<T> requiredType, Object... args)
     * 須要: queryForObject(String sql, RowMapper<T> rowMapper, Object... args)
     * 1.RowMapper<T> 指定如何去映射行結果集的行,經常使用實現類 BeanPropertyRowMapper
     * 2.不支持級聯屬性 JdbcTemplate 是一個小工具,不是 ORM 框架
     */
    public void testQueryForObject() {
        String sql = "select * from employee where id = ?";
    //Employee employee  =jdbcTemplate.queryForObject(sql,Employee.class,1);
        RowMapper<Employee> rowMapper = new BeanPropertyRowMapper<>(Employee.class);
        Employee employee = jdbcTemplate.queryForObject(sql, rowMapper, 1);
        System.out.println(employee);
        //結果:Employee{id=1, lastName='sansan', firstName='tang', email='fenm@qq.com'}
    }

    /**
     * 讀取一條數據,獲得對應的對象
     * 配置Dao,調用Dao封裝方法
     */
    public void testQueryForObjectByDao() {
        //EmployeeDao dao = new EmployeeDao();
        //這裏須要bean配置,直接new是傻了,EmployeeDao類上 @Repository自動實例
        EmployeeDao dao = ctx.getBean(EmployeeDao.class);
        Employee employee = dao.getEmployee(1);
        System.out.println(employee);
        //結果:Employee{id=1, lastName='sansan', firstName='tang', email='fenm@qq.com'}
    }

    /**
     * 查詢:實體類集合
     * 不是調用的 queryForList
     */
    public void TestQueryForList() {
        String sql = "select * from employee";
        RowMapper<Employee> rowMapper = new BeanPropertyRowMapper<>(Employee.class);
        List<Employee> employee = jdbcTemplate.query(sql, rowMapper);
        System.out.println(employee);
        //[Employee{id=1, lastName='sansan', firstName='tang', email='fenm@qq.com'},
        // Employee{id=2, lastName='li', firstName='xiao', email='denm@qq.com'}]
    }

    /**
     * 查詢:單個的值
     */
    public void testQueryForOne() {
        String sql = "select count(*) from employee";
        Long count = jdbcTemplate.queryForObject(sql, Long.class);
        System.out.println(count);  //2
    }

    public void TestConnection() throws SQLException {
        DataSource dataSource = (DataSource) ctx.getBean("datasource");
        System.out.println(dataSource.getConnection());
    }
}

Dao封裝方法

@Repository
public class EmployeeDao {

    @Autowired
    private  JdbcTemplate jdbcTemplate;

    public  Employee getEmployee(int id) {
        String sql = "select * from employee where id = ?";
        RowMapper<Employee> rowMapper  = new BeanPropertyRowMapper<>(Employee.class);
        Employee employee  =jdbcTemplate.queryForObject(sql,rowMapper,id);
      return employee;
    }
}

調用

/**
     * 讀取一條數據,獲得對應的對象
     * 配置Dao,調用Dao
     */
    public void testQueryForObjectByDao() {
        //EmployeeDao dao = new EmployeeDao();
        //這裏須要bean配置,直接new是傻了,EmployeeDao類上 @Repository自動實例
        EmployeeDao dao = ctx.getBean(EmployeeDao.class);
        Employee employee = dao.getEmployee(1);
        System.out.println(employee);
        //結果:Employee{id=1, lastName='sansan', firstName='tang', email='fenm@qq.com'}
    }

8.1 在JDBC使用具名參數 NamedParameterJdbcTemplate

  • 在 SQL 語句中使用具名參數時, 能夠在一個 Map 中提供參數值, 參數名爲鍵
  • 也可使用 SqlParameterSource 參數
  • 批量更新時能夠提供 Map 或 SqlParameterSource 的數組

bean

<!--配置namedParameterJdbcTemplate,該對象可以使用具名參數
  其沒有無參構造器,因此必須制定構造器-->
  <bean id="namedParameterJdbcTemplate" class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate">
    <constructor-arg ref="datasource"></constructor-arg>
  </bean>

實現

NamedParameterJdbcTemplate namedParameterJdbcTemplate = ctx.getBean(NamedParameterJdbcTemplate.class);

 /**
     * update(String sql, Map<String, ?> paramMap)
     * Map<String, ?> paramMap 能夠爲參數起名字。
     * 1. 優勢:若多個參數,不用對應位置,直接對應參數名
     * 2. 缺點:麻煩。
     */
    public void testNamedParameterJdbcTemplate() {
        String sql = "insert into employee(`last_name`, `first_name`, `email`) VALUES (:ln,:fn,:email) ";
        Map<String,Object> paramMap = new HashMap<>();
        paramMap.put("ln","ww");
        paramMap.put("fn","cc");
        paramMap.put("email","wccw@qq.com");
        namedParameterJdbcTemplate.update(sql,paramMap);
    }

    /**
     * 具名參數可使用 update(String sql, SqlParameterSource paramSource)
     * 1.SQL 語句中參數名和類屬性一致
     * 2.使用 SqlParameterSource 的 BeanPropertySqlParameterSource 實現類做爲參數,
     * (:字段)具備setter與getter方法。
     */
    public void testNamedParameterJdbcTemplateWithParamSource() {
        String sql = "insert into employee(`last_name`, `first_name`, `email`) VALUES (:lastName,:firstName,:email) ";
        Employee employee = new Employee();
        employee.setLastName("XYZ");
        employee.setFirstName("XYZ");
        employee.setEmail("XYZ@qq.com");
        SqlParameterSource source  = new BeanPropertySqlParameterSource(employee);
        namedParameterJdbcTemplate.update(sql,source);
    }

9 事務

事務的四個關鍵屬性(ACID)

  • 原子性(atomicity): 事務是一個原子操做, 由一系列動做組成. 事務的原子性確保動做要麼所有完成要麼徹底不起做用.
  • 一致性(consistency): 一旦全部事務動做完成, 事務就被提交. 數據和資源就處於一種知足業務規則的一致性狀態中.
  • 隔離性(isolation): 可能有許多事務會同時處理相同的數據, 所以每一個事物都應該與其餘事務隔離開來, 防止數據損壞.
  • 持久性(durability): 一旦事務完成, 不管發生什麼系統錯誤, 它的結果都不該該受到影響. 一般狀況下, 事務的結果被寫到持久化存儲器中

Repository層

public interface BookShopDao {

    //根據書號找單價
    public int findBookPriceByIsbn(String isbn);

    //更新書的庫存(書號對應的庫存-1)
    public boolean updateBookStock(String isbn);

    //更新用戶帳戶餘額:username的 blance -price
    public boolean updateUserAccount(String username,int price);
}

實現類 BookShopDao

@Repository
public class BookShopDaoImpl implements BookShopDao {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Override
    public int findBookPriceByIsbn(String isbn) {
        String sql = "select price from book where isbn = ?";
        return jdbcTemplate.queryForObject(sql, Integer.class, isbn);
    }

   @Override
    public boolean updateBookStock(String isbn) {
        //檢查庫存是否不夠,若不夠則拋出異常
        String sql2 = "select stock from book_stock where isbn = ?";
        int stock = jdbcTemplate.queryForObject(sql2, Integer.class, isbn);
        if (stock == 0) {
            throw new BookShopException("庫存不足");
        } else {
            String sql = "update book_stock set stock = stock-1 where isbn = ?";
            return (jdbcTemplate.update(sql, isbn)) > 0;
        }


    }

    @Override
    public boolean updateUserAccount(String username, int price) {
        //驗證餘額不足
        String sql2 = "select balance from account where username = ?";
        int balance = jdbcTemplate.queryForObject(sql2, Integer.class, username);
        if (balance < price) {
            throw new AccountException("餘額不足");
        } else {
            String sql = "update account set balance = balance-? where username = ?";
            return (jdbcTemplate.update(sql, price, username)) > 0;
        }
    }
}

service層

public interface BookShopService {
    //某人買一本書
    boolean purchase(String isbn, String username);
}

service實現

@Service
public class BookShopServiceImpl implements BookShopService {

    @Autowired
    private BookShopDao bookShopDao;

    @Override
    public boolean purchase(String isbn, String username) {
        //1.獲取書單價
        int price = bookShopDao.findBookPriceByIsbn(isbn);
        //2.更新書的庫存
        bookShopDao.updateBookStock(isbn);
        //3.更新用戶餘額
        return bookShopDao.updateUserAccount(username, price);
    }
}

demo實現(沒有事務的狀況下,出現只扣庫存的操做)

public class TransactionDemo {
    static  ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
    static BookShopService bookShopService  = ctx.getBean(BookShopService.class);

    public static void main(String[] args) {
        testpurchase();
    }

    public static void testpurchase(){
        System.out.println(bookShopService.purchase("0001","Tom"));
    }
    //餘額不足
}

爲了方便查看是哪一個方法的錯誤,實現了2個自定義異常,重寫RuntimeException方法

public class AccountException extends RuntimeException{...}
public class BookShopException extends RuntimeException{...}

9.1 聲明式事務 @Transactional

  • 編程式事務管理: 將事務管理代碼嵌入到業務方法中來控制事務的提交和回滾. 在編程式管理事務時, 必須在每一個事務操做中包含額外的事務管理代碼.
  • 聲明式事務管理: 大多數狀況下比編程式事務管理更好用. 它將事務管理代碼從業務方法中分離出來, 以聲明的方式來實現事務管理. 事務管理做爲一種橫切關注點, 能夠經過 AOP 方法模塊化. Spring 經過 Spring AOP 框架支持聲明式事務管理.

bean

<!--配置事務管理器-->
  <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="datasource"></property>
   </bean>

  <!--啓用事務註解 http://www.springframework.org/schema/tx-->
  <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>

在實現類添加@Transactional

@Service
public class BookShopServiceImpl implements BookShopService {

    @Autowired
    private BookShopDao bookShopDao;

    //添加事務註解
    @Transactional
    @Override
    public boolean purchase(String isbn, String username) {
        //1.獲取書單價
        int price = bookShopDao.findBookPriceByIsbn(isbn);
        //2.更新書的庫存
        bookShopDao.updateBookStock(isbn);
        //3.更新用戶餘額
        return bookShopDao.updateUserAccount(username, price);
    }
}

疑難:Spring @transaction不起做用,Spring事物注意事項

  1. 在須要事務管理的地方加@Transactional 註解。@Transactional 註解能夠被應用於接口定義和接口方法、類定義和類的 public 方法上 。

  2. @Transactional 註解只能應用到 public 可見度的方法上 。 若是你在 protected、private 或者 package-visible 的方法上使用 @Transactional 註解,它也不會報錯, 可是這個被註解的方法將不會展現已配置的事務設置。

  3. 注意僅僅 @Transactional 註解的出現不足於開啓事務行爲,它僅僅 是一種元數據。必須在配置文件中使用配置元素,才真正開啓了事務行爲。

重點事項:

Spring事物是基於類和接口的(通俗理解即:在調用的時候不能再同一個類裏面被調用,必須調用外面的類去作事物操做)

Spring的事物必須是可見的(即:定義的方法必須是public的)

9.1.1 事務傳播行爲 REQUIRES_NEW

定義消費接口

public interface CashierService {
    public boolean checkout(String username,List<String> isbns);
}

實現

@Service
public class CashierServiceImp implements CashierService {

    @Autowired
    private BookShopService bookShopService;

    @Transactional
    @Override
    public boolean checkout(String username, List<String> isbns) {

        try {
            for (String isbn : isbns) {
                bookShopService.purchase(isbn,username);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
}

測試

static CashierService cashierService = ctx.getBean(CashierService.class);

 public static void main(String[] args) {
        tesCashier();
    }
 public static void tesCashier() {
        List<String> list = Arrays.asList("0001", "0002");
        System.out.println(cashierService.checkout("Tom",list));
    }


默認傳播模式 propagation = Propagation.REQUIRES
若是同時買0001與0002的價格不夠,就不能購買成功。


如今該爲 REQUIRES_NEW 模式:
只要能購買成功0001商品,就能成功,後面不能購買時,報錯。
至關於purchase分爲一個小的事務。

9.1.2 屬性(隔離級別&回滾&只讀&過時)

併發致使的問題

  • 當同一個應用程序或者不一樣應用程序中的多個事務在同一個數據集上併發執行時, 可能會出現許多意外的問題
  • 併發事務所致使的問題能夠分爲下面三種類型:
    • 髒讀: 對於兩個事物 T1, T2, T1 讀取了已經被 T2 更新但 尚未被提交的字段. 以後, 若 T2 回滾, T1讀取的內容就是臨時且無效的.
    • 不可重複讀:對於兩個事物 T1, T2, T1 讀取了一個字段, 而後 T2 更新了該字段. 以後, T1再次讀取同一個字段, 值就不一樣了.
    • 幻讀:對於兩個事物 T1, T2, T1 從一個表中讀取了一個字段, 而後 T2 在該表中插入了一些新的行. 以後, 若是 T1 再次讀取同一個表, 就會多出幾行.

隔離級別

//添加事務註解
    //isolation  隔離級別,常取值 READ_COMMITTED
    //noRollbackFor = {AccountException.class} 對哪一個異常不回滾 ,聲明爲 Class[] 類型的
    //rollbackFor 碰見必須回滾 ,聲明爲 Class[] 類型的
    //readOnly = true 只讀事務屬性: 表示這個事務只讀取數據但不更新數據, 這樣能夠幫助數據庫引擎優化事務.
    //timeout(s)  指定強制回滾以前事務佔用時間(秒)
    @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT
            , noRollbackFor = {AccountException.class}, readOnly = true, timeout = 3
    )

9.2 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:tx="http://www.springframework.org/schema/tx"
  xmlns:aop="http://www.springframework.org/schema/aop"
  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
  http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

  <!--引用外部文件db.properties-->
  <context:property-placeholder location="classpath:db.properties"/>

  <!--配置jdbc-->
  <bean id="datasource" class="org.apache.commons.dbcp2.BasicDataSource">
    <property name="driverClassName" value="${drivername}"></property>
    <property name="url" value="${url}"></property>
    <property name="username" value="${user}"></property>
    <property name="password" value="${pass}"></property>
  </bean>

  <!--配置Spring的JdbcTemplate-->
  <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <property name="dataSource" ref="datasource"></property>
  </bean>

  <!--配置Bean-->
  <bean id="bookShopDao" class="com.cnblogs.tangge.TransactionXML.BookShopDaoImpl">
    <property name="jdbcTemplate" ref="jdbcTemplate"></property>
  </bean>
  <bean id="bookShopService" class="com.cnblogs.tangge.TransactionXML.BookShopServiceImpl">
    <property name="bookShopDao" ref="bookShopDao"></property>
  </bean>
  <bean id="cashierService" class="com.cnblogs.tangge.TransactionXML.CashierServiceImp">
    <property name="bookShopService" ref="bookShopService"></property>
  </bean>

  <!--1.配置事務管理器-->
  <bean id="transactionManager"
    class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="datasource"></property>
  </bean>

  <!--2.配置事務屬性-->
  <tx:advice id="txadvice" transaction-manager="transactionManager">
    <tx:attributes>
      <!--根據方法取事務屬性-->
      <tx:method name="purchase" propagation="REQUIRES_NEW"/>
      <tx:method name="get*" read-only="true"/>
      <tx:method name="find*" read-only="true"/>
      <tx:method name="*"/>
    </tx:attributes>
  </tx:advice>

  <!--3.配置事務切入點,切入點及事務關聯起來-->
  <aop:config>
    <aop:pointcut id="purchase"
      expression="execution(* com.cnblogs.tangge.TransactionXML.BookShopServiceImpl.purchase(..))"/>
    <aop:advisor advice-ref="txadvice" pointcut-ref="purchase"></aop:advisor>
  </aop:config>

  <aop:config>
    <aop:pointcut id="checkout"
      expression="execution(* com.cnblogs.tangge.TransactionXML.CashierServiceImp.checkout(..))"/>
    <aop:advisor advice-ref="txadvice" pointcut-ref="checkout"></aop:advisor>
  </aop:config>
</beans>
相關文章
相關標籤/搜索