Spring源碼分析

Spring介紹

什麼是Spring?

百度百科的介紹java

 

Spring官方網址: http://spring.io/mysql

咱們常常說的Spring其實指的是 Spring Framework (Spring 框架)程序員

爲何學習Spring?

 

 好處

耦合和內聚介紹

  耦合性(Coupling),也叫耦合度,是對模塊間關聯程度的度量。web

  在軟件工程中,耦合指的就是對象之間的依賴性。對象之間的耦合越高,維護成本越高。所以對象的設計應使類與架構之間的耦合最小。軟件設計中一般用耦合度和內聚度做爲衡量模塊獨立程度的標準。劃分模塊的一個準則就是高內聚低耦合。spring

  內聚標誌一個模塊內各個元素彼此結合的緊密程度,它是信息隱蔽和局部化概念的天然擴展。內聚是從功能角度來度量模塊內的聯繫,一個好的內聚模塊應當適當作一件好事。sql

  內聚和耦合是密切相關的,同其餘模塊存在高耦合的模塊意味着低內聚,而高內聚的模塊意味着該模塊同其餘模塊之間是低耦合。在進行軟件設計時,應力爭作到高內聚,低耦合數據庫

Spring體系結構

 

 Spring核心概念介紹

  IoC(核心中的核心):Inverse of Control,控制反轉。對象的建立權力由程序反轉給Spring框架。express

  AOP:Aspect Oriented Programming,面向切面編程。在不修改目標對象的源代碼狀況下,加強IoC容器中Bean的功能。apache

  DI:Dependency Injection,依賴注入。在Spring框架負責建立Bean對象時,動態的將依賴對象注入到Bean組件中!!編程

  Spring容器:指的就是IoC容器。

 Spring IoC原理分析

什麼是IoC容器?

  所謂的IoC容器就是指的是Spring中Bean工廠裏面的Map存儲結構(存儲了Bean的實例)。

Spring框架中的工廠有哪些?

  ApplicationContext接口()

    實現了BeanFactory接口

    實現ApplicationContext接口的工廠,能夠獲取到容器中具體的Bean對象

  BeanFactory工廠(是Spring架構早期的建立Bean對象的工廠接口)

    實現BeanFactory接口的工廠也能夠獲取到Bean對象

其實經過源碼分析,無論是BeanFactory仍是ApplicationContext,其實最終的底層BeanFactory都是DefaultListableBeanFactory

  ApplicationContext和BeanFactory的區別?

  建立Bean對象的時機不一樣:

    BeanFactory採起延遲加載,第一次getBean時纔會初始化Bean。

    ApplicationContext是加載完applicationContext.xml時,就建立具體的Bean對象的實例。(只對BeanDefition中描述爲時單例的Bean,才進行餓漢堡式加載)

 

 

 如何建立Web環境中的IoC容器?

建立方式

  • ApplicationContext接口經常使用實現類

    ClassPathXmlApplicationContext:

      它是從類的根路徑下加載配置文件  推薦使用這種

    FileSystemXmlApplicationContext:

      它是從磁盤路徑上加載配置文件,配置文件能夠在磁盤的任意位置。

    AnnotationConfigApplicationContext:

      當咱們使用註解配置容器對象時,須要使用此類來建立Spring容器。它用來讀取註解。

  • Java應用中建立IoC容器:(瞭解)

    ApplicationContext context=new ClassPathXmlApplicationContext(xml路徑);

  • Web應用中建立IoC容器:(重點)

    web.xml中配置ContextLoaderListener接口,並配置ContextConfigLocation參數

    web容器啓動以後加載web.xml,此時加載ContextLoaderListener監聽器(實現了ServletContextListener接口,該接口的描述請見下面的《三類八種監聽器》)

    ContextLoaderListener監聽器會在web容器啓動的時候,出發ContextInitialized()方法

    ContextInitialized()方法會調用initWebApplicationContext()方法,該方法負責建立Spring容器(DefaultListableBeanFactory)

【Web三類八種監聽器】

  監聽域對象的生命週期

    ServletContextListener:

      建立:服務器啓動

      銷燬:服務器正常關閉

      spring ContextLoaderListener(服務器啓動時負責加載spring配置文件)

     HttpSessionListener

      建立:第一次訪問request.getHttpSession()

      銷燬:調用invalidate();非法關閉;過時

    ServletRequestListener

      建立:每一次訪問

      銷燬:相應結束

  監聽域對象的屬性:(添加、刪除、替換)

    ServletContextAttributeListener

    HttpSessionAttributeListener

    ServletRequestAttributeListener

  監聽HttpSession中JavaBean的改變:

    HttpSessionBindingListener(HttpSession和JavaBean對象的綁定和解綁)

    HttpSessionActivationListener(HttpSession的序列化,活化,純化)

源碼分析

參考資料中的源碼中的工程《Spring-sourcecode》

1.web服務器(tomcat)啓動會加載web.xml(啓動ContextLoaderListener監聽器):

 

 2.建立web環境中的Spring容器

 

 3.ContextLoader類中建立Spring容器並初始化容器中的Bean實例

 

 4.configureAndRefreshWebApplicationContext方法中調用初始化Bean的refresh方法

 

 圖示

該圖主要是分析上面第三步驟中【建立Spring容器】的圖示

 

 IoC容器如何建立Bean對象?

源碼分析

源碼來源於AbstractApplicationContext類:

@Override
    public void refresh() throws BeansException, IllegalStateException {
        synchronized (this.startupShutdownMonitor) {
            // Prepare this context for refreshing.
            prepareRefresh();

              //1.建立真正的Spring容器(DefaultListableBeanFactory)
              //2.加載BeanDefition(描述要初始化的Bean的信息)
              //3.將BeanDefition註冊到BeanDefitionRegistry
            // Tell the subclass to refresh the internal bean factory.
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

            // Prepare the bean factory for use in this context.
            prepareBeanFactory(beanFactory);

            try {
                // Allows post-processing of the bean factory in context subclasses.
                postProcessBeanFactory(beanFactory);

                  //執行實現了BeanFactoryPostProcessor接口的Bean
                  //好比PropertyPlaceHolderConfigurer(context:property-placeholer)就是此處被調用的,替換掉BeanDefition中的佔位符(${})中的內容
                // Invoke factory processors registered as beans in the context.
                invokeBeanFactoryPostProcessors(beanFactory);

                  //註冊BeanPostProcessor(後置處理器)
                  //好比容器自動裝載了一個AutowiredAnnotationBeanPostProcessor後置處理器(實現@Autowired註解功能)
                // Register bean processors that intercept bean creation.
                registerBeanPostProcessors(beanFactory);

                // Initialize message source for this context.
                initMessageSource();

                // Initialize event multicaster for this context.
                initApplicationEventMulticaster();

                // Initialize other special beans in specific context subclasses.
                onRefresh();

                // Check for listener beans and register them.
                registerListeners();

                  //初始化非懶加載方式的單例Bean實例
                // Instantiate all remaining (non-lazy-init) singletons.
                finishBeanFactoryInitialization(beanFactory);

                // Last step: publish corresponding event.
                finishRefresh();
            }

            catch (BeansException ex) {
                if (logger.isWarnEnabled()) {
                    logger.warn("Exception encountered during context initialization - " +
                            "cancelling refresh attempt: " + ex);
                }

                // Destroy already created singletons to avoid dangling resources.
                destroyBeans();

                // Reset 'active' flag.
                cancelRefresh(ex);

                // Propagate exception to caller.
                throw ex;
            }

            finally {
                // Reset common introspection caches in Spring's core, since we
                // might not ever need metadata for singleton beans anymore...
                resetCommonCaches();
            }
        }
    }

圖示

 

Spring 容器初始化源碼分析

容器初始化主流程分析

主流程入口

ApplicationContext context = new ClassPathXmlApplicationContext(「spring.xml」)

ClassPathXmlApplicationContext類:重載的構造方法依次調用,進入下面代碼

 

AbstractApplicationContextrefresh方法:初始化spring容器的核心代碼

@Override
    public void refresh() throws BeansException, IllegalStateException {
        synchronized (this.startupShutdownMonitor) {
            //一、 Prepare this context for refreshing.
            prepareRefresh();

               //建立DefaultListableBeanFactory(真正生產和管理bean的容器)
               //加載BeanDefition並註冊到BeanDefitionRegistry
               //經過NamespaceHandler解析自定義標籤的功能(好比:context標籤、aop標籤、tx標籤)
            //二、 Tell the subclass to refresh the internal bean factory.
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

            //三、 Prepare the bean factory for use in this context.
            prepareBeanFactory(beanFactory);

            try {
                //四、 Allows post-processing of the bean factory in context subclasses.
                postProcessBeanFactory(beanFactory);

                     //實例化並調用實現了BeanFactoryPostProcessor接口的Bean
                     //好比:PropertyPlaceHolderConfigurer(context:property-placeholer)
                     //就是此處被調用的,做用是替換掉BeanDefinition中的佔位符(${})中的內容
                //五、 Invoke factory processors registered as beans in the context.
                invokeBeanFactoryPostProcessors(beanFactory);

                     //建立並註冊BeanPostProcessor到BeanFactory中(Bean的後置處理器)
                     //好比:AutowiredAnnotationBeanPostProcessor(實現@Autowired註解功能)
                     //      RequiredAnnotationBeanPostProcessor(實現@d註解功能)
                     //這些註冊的BeanPostProcessor
                //六、 Register bean processors that intercept bean creation.
                registerBeanPostProcessors(beanFactory);

                //七、 Initialize message source for this context.
                initMessageSource();

                //八、 Initialize event multicaster for this context.
                initApplicationEventMulticaster();

                //九、 Initialize other special beans in specific context subclasses.
                onRefresh();

                //十、 Check for listener beans and register them.
                registerListeners();

                     //建立非懶加載方式的單例Bean實例(未設置屬性)
                     //填充屬性
                     //初始化實例(好比調用init-method方法)
                     //調用BeanPostProcessor(後置處理器)對實例bean進行後置處理
                //十一、 Instantiate all remaining (non-lazy-init) singletons.
                finishBeanFactoryInitialization(beanFactory);

                //十二、 Last step: publish corresponding event.
                finishRefresh();
            }

            catch (BeansException ex) {
                if (logger.isWarnEnabled()) {
                    logger.warn("Exception encountered during context initialization - " +
                            "cancelling refresh attempt: " + ex);
                }

                // Destroy already created singletons to avoid dangling resources.
                destroyBeans();

                // Reset 'active' flag.
                cancelRefresh(ex);

                // Propagate exception to caller.
                throw ex;
            }

            finally {
                // Reset common introspection caches in Spring's core, since we
                // might not ever need metadata for singleton beans anymore...
                resetCommonCaches();
            }
        }
    }

圖示

 

建立BeanFactory流程分析

 獲取新的BeanFactory子流程

子流程入口(從主流程refresh方法中的第二步開始)

 

 調用AbstractApplicationContext中的obtainFreshBeanFactory方法

 

調用AbstractRefreshableApplicationContext的refreshBeanFactory方法 

 

 加載解析BeanDefinition子流程(loadDefinitions方法)

源碼分析

子流程入口(AbstractRefreshableApplicationContext類的方法)

 

 此處依次調用多個類的loadBeanDefinitions方法(AbstractXmlApplicationContextà AbstractBeanDefinitionReaderà XmlBeanDefinitionReader),一直調用到XmlBeanDefinitionReader 類的doLoadBeanDefinitions方法

 

對於doLoadDocument方法不是咱們關注的重點,咱們進入到該類的registerBeanDefinitions方法看看

此處有兩個地方是咱們關注的:一個createRederContext方法,一個是DefaultBeanDefinitionDocumentReader類的registerBeanDefinitions方法,先進入createRederContext方法看看

至此,14個NamespaceHandlerResolver初始化成功。而後咱們再進入DefaultBeanDefinitionDocumentReader類的registerBeanDefinitions方法

繼續進入到該類的doRegisterBeanDefinitions方法看看,這是真正幹活的方法

繼續進入parseBeanDefinitions方法

咱們看到有兩種解析方案,先看看parseDefaultElement方法

不過咱們重點看看BeanDefinitionParserDelegate類的parseCustomElement方法(AOP標籤、tx標籤的解析都是在該步驟中完成的

getNamespaceURI方法的做用一目瞭然,咱們就不去追蹤了,接下來咱們進入DefaultNamespaceHandlerResolver類的resolve方法看看:

 

在上面代碼中,咱們看到了一行代碼:namespaceHandler.init();這個方法是很重要的。它實現了自定義標籤處處理類的註冊工做,不過NamespaceHandler是一個接口,具體的init方法須要不一樣的實現類進行實現,咱們經過AopNamespaceHandler瞭解一下init的做用,其中aop:config標籤是由ConfigBeanDefinitionParser類進行處理

 

至此咱們瞭解到了xml中的aop標籤都是由哪些類進行處理的了不過init方法只是註冊了標籤和處理類的對應關係那麼何時調用處理類進行解析的呢咱們再回到BeanDefinitionParserDelegate類的parseCustomElement方法看看

咱們看到,最後一行執行了parse方法,那麼parse方法,在哪呢?咱們須要到NamespaceHandlerSupport類中去看看,它是實現NamespaceHandler接口的,而且AopNamespaceHandler是繼承了NamespaceHandlerSupport類,那麼該方法也會繼承到AopNamespaceHandler類中。

  至此,整個XML文檔的解析工做,包括bean標籤以及自定義標籤如何解析爲BeanDefinition信息的過程咱們已經瞭解了

  後續具體想了解哪一個自定義標籤的處理邏輯,能夠自行去查找xxxNamespaceHandler類進行分析。

圖示

 

建立Bean流程分析 

子流程入口

咱們進入finishBeanFactoryInitialization方法看看

繼續進入DefaultListableBeanFactory類的preInstantiateSingletons方法,咱們找到下面部分的代碼,看到工廠Bean或者普通Bean,最終都是經過getBean的方法獲取實例的。

繼續跟蹤下去,咱們進入到了AbstractBeanFactory類的doGetBean方法,這個方法中的代碼不少,咱們直接找到核心部分

 

接着進入到AbstractAutowireCapableBeanFactory類的方法找到如下代碼部分

咱們終於找到核心的地方了,進入doCreateBean方法看看,該方法咱們關注兩塊重點區域

 

 

對於如何建立Bean的實例,和填充屬性,暫時先不去追蹤了,咱們先去看看initializeBean方法是如何調用BeanPostProcessor的,由於這個牽扯到咱們對於AOP動態代理的理解。

 Spring Ioc基於XML的使用

建立工程

環境準備

  • maven
  • jdk
  • spring
  • Eclipse

工程搭建

POM文件

<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.kkb</groupId>
    <artifactId>spring</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <dependencies>
        <!-- spring 核心組件中的4個依賴 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>5.0.7.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.7.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>5.0.7.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-expression</artifactId>
            <version>5.0.7.RELEASE</version>
        </dependency>

         <!-- 單元測試Junit -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <!-- 配置Maven的JDK編譯級別 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.2</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

Spring配置文件(只編寫配置文件頭)

<?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">

</beans>

具體實現

  在Spring的XML配置文件中配置一個bean標籤,該標籤最終會被加載爲一個BeanDefition對象(描述對象信息)

思路:

  編寫UserService接口的實現類

  將UserService實現類交給Spring IoC容器管理

  從Spring IoC容器中獲取UserService實現類

編寫接口:UserService

 

 

 編寫實現類:UserServiceImpl

 

 編寫XML文件:applicationContext.xml

 

 編寫單元測試代碼:TestSpring

 

bean標籤詳解

bean標籤做用:

  用於配置對象讓Spring來建立

  默認狀況下它調用的是類中的無參構造函數。若是沒有無參構造函數則不能建立成功。

bean標籤屬性:

  id:給對象在容器中提供一個惟一標識。用於獲取對象。

  class:指定類的全限定類名。用於反射建立對象。默認狀況下調用無參構造函數

  scope:指定對象的做用範圍。

    singleton默認值,單例的(在整個容器中只有一個對象)。

    prototype:多例的。

    request:web項目中,Spring建立一個Bean的對象,將對象存入到request域中。

    session:web項目中,Spring建立一個Bean的對象,將對象存入到session域中。

    global session:web項目中,應用在Portlet環境,若是沒有Portlet環境那麼globalSession至關於session。

    init-method:指定類中的初始化方法名稱。

    destroy-method:指定類中銷燬方法名稱。好比DataSource的配置中通常須要指定destroy-method="close"。

bean的做用範圍:

  單例對象:scope="singleton"

    一個應用只有一個對象的實例。它的做用範圍就是整個應用

    生命週期:

      對象出生:當應用加載,建立容器時,對象就被建立了。

      對象活着:只要容器在,對象一直活着。

      對象死亡:當應用卸載,銷燬容器時,對象就被銷燬了。

  多例對象:scope="prototype"

    每次訪問對象時,都會從新建立對象實例。

    生命週期:

      對象出生:當使用對象時,建立新的對象實例。

      對象活着:只要對象在使用中,就一直活着。

      對象死亡:當對象長時間不用時,被java的垃圾回收期回收了。

實例化bean的三種方式

  第一種:使用默認無參構造函數(重點)

    在默認狀況下:它會根據默認無參構造函數來建立類對象。

    若是bean中沒有默認無參構造函數,將會建立失敗

<bean id="userService" class="com.chenyanbin.spring.service.UserServiceImpl">

  第二種:靜態工廠(瞭解)

/**
* 模擬一個靜態工廠,建立業務層實現類
*/
public class StaticFactory {
  public static UserService createUserService(){
    return new UserServiceImpl();
  }
}
<!--此種方法時:  
使用StaticFactory類中的靜態方法createUserService建立對象,並存入Spring容器
id屬性:指定bean的id,用於從容器中獲取
class屬性:指定靜態工廠的全限定類名
factory-method屬性:指定生產對象的靜態方法
-->
<bean id="userService" class="com.chenyanbin.spring.factory.StaticFactory" factory-method="createUserService"></bean>

  第三種:實例工廠(瞭解)

/**
*    模擬一個實例工廠,建立業務層實現類
*    此工廠建立對象,必須現有工廠實例對象,再調用方法
*/
public class InstanceFactory {
    public UserService createUserService(){
        return new UserServiceImpl();
    } 
}
<!--
此種方式是:
    先把工廠的建立交給spring來管理。
    而後在使用工廠的bean來調用裏面的方法
        factory-ben屬性:用於指定實例工廠bean的id。
        factory-method屬性:用於指定實例工廠中建立對象的方法。
-->
<bean id="instanceFactory" class="com.chenyanbin.factory.InstanceFactory"></bean>
<bean id="userService" factory-bean="instanceFactory" factory-method="createUserService"></bean>

Spring DI(依賴注入)介紹

概述

  什麼是依賴?

    依賴指的就是Bean實例中的屬性

    屬性分爲:簡單類型(8種基本類型和String類型)的屬性、POJO類型的屬性、集合數組類型的屬性。

  什麼是依賴注入

    依賴注入:Dependency Injection。它是Spring框架核心IoC的具體實現。

  爲何要進行依賴注入?

    咱們的程序在編寫時,經過控制反轉,把對象的建立交給了Spring,可是代碼中不可能出現沒有依賴的狀況。

    IoC解耦只是下降他們的依賴關係,但不會消除。例如:咱們的業務層仍會調用持久層的方法。那這種業務層和持久層的依賴關係,在使用Spring以後,就讓Spring來維護了。

    簡單的說,就是坐等框架把持久層對象傳入業務層,而不用咱們本身去獲取。

依賴注入的方式(基於XML)

 構造函數注入

  顧名思義,就是使用類中的構造函數,給成員變量賦值。

  注意,賦值的操做不是咱們本身作的,而是經過配置的方式,讓Spring框架來爲咱們注入。

  具體代碼以下:

public class UserServiceImpl implements UserService {
    private int id;
    private String name;
    
    public UserServiceImpl(int id, String name) {
        this.id = id;
        this.name = name;
    }
    @Override
    public void saveUser() {
        System.out.println("保存用戶:id爲"+id+",name爲"+name+"   Service實現");
    }
}
<!--使用構造函數的方式,給Service中的屬性傳值要求:類中須要提供一個對應參數列表的構造函數。
        涉及的標籤:constructor-arg
            index:指定參數在構造函數參數列表的索引位置
            name:指定參數在構造函數中的名稱
            value:它能賦的值是基本數據類型和String類型
            ref:他能賦的值是其餘bean類型,也就是說,必須得是在配置文件中配置過的bean
--> 
<bean id="userService" class="com.chenyanbin.spring.service.UserServiceImpl">
    <constructor-arg name="id" value="1"></constructor-arg>
    <constructor-arg name="name" value="zhangsan"></constructor-arg>
</bean>

set方法注入(重點)

  set方法注入又分爲手動裝配方式注入自動裝配方式注入

    手動裝配方式(XML方式):bean標籤的子標籤property須要在類中指定set方法

    自動裝配方式(註解方式):@Autowired註解、@Resource註解。

      @Autowired:一部分功能是查詢實例,從spring容器中根據類型(java類)獲取對應的實例。另外一部分功能就是賦值,將找到的實例,裝配和另外一個實例的屬性值。(注意事項:一個java類型在同一個spring容器中,只能有一個實例)

      @Resource:一部分功能是查詢實例,從spring容器中根據Bean的名稱(Bean標籤的名稱)獲取對應的實例。另外一部分功能就是賦值,將找到的實例,裝配給另外一個實例的屬性值。

  使用p名稱空間注入數據(本質上仍是調用set方法)

1.步驟一:須要先引入p名稱空間

  在schema的名稱空間中加入該行:

xmlns:p="http://www.springframework.org/schema/p"

2.步驟二:使用p名稱空間的語法

p:屬性名=""
p:屬性名-ref=""

3.步驟三:測試

<bean id="person" class="com.chenyanbin.spring.demo.Person" p:pname="隔壁老王" p:car2-ref="car2" />
<bean id="car2" class="com.chenyanbin.spring.demo.Car2" />

依賴注入不一樣類型的屬性(基於XML)

簡單類型(value)

<bean id="userService" class="com.chenyanbin.spring.service.UserServiceImpl">
    <property name="id" value="1"></property>
    <property name="name" value="zhangsan"></property>
</bean>

引用類型(ref)

ref就是reference的縮寫,是引用的意思

<bean id="userService" class="com.chenyanbin.spring.service.UserServiceImpl">
    <property name="userDao" ref="userDao"></property>
</bean>
<bean id="userDao" class="com.chenyanbin.spring.dao.UserDaoImpl"></bean>

集合類型(數組)

1.若是是數組或者List集合,注入配置文件的方式是同樣的

<bean id="collectionBean" class="com.chenyanbin.demo5.CollectionBean">
    <property name="arrs">
        <list>
<!--若是集合內是簡單類型,使用value子標籤,若是是POJO類型,則使用bean標籤-->
            <value>張三</value>
            <value>李四</value>
            <value>王五</value>
        </list>
    </property>
</bean>

2.若是是Set集合,注入的配置文件方式以下:

<property name="sets">
    <set>
<!--若是集合內是簡單類型,使用value子標籤,若是是POJO類型,則使用bean標籤-->
        <value>哈哈</value>
        <value>喜喜</value>
    </set>
</property>

3.若是是Map集合,注入的配置方式以下

<property name="map">
    <map>
        <entry key="老王" value="18" />
        <entry key="王五" value="19" />
    </map>
</property>

4.若是是Properties集合的方式,注入的配置以下:

<property name="pro">
    <props>
        <prop key="userName">root</prop>
        <prop key="userPassword">123</prop>
    </props>
</property> 

練習demo

Spring IoC和DI基於註解使用

  學習基於註解的IoC配置,你們腦海裏首先得有一個認識,即註解配置和xml配置要實現的功能都是同樣的,都是要下降程序間的耦合。只是配置的形式不同。

  關於實際的開發中到底使用xml仍是註解,每家公司有着不一樣的使用習慣。因此這兩種配置方式都須要掌握

  在講解註解配置時,採用上一章節的案例,把Spring的xml配置內容改成使用註解逐步實現。

IoC註解使用

第一步:Spring配置文件中,配置context:component-scan標籤

 

 第二步:類上面加上註解@Component,或者它的衍生註解@Controller、@Service、@Repository

 

 經常使用註解

IoC註解(建立對象)

至關於:<bean id="" class=""></bean>

@Component註解

做用:
    把資源讓Spring來管理。至關於在XML中配置一個bean。
屬性:
    value:指定bean的id。
若是不指定value屬性,默認bean的id時當前類的類型。首字母小寫

@Controller、@Service、@Repository註解

他們三個註解都是針對@Component的衍生註解
他們的做用及屬性都是一摸同樣的。他們只不過是提供了更加明確的語義化。
    @Controller:通常用於表現層的註解。
    @Service:通常用於業務層的註解。
    @Repository:通常用於持久層的註解。
細節:若是註解中有且只有一個屬性要賦值時,且名稱是value,value在賦值是能夠不寫。

 DI註解(依賴注入)

至關於:<property name="" ref="">

@Autowired

  默認按類型裝配(byType)

  這個註解是spring自身的

  默認狀況下必需要求依賴對象必須存在,若是要容許null值,能夠設置它的required屬性爲false,如:@Autowired(required=false)

  如何咱們想使用名稱裝配能夠結合@Qualifier註解進行使用

@Qualifier

  在自動按照類型注入的基礎之上,再按照Bean的id注入。

  它在給字段注入時不能獨立使用,必須和@Autowire一塊兒使用;可是給方法參數注入時,能夠單獨使用。

@Resource

  默認按照名稱(byName)進行裝配,名稱能夠經過name屬性進行指定,若是沒有指定name屬性,當註解寫在字段上時,默認取字段名進行按照名稱查詢,當找不到域名稱匹配的bean時才按照類型進行裝配。

  可是須要注意的是,若是name屬性一旦指定,就只會按照名稱進行裝配。

推薦使用@Resource註解,由於這個註解是屬於J2EE的,減小了與spring的耦合。這樣代碼看起來就比較優雅。

至關於:<property name="" value="">

@Value

  給基本類型和String類型注入值

  可使用佔位符獲取屬性文件中的值。

@Value("${name}") //name是properties文件中的key

用例

UserServiceImpl.java

    @Value("${id}")
    private int id;
    
    @Override
    public void saveUser() {
        // TODO Auto-generated method stub
        System.out.println("IoC演示之UerService:"+id);
    }

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">
    <!-- 組件掃描器,主要是spring使用,用來掃描帶有指定註解的類,將這些加載成BeanDefinition -->
    <context:component-scan
        base-package="com.cyb.spring.service" />
        <!-- 佔位符,location:路徑 -->
        <context:property-placeholder location="classpath:data.properties"/>
</beans>

data.properties

id=123

改變做用範圍

@Scope

至關於:<bean id="" class="" scope="">

做用:

  指定bean的做用範圍

屬性:

  value:指定範圍的值

    取值:singleton prototype request session globalsession

和生命週期相關

至關於:<bean id="" class="" init-method="" destroy-method="">

@PostConstruct和@PreDestroy

關於註解和XML的選擇問題

  註解的優點:

    配置簡單,維護方便(咱們找到類,就至關於找到了對應的配置)。

  XML的優點:

    修改時,不用改源碼。不涉及從新編譯和部署。

  Spring管理Bean方式的比較

 

 

 Spring的純註解配置

  到這裏,基於註解的IoC配置已經完成,可是你們都發現了一個問題:咱們依然離不開spring的xml配置文件,那麼能不能不寫這個applicationContext.xml,全部配置都用註解來實現呢?

  須要注意如下,咱們選擇那種配置的原則是簡化開發和配置方便,而非追求某種技術。

待改造的問題

  想想能不能將如下這些bean的配置都從xml中去掉,而且最終將xml也去掉。

  若是能夠,那麼咱們就能夠脫離xml配置了。

  • 註解掃描配置(能不能去掉)
<!--開啓註解並掃描指定包中帶有註解的類-->
<context:component-scan base-package="com.chenyanbin.spring.service" />
  • 非自定義的Bean配置(好比:SqlSessionFactory和BasicDataSource配置)
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" value="dataSource"></property>
</bean>
  • 去掉XML後,如何建立ApplicationContext
以前建立ApplicationContext都是經過讀取XML文件進行建立的。
ApplicationContext context=new ClassPathXmlApplicationContext("beans.xml");

新的註解

  @Configuration

  介紹:

    從Spring3.0,@Configuration用於定義配置類,可替換XML配置文件

    至關於<beans>根標籤

    配置類內部包含有一個或多個被@Bean註解的方法,這些方法將會被AnnotationConfigApplicationContextAnnotationConfigWebApplicationContext類進行掃描,並用於構建bean定義,初始化Spring容器。

  屬性:

    value:用於指定配置類的字節碼

  示例代碼:

    @Configuration
    public class SpringConfiguration {
        //spring容器初始化時,會調用配置類的無參構造函數
        public SpringConfiguration(){
            System.out.println(「容器啓動初始化。。。」);
        }
    }

  @Bean

  介紹:

    @Bean標註在方法上(返回某個實例的方法),等價於spring配置文件中的<bean>

  做用:

    註冊bean對象

    主要用來配置非自定義的bean,好比DruidDataSource、SqlSessionFactory

  屬性:

    name:給當前@Bean註解方法建立的對象指定一個名稱(即bean的id)。

    若是不指定,默認與標註的方法名相同

    @Bean註解默認做用域爲單例singleton做用域,可經過@Scope("prototype")設置爲原型做用域;

  示例代碼:

public class SpringConfiguration {
        //spring容器初始化時,會調用配置類的無參構造函數
        public SpringConfiguration(){
            System.out.println(「容器啓動初始化。。。」);
    }
    @Bean
    @Scope(「prototype」)
    public UserService userService(){
        return new UserServiceImpl(1,「張三」);
    }
}

  @ComponentScan

  介紹:

    至關於context:component-scan標籤

    組件掃描器,掃描@Component、@Controller、@Service、@Repository註解的類。

    該註解是編寫在類上面的,通常配合@Configuration註解一塊兒使用。

  屬性:

    basePackages:用於指定要掃描的包

    value:和basePackages做用同樣

  示例代碼:

  Bean類(Service類):

@Service
public class UserServiceImpl implements UserService {
    @Override
    public void saveUser() {
        System.out.println("保存用戶   Service實現");
    }
}

  配置類:

@Configuration
@ComponentScan(basePackages="com.kkb.spring.service")
public class SpringConfiguration {

    public SpringConfiguration() {
        System.out.println("容器初始化...");
    }
    
//    @Bean
//    @Scope("prototype")
//    public UserService userService() {
//        return new UserServiceImpl(1,"張三");
//    }
}

  @PropertySource

  介紹

    加載properties配置文件

    編寫在類上面

    至關於 context:property-placeholder 標籤

  屬性

    value[]:用於指定properties文件路徑,若是在類路徑下,須要寫上classpath

  示例代碼

配置類:

@Configuration
@PropertySource(「classpath:jdbc.properties」)
public class JdbcConfig {
    @Value("${jdbc.driver}")
    private String driver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String username;
    @Value("${jdbc.password}")
    private String password;

/**
*建立一個數據源,並存入 spring 容器中
*@return
*/
@Bean(name="dataSource")
public DataSource createDataSource() {
    try {
        ComboPooledDataSource ds = new ComboPooledDataSource();                     
        ds.setDriverClass(driver);
        ds.setJdbcUrl(url); 
        ds.setUser(username); 
        ds.setPassword(password); 
        return ds;
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
    }
}  

properties文件:

jdbc.driver=com.mysql.jdbc.Driver 
jdbc.url=jdbc:mysql:///spring
jdbc.username=root 
jdbc.password=root

  問題:

    當系統中有多個配置類時怎麼辦?想想以前使用XML配置的時候時如何解決該問題的。

  @Import

  介紹

    用來組合多個配置類

    至關於spring配置文件中的Import標籤

    在引入其餘配置類時,能夠不用再寫@Configuration註解。當前,寫上也沒問題。

  屬性

    value:用來指定其餘配置類的字節碼文件

示例代碼

@Configuration
@ComponentScan(basePackages = "com.kkb.spring")
@Import({ JdbcConfig.class})
public class SpringConfiguration {
}


@Configuration
@PropertySource("classpath:jdbc.properties")
    public class JdbcConfig{
}

  經過註解獲取容器

  • Java應用(AnnotationConfigApplicationContext)
ApplicationContext context = new    AnnotationConfigApplicationContext(SpringConfiguration.class);
UserService service = context.getBean(UserService.class);
service.saveUser();
  • Web應用(AnnotationConfigWebApplicationContext)
<web-app>
    <context-param>
        <param-name>contextClass</param-name>
        <param-value>
            org.springframework.web.context.
            support.AnnotationConfigWebApplicationContext
        </param-value>
    </context-param>
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
            com.kkb.spring.test.SpringConfiguration
        </param-value>
    </context-param>
    <listener>
        <listener-class>
            org.springframework.web.context.ContextLoaderListener
        </listener-class>
    </listener>
</web-app>

Spring分模塊開發

  分模塊開發場景描述

    表現層:spring配置文件,只想管理表現層的Bean

    業務層:spring配置文件,只想管理業務層的Bean,而且進行事務控制

    持久層:spring配置文件,只想管理持久層的Bean,而且還有須要管理數據源的Bean

  爲了方便管理項目中不一樣層的Bean對象,通常都是將一個spring配置文件,分解爲多個spring配置文件。

  分解以後的spring配置文件如何一塊兒被加載呢?

    一種就是同時指定多個配置文件的地址一塊兒加載

    另外一種就是:定義一個import.xml文件,經過import標籤將其餘多個spring配置文件導入到該文件中,tomcat啓動時只須要加載import.xml就能夠。

IoC和DI總結

 

@Autowired註解,它是如何生效的?AutowiredBeanPostProcessor類

IoC和DI使用

  XML配置方式

  註解+XML配置方式

    @Component

    @Controller

    @Service

    @Repository

    @Autowired

    @Resource

  純註解方式

    @Configuration

    @Bean

    @ComponentScan

    @PropertySource

    @Import

Spring整合Junit

單元測試問題

  在測試類中,每一個測試方法都有如下兩行代碼:

ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService service1 = context.getBean(UserService.class);

  咱們使用單元測試要測試的是業務問題,以上兩端代碼明顯不是業務代碼

  可是這兩行代碼的做用是獲取容器,若是不寫的話,直接會提示空指針異常。因此又不能輕易刪掉。

解決思路分析

  針對上述問題,咱們須要的是程序能自動幫咱們建立容器。一旦程序能自動爲咱們建立spring容器,咱們就無需手動建立了,問題也就解決了。

  但緊接的問題就是Juit它自己不認識spring,更沒法幫助建立spring容器了,不過好在Junit給咱們暴露一個註解(@RunWith),可讓咱們替換掉它的運行器。

  這時,咱們須要依靠spring框架,由於它提供了一個運行器,能夠讀取配置文件(或註解)來建立容器。咱們只需告訴它配置文件在哪就好了。

具體實現

添加依賴

  添加spring-test包便可

        <!-- spring單元測試組件包 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.2.0.RELEASE</version>
        </dependency>

經過@RunWith註解,指定spring的運行器

  Spring的運行器是SpringJunit4ClassRunner

package com.cyb.spring.test;

import javax.annotation.Resource;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.cyb.spring.configuration.SpringConfiguration;
import com.cyb.spring.service.UserService;

//@RunWith:Junit自身的註解,它的做用是能夠指定一個新的運行器
//SpringJUnit4ClassRunner.class:spring提供的單元測試運行器
@RunWith(SpringJUnit4ClassRunner.class)
//@ContextConfiguration:SpringJUnit4ClassRunner運行器須要的上下文配置信息,方便建立spring容器
//classes:純註解方式時,讀取配置類
//locations:xml方式時,讀取配置文件
@ContextConfiguration(classes = SpringConfiguration.class)
//@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class TestSpring2 {
    @Resource
    private UserService service;
    //執行@Test方法以前調用該方法

    @Test
    public void test1() {
        service.saveUser();
    }
}

 

 

 

 

 

 

 

 Demo示例源碼

經過@ContextConfiguration註解,指定spring運行器須要的配置文件路徑

經過@Autowired註解給測試類中的變量注入數據

 

 Spring AOP原理分析

AOP介紹

  什麼是AOP?

 

   在軟件業,AOP爲Aspect Oriented Programming的縮寫,意爲:面向切面編程

  AOP是一種編程範式,隸屬於軟工範疇,指導開發者如何組織程序結構

  AOP最先由AOP聯盟的組織提出的,制定了一套規範,spring將AOP思想引入到框架中,必須遵照AOP聯盟的規範

  經過預編譯方式和運行期動態代理實現程序功能的統一維護的一種技術

  AOP是OOP的延續,是軟件開發中的一個熱點,也是spring框架中的一個重要內容,是函數式編程的一種衍生範式

  利用AOP能夠對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度下降,提升程序的可重用性,同時提升了開發的效率

AOP的做用及優點是什麼? 

做用:

  AOP採用橫向抽取機制,取代了傳統縱向繼承體系重複性代碼(性能監視、事務管理、安全檢查、緩存)

  在程序運行期間,不修改源碼對已有方法進行加強。

  將業務邏輯和系統處理的代碼(關閉鏈接、事務管理、操做日誌記錄)解耦。

優點:

  • 減小重複代碼
  • 提升開發效率
  • 維護方便

 

 

 

 

 AOP相關術語介紹

  1. Joinpoint(鏈接點):所謂鏈接點是指那些被攔截到的點。在spring中,這些點指的是方法,由於spring只支持類型的鏈接點
  2. Pointcut(切入點):所謂切入點是指咱們要對那些Joinpoint進行攔截的定義
  3. Advice(通知/加強):所謂通知是指攔截到Joinpoint以後所要作的事情就是通知。通知分爲前置通知,後置通知,異常通知,最終通知,環繞通知(切面要完成的功能)
  4. Introduction(引介):引介時一種特殊的通知在不修改類代碼的前提下,Introduction能夠在運行期爲類動態地添加一些方法或Field
  5. Target(目標類):代理的目標對象
  6. Weaving(織入):是指把加強應用到目標對象來建立新的代理對象的過程
  7. Proxy(代理):一個類被AOP織入加強後,就產生一個結果代理類
  8. Aspect(切面):是切入點和通知的結合,在之後的開發中編寫和配置的

 

 

 AOP實現指AspectJ(瞭解)

  AspectJ是一個java實現的AOP框架,它可以對java代碼進行AOP編譯(通常在編譯期進行),讓java代碼具備AspectJ的AOP功能(固然須要特殊的編譯器)

  能夠這樣說AspectJ是目前實現AOP框架中最成熟,功能最豐富的語言,更幸運的是,AspectJ與java程序徹底兼容,幾乎是無縫關聯,所以對於有java編程基礎的工程師,上手和使用很是容易。

  瞭解AspectJ應用到java代碼的過程(這個過程稱爲織入),對於織入這個概念,能夠簡單理解爲Aspect(切面)應用到目標函數(類)的過程。

  對於這個過程,通常分爲動態織入和靜態織入,動態織入的方式是在運行時動態將要加強的代碼織入到目標類中,這樣每每時經過動態代理技術完成的。如java JDK的動態代理(Proxy,底層經過反射實現)或者CGLIB的動態代理(底層經過繼承實現),Spring AOP採用的就是基於運行時加強的代理技術。

  AspectJ採用的就是靜態織入的方式。AspectJ主要採用的是編譯期織入,這個期間使用AspectJ的acj編譯器(相似javac)把aspect類編譯成class字節碼後,在java目標類編譯時織入,即先編譯aspect類再編譯目標類。

 

 

Spring AOP實現原理分析 

  Spring AOP是經過動態代理技術實現的,而動態代理是基於反射設計的。

  動態代理技術的實現方式有兩種:基於接口的JDK動態代理和基於繼承的CGLib動態代理。

 

 JDK動態代理

  目標對象必須實現接口

使用Proxy類來生成代理對象的一些代碼以下

 /**
     * 使用JDK的方式生成代理對象
     * @author Administrator
     */
    public class MyProxyUtils {
        public static UserService getProxy(final UserService service) {
            // 使用Proxy類生成代理對象
            UserService proxy = 
                 (UserService) Proxy.newProxyInstance(
                    service.getClass().getClassLoader(),
                    service.getClass().getInterfaces(), 
                    new InvocationHandler() {

                        // 代理對象方法一執行,invoke方法就會執行一次
                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                            if("save".equals(method.getName())){
                                System.out.println("記錄日誌...");
                                // 開啓事務
                            }
                            // 提交事務
                            // 讓service類的save或者update方法正常的執行下去
                            return method.invoke(service, args);
                        }
                    });
            // 返回代理對象
            return proxy;
        }
    }

CGLib動態代理

  目標對象不須要實現接口

  底層是經過繼承目標對象產生代理子對象(代理子對象中繼承了目標對象的方法,並能夠對該方法進行加強)

    /**
     * 使用CGLib動態代理技術實現
     * 它是基於繼承的方式實現的
     * @param service
     * @return
     */
    public static UserService getProxyByClib(UserService service) {
        //建立加強器
        Enhancer enhancer=new Enhancer();
        //設置須要加強類的類對象
        enhancer.setSuperclass(UserServiceImpl.class);
        //設置回調函數
        enhancer.setCallback(new MethodInterceptor() {
            //methodProxy:代理以後的對象的方法引用
            @Override
            public Object intercept(Object object, Method method, Object[] arg, MethodProxy methodProxy) throws Throwable {
                Long start= System.currentTimeMillis();
                System.out.println("記錄程序開始時間。。。"+start);
                //由於代理對象是目標對象的子類
                //該行代碼,實際調用的是目標對象的方法
                //object:代理對象
                Object object2=methodProxy.invokeSuper(object, arg);
                Long end= System.currentTimeMillis();
                System.out.println("記錄程序結束時間。。。"+end);
                System.out.println("記錄總時長.."+(end-start));
                return object2;
            }
        });
        return (UserService)enhancer.create();
    }

Spring AOP使用

Spring AOP開發須要明確的事情

  a、開發階段(咱們作的)

    編寫核心業務代碼(開發主線):大部分程序員來作,要求熟悉業務需求。

    把公司代碼抽取出來,製做成通知。(開發階段最後在作):AOP編程人員來作。

    在配置文件中,聲明切入點與通知間的關係,即切面。:AOP編程人員來作。

  b、運行階段(Spring框架完成的)

    Spring框架監控切入點方法的執行。一旦監控到切入點方法被運行,使用代理機制,動態建立目標對象的代理對象,根據通知類別,在代理對象的對應位置,將通知對應的功能織入,完成完整的代碼邏輯運行。

編寫目標類

編寫接口和實現類(目標對象)

UserService接口

UserServiceImpl實現類

配置目標類,將目標類交給Spring IoC容器管理

 

 基於AspectJ的XML實現

實現步驟

POM.XML

        <!-- 基於AspectJ的aop依賴 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>5.0.7.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>aopalliance</groupId>
            <artifactId>aopalliance</artifactId>
            <version>1.0</version>
        </dependency>

編寫通知(加強類,一個普通的類)

 

 配置通知,將通知類交給Spring IoC容器管理

 

配置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
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">
</beans>

 直接下載

切入點表達式

  execution([修飾符] 返回值 包名.類名.方法名(參數))

    execution:必須有

    修飾符:可省略

    返回值類型:必需要,可是可使用*通配符

    包名:

      多級包之間使用.分割

      包名可使用*代替,多級包名可使用多個*代替

      若是想省略中間的報名可使用..

    類名:

      可使用*代替

      也能夠寫成*DaoImpl

    方法名:

      也可使用*代替

      也能夠寫成add*

    參數:

      參數使用*代替

      若是有多個參數,可使用..代替

 

 通知類型

  通知類型(五種):前置通知、後置通知、最終通知、環繞通知、異常拋出通知。

  前置通知:

    執行時機:目標對象方法以前執行通知

    配置文件:<aop:before method="before" pointcut-ref="myPointcut" />

    應用場景:方法開始時能夠進行校驗

  後置通知:

    執行時機:目標對象方法以後執行通知,有異常則不執行了

    配置文件:<aop:after-returning method="afterReturning" pointcut-ref="myPointcut" />

  應用場景:能夠修改方法的返回值

  最終通知:

    執行時機:目標對象方法以後執行通知,有沒有異常都會執行

    配置文件:<aop:after method="after" pointcut-ref="myPointcut" />

    應用場景:例如像釋放資源

  環繞通知

    執行時機:目標對象方法以前和以後都會執行。

    配置文件:<aop:around method="around" pointcut-ref="myPointcut" />

    應用場景:事務、統計代碼執行時機

  異常拋出通知:

    執行時機:在拋出異常後通知

    配置文件:<aop:after-throwing method="afterThrowing" pointcut-ref="myPointcut" />

    應用場景:包裝異常

 基於AspectJ的註解實現

實現步驟

  編寫切面類(注意不是通知類,由於該類中能夠指定切入點)

 

 配置切面類

<context:component-scan back-package="com.chenyanbin.spring" />

開啓AOP自動代理

 pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>spring-demo7</groupId>
    <artifactId>spring-demo7</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <dependencies>
        <!-- Spring IoC組件依賴 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>5.2.0.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>5.2.0.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.0.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-expression</artifactId>
            <version>5.2.0.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>javax.annotation</groupId>
            <artifactId>javax.annotation-api</artifactId>
            <version>1.3.1</version>
        </dependency>
        <!-- 單元測試 -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
        <!-- spring單元測試組件包 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.2.0.RELEASE</version>
        </dependency>
        <!-- 基於AspectJ的aop依賴 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>5.0.7.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>aopalliance</groupId>
            <artifactId>aopalliance</artifactId>
            <version>1.0</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <!-- 配置Maven的JDK編譯級別 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.2</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

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"
    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">
    <!-- 組件掃描器,掃描切面類 -->
    <context:component-scan
        base-package="com.cyb.spring"></context:component-scan>
        <!-- 開啓AOP自動代理 -->
        <aop:aspectj-autoproxy />
</beans>

直接下載

 環繞通知註解配置

  @Around

  做用:

    把當前方法當作是環繞通知屬性。

  value:

    用於指定切入點表達式,還能夠指定切入點表達式的引用。

 

 定義通用切入點

  使用@PointCut註解在切面類中定義一個通用的切入點,其餘通知能夠引用該切入點

 

 不使用XML的配置方式

  @Configuration

  @ComponentScan(basePackages="com.chenyanbin")

  @EnableAspectJAutoProxy

  public class SpringConfiguration{  

  }

Spring 應用之Spring JDBC實現

JdbcTemplate類的入門使用

  POM.XML

    • MySQL數據庫的驅動包
    • Spring-jdbc.jar
    • Spring-tx.jar

  編寫測試代碼

    @Test
    public void run1(){
        // 建立鏈接池,先使用Spring框架內置的鏈接池
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql:///spring ");
        dataSource.setUsername("root");
        dataSource.setPassword("root");
        // 建立模板類
        JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
        // 完成數據的添加
        jdbcTemplate.update("insert into t_account values (null,?,?)", "測試",10000);
    }

Spring管理JdbcTemplate

  步驟一:Spring管理內置的鏈接池

<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql:///spring_day03"/>
    <property name="username" value="root"/>
    <property name="password" value="root"/>
</bean>

  步驟二:Spring管理模板類

<bean  id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <property name="dataSource" ref="dataSource"/>
</bean>

  步驟三:編寫測試程序

    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration("classpath:applicationContext.xml")
    public class Demo2{
            @Resource(name="jdbcTemplate")
            private JdbcTemplate jdbcTemplate;

            @Test
            public void run(){
                jdbcTemplate.update("insert into t_account values (null,?,?)", "測試2",10000);
            }

    }

Spring管理第三方DataSource

    管理DBCP鏈接池
    * 先引入DBCP的2個jar包
        * com.springsource.org.apache.commons.dbcp-1.2.2.osgi.jar
        * com.springsource.org.apache.commons.pool-1.5.3.jar
        若是是maven環境,須要填寫GAV座標
    * 編寫配置文件
        <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
            <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
            <property name="url" value="jdbc:mysql:///spring "/>
            <property name="username" value="root"/>
            <property name="password" value="root"/>
        </bean>

    管理C3P0鏈接池
    * 先引入C3P0的jar包
        * com.springsource.com.mchange.v2.c3p0-0.9.1.2.jar

    * 編寫配置文件
        <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
            <property name="driverClass" value="com.mysql.jdbc.Driver"/>
            <property name="jdbcUrl" value="jdbc:mysql:///spring"/>
            <property name="user" value="root"/>
            <property name="password" value="root"/>
        </bean>

使用JdbcTemplate完成增刪改查操做

    增刪改查的操做
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration("classpath:applicationContext.xml")
    public class SpringDemo {

        @Resource(name="jdbcTemplate")
        private JdbcTemplate jdbcTemplate;

        @Test
        // 插入操做
        public void demo1(){
            jdbcTemplate.update("insert into account values (null,?,?)", "冠希",10000d);
        }

        @Test
        // 修改操做
        public void demo2(){
            jdbcTemplate.update("update account set name=?,money =? where id = ?", "思雨",10000d,5);
        }

        @Test
        // 刪除操做
        public void demo3(){
            jdbcTemplate.update("delete from account where id = ?", 5);
        }

        @Test
        // 查詢一條記錄
        public void demo4(){
            Account account = jdbcTemplate.queryForObject("select * from account where id = ?", new BeanMapper(), 1);
            System.out.println(account);
        }

        @Test
        // 查詢全部記錄
        public void demo5(){
            List<Account> list = jdbcTemplate.query("select * from t_account", new BeanMapper());
            for (Account account : list) {
                System.out.println(account);
            }
        }
    }

    class BeanMapper implements RowMapper<Account>{
        public Account mapRow(ResultSet rs, int arg1) throws SQLException {
            Account account = new Account();
            account.setId(rs.getInt("id"));
            account.setName(rs.getString("name"));
            account.setMoney(rs.getDouble("money"));
            return account;
        }
    }

Spring dao開發之jdbcDaoSupport

1. 步驟一:建立WEB工程,引入須要的jar包
    * IOC的6個包
    * AOP的4個包
    * C3P0的1個包
    * MySQL的驅動包
    * JDBC的2個包
    * 整合JUnit測試包

2. 步驟二:引入配置文件
    * 引入配置文件
        * 引入log4j.properties

        * 引入applicationContext.xml
            <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
                <property name="driverClass" value="com.mysql.jdbc.Driver"/>
                <property name="jdbcUrl" value="jdbc:mysql:///spring "/>
                <property name="user" value="root"/>
                <property name="password" value="root"/>
            </bean>

3. 步驟三:建立對應的包結構和類
    * com.kkb.demo1
        * AccountService
        * AccountServlceImpl
        * AccountDao
        * AccountDaoImpl

4. 步驟四:引入Spring的配置文件,將類配置到Spring中
    <bean id="accountService" class="com.kkb.demo1.AccountServiceImpl">
    </bean>

    <bean id="accountDao" class="com.kkb.demo1.AccountDaoImpl">
    </bean>

5. 步驟五:在業務層注入DAO ,在DAO中注入JDBC模板(強調:簡化開發,之後DAO能夠繼承JdbcDaoSupport類)
    <bean id="accountService" class="com.kkb.demo1.AccountServiceImpl">
        <property name="accountDao" ref="accountDao"/>
    </bean>

    <bean id="accountDao" class="com.kkb.demo1.AccountDaoImpl">
        <property name="dataSource" ref="dataSource"/>
    </bean>

6. 步驟六:編寫DAO和Service中的方法
    public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao {
        public void outMoney(String out, double money) {
            this.getJdbcTemplate().update("update t_account set money = money = ? where name = ?", money,out);
        }
        public void inMoney(String in, double money) {
            this.getJdbcTemplate().update("update t_account set money = money + ? where name = ?", money,in);
        }
    }

7. 步驟七:編寫測試程序.
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration("classpath:applicationContext.xml")
    public class Demo1 {

        @Resource(name="accountService")
        private AccountService accountService;

        @Test
        public void run1(){
            accountService.pay("冠希", "美美", 1000);
        }
    }

Spring 應用之事務支持

事務回顧

  事務:指的是邏輯上一組操做,組成這個事務的各個執行單元,要麼一塊兒成功,要麼一塊兒失敗!

事務特性

  • 原子性
  • 一致性
  • 隔離性
  • 持久性

若是不考慮隔離性,引起安全性問題

  • 讀問題
    • 髒讀
    • 不可重複讀
    • 虛讀

 

如何解決安全性問題

  讀問題解決,設置數據庫隔離級別

Spring 框架的事務管理相關的類和API

  1. PlatformTransactionManager接口:平臺事務管理器(真正管理事務的類)。該接口有具體的實現類,根據不一樣持久層框架,須要選擇不一樣的實現類!
  2. TransactionDefinition接口:事務定義信息(事務的隔離級別,傳播行爲,超時,只讀)
  3. TransactionStatus接口:事務的狀態
  4. 總結:上述對象之間的關係,平臺事務管理器真正管理事務對象,根據事務定義的信息 TransactionDefition 進行事務管理,在管理事務中產生一些狀態,將狀態記錄到 TransactionStatus中
  5. PlaformTransactionManager接口中實現類和經常使用的方法
    • 接口的實現類
      • 若是使用的 Spring的JDBC模板或者MyBatis框架,須要選擇 DataSourceTransactionManager實現類
      • 若是使用的是Hibernate框架,須要選擇HibernateTransactionManager實現類
    • 該接口的經常使用方法
      • void commit(TransactionStatus status)
      • TransactionStatus getTransaction(TransactionDefinition definition)
      • void rollback(TransactionStatus status)
  6. TransactionDefinition
    • 事務隔離級別的常量
        • static int ISOLATION_DEFAULT  --採用數據庫的默認隔離級別
        • static int ISOLATION_READ_UNCOMMITTED
        • static int ISOLATION_READ_COMMITTED
        • static int ISOLATION_REPEATABLE_READ
        • static int ISOLATION_SERIALIZABLE
    • 事務的傳播行爲常量(不用設置,使用默認值)
      • 先解釋什麼是事務的傳播行爲:解決的是業務層之間的方法調用
      • PROPAGATION_REQUIRED(默認值) --A中有事務,使用A中的事務,若是沒有,B就會開啓一個新的事務,將A包含進來.(保證A,B在同一個事務中),默認值
      • PROPAGATION_SUPPORTS --A中有事務,使用A中的事務。若是A中沒有事務,那麼B也不使用事務
      • PROPAGATION_MANDATORY --A中有事務,使用A中事務,若是A沒有事務,拋出異常
      • PROPAGATION_REQUIRES_NEW --A中有事務,將A中的事務掛起,B建立一個新的事務。(保證A,B沒有在一個事務中)
      • PROPAGATION_NOT_SUPPORTED --A中有事務,將A中的事務掛起
      • PROPAGATION_NEVER --A中有事務,拋出異常、
      • PROPAGATION_NESTED --嵌套事務,當A執行以後,就會在這個位置設置一個保存點,若是B沒有問題,執行經過,若是B出現異常,運行客戶根據需求回滾(選擇回滾到保存點或者是最初始狀態)

 Spring 框架事務管理的分類

  1. Spring 的編程式事務管理(不推薦使用)
    • 經過手動編寫代碼的方式完成事務的管理(不推薦)
  2. Spring的聲明式事務管理(底層採用AOP的技術)
    • 經過一段配置的方式完成事務的管理

編程式事務管理(瞭解)

1. 說明:Spring爲了簡化事務管理的代碼:提供了模板類 TransactionTemplate,因此手動編程的方式來管理事務,只須要使用該模板類便可!!

2. 手動編程方式的具體步驟以下:
    1. 步驟一:配置一個事務管理器,Spring使用PlatformTransactionManager接口來管理事務,因此我們須要使用到他的實現類!!
        <!-- 配置事務管理器 -->
        <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <property name="dataSource" ref="dataSource"/>
        </bean>

    2. 步驟二:配置事務管理的模板
        <!-- 配置事務管理的模板 -->
        <bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
            <property name="transactionManager" ref="transactionManager"/>
        </bean>

    3. 步驟三:在須要進行事務管理的類中,注入事務管理的模板
        <bean id="accountService" class="com.itheima.demo1.AccountServiceImpl">
            <property name="accountDao" ref="accountDao"/>
            <property name="transactionTemplate" ref="transactionTemplate"/>
        </bean>

    4. 步驟四:在業務層使用模板管理事務:
        // 注入事務模板對象
        private TransactionTemplate transactionTemplate;
        public void setTransactionTemplate(TransactionTemplate transactionTemplate) {
            this.transactionTemplate = transactionTemplate;
        }

        public void pay(final String out, final String in, final double money) {
            transactionTemplate.execute(new TransactionCallbackWithoutResult() {

                protected void doInTransactionWithoutResult(TransactionStatus status) {
                    // 扣錢
                    accountDao.outMoney(out, money);
                    int a = 10/0;
                    // 加錢
                    accountDao.inMoney(in, money);
                }
            });
        }

聲明式事務管理

聲明式事務管理又分紅兩種方式

    • 基於AspectJ的XML方式(重點掌握)
    • 基於AspectJ的註解方式(重點掌握)

事務管理之基於AspectJ的XML方式(重點掌握)

準備轉帳環境

  業務層:

    AccountService

    AccountServiceImpl

 

持久層

  AccountDao

  AccountDaoImpl

spring配置

 

 單元測試代碼:

 

 配置事務管理的AOP

  平臺事務管理器:DataSourceTransactionManager

 

 事務通知:<tx:advice id="" transaction-manager="" />

 

 AoP配置

  <aop:config>

    <aop:advisor advice-ref="" pointcut="" />

  </aop:config>

 

 事務管理之基於AspectJ的註解方式(重點掌握)

Service類上或者方法上加註解:

  類上加@Transactional:表示該類中全部方法都被事務管理

  方法上加@Transactional:表示只有改方法被事務管理

開始事務註解:

相關文章
相關標籤/搜索