Spring 5 設計模式 (chapter 1)

開始使用Spring Framework 5.0和設計模式

介紹Spring框架

Spring簡化了應用程序開發,并消除了許多依賴項其他api。讓我們來看一些例子,作為應用程序開發人員,可以從Spring平臺獲益:

  • 所有的應用程序類都是簡單的POJO類——Spring不是入侵性的。它不需要您擴展框架類或為大多數用例實現框架接口。
  • Spring應用程序不需要Java EE應用服務器,但是它們可以部署在任何一個Java EE應用服務器上。
  • 您可以使用Spring框架中的事務管理來執行數據庫事務中的方法,而不需要任何第三方事務API。(Spring內部封裝好了)
  • 使用Spring,您可以使用Java方法作為請求處理程序方法或遠程方法,就像servlet API的service()方法,但不需要處理servlet容器的servlet API。
  • Spring允許您在應用程序中使用本地java方法作為消息處理程序方法,而無需使用java消息服務(JMS)API。(封裝了JMS)
  • Spring還允許您使用本地java方法作為管理操作,而不用在應用程序中使用java管理擴展(JMX)API。(封裝了JMX)
  • Spring作為應用程序對象的容器。您的對象不必擔心查找和建立彼此的連接。(IOC容器來管理對象的生命周期)
  • Spring實例化bean并將對象的依賴項注入
    應用程序——它作為bean的生命周期管理器。

利用Spring簡化應用程序開發

我們需要討論或至少提供一些基本的實現和對設計模式的考慮,以及為企業應用程序開發設計基礎設施的最佳實踐。Spring使用以下策略使java開發變得容易和可測試:

  • Spring使用POJO模式的力量來進行輕量級的、最小侵入式的企業應用開發
  • 它使用依賴注入模式(DI模式)的力量來實現松散耦合,并使系統面向接口
  • 它使用了修飾符和代理設計模式的力量,通過切面和公共約定來實現聲明性編程
  • 它使用切面和模板設計模式的力量來消除樣板代碼。

在本章中,我將解釋這些概念,并展示了Spring如何簡化Java開發的具體例子。讓我們從探索如何通過使用POJO模式來鼓勵基于POJO的開發來探索Spring如何保持最小的入侵性。

使用POJO模式的力量

有許多的Java開發框架,它們通過強制你繼承或者實現它們已經存在的類和接口來開發,Struts,Tapestry和早期版本的EJB采用了這種方法。這些框架的編程模型是基于入侵式的編程模型。這使得代碼很難在系統中發現bug,有時會使代碼變得難以理解。
但是,如果你使用Spring框架,你不需要繼承或者實現任何類和接口,只是簡單的基于POJO的實現,這是一種非侵入式的編程模型,它可以使得代碼很容易的發現bug,也可以讓我們的代碼更好的理解。
Spring允許您使用非常簡單的非Spring類進行編程,這意味著不需要實現特定于Spring的類或接口,因此基于Spring的應用程序中的所有類都是POJO。這意味著您可以在不依賴Spring庫的情況下編譯和運行這些文件;您甚至不能意識到Spring框架正在使用這些類。在基于java的配置中,您將使用Spring注釋,這是Spring應用程序中最糟糕的情況。

讓我們來看看下面這個例子:

    package com.packt.chapter1.spring;
    public class HelloWorld {
    public String hello() {
    return "Hello World";
    }
    }

上面的類是一個簡單的POJO類,沒有與框架相關的特殊指示或實現,使它成為Spring組件。所以這個類可以在一個Spring應用程序中運行,就像在一個非Spring的應用程序中一樣。這是Spring非侵入式編程模型的美妙之處。Spring賦予POJO的另一種方式是使用DI模式與其他POJO協作。讓我們看看DI如何幫助解耦組件。

注入POJO之間的依賴關系

“依賴項注入”一詞并不新,它被PicoContainer使用。依賴注入是一種設計模式,可以促進Spring組件之間的松散耦合——也就是說,在不同的協作POJO之間。因此,通過將DI應用于復雜的編程,您的代碼將變得更簡單,更容易理解,更容易測試。.

在您的應用程序中,許多對象根據您的需求一起為特定的功能工作。對象之間的這種協作實際上稱為依賴注入。在工作組件之間注入依賴可以幫助您在松耦合的情況下對應用程序中的每個組件進行單元測試。

在一個工作應用程序中,最終用戶希望看到輸出。為了創建輸出,應用程序中的一些對象一起工作,有時耦合。因此,當您編寫這些復雜的應用程序類時,考慮這些類的可重用性,并使這些類盡可能獨立。這是一個最好的編碼實踐,它將幫助您獨立地測試這些類。

DI是怎么樣讓開發和測試這樣簡單?

讓我們看看你的應用程序中的DI模式實現。 它使事情變得容易理解,松散耦合,并可跨應用程序進行測試。假設我們有一個簡單的程序,每個類都在共同完成一些業務任務,并幫助建立業務需求和期望。這意味著應用程序中的每個類都與其他協作對象(其依賴項)一起對業務任務負有責任。 我們來看下面的圖片。 對象之間的依賴關系可以在依賴對象之間創建復雜性和緊密耦合:

TransferService組件依賴于其他兩個組件: TransferRepository和AccountRepository

一個典型的應用系統由幾個部分組成,以執行一個用例。例如,考慮下傳輸服務類。
TransferService使用直接實例化:

package com.packt.chapter1.bankapp.transfer;
public class TransferService {
private AccountRepository accountRepository;
public TransferService () {
this.accountRepository = new AccountRepository();
}
public void transferMoney(Account a, Account b) {
accountRepository.transfer(a, b);
}
}

TransferService對象需要一個AccountRepository對象來從賬戶a轉賬到賬戶b。 因此,它直接創建一個AccountRepository對象的實例并使用它。 但是,直接實例化會增加耦合性,并在整個應用程序中散布對象創建代碼,這使得難以為TransferService編寫單元測試,因為在這種情況下,無論何時當你要 使用assert來進行單元測試,測試TransferService類的transferMoney()方法,那么AccountRepository類的transfer()方法在本次測試中也被認為是不可能的。 因為開發人員并不知道AccountRepository對TransferService類的依賴關系; 至少,開發人員無法使用單元測試來測試TransferService類的transferMoney()方法。

在企業應用程序中,耦合是非常危險的,它把你推向了一個在將來無法對應用程序進行任何改進的情況下,如果此類應用程序的進一步更改可能會產生大量的錯誤,并且修復這些錯誤可能會產生新的錯誤。

緊耦合元件是這些應用中主要問題的原因之一。 不必要的緊密耦合的代碼使您的應用程序變得不可維護,隨著時間的推移,代碼將不會被重用,因為其他開發人員無法理解。 但是,有時企業應用程序需要一定程度的耦合,因為在現實世界中,完全不耦合的組件是不可能的。 應用程序中的每個組件對于角色和業務需求都有一定的責任,所有組件都必須知道其他組件的責任。 這意味著有時需要耦合,因此我們必須非常仔細地管理所需組件之間的耦合。

使用依賴組件的工廠幫助模式(Using factory helper pattern for dependent components)

讓我們使用工廠模式來嘗試另一個管理依賴對象的方法。此設計模式基于GOF工廠設計模式,通過使用工廠方法創建對象實例。 所以這個方法實際上集中了新操作符的使用。 它根據客戶端代碼提供的信息創建對象實例。 這種模式在依賴注入策略中被廣泛使用。
TransferService使用工廠輔助:

package com.packt.chapter1.bankapp.transfer;
public class TransferService {
private AccountRepository accountRepository;
public TransferService() {
this.accountRepository =
AccountRepositoryFactory.getInstance("jdbc");
}
public void transferMoney(Account a, Account b) {
accountRepository.transfer(a, b);
}
}

在上面的代碼中,我們使用了工廠模式來創建一個AccountRepository,在軟件工程中,應用程序設計和開發的最佳實踐之一是編程接口(program-to-interface (P2I) )
根據這一實踐,具體類必須實現調用者的客戶端代碼中使用的接口,而不是使用具體的類。通過使用P2I,您可以改進前面的代碼。因此,我們可以輕松地將其替換為接口的不同實現,對客戶端代碼的影響很小。因此,編程接口為我們提供了一種低耦合的方法。換句話說,沒有直接依賴于具體的實現導致低耦合。讓我們看看下面的代碼。這里,AccountRepository是一個接口而不是一個類:


public interface AccountRepository{
void transfer();
//other methods
}

因此,我們可以按照我們的需求來實現它,它依賴于客戶機的基礎設施。假設我們希望在使用JDBC API的開發階段使用AccountRepository。我們可以為AccountRepositry接口提供一個JdbcAccountRepositry具體實現,如下所示:

public class JdbcAccountRepositry implements AccountRepositry{
//...implementation of methods defined in AccountRepositry
// ...implementation of other methods
}

在這個模式中,對象是由工廠類創建的,以使其易于維護,從而避免將對象創建的代碼分散到其他業務組件上。通過使用工廠輔助程序,也可以使對象創建可配置。這種技術為緊密耦合提供了一個解決方案,但是我們仍然在為獲取協作組件添加工廠類到業務組件。下面我們來看下一部分的DI模式,看看如何解決這個問題。

使用依賴組件的DI模式

根據DI模式,在某些工廠或第三方創建對象時,依賴對象會得到它們的依賴項。該工廠以這樣一種方式協調系統中的每個對象,而每個依賴對象并不期望創建它們的依賴項。這意味著我們應該要專注于定義依賴項,而不是解決企業應用程序中協作對象的依賴關系。讓我們看一下下面的圖像。我們可以看到依賴項被注入到需要它們的對象中:

Dependency injection between the different collaborating components in the application

為了展示這一點,我們將在下一節看到TransferService,一個 TransferService依賴于AccountRepositoryTransferRepository,在這里,TransferService可以通過任何類型的TransferRepository實現轉賬,也就是說,我們可以使用JdbcTransferRepositoryJpaTransferRepository,這取決于根據部署環境的情況。TransferServiceImpl足夠靈活,可以接受它所提供的任何TransferRepository:

package com.packt.chapter1.bankapp;
public class TransferServiceImpl implements TransferService {
private TransferRepository transferRepository;
private AccountRepository accountRepository;
public TransferServiceImpl(TransferRepository transferRepository,
AccountRepository accountRepository) {
this.transferRepository =
transferRepository;//TransferRepository is injected
this.accountRepository = accountRepository;
//AccountRepository is injected
}
public void transferMoney(Long a, Long b, Amount amount) {
Account accountA = accountRepository.findByAccountId(a);
Account accountB = accountRepository.findByAccountId(b);
transferRepository.transfer(accountA, accountB, amount);
}
}

這里我們可以看到TransferServiceImpl并沒有創建他自己的依賴實現,在構建時,我們已經將repository的實現作為構造函數的參數。這是一個稱為構造函數注入的DI類型。
這里我們已經傳遞了repository接口類型作為構造函數的參數。現在TransferServiceImpl可以使用任何repositorie接口的實現,比如JDBC,JPA,或者mock對象,關鍵點是TransferServiceImpl并沒有和特定的repositorie實現進行耦合,無論哪種repository用于將數量從一個帳戶轉移到另一個帳戶,只要它實現了repository相應的接口就行。

如果您正在使用Spring框架的DI模式,那么松耦合是關鍵的優點之一。DI模式總是促進P2I(面向接口編程),因此每個對象都知道其與相關接口的依賴關系,而不是相關的實現,因此依賴項可以很容易地與該接口的另一個實現交換,而不是更改到依賴的類實現。

通過創建關聯來組裝應用系統的方法
應用部分或組件被稱為wiring

在Spring中,有很多方法可以將協作組件連接到一起,從而形成一個應用程序系統。例如,我們可以使用XML配置文件或Java配置文件。
現在,讓我們看看如何將TransferRepositoryAccountRepository的依賴關系注入到Spring的TransferService中:


<?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="transferService"
class="com.packt.chapter1.bankapp.service.TransferServiceImpl">
<constructor-arg ref="accountRepository"/>
<constructor-arg ref="transferRepository"/>
</bean>
<bean id="accountRepository" class="com.
packt.chapter1.bankapp.repository.JdbcAccountRepository"/>
<bean id="transferRepository" class="com.
packt.chapter1.bankapp.repository.JdbcTransferRepository"/>
</beans>

這里,TransferServiceImplJdbcAccountRepositoryJdbcTransferRepository在Spring中聲明為bean。在TransferServiceImpl bean的例子中,它是構造的,它傳遞一個引用到AccountRepositoryTransferRepositorybean作為構造函數的參數。您可能想知道,Spring還允許您使用Java表達相同的配置。

Spring還可以使用基于Java注解來代替XML:


package com.packt.chapter1.bankapp.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.packt.chapter1.bankapp.repository.AccountRepository;
import com.packt.chapter1.bankapp.repository.TransferRepository;
import
com.packt.chapter1.bankapp.repository.jdbc.JdbcAccountRepository;
import
com.packt.chapter1.bankapp.repository.jdbc.JdbcTransferRepository;
import com.packt.chapter1.bankapp.service.TransferService;
import com.packt.chapter1.bankapp.service.TransferServiceImpl;
@Configuration
public class AppConfig {
@Bean
public TransferService transferService(){
return new TransferServiceImpl(accountRepository(),
transferRepository());
}
@Bean
public AccountRepository accountRepository() {
return new JdbcAccountRepository();
}
@Bean
public TransferRepository transferRepository() {
return new JdbcTransferRepository();
}
}

無論您使用的是基于xml的或基于java的配置的依賴注入模式,它們好處都是一樣的:

  • 依賴注入促進松耦合。您可以使用最佳實踐P2I刪除硬編碼的依賴項,您可以通過使用工廠模式及其內置的可切換實現和可插入實現來從應用程序外部提供依賴關系
  • DI模式促進了面向對象編程的組合設計,而不是繼承編程

盡管TransferService依賴于一個AccountRepositoryTransferRepository,但它并不關心應用程序中使用AccountRepositoryTransferRepository的什么類型(JDBC或JPA)。只有Spring通過它的配置(XML或基于java)知道所有組件是如何組合在一起的,并使用DI模式來實例化它們所需要的依賴關系。DI使那些依賴類的依賴關系不再變化——也就是說,我們可以使用JDBC實現或JPA實現,而不需要更改AccountService的實現。

在Spring應用程序中,一個實現的應用程序上下文(Spring提供了基于java的AnnotationConfigApplicationContext和基于xmlClassPathXmlApplicationContext實現)加載bean定義并將它們綁在一起成一個Spring容器。Spring的應用程序上下文在啟動時為應用程序創建和連接Spring bean。使用基于java的配置來查看Spring應用程序上下文的實現——它加載Spring配置文件(AppConfig) 或者 Sprig.xml)位于應用程序的類路徑中。在以下代碼中,TransferMain類的main()方法使用AppConfig所類加載配置類。獲取一個AccountService類的對象。

Spring提供了基于java配置的方式來代替XML:


package com.packt.patterninspring.chapter1.bankapp;

import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import com.packt.patterninspring.chapter1.bankapp.config.AppConfig;
import com.packt.patterninspring.chapter1.bankapp.model.Amount;
import com.packt.patterninspring.chapter1.bankapp.service.TransferService;

public class TransferMain {

    public static void main(String[] args) {
        //Load Spring context
        ConfigurableApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
        //Get TransferService bean
        TransferService transferService = applicationContext.getBean(TransferService.class);
        //Use transfer method
        transferService.transferAmmount(100l, 200l, new Amount(2000.0));
        applicationContext.close();
    }

}

在這里我們快速介紹依賴注入模式。在本書即將出版的章節中,你會學到更多關于DI模式的知識。現在,讓我們看看另一種使用Spring的聲明式編程模型通過方面和代理模式來簡化Java開發的方法。

應用橫切關注點的切面

在Spring應用程序中,DI模式為我們提供了協作軟件組件之間的松散耦合,

但是Spring AOP的面向方面編程使您能夠捕獲在整個應用程序中重復的常見功能。因此,我們可以說,Spring AOP促進了松散耦合,并允許以最優雅的方式分離的橫切關注點。它允許通過聲明透明地應用這些服務。在Spring AOP中,可以編寫自定義方面并以聲明的方式配置它們。

在您的應用程序中許多地方需要的通用功能是:

  • 日志和跟蹤
  • 事務管理
  • 安全
  • 緩存
  • 錯誤處理
  • 性能監控
    -自定義業務規則

這里列出的組件并不是您的核心應用程序的一部分,但是這些組件有一些額外的職責,通常稱為交叉關注點,因為它們在系統中跨多個組件,超出了它們的核心職責。如果您將這些組件與您的核心功能相結合,從而實現了不模塊化的橫切關注點,那么它將有兩個主要問題:

  • Code tangling:關注點的耦合意味著交叉的關注點代碼,例如安全問題、事務關注和日志關注,與應用程序中的業務對象的代碼相結合。
  • Code scattering: 代碼散射指的是跨模塊分布的相同關注點。這意味著您關心的安全性、事務和日志記錄都分布在系統的所有模塊中。換句話說,您可以說整個系統中都存在相同的問題代碼。

下圖說明了這種復雜性。業務對象與橫切關系密切相關。不僅每個對象知道它被記錄、保護并參與事務上下文,而且每個對象也負責執行只分配給它的服務:


Cross-cutting concerns, such as logging, security and transaction, are often scattered about in modules where those tasks are not their primary concern

Spring AOP使交叉關注點的模塊化避免了纏結和散射。您可以對應用程序的核心業務組件進行這些模塊化的關注,而不會影響上述組件。這些方面確保了pojo仍然是簡單的。Spring AOP通過使用代理設計模式使這種魔力成為可能。我們將在本書的后續章節中進一步討論代理設計模式。

Spring AOP 是怎么工作的

下面的幾個點描述了Spring AOP的工作:

  • 實現你的主要的程序邏輯 : 專注于核心問題意味著,當您在編寫應用程序業務邏輯時,您不需要擔心添加額外的功能,例如日志、安全性和事務,在業務代碼與spring AOP之間進行處理。
  • 編寫一些切面來實現您的橫切關注點: Spring提供了許多切面,這意味著您可以在Spring AOP中以獨立單元的形式編寫額外的功能。這些方面在應用程序邏輯代碼之外有更多的跨切關注點。
  • ** 將各個切面織入到應用程序中去:將交叉橫切的行為添加到正確的位置,也就是說,在寫了其他的方面之后職責,您可以在應用程序邏輯代碼中以聲明的方式將它們注入到正確的位置。

讓我們來看看Spring AOP 的說明:

基于aop的系統進化——這使得應用程序組件專注于它們的特定 業務功能

在前面的圖中,Spring AOP將橫切關注點分離開來,例如,安全、事務和日志,來自業務模塊,即BankServiceCustomerServiceReportingService。這些橫切關注點應用于應用程序運行時的業務模塊的預定義點(前面圖中的條紋)。假設您希望在調用transferAmmount()傳輸服務方法之前和之后使用LoggingAspect的服務來記錄消息。下面的清單顯示了您可能使用的LoggingAspect類。


package com.packt.patterninspring.chapter1.bankapp.aspect;

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class LoggingAspect {
    
    @Before("execution(* *.transferAmmount(..))")
    public void logBeforeTransfer(){
        System.out.println("####LoggingAspect.logBeforeTransfer() method called before transfer amount####");
    }
    
    @After("execution(* *.transferAmmount(..))")
    public void logAfterTransfer(){
        System.out.println("####LoggingAspect.logAfterTransfer() method called after transfer amount####");
    }
}

要將LoggingAspect轉換為aspect bean,您需要做的就是將其聲明為Spring配置文件中的一個Bean。另外,為了使它成為一個切面,您必須在這個類中添加@ aspect注釋。這是更新后的AppConfig。java文件,修改為將LoggingAspect聲明為一個切面。


package com.packt.patterninspring.chapter1.bankapp.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

import com.packt.patterninspring.chapter1.bankapp.aspect.LoggingAspect;
import com.packt.patterninspring.chapter1.bankapp.repository.AccountRepository;
import com.packt.patterninspring.chapter1.bankapp.repository.TransferRepository;
import com.packt.patterninspring.chapter1.bankapp.repository.jdbc.JdbcAccountRepository;
import com.packt.patterninspring.chapter1.bankapp.repository.jdbc.JdbcTransferRepository;
import com.packt.patterninspring.chapter1.bankapp.service.TransferService;
import com.packt.patterninspring.chapter1.bankapp.service.TransferServiceImpl;

@Configuration
@EnableAspectJAutoProxy
public class AppConfig {
    
    @Bean
    public TransferService transferService(){
        return new TransferServiceImpl(accountRepository(), transferRepository());
    }
    @Bean
    public AccountRepository accountRepository() {
        return new JdbcAccountRepository();
    }
    @Bean
    public TransferRepository transferRepository() {
        return new JdbcTransferRepository();
    }
    @Bean
    public LoggingAspect loggingAspect() {
        return new LoggingAspect();
    }
}

在這里,我們使用了基于Java的Spring AOP配置來聲明LoggingAspectbean來作為一個切面,首先,我們要定義LoggingAspect為一個Bean,然后我們用@Aspect注解來注釋這個Bean。

我們使用@ before注釋來注釋LoggingAspect的logBeforeTransfer(),以便在執行transferAmount()之前調用該方法。這是在建議之前。然后,我們使用@ after注釋來標注另一個LoggingAspect的方法,聲明logAfterTransfer()方法應該在transferAmount()執行之后調用。這就是所謂的“advice(通知)”。
@ enableaspectjautoproxy用于在應用程序中啟用Spring AOP特性。這個注釋實際上強制您將代理應用于spring配置文件中定義的某些組件。稍后我們將討論更多關于Spring AOP的內容,在C - hapter 6中,使用代理和裝飾器模式的Spring面向方面編程。就目前而言,您已經要求Spring在調用TransferService類的transferAmount()之前來調用logBeforeTransfer()和logAferTransfer()方法,這就足夠了。現在,從這個例子中有兩點需要注意:

  • LoggingAspect仍然是一個POJO(如果忽略@ aspect注釋或者使用基于xml的配置)——它沒有說明它將被用作一個方面。
  • 重要的一方面是,LoggingAspect可以應用于TransferService,而不需要將其顯式地調用。事實上,TransferService仍然完全不知道LoggingAspect的存在。

讓我們換一種方式來簡化Java開發。

Applying the template pattern to eliminate boilerplate code(應用模板模式消除樣板代碼)

在企業應用程序的某一點上,我們看到了一些代碼,這些代碼看起來就像我們之前在同一個應用程序中編寫的代碼。這就是樣板代碼。我們經常需要在同一個應用程序中反復編寫代碼,以實現應用程序不同部分的共同需求。不幸的是,有很多地方的Java api涉及一堆樣板代碼。一個常見的例子是 ,當我們使用JDBC從數據庫查詢數據的時候,就會看到很多樣板代碼,如果你從來沒有用過JDBC,你可能要寫下面的代碼來解決問題:

  • 從連接池獲取一個數據庫連接
  • 創建一個PreparedStatement對象
  • 綁定SQL參數
  • 執行PreparedStatement對象
  • 從ResultSet對象中檢索數據并填充數據容器對象
  • 釋放所有的數據庫資源

讓我們看看下面的代碼,它包含了與Java的JDBC API的樣板代碼:


public Account getAccountById(long id) {
  Connection conn = null;
  PreparedStatement stmt = null;
  ResultSet rs = null;
  try {
    conn = dataSource.getConnection();
    stmt = conn.prepareStatement(
    "select id, name, amount from " +
    "account where id=?");
    stmt.setLong(1, id);
    rs = stmt.executeQuery();
    Account account = null;
  if (rs.next()) {
    account = new Account();
    account.setId(rs.getLong("id"));
    account.setName(rs.getString("name"));
    account.setAmount(rs.getString("amount"));
  }
    return account;
  } catch (SQLException e) {
  } finally {
    if(rs != null) {
  try {
    rs.close();
  } catch(SQLException e) {}
  }
    if(stmt != null) {
    try {
      stmt.close();
        } catch(SQLException e) {}
    }
    if(conn != null) {
    try {
      conn.close();
    } catch(SQLException e) {}
  }
  }
    return null;
  }

在前面的代碼中,我們可以看到JDBC代碼查詢數據庫的帳戶名和數量。對于這個簡單的任務,我們必須創建一個連接,然后創建一個語句,最后查詢結果。
我們還必須捕獲SQLException,一個被檢查的異常,即使它被拋出的情況并不多。
最后,我們必須清理這些混亂,關閉連接語句和結果集。這也可能強制它處理JDBC的異常,因此您也必須在這里捕獲SQLException
這種樣板代碼嚴重損害了可重用性。

Spring JDBC通過使用模板設計模式解決了樣板代碼的問題,它通過刪除模板中的通用代碼使代碼看起來變得非常簡單。這使得數據訪問代碼非常干凈,并防止了一些煩人的問題,比如連接泄漏,因為Spring框架確保了所有數據庫資源都能正常釋放。

Spring中的模板設計模式

讓我們來看看怎么在Spring中使用模板模式:

  • 定義算法的概要或框架
  1. 將細節留給特定的實現,以后的子類來實現。
  2. 隱藏大量的樣板代碼。
  • Spring提供了很多樣板類
  • JdbcTemplate
  • JmsTemplate
  • RestTemplate
  • WebServiceTemplate
  • 大多數隱藏了低級的資源管理

讓我們看一下前面的代碼,這此我們使用Spring JdbcTemplate,來看看它是如何使用的
消除樣板代碼。

使用jdbctemplate讓您的代碼專注于任務:


public Account getAccountById(long id) {
  return jdbcTemplate.queryForObject(
    "select id, name, amoount" +
    "from account where id=?",
    new RowMapper<Account>() {
      public Account mapRow(ResultSet rs,
        int rowNum) throws SQLException {
          account = new Account();
          account.setId(rs.getLong("id"));
          account.setName(rs.getString("name"));
          account.setAmount(rs.getString("amount"));
          return account;
          }
        },
  id);
}

正如你所看到的在前面的代碼中,這個新版本的getAccountById()是相比之前的樣板代碼要簡單得多,這里的方法是集中在從數據庫中選擇一個帳戶,而不是創建一個數據庫連接、創建一個語句,執行查詢,處理SQL異常,最后關閉連接。使用該模板,您必須提供SQL查詢和一個RowMapper,用于將結果集數據映射到模板的queryForObject()方法中的域對象。該模板負責執行此操作的所有操作,如數據庫連接等。它還在框架背后隱藏了大量的樣板代碼。

在本節中,我們看到了Spring如何利用基于pojo的開發和設計模式(比如DI模式、使用代理模式和模板方法設計模式)的力量來簡化Java開發的復雜性。

在下一節中,我們將討論如何使用Spring容器來創建和管理應用程序中的Spring bean。

使用Spring容器用工廠模式來管理bean

Spring提供給我們一個容器,我們的應用程序對象都生活在這個Spring容器中,在下面的這個圖中,容器的職責就是創建和管理對象。


In a Spring application, our application objects live in this Spring container

Spring容器還根據其配置將許多對象連接在一起。它配置了一些初始化的參數,并從頭到尾管理其完整的生命周期。

基本上,有兩種不同類型的Spring容器:

  • Bean factory
  • Application contexts

Bean factory

在Spring框架中,org.springframework.beans.factory.BeanFactory這個接口提供了bean factory,這就是Spring IoC容器。XmlBeanFactory是這個接口的一個實現。這個容器從一個XML文件讀取配置元數據。它基于GOF工廠方法設計模式——它以復雜的方式創建、管理、緩存和連接應用程序對象。bean工廠僅僅是一個對象池,對象是由配置創建和管理的。對于小型應用程序,這是足夠的,但是企業應用程序需要更多,所以spring提供了另一個具有更多特性的spring容器。

在下一節中,我們將學習應用程序上下文和Spring
怎么在應用程序中創建它。

Application contexts

在Spring容器中,org.springframework.context.ApplicationContext接口提供了Spring IoC容器,它只是bean工廠的包裝器,提供了一些額外的應用程序上下文服務,比如支持AOP,聲明性事務、安全性和工具支持(例如支持國際化所需的消息資源)以及向事件偵聽器發布應用程序事件的能力。

在application context中創建一個容器

Spring提供了幾種應用程序上下文作為bean容器。ApplicationContext接口的多個核心實現如下所示:

  • FileSystemXmlApplicationContext: 這個類是ApplicationContext的實現,它從位于文件系統的配置文件(XML)中加載應用程序上下文bean定義。
  • ClassPathXmlApplicationContext: 這個類是ApplicationContext的實現,它從位于應用程序類路徑的配置文件(XML)中加載應用程序上下文bean定義。
  • AnnotationConfigApplicationContext: 這個類是ApplicationContext的實現,它從應用程序的類路徑加載來自配置類(基于Java)的應用程序上下文bean定義。

Spring為您提供了一個web感知的ApplicationContext接口實現,如下所示:

  • XmlWebApplicationContext: 這個類是ApplicationContext的web感知實現,它從web應用程序中包含的配置文件(XML)中加載應用程序上下文bean定義。
  • AnnotationConfigWebApplicationContext:這個類是一個web感知的ApplicationContext實現,它從一個或多個基于java的配置類中加載Spring web應用程序上下文bean定義。

我們可以使用其中任何一個實現將bean加載到一個bean工廠中。這取決于我們的應用程序配置文件位置。例如,如果您想加載配置文件spring。xml文件系統的一個特定的位置,春天為你提供了一個FileSystemXmlApplicationContext,類,尋找春天的配置文件。xml在文件系統中的特定位置:

ApplicationContext context = new
FileSystemXmlApplicationContext("d:/spring.xml");

同樣,您也可以加載應用程序配置文件spring。xml應用程序的類路徑使用ClassPathXmlApplicationContext類,它查找配置文件spring.xml。包括類路徑中的xml(包括JAR文件):

ApplicationContext context = new
ClassPathXmlApplicationContext("spring.xml");

如果你使用的是Java配置,我們可以這么做:

ApplicationContext context = new
AnnotationConfigApplicationContext(AppConfig.class);

加載配置文件并獲取應用程序上下文之后,我們可以通過調用應用程序上下文的getBean()方法從Spring容器中獲取bean:

TransferService transferService =
context.getBean(TransferService.class);

容器中的bean的生命周期

Spring應用程序上下文使用工廠方法設計模式按照給定配置在容器中創建Spring bean。因此,Spring容器有責任管理從創建到銷毀的bean的生命周期。在普通java應用程序中,java的new關鍵字用于實例化bean,實例化之后它已經可以使用了。一旦bean不再使用,它就有資格進行垃圾收集。但是在Spring容器中,bean的生命周期更詳細。下圖顯示了典型的Spring bean的生命周期:

The life cycle of a Spring bean in the Spring container is as follows:

Spring容器中的Spring bean的生命周期如下:

  1. 加載所有bean定義,創建有序圖。
  2. 實例化和運行BeanFactoryPostProcessors(你可以在這里更新bean定義)
  3. 實例化每一個Bean
  4. Spring將值和bean引用注入到bean的屬性中。
  5. 如果任何bean實現它,Spring將bean的ID傳遞給BeanNameAware接口的setBeanName()方法。
  6. Spring將bean工廠自身的引用傳遞給BeanFactoryAwaresetBeanFactory()方法,如果任意bean實現了它。
  7. Spring將應用程序上下文本身的引用傳遞給應用程序的setApplicationContext()方法,如果有任何bean實現它的話。
  8. BeanPostProcessor是一個接口,Spring允許您實現它
    您的bean,并在調用初始化器之前,在Spring bean容器通過調用 postProcessBeforeInitialization()來修改bean的實例。
  9. 如果您的bean實現了InitializingBean接口,Spring調用它的afterPropertiesSet()方法來初始化應用程序的任何進程或加載資源。它依賴于指定的初始化方法。還有其他方法可以實現這一步驟,例如,您可以使用< bean >標記的init方法、@ bean注釋的initMethod屬性和JSR 250的@ postconstruct注釋。
  10. 現在您的bean已經可以在步驟中使用了,并且您的應用程序可以使用應用程序上下文的getBean()方法來訪問這個bean。您的bean在應用程序上下文中仍然存在,直到它通過調用應用程序上下文的close()方法關閉為止。
  11. 如果您的bean實現了處理bean接口,Spring調用它的destroy()方法來銷毀任何進程或清除應用程序的資源。還有其他方法可以實現這一步驟,例如,您可以使用< bean >標記的破壞方法、@ bean注釋的destroy方法屬性和JSR 250的@ predestroy注釋。
  12. 這些步驟顯示了容器中的Spring bean的生命周期。
  13. 下一節將描述Spring提供的模塊

Spring modules

Spring框架對于一組特定的功能有幾個不同的模塊,
他們互不依賴。這個系統非常靈活,所以在企業應用開發中,開發者可以只選擇他們需要的模塊。比如,例如,開發人員可以只使用Spring DI模塊,并使用非Spring組件構建應用程序的其余部分。因此,Spring提供了與其他框架和api一起工作的集成點——例如,您可以只使用Struts應用程序來使用Spring Core DI模式。如果開發團隊更精通使用Struts,那么可以使用它而不是Spring MVC,而其他應用程序則使用Spring組件和特性,比如JDBC和事務。因此,開發人員需要使用Struts應用程序來部署所需的依賴關系,不需要添加整個Spring框架。

下面是整個模塊結構的概述:

Spring框架的不同模塊

讓我們來看看你每個模塊:

核心Spring容器

Spring框架的這個模塊使用了大量的設計模式,如工廠方法設計模式、DI模式、抽象工廠設計模式、單體設計模式、原型設計模式等等。所有其他Spring模塊都依賴于此模塊。在配置應用程序時,您將隱式地使用這些類。它也被稱為IoC容器,是Spring支持依賴注入的中心,它管理著Spring中的bean創建、配置和管理應用程序。您可以通過使用BeanFactory的實現或ApplicationContext的實現來創建Spring容器。這個模塊包含Spring bean factory,它是提供DI的Spring的一部分。

Spring AoP 模塊

Spring AOP是一個基于java的AOP框架,其中包含了AspectJ集成。它使用面向切面編織的動態代理,并著重于使用AOP來解決企業問題。此模塊基于代理和裝飾器設計模式。該模塊實現了橫切關注點的模塊化,避免了(avoid tangling and eliminate scattering.)糾纏和消除了散射。

與DI一樣,它支持核心業務服務和橫切關注點之間的松散耦合。您可以實現自定義切面,并在應用程序中以聲明方式對它們進行配置,而不會影響業務對象的代碼。它在代碼中提供了很大的靈活性;您可以刪除或更改方面邏輯,而無需涉及業務對象的代碼。這是spring框架的一個非常重要的模塊,所以我將在第6章中詳細討論它,這是本書的代理和裝飾模式。

Spring DAO -數據訪問和集成

Spring DAO和Spring JDBC通過使用模板來刪除公共代碼使生活變得非常簡單。模板實現了GOF模板方法設計模式,并提供了合適的擴展點來插入自定義代碼。如果您使用的是傳統的JDBC應用程序,則必須編寫大量的樣板代碼,例如,創建數據庫連接、創建語句、查找結果集、處
SQLException,最后關閉連接。如果您使用的是帶有DAO層的Spring JDBC框架,那么您不必編寫樣板代碼,這與傳統的JDBC應用程序不同。這意味著,Spring允許您保持應用程序代碼的干凈和簡單

Sping ORM

Spring還支持ORM解決方案,它提供了與ORM工具的集成,以便在關系數據庫中輕松地持久化POJO對象。這個模塊實際上為Spring DAO模塊提供了一個擴展。與基于jdbc的模板一樣,Spring提供了ORM模板來處理主要的ORM產品,如Hibernate、JPA、OpenJPA、TopLink、iBATIS等等。

Spring web MVC

Spring為企業web應用程序提供了一個web和遠程模塊。該模塊幫助構建高度靈活的web應用程序,充分利用了Spring IOC容器的全部優點。這個Spring模塊使用了MVC架構模式、前端控制器模式和DispatcherServlet模式等模式,并與servlet API無縫集成。Spring web模塊非常可插入和靈活。我們可以添加任何視圖技術,如JSP、FreeMarker、Velocity等。我們還可以使用spring IOC和DI將其與其他框架集成,例如Struts、Webwork和JSF。

Spring 5.0 的新特性

Spring 5.0是Spring可用的最新的版本。在Spring 5.0中有很多令人興奮的新特性,包括:

  • 支持JDK 8 + 9和Java EE 7基線:
    • Spring 5支持Java 8作為最低要求,因為整個框架代碼庫是基于Java 8的。
    • Spring框架至少需要Java EE 7來運行Spring Framework 5.0應用程序。這意味著它需要Servlet 3.1、JMS 2.0、JPA 2.1。
  • 棄用和刪除包、類和方法:
    • 在Spring 5.0中,一些包已被刪除或棄用。它有一個叫做mock的包。靜態從spring-aspects刪除模塊,因此沒有對AnnotationDrivenStaticEntityMockingControl的支持。
    • web.view等包。tiles2和orm。在Spring 5.0中也刪除了hibernate3 / hibernate4。現在,在最新的spring框架中,tile 3和Hibernate 5正在被使用。
    • pring 5.0框架不支持Portlet、Velocity、JasperReports、XMLBeans、JDO、Guava(等等)。
    • 在Spring 5.0中刪除了一些早期版本的棄用類和方法。
  • 添加了新的響應式編程模型:
    • 在Spring 5.0框架中引入了這種編程模型。讓我們看看下面列出的關于反應性編程模型的列表。
    • Spring 5引入了Spring - core模塊DataBuffer和encoder / decoder抽象,將非阻塞語義引入到響應性編程模型中。
    • 使用反應模型,Spring 5.0提供了使用JSON(Jackson)和XML(JAXB)支持的HTTP消息codec實現的Spring - web模塊。
    • Spring反應式編程模型為@ controller編程模型添加了一個新的Spring -web -reactive,將響應流調整為Servlet 3.1容器,以及非Servlet運行時,如Netty和Undertow。
    • Spring 5.0還引入了一個新的WebClient,它在客戶端提供了響應性支持來訪問服務。

如這里所列,您可以看到Spring Framework 5中有許多令人興奮的新特性和增強。所以在這本書中,我們將通過例子和他們所采用的設計模式來研究這些新特性。

總結

讀完這一章之后,您現在應該對Spring框架及其最常用的設計模式有一個很好的概述。我強調了J2EE傳統應用程序的問題,以及Spring如何通過使用大量的設計模式和良好的實踐來創建應用程序來解決這些問題并簡化Java開發。Spring的目標是使企業Java開發更容易,并促進松散耦合的代碼。
我們還討論了跨切關注點的Spring AOP和用于松耦合和可插拔彈簧的DI模式組件,以便對象不需要知道它們的依賴關系來自哪里,以及它們是如何實現的。Spring框架是最佳實踐和有效對象設計的推動者。Spring框架有兩個重要的特性——首先它有一個Spring容器來創建和管理bean的生命,其次它提供了對多個模塊和集成的支持,以幫助簡化Java開發。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,973評論 19 139
  • Spring Boot 參考指南 介紹 轉載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,970評論 6 342
  • 文章作者:Tyan博客:noahsnail.com 3.4 Dependencies A typical ente...
    SnailTyan閱讀 4,206評論 2 7
  • 什么是Spring Spring是一個開源的Java EE開發框架。Spring框架的核心功能可以應用在任何Jav...
    jemmm閱讀 16,561評論 1 133
  • “我不能再靠近了。”渡一邊摁住幾乎要被狂風卷走的禮帽,一邊艱難地說道,“我只能把你引到這里,里面的路只能靠你一人去...
    鶴九0閱讀 482評論 0 0