1. 程式人生 > 其它 >Spring 全程學習筆記

Spring 全程學習筆記

技術標籤:JAVA框架及難點

spring

一、Spring簡介

1、spring是什麼

Spring是分層的java se/ee應用full-stack輕量級開源框架,以IOC(Inverse Of Control 控制反轉 )和AOP(Aspect Oriented Programming 面向切面程式設計)為核心。

提供了展現層SpringMVC和持久層Spring JDBCTemplate以及業務層事務管理等眾多的企業級應用技術,還能整合開源世界眾多著名的第三方框架和類庫,逐漸成為使用最多的java ee企業應用開源框架。

2、spring發展歷程

1997年,IBM提出了EJB的思想

1998年,SUN公司制定開發標準規範EJB1.0

1999年,EJB1.1釋出(Enterprise JavaBean)

2001年,EJB2.0釋出

2003年,EJB2.1釋出

2006年,EJB3.0釋出

Rod Johnson,spring之父,Expert One-to-One Design and Development(2002),闡述了J2EE使用EJB開發設計的有點及解決方案。

Expert One-to-One J2EE Development without EJB(2004) 闡述了J2EE不使用EJB的解決方案。(Spring雛形)

3、spring的優勢

1.方便解耦,簡化開發

通過spring提供的IOC容器,可以將物件間的以來關係交由spring進行控制,避免硬編碼所造成的過度耦合,使用者也不必再為單例模式類、屬性檔案解析等這些很底層的需求編寫程式碼,可以更專注於上層的應用。

1.耦合是什麼?
耦合就是程式間的依賴關係。包括類之間的耦合和方法間的耦合。

2.解耦是什麼?
解耦就是降低程式間的以來關係。
在實際開發中,我們應該做到編譯器不依賴,執行時才依賴。

3.解耦的思路?
首先我們使用反射來建立物件,避免使用new關鍵字。
其次我們通過讀取配置檔案來獲取要建立的物件的全限定類名。

2.AOP程式設計的支援

通過Spring的AOP功能,方便進行面向切面程式設計,許多不需要傳統OOP(object oriented programming)實現的功能可以通過AOP輕鬆實現。

1.什麼是aop? 
aop就是面向切面程式設計,通過預編譯方式和執行期動態代理實現程式功能的統一維護的一種技術。
aop是oop的延續,是軟體開發中的一個熱點,也是spring框架中的一個重要內容,是函數語言程式設計的一種衍生範型。
通過aop可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度變低,提高程式的可重用性。
簡單的來說就是把我們程式重複的程式碼抽取出來,在需要執行的時候,使用動態代理的技術,在不修改原始碼的基礎上對已有方法進行增強。

2.aop的作用及優勢?
在程式執行期間,不修改原始碼對已有方法進行增強。
減少重複程式碼 提高開發效率 維護方便

3.aop的實現方式:使用動態代理

3.宣告式事務的支援

可以將我們從單調煩悶的事務管理程式碼中解脫出來,通過宣告式方式靈活的進行事務管理,提高開發效率和質量。

4.方便程式的測試

可以用非容器以來的程式設計方式進行幾乎所有的測試工作,測試不再是昂貴的操作,而是隨手可做的事情。

5.方便整合各種優秀框架

spring對各種優秀框架都能進行整合使用。

6.降低了JavaEE API的使用難度

spring對於JavaEE進行了薄薄的封裝,使用這些api的難度大大降低。

7.java原始碼是經典的學習範例。

spring原始碼中使用的演算法和設計模式都是極為巧妙的。

4、spring的體系結構

二、Spring快速入門

1、spring程式開發步驟

1.匯入Spring開發的基本包座標

2.編寫Dao介面和實現類

3.建立Spirng核心配置檔案

4.在Spring配置檔案中配置要產生例項物件的全限定類名

5.使用Spring的API獲取Bean例項

2、spring對bean的管理細節

1.spring建立bean的三種方式

第一種方式:使用預設建構函式建立
在spring的配置檔案中使用bean標籤,配置id和class屬性以後,且沒有其他屬性和標籤時,
採用的就是預設建構函式建立bean物件,此時如果類中沒有預設建構函式,則物件無法建立。

<bean id="accountService" class = "com.xzy.service.impl.AccountService"></bean>

預設建構函式如下:
public AccountServiceImpl(){
	System.our.println("物件建立了!");
}
第二種方式:使用普通工廠中的方法建立物件(使用某個類中的方法建立物件,並存入spring容器中)
假如我們想要使用的類,是存在其他jar包中,這裡我們無法修改原始碼,只能使用這種方法。

<bean id="instanceFactory" class="com.xzy.factory.InstanceFactory"></bean>
<bean id="accountService" factory-bean="instanceFactory" factory-method="getAccountService"></bean>
第三種方法:使用靜態工廠中的靜態方法建立物件(使用某個勒種的靜態方法建立物件,並存入spring容器)
<bean id="accountService" class="com.xzy.factory.StaticFactory" factory-method="getAccountService"></bean>

2.bean物件的作用範圍

bean標籤的scope屬性用來指定bean的作用範圍

singleton: 單例(也是預設值)
prototype: 多例
request: 作用於web應用的請求範圍
session: 作用於web應用的會話範圍
global-session: 作用於叢集環境的會話範圍,如果不是叢集環境,那麼就是session

3.bean物件的生命週期

單例物件: 一般使用sintleton方式,也就是即時載入
出生:當容器建立時物件出生
活著:只要容器還在,那麼單例物件就一直活著
死亡:容器消失,單例物件死亡

多例物件:一般使用prototype方式,也就是延時載入
出生:只有使用物件時物件才出生
活著:物件在使用過程中就一直活著
死亡:由jvm中垃圾回收機制回收

3、spring中的依賴注入(DI IOC)

1.注入的資料

第一種:基本型別和String
<bean id="accountService2" class="com.xzy.service.impl.AccountServiceImpl2">
        <property name="name" value="test2"></property>
        <property name="age" value="21"></property>
        <property name="birthday" ref="now"></property>
    </bean>



第二種:其他bean,例如在配置檔案中或者註解配置過的bean
<bean id="accountService" class="com.xzy.service.impl.AccountServiceImpl">
        <constructor-arg name="name" value="test"></constructor-arg>
        <constructor-arg name="age" value="18"></constructor-arg>
        <constructor-arg name="birthday" ref="now"></constructor-arg>
    </bean>

    <bean id="now" class="java.util.Date"></bean>



第三種:複雜型別或集合型別等
 <bean id="accountService3" class="com.xzy.service.impl.AccountServiceImpl3">
        <property name="myStrs">
            <array>
                <value>aaa</value>
                <value>bbb</value>
                <value>ccc</value>
            </array>
        </property>

        <property name="myList">
            <list>
                <value>aaa</value>
                <value>bbb</value>
                <value>ccc</value>
            </list>
        </property>

        <property name="mySet">
            <set>
                <value>aaa</value>
                <value>bbb</value>
                <value>ccc</value>
            </set>
        </property>

        <property name="myMap">
            <map>
                <entry key="testa" value="aaa"></entry>
                <entry key="testb" value="bbb"></entry>
                <entry key="testc" value="ccc"></entry>
            </map>
        </property>

        <property name="myPropers">
            <props>
                <prop key="testA">aaa</prop>
                <prop key="testB">bbb</prop>
                <prop key="testC">ccc</prop>
            </props>
        </property>
    </bean>

2.注入的方式

//如果是經常變化的資料,那麼並不適用注入的方式
第一種:使用構造方法注入
private String name;
private Integer age;
private Date birthday;
public AccountServiceImpl(String name,Integer age,Date birthday){
	this.name = name;
  this.age = age;
  this.birthday = birthday;
}

 <!--
    建構函式的注入:
    標籤中屬性的具體解釋:
        type:用以指定要注入資料的資料型別,該資料型別也是建構函式中某個或某些引數得型別
        index:用於指定要注入的資料給建構函式中指定索引位置的引數賦值,索引的位置是從0開始
        name:用於指定給建構函式中指定名稱的引數賦值

        =================以上三個用於指定建構函式中那個引數賦值==================================

        value:用於提供基本型別和String型別的資料
        ref:用於引用其他bean型別的資料.指的就是spring的容器中出現的bean物件

        優勢:在獲取bean物件時,注入資料是必須的操作,否則物件無法建立成功.
        缺點:改變了bean物件的例項化方式,使我們在建立物件時,如果我們不需要該物件,必須自己提供資料.

    -->
    <bean id="accountService" class="com.xzy.service.impl.AccountServiceImpl">
        <constructor-arg name="name" value="test"></constructor-arg>
        <constructor-arg name="age" value="18"></constructor-arg>
        <constructor-arg name="birthday" ref="now"></constructor-arg>
    </bean>

    <bean id="now" class="java.util.Date"></bean>
第二種:使用set方法注入(更常用的方式)
  
 //如果是經常變化的資料,並不適用於注入的方式

    private String name;
    private Integer age;
    private Date birthday;

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

    public void setAge(Integer age) {
        this.age = age;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }
<!--
    使用set方法注入:使用標籤 property
    標籤的屬性:
        name:用以指定注入時所呼叫的set方法名稱
        value和ref和上述一樣

		優勢:建立物件時沒有明確的限制,可以直接使用預設建構函式
		缺點:如果某個成員必須有值,則set方法無法保證能一定注入
    -->
    <bean id="accountService2" class="com.xzy.service.impl.AccountServiceImpl2">
        <property name="name" value="test2"></property>
        <property name="age" value="21"></property>
        <property name="birthday" ref="now"></property>
    </bean>
第三種:使用註解方式注入
在使用這種方式前需要匯入相應的xml約束空間,然後開啟包掃描
<context:component-scan base-package="com.xzy"></context:component-scan>

用於建立物件的,作用就是把當前類通過反射建立一個物件,然後存入到spring容器中
這裡的value是用於指定類的id,如果不寫預設為當前類名
@Component
@Controller
@Service
@Repository


用於注入資料的
@Autowired 自動按照型別注入(前提是有唯一一個bean和它匹配)如果沒有或者型別多於一個,那麼就會報錯。
@Qualifier 在按照類注入的基礎之上再按照名稱注入,在單獨給類成員注入時不能使用,但是在給方法單數注入時可以使用。
@Resource  直接按照bean的id注入 使用name屬性指定bean的id
以上三個注入都只能注入bean型別的資料,而基本型別和string型別無法使用上述註解實現
另外,集合型別的注入只能使用xml來實現。
@Value 用以注入基本型別和string型別的值,可以使用spirng中的spel表示式


用於改變作用範圍的
@Scope 用於指定bean的作用範圍,如果不寫預設為singleton


和生命週期相關的
@Predestroy 用於指定銷燬方法
@PostConstruct 用以指定初始化方法

其他
@Bean 把當前方法的返回值作為bean物件存入到spring容器中

4、spring Aop實現

1.動態代理實現

public static void main(String[] args) {
    //匿名內部類訪問外部成員方法時需要使用final修飾
        final Producer producer = new Producer();
        /**
         * 動態代理:
         *  特點:位元組碼隨用隨建立,在不修改原始碼的基礎上對方法進行增強
         *  分類:基於介面的動態代理和基於子類的動態代理
         *
         *  基於介面的動態代理: 涉及的類Proxy,提供者:jdk官方
         *      如何建立代理物件:使用Proxy類中的newProxyInstance方法
         *  建立代理物件的要求:被代理類最少實現一個介面,如果沒有則不能使用
         *  newProxyInstance方法的引數:
         *   ClassLoader 用於載入代理物件位元組碼的,和被代理物件使用相同的類載入器
         *   Class[] 位元組碼陣列 它是讓代理物件和被代理物件有相同方法
         *   InvocationHandler 它是讓我們寫如何代理,我們一般都是寫一個該介面的實現類,通常情況下都是匿名內部類.
         */
        IProducer proxyProducer = (IProducer) Proxy.newProxyInstance(producer.getClass().getClassLoader(),
                producer.getClass().getInterfaces(),
                new InvocationHandler() {
                    /**
                     * @description:  執行被代理物件的任何介面方法都會經過該方法
                     * @Param proxy 代理物件的引用
                     * @Param method 表示當前執行的方法
                     * @Param args 當前執行方法所需的引數
                     * @return: java.lang.Object  和被代理物件方法有相同的返回值
                     */
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        //提供增強的方法
                        Object returnValue = null;
                        //1.獲取方法執行的引數
                        Float money = (Float)args[0];
                        //2.判斷當前方法是不是銷售
                        if("saleProduct".equals(method.getName())){
                            returnValue=method.invoke(producer,money*0.8f);
                        }
                        return returnValue;

                    }
                });
        proxyProducer.saleProduct(10000f);
    }
 public static void main(String[] args) {
    //匿名內部類訪問外部成員方法時需要使用final修飾
        final Producer producer = new Producer();
        /**
         * 動態代理:
         *  特點:位元組碼隨用隨建立,在不修改原始碼的基礎上對方法進行增強
         *  分類:基於介面的動態代理和基於子類的動態代理
         *
         *  基於子類的動態代理: 涉及的類Enhancer,提供者:第三方cglib庫
         *      如何建立代理物件:使用Enhancer類中的create方法
         *  建立代理物件的要求:被代理類不能是最終類
         *  create方法的引數:
         *   Class 位元組碼 用以指定被代理物件的位元組碼
         *   Callback 用於提供增強的程式碼 我們一般用的是該介面的實現類MethodInterceptor
         */

        Producer cglibProducer = (Producer) Enhancer.create(producer.getClass(), new MethodInterceptor() {
            /**
             * @description:  執行此物件的任何方法都會經過該方法
             * @Param obj  和invoke中的方法中的proxy是一樣的
             * @Param method
             * @Param args
             * @Param proxy  當前執行方法的代理物件
             * @return: java.lang.Object
             */
            public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
                //提供增強的方法
                Object returnValue = null;
                //1.獲取方法執行的引數
                Float money = (Float)args[0];
                //2.判斷當前方法是不是銷售
                if("saleProduct".equals(method.getName())){
                    returnValue=method.invoke(producer,money*0.8f);
                }
                return returnValue;
            }
        });

        cglibProducer.saleProduct(12000f);
    }

2.aop相關術語

1.Joinpoint 連線點  所謂連線點就是指那些被攔截到的點,在spring中,這些點指的是方法,因為spring只支援方法型別的連線點。
通俗的來說,這裡的連線點指的就是業務層的所有方法,包括被增強的方法和沒有被增強的方法。

2.Pointcut 切入點 所謂切入點是指我們要對哪些joinpoint進行攔截的定義。
通俗來說,這裡的切入點指的就是業務層被增強的那些方法。

3.Advice 通知/增強 所謂通知就是指攔截到joinpoint之後所要做的事情就是通知。
通知的型別:前置通知,後置通知,異常通知,最終通知,環繞通知。

4.Introduction 引介 引介是一種特殊的通知在不修改程式碼的前提下,introduction可以在執行期間為類動態地新增一些方法或者Field。

5.Target 目標物件 也就是代理的目標物件。

6.Weaving 織入 是指把增強應用到目標物件來創諂媚新的代理物件的過程。
spring採用動態代理織入,而AspectJ採用編譯器織入和類裝載期織入。

7.Proxy 代理 一個雷被aop織入增強後,就產生一個結果代理類。

8.Aspect 切面 是切入點和通知的結合。

3.學習spring aop我們必須明確的事情

a 開發階段(我們做的)
	編寫核心業務程式碼:大部分程式設計師來做,要求熟悉業務需求。
	把公用程式碼抽取出來,製作成通知。aop程式設計人員來做,一般是開發階段最後實現。
	在配置檔案中,宣告切入點與通知間的關係,及切面,一般是aop程式設計人員來做。
b 執行階段(spring框架幫我們完成的)
	spring框架監控切入點方法的執行,一旦監控到切入點方法被執行,使用代理機制,動態創諂媚目標物件的代理物件,根據通知型別,在代理物件的對應位置,將通知對應的功能織入,完成完整的程式碼邏輯執行。

4.關於代理的選擇

在spring中,框架會根據目標類是否實現了介面來決定採取哪種動態代理的方式。

5.基於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/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">
    <!--配置springioc 把service物件配置進來-->
    <bean id="accountService" class="com.service.impl.AccountServiceImpl"></bean>

    <!--
    spring中基於xml的aop配置步驟:
        1.把通知bean也交給spring來管理
        2.使用aop:config標籤標示開始aop的配置
        3.使用aop:aspect標籤表示開始配置切面
            id 是給切面提供一個唯一標識
            ref 是指定通知類bean的id
        4.在aop:aspect標籤的內部使用對應標籤來配置通知的型別
        我們現在事例方法在切入點方法執行之前執行,所以是前置通知
        pointcut用以指定切入點表示式,該表示式的含義指的是對業務層中哪些方法進行增強

        切入點表示式的寫法:
        關鍵字:execution
        表示式:  訪問修飾符 包名.包名.包名...類名.方法名(引數列表)
        標準寫法:public   com.service.impl.AccountServiceImpl.saveAccount()
            訪問修飾符可以省略
            返回值可以使用萬用字元,表示任意返回值
            * com.service.impl.AccountServiceImpl.saveAccount()
            包名可以使用萬用字元,表示任意包,但是幾級包需要寫幾個*
            包名可以使用..表示當前包及其子包
            類名和方法名都可以使用*來通配
            方法引數列表:基本資料型別直接寫名稱 int  引用資料型別寫包名.類名的方式 java.lang.String
        全通配寫法: * *..*.*(..)

        實際開發中,切入點表示式一般切到業務層實現類下的所有方法
        * com.xzy.service.impl.*.*(..)
    -->
    <bean id="logger" class="com.utils.Logger"></bean>

    <aop:config>
        <aop:aspect id="logAdvice" ref="logger">
            <!--
            前置通知 在切入點方法執行之前執行
            後置通知 在切入點方法正常執行之後執行
            異常通知 在切入點方法執行產生異常之後執行
            最終通知 無論切入點方法是否正常執行都在其後面執行

            後置通知和異常通知兩個只能執行其中的一個.
            -->
            <aop:before method="printLog" pointcut="execution(public void com.service.impl.AccountServiceImpl.saveAccount())"></aop:before>
            <aop:after-returning method="afterLog" pointcut="execution(public void com.service.impl.AccountServiceImpl.saveAccount())"></aop:after-returning>
            <aop:after-throwing method="exceptionLog" pointcut="execution(public void com.service.impl.AccountServiceImpl.saveAccount())"></aop:after-throwing>
            <aop:after method="finalLog" pointcut-ref="pt1"></aop:after>

            <!--配置切入點表示式,id指定表示式的唯一表示 expression用以指定表示式內容-->
            <aop:pointcut id="pt1" expression="execution(* com.service.impl.*.*(..))"/>

            <!--配置環繞通知-->

        </aop:aspect>
    </aop:config>
</beans>

6.基於註解實現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"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd
        https://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">
    <!--配置要掃描的包-->
    <context:component-scan base-package="com"></context:component-scan>

    <!--配置spring開啟註解aop的支援-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
@Component("logger")
@Aspect//表示當前類是一個切面類
public class Logger {
    /** 
     * @description: 用以列印日誌,計劃讓其在切入點方法之前執行,切入點方法啊就是業務層方法 
     * @Param 
     * @return: void 
     */ 
    public void printLog(){
        System.out.println("log類中的日誌類開始記錄日誌了");
    }

    @Pointcut("execution(* com.service.impl.*.*(..))")
    private void pt1(){}


    @Before("pt1()")
    public void beforeLog(){
        System.out.println("前置通知");
    }

    @AfterReturning("pt1()")
    public void afterLog(){
        System.out.println("後置通知");
    }

    @AfterThrowing("pt1()")
    public void exceptionLog(){
        System.out.println("異常通知");
    }

    @After(("pt1()"))
    public void finalLog(){
        System.out.println("最終通知");
    }

    /**
     * @description: 環繞通知
     * 會出現問題:當我們配置了環繞通知之後,切入點方法沒有執行,而通知方法執行了
     * 分析:通過對比動態代理中的環繞通知程式碼分析,動態代理中的環繞通知有明確的切入點方法呼叫,而我們的程式碼中沒有.
     * 解決:spring框架為我們提供了一個介面:ProceedingJoinPoint,該介面有一個方法proceed(),此方法就相當於明確呼叫切入點方法.
     *      該介面可以作為環繞通知的方法引數,在程式執行時,spring框架回味我們提供該介面的實現供我們使用.
     * @Param
     * @return: void
     */
    //@Around("pt1()")
    public Object aroundLog(ProceedingJoinPoint pjp){
        //明確呼叫切入點方法
        Object rtValue = null;
        try{
            Object[] args = pjp.getArgs();
            System.out.println("環繞通知 前置");//這裡就成前置通知
            pjp.proceed(args);
            System.out.println("環繞通知 後置");//這裡就成了後置通知
            return rtValue;
        }catch(Throwable t){
            System.out.println("環繞通知 異常");//這裡就成了異常通知
            throw new RuntimeException("執行時異常");
        }finally {
            System.out.println("環繞通知 最終");//這裡就成了最終通知
        }
    }
}

5.spring的事務控制

關於spring事務控制我們要明確的:
1.JavaEE體系進行分層開發,spring提供了分層設計業務層的事務處理解決方案。
2.spirng框架為我們提供了一組事務控制的介面。
3.spring的事務控制都是基於aop的,可以通過程式設計的方式實現,也可以使用配置的方式實現,我們希望通過配置的方式實現。

基於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:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.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">

    <!-- 配置業務層-->
    <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
        <property name="accountDao" ref="accountDao"></property>
    </bean>

    <!-- 配置賬戶的持久層-->
    <bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl">
        <property name="dataSource" ref="dataSource"></property>
    </bean>


    <!-- 配置資料來源-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/eesy"></property>
        <property name="username" value="root"></property>
        <property name="password" value="1234"></property>
    </bean>

    <!-- spring中基於XML的宣告式事務控制配置步驟
        1、配置事務管理器
        2、配置事務的通知
                此時我們需要匯入事務的約束 tx名稱空間和約束,同時也需要aop的
                使用tx:advice標籤配置事務通知
                    屬性:
                        id:給事務通知起一個唯一標識
                        transaction-manager:給事務通知提供一個事務管理器引用
        3、配置AOP中的通用切入點表示式
        4、建立事務通知和切入點表示式的對應關係
        5、配置事務的屬性
               是在事務的通知tx:advice標籤的內部

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

    <!-- 配置事務的通知-->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <!-- 配置事務的屬性
                isolation:用於指定事務的隔離級別。預設值是DEFAULT,表示使用資料庫的預設隔離級別。
                propagation:用於指定事務的傳播行為。預設值是REQUIRED,表示一定會有事務,增刪改的選擇。查詢方法可以選擇SUPPORTS。
                read-only:用於指定事務是否只讀。只有查詢方法才能設定為true。預設值是false,表示讀寫。
                timeout:用於指定事務的超時時間,預設值是-1,表示永不超時。如果指定了數值,以秒為單位。
                rollback-for:用於指定一個異常,當產生該異常時,事務回滾,產生其他異常時,事務不回滾。沒有預設值。表示任何異常都回滾。
                no-rollback-for:用於指定一個異常,當產生該異常時,事務不回滾,產生其他異常時事務回滾。沒有預設值。表示任何異常都回滾。
        -->
        <tx:attributes>
            <tx:method name="*" propagation="REQUIRED" read-only="false"/>
            <tx:method name="find*" propagation="SUPPORTS" read-only="true"></tx:method>
        </tx:attributes>
    </tx:advice>

    <!-- 配置aop-->
    <aop:config>
        <!-- 配置切入點表示式-->
        <aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"></aop:pointcut>
        <!--建立切入點表示式和事務通知的對應關係 -->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"></aop:advisor>
    </aop:config>

    
</beans>

基於註解的事務控制

<!-- 配置事務管理器 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
<!--開啟spring對註解事務的支援-->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
然後在需要事務支援的地方加@Transaction註解即可