Spring實戰 | 第一部分 Spring的核心(第一章 Spring之旅)

  • Spring的bean容器
  • 介紹Spring的核心模塊
  • 更爲強大的Spring生態系統
  • Spring的新功能

1、簡化java開發前端

爲了下降Spring開發的複雜性,Spring採起了如下4鐘關鍵策略:java

  • 基於POJO的輕量級和最小侵入性編程;
  • 經過依賴注入和麪向接口實現鬆耦合;
  • 基於切面和慣性進行聲明式編程;
  • 經過切面和模板減小樣式代碼。

一、激發POJO潛能(POJO即普通的java類)web

二、依賴注入(dependency injection DI)spring

依賴注入這個詞讓人望而生畏,如今已經演變成一項複雜的編程技巧或設計模式理念。但事實證實依賴注入並不像它聽上去那麼複雜,在項目中應用DI,你會發現你的代碼會變得異常簡單並且更容易理解和測試。編程

① DI功能是如何實現的設計模式

程序都是由多個類組合而成的,這些類之間進行協做完成特定的業務邏輯。按照傳統的作法,每一個對象負責管理與本身相互協做的對象(即它所依賴的對象)的引用,這將會致使高度耦合和難以測試的代碼。安全

② 耦合具備兩面性。網絡

一方面,緊耦合的代碼難以測試、難以複用、難以理解,而且典型地表現出「打地鼠」式的bug特性(修復一個bug,將會出現一個或者更多的新bug)。另外一方面,必定程序的耦合又是必須的,徹底沒有耦合的代碼啥也作不了。爲了完成有實際意義的功能,不一樣的類必須以適當的方式進行交互。總而言之,耦合是必須的,但應當被當心謹慎的管理。session

經過DI,對象的依賴關係將由系統中負責協調各隊的第三方組件在常見對象的時候進行設定。對象無需自行建立或管理他們的依賴關係,以下圖,依賴關係將自動注入到須要他們的對象中去。app

③ 依賴注入的方式

spring支持3種依賴注入的方式

  • 屬性注入

屬性注入即經過setter方法注入bean的屬性值或依賴的對象

屬性注入使用元素,使用name屬性指定bean的屬性名稱,value屬性或子節點屬性值

屬性注入是實際開發中最多見的注入方式

public void setName(String name)
{
    System.out.println("setName:"+name);
    this.name=name;
}
<bean id="helloWorld" class="spring.bean.HelloWorld">
    <property name="name" value="Spring"></property>
</bean>
  • 構造器注入

經過構造方法注入bean的屬性值或者依賴的對象(引用),保證了bean實例在實例化後就可使用

構造器注入在元素裏聲明屬性,沒有name屬性

建立一個People對象

package com.container;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class People {
        private String name;
        private String sex;
        private int age;

        public People(String name, int age) {
            this.name = name;
            this.age = age;
        }

        public People(String name, String sex) {
            this.name = name;
            this.sex = sex;
        }

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

        public static void main(String[] args) {
            ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-config.xml");
            People people1 = (com.container.People) applicationContext.getBean("people1");
            System.out.println(people1);
            People people2 = (com.container.People) applicationContext.getBean("people2");
            System.out.println(people2);
        }
    }
<?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="people1" class="com.container.People">
        <constructor-arg value="江疏影" type="java.lang.String"></constructor-arg>
        <constructor-arg value="20" type="int"></constructor-arg>
    </bean>
    <bean id="people2" class="com.container.People">
        <constructor-arg value="江疏影" type="java.lang.String"></constructor-arg>
        <constructor-arg value="man" type="java.lang.String"></constructor-arg>
    </bean>
</beans>

  • 工廠方法注入(知道就行,不推薦)

三、應用切面

DI可以讓相互協做的軟件組件保持鬆散耦合,而面向切面編程(aspect-oriented programming,AOP)容許你把遍及應用各處的功能分離出來造成可重用的組件。

面向切面編程每每被定義爲促使軟件系統實現關注點分離的一項技術。系統由許多不一樣的組件組成,每個組件各負責一塊特定功能。除了實現自身核心的功能外,這些組件還常常承擔着額外的職責。諸如日誌、事務管理和安全這樣的系統服務也會融入到核心業務組件中,這些系統服務一般被稱爲橫切關注點,由於它們會跨越系統的多個組件。

若是將這些關注點所有分散到多個組件中去,你的代碼將會帶來雙重的複雜性。

  • 實現系統關注點功能的代碼將會重複的出如今多個組件中,這就意味着若是你要改變這些關注點的邏輯,必須修改各個模塊中的相關實現。即便你把關注點對象抽象爲一個獨立的模塊,其它模塊只要調用它的方法,但方法的調用仍是會出重複出如今各個模塊中。
  • 組件會由於那些與自身核心業務無關的代碼而變得混亂。一個向地址簿增長地址條目的方法應該只關注如何添加地址,而不該該關注它是否是安全的或者是否須要支持事務。

下圖展現了這種複雜性,左邊的業務代碼與系統服務結合的過於緊密。每一個對象不但要知道它須要記日誌、進行安全控制和參與事務,還要親自執行這些服務。

AOP可以使這些服務模塊化,並以聲明的方式將它們應用到它們須要影響的組件中去。而形成的結果就是這些組件會具備更高的內聚性而且會更加關注自身的業務,徹底不須要了解設計系統服務所帶來的複雜性。總之,AOP可以確保POJO的簡單性。

以下圖所示,咱們能夠把切面想象爲覆蓋在不少組件之上的一個外殼。應用是由那些實現各自業務功能的模塊組成,藉助AOP,可使用各類功能層去包裹核心業務層,這些層以聲明的方式靈活的應用在系統中,你的核心應用甚至不知道他們的存在,這是一個很是強大的理念,能夠將安全、事務和日誌關注點與核心業務邏輯相分離。

經過少許的XML配置,聲明一個Spring切面。

四、使用模塊消除樣板式代碼

2、Spring容器

Spring容器,顧名思義是用來容納東西的,裝的就是Bean。Spring容器負責建立、配置、管理Bean。spring容器有兩個核心接口:BeanFactory和ApplicationContext接口,後者是前者的子接口。在基於spring的Java EE程序中,全部的組件都被當成Bean來處理,包括數據源對象、hibernate的sessionFactory、事務管理等,程序中的全部Java類均可以被當成spring容器中的bean。

在基於Spring的應用中,你的應用對象生存於Spring容器(container)中。以下圖所示,Spring容器負責建立對象,裝配它們,配置它們並管理它們的整個生命週期,從生存到死亡(在這裏可能就是new到finalize())。

一、spring容器

spring容器的核心接口是BeanFactory,它有一個子接口就是ApplicationContext。ApplicationContext也被稱爲spring上下文。

調用者只須要使用getBean()方法便可得到指定bean的引用。對於大部分的Java程序而言,使用ApplicationContext做爲spring容易更爲方便。其經常使用的實現類有FileSystemXmlApplicationContext、ClassPathXmlApplicationContext和AnnotationConfigXmlApplicationContext。若是Java web中使用spring容器,則一般有XmlWebApplicationContext、AnnotationConfigWebApplicationContext兩個容器。

建立spring容器的實例時,必須提供spring容器管理的bean的配置文件,也就是咱們常說的spring.xml配置文件。所以在建立beanFactory時配置文件做爲參數傳入。xml配置文件通常以resource對象傳入。resource是spring提供的資源訪問接口,經過該接口spring更簡單、透明的訪問磁盤,網絡系統和類路徑上的相關資源。

對於獨立的Java EE應用程序,能夠經過以下方法來實例化BeanFactory。

//在當前項目類路徑下搜索配置文件
ApplicationContext appContext = new ClassPathXmlApplicationContext("beans_7_3_3.xml");
//在文件系統搜索配置文件
appContext = new FileSystemXmlApplicationContext("D:\\spring-tool-workspace\\myspring\\src\\beans_7_3_3.xml");
//獲取chinese的Bean,而且返回的類型爲Chinese
Person chinese = appContext.getBean("chinese", Chinese.class);
chinese.useAxe();

二、使用ApplicationContext

大部分時間,都不會使用beanFactory實例做爲spring容器,而是使用ApplicationContext做爲spring容器,所以spring容器也被稱爲spring上下文。ApplicationContext加強了beanFactory的功能,提供了不少有用、方便開發的功能。

在web中能夠利用如contextLoader的支持類,在web應用啓動的時候自動建立ApplicationContext。

除了提供beanFactory所支持的所有功能外,application還額外的提供以下功能:

① ApplicationContext會默認初始化全部的singleton bean(單例bean),也能夠經過配置取消。

② ApplicationContext繼承了messageSource接口,所以提供國際化支持。

③ 資源訪問,好比URL和文件。

④ 事件機制。

⑤ 同時加載多個配置文件。

⑥ 以聲明式方式啓動並建立spring容器。

ApplicationContext包括beanFactory的全部功能,並提供了一些額外的功能,優先使用ApplicationContext。對於在內存消耗的才使用beanFactory。

當系統建立ApplicationContext容器時,會默認初始化singleton bean,包括調用構造器建立該bean的實例,經過元素驅動spring調用setting方法注入所依賴的對象。這就意味着,系統前期建立ApplicationContext會有很大的開銷,可是一旦初始化完成後面獲取bean實例就會擁有較好的性能。爲了阻止在使用ApplicationContext做爲spring容器初始化singleton bean能夠在元素添加lazy-init="true"屬性。

三、ApplicationContext的國際化支持

ApplicationContext接口繼承了MessageSource接口,所以具有國際化功能。

//MessageSource接口提供的國際化的兩個方法
String getMessage(String code, Object [] args, Locale loc){
}
String getMessage(String code, Object[]args, String default, Locale loc){
}

spring國際化的支持,實際上是創建在Java國際化的基礎上的。其核心思路將程序中須要國際化的消息寫入資源文件,而代碼中僅僅使用國際化信息響應的key。

四、ApplicationContext的事件機制

ApplicationContext的事件機制是觀察者設計模式的實現。經過ApplicationEvent和ApplicationListener接口實現,前者是被觀察者,後者是觀察者。

spring事件框架有兩個核心的接口:

ApplicationEvent(事件):必須由ApplicationContext來發布。

ApplicationListener(監聽器):實現了此接口就能夠擔任容器中的監聽器bean。

實際上,spring的事件機制是由事件(實現ApplicationEvent接口的類)、事件源(也就是spring容器,而且有Java代碼顯示的觸發)、監聽器(ApplicationListener接口實現類)。這就像咱們在頁面點擊一個button。button是事件源,單機的這個動做就是事件,處理函數就是監聽器。

如下代碼演示spring事件機制:

import org.springframework.context.ApplicationEvent;

public class EmailEvent extends ApplicationEvent{
    private String address;
    private String text;
    public EmailEvent(Object source) {
        super(source);
    }

    public EmailEvent(Object source, String address, String text) {
        super(source);
        this.address = address;
        this.text = text;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public String getText() {
        return text;
    }

    public void setText(String text) {
        this.text = text;
    }
}
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class EmailNotifier implements ApplicationListener {
    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        //處理email事件
        if(event instanceof EmailEvent){
            EmailEvent email = (EmailEvent) event;
            System.out.println(email.getAddress()+"  "+email.getText());
        }else {
            //輸出spring容器的內置事件
            System.out.println("其它事件:"+event);
        }
    }

    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans_7_4_4.xml");
        EmailEvent emailEvent = applicationContext.getBean("emailEvent",EmailEvent.class);
        applicationContext.publishEvent(emailEvent);
    }
}
<?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 class="EmailNotifier"></bean>
    <bean id="emailEvent" class="EmailEvent">
        <constructor-arg value="test"></constructor-arg>
        <constructor-arg value="123@qq.com"></constructor-arg>
        <constructor-arg value="this is a test"></constructor-arg>
    </bean>
</beans>

從上面的代碼能夠看出,事件監聽器不只監聽到了咱們程序顯示觸發的事件,還監聽了spring容器內置的事件。若是實際開發須要,咱們能夠在spring容器初始化或銷燬時回調自定義方法,就能夠經過上面的事件監聽機制來完成。

spring提供了以下幾個內置對象:

ContextRefreshedEvent、ContextStartedEvent、ContextClosedEvent、ContextStoppedEvent、RequestHandledEvent。

五、讓bean獲取spring容器

上面都是經過ApplicationContext建立spring容器,再調用spring容器的getBean()方法獲取bean。這種狀況下,程序老是持有spring容器的引用。可是在web應用中,咱們能夠用聲明式的方法來建立spring容器:在web.xml文件中配置一個監聽,讓這個監聽類幫咱們來建立spring容器,前端MVC框架直接調用bean,使用依賴注入功能,無需訪問spring容器自己。

在某些特殊狀況下,bean須要實現某個功能(好比:bean須要輸出國際化信息,或向spring容器發佈事件),這些功能都須要藉助spring容器來完成。就是說咱們須要將spring容器做爲一個bean來注入到其它bean中,只不過spring容器bean是一個容器級別的bean。

爲了讓bean獲取它所在容器的引用,可讓bean實現beanFactoryAware接口。該接口只有一個方法setBeanFactory(BeanFactory beanFactory)方法,方法的beanFactory參數指向spring容器,會由spring容器注入。咱們bean中定義一個setter方法後,一般都是由在配置文件中配置元素來驅動spring容器來注入依賴bean的,可是這裏咱們並無這樣作,這是由於一個bean若是實現了beanFactory接口,spring在建立該bean時,會自動注入spring容器自己。與beanFactoryAware接口相似的還有BeanNameAware、ResourceLoaderAware接口,這些接口都會提供相似的setter方法,這些方法會由spring容器來注入。

六、bean的生命週期

正確理解Spring bean的生命週期很是重要,由於你或許要利用Spring提供的擴展點來自定義bean的建立過程。下圖展現了bean裝載到Spring應用上下文中的一個典型的生命週期過程。

正如你所見,在bean準備就緒前,bean工廠執行了若干啓動步驟。

如今你已經瞭解瞭如何建立和加載一個Spring容器。可是一個空的容器並無太大的價值,在你把東西放進去以前,它什麼也沒有。爲了從Spring的DI中受益,咱們必須將應用對象裝配進Spring容器中。咱們將在第二章對bean裝配進行更詳細的探討。

咱們如今首先瀏覽一下Spring的體系結果,瞭解一下Spring框架的基本組成部分和最新版本的Spring所發佈的新特性。

3、俯瞰Spring風景線

一、Spring模塊

Spring發佈版本中lib目錄下有多個jar文件。

Spring核心容器

Spring的AOP模塊

數據訪問與集成

 web與遠程調用

測試

Spring  Boot(第二十一章介紹)

4、Spring的新功能

5、小結

Spring致力於簡化企業級Java開發,促進代碼的鬆散耦合,成功的關鍵在於依賴注入和AOP。

 

Spring實戰@目錄

相關文章
相關標籤/搜索