Green Beans: Putting the Spring in Your Step (and Application)

工程 | Josh Long | 2010年11月09日 | ...

Spring框架在2003年成為事實上的標準,並一直以來幫助人們用更簡潔的程式碼構建更大、更好的應用程式。在本文中,我們將討論使用Spring元件模型配置應用程式的可用選項。我們將從最簡單的形式構建一個簡單的應用程式,並對其進行重構,以利用Spring框架中的許多簡化功能,這些功能使其成為當今應用程式的事實標準,並將繼續如此。

當今的企業Java應用程式有許多協同工作的物件,它們協同工作以達成一個目標,通常是具有內在商業價值的目標。這個物件圖很深,即使在簡單的情況下也是如此。例如,考慮一個想要與資料庫通訊的服務(可能是一個支援處理客戶資料的服務?)。這樣的服務需要一個數據源,並且可以選擇某種便利庫來促進資料庫訪問,無論是透過JDBC、JPA、JDO、NoSQL選項等。在這種圖中,直接在需要它們的站點建立物件是很誘人的。在這種系統中,物件構造或獲取的知識分散在使用它們的bean中。如果——就像資料庫javax.sql.DataSource一樣——該物件在多個地方都需要,那麼在一個地方設定所有物件然後再共享新建立的例項會更清晰。這具有將易出錯和易變的配置資訊集中在一個易於更改的地方的優點(例如,當開發和生產環境中的資料庫憑據不同時)。

這是人們使用Spring的主要原因之一——因為Spring使人們能夠集中描述這些協同工作的物件。從Spring的早期版本開始,就有一個XML檔案用於描述物件圖。在早期(約2003年),該檔案使用DTD,但現在使用XML Schema。下面是我們用Spring的XML格式描述的示例服務。隨著進展,我們將逐步刪除更多的XML配置。每個bean元素描述一個將被建立的物件,並賦予它一個id。每個property元素描述物件上的一個setter方法,以及應該賦予它的值。這些setter由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">

    <bean class="org.springframework.jdbc.datasource.DriverManagerDataSource" id="ds">
        <property name="driverClassName" value="org.h2.Driver"/>
        <property name="url" value="jdbc:h2:~/cs"/>
        <property name="username" value="sa"/>
        <property name="password" value=""/>
    </bean>

    <bean class="org.springframework.jdbc.core.JdbcTemplate" id="jdbcTemplate">
      <constructor-arg ref="ds"/>
    </bean>

    <bean class="org.springframework.samples.DatabaseCustomerService" id="databaseCustomerService">
        <property name="jdbcTemplate" ref="jdbcTemplate"/>
    </bean>

    <bean class="org.springframework.samples.CustomerClient" id="client">
        <property name="customerService" ref="databaseCustomerService"/>
    </bean>

</beans>

很簡單,對吧?好處是我們可以在不瞭解具體實現的情況下,針對介面編寫程式碼。在上面的示例中,我們例項化一個DriverManagerDataSource,並透過ref屬性將其傳遞給JdbcTemplate例項的建構函式。ref告訴Spring框架您想傳遞對同一容器中配置的另一個bean的引用。類似地,在我們的示例中,我們傳遞了一個對CustomerClient例項的引用,但在消費Java程式碼中,我們針對CustomerService介面進行編碼,而不是特定的DatabaseCustomerService型別。

這種簡單的設定賦予我們大量的間接性和靈活性。現在物件建立和構造已移至Spring配置,我們可以隱藏Spring配置中非常複雜的設定,讓我們的程式碼對此一無所知。一種隱藏複雜構造邏輯的常見方法是使用工廠模式。工廠模式尤其適用於您正在構建大量需要協同工作的物件,或者您想在建立物件時考慮許多不同因素的情況。本質上,您正在提供一種比任何單個類的建構函式自然能做到的更強大的物件建立描述方式。Spring明確支援此模式。如果配置的bean實現了org.springframework.beans.factory.FactoryBean<T>介面,則將呼叫介面上的getObject()方法,其結果將是Spring上下文中可用的物件。這種做法在Spring框架本身中被廣泛使用,以提供一種可重用方式方便地構造複雜的物件圖。

在我們的示例中,我們使用一個名為H2的嵌入式資料庫,它是一個功能強大的記憶體Java資料庫。通常,嵌入式資料庫在開發環境中使用。一種常見的做法是在開發中使用嵌入式資料庫來快速測試和重置資料集。通常,這還包括從SQL指令碼載入資料來引導嵌入式資料庫。Spring框架為配置嵌入式資料來源和然後評估javax.sql.DataSource提供顯式支援。

Spring 3.1中的特定環境Bean - 在Spring框架的下一個迭代中,我們將引入對特定環境Bean的支援。特定環境Bean提供了一種更直接的方式,根據某些環境開關是否為真來“開啟”Bean。這在少數定義明確的場景中很有用,在這些場景中,Bean配置在不同環境中會發生劇烈變化。這裡的示例——在測試中使用嵌入式資料庫,而在生產中使用另一個javax.sql.DataSource——就是這樣的場景。當然,使用Spring 3.0或更早版本實現相同靈活性的其他方法包括FactoryBeanPropertyPlaceHolderConfigurer

回顧我們之前的例子,我們可以像這樣宣告我們的javax.sql.DataSource,名為ds,而不是

    <bean id="ds" class="org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseFactoryBean">
        <property name="databaseType" value="H2"/>
        <property name="databasePopulator">
            <bean class="org.springframework.jdbc.datasource.init.ResourceDatabasePopulator">
                <property name="scripts" value="setup.sql"/>
            </bean>
        </property>
    </bean> 

這個FactoryBean負責讀取任何SQL指令碼(這裡我只指定了一個名為setup.sql的檔案,但您可以指定任意數量的檔案,逗號分隔)並將它們載入到資料庫中,然後以簡單方便的方式返回一個javax.sql.DataSource例項。

我們已經看到了FactoryBean可以有多強大的作用。一個設計良好的FactoryBean將暴露最可能在設定物件時有用的選項,並且在執行時它還將提供關於各種選項無效配置的反饋。然而,XML可以透過XML Schema提供的驗證在設計時提供更多指導。這就是為什麼Spring框架長期以來支援使用基於XML Schema的名稱空間,這些名稱空間提供更多的反饋和簡化。讓我們回顧一下嵌入式資料庫的例子。Spring框架提供了一個用於配置嵌入式資料來源的名稱空間。要在Spring中使用名稱空間,只需限定名稱空間並新增對schemaLocation元素的引用。之前的Spring配置檔案,在修改以支援JDBC 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:jdbc="http://www.springframework.org/schema/jdbc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans  http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd">
  <!-- ...  same as before ... -->
</beans>

這個宣告將使名稱空間中的限定元素在任何現代IDE的XML自動完成功能中得到提議。安裝後,我們現在可以用更簡潔的東西替換以前的宣告。

   <jdbc:embedded-database id="ds" type="H2">
        <jdbc:script location="classpath:setup.sql"/>
   </jdbc:embedded-database>

這在功能上與之前的示例等效:它建立一個嵌入式資料庫,並在嵌入式資料庫啟動時評估指令碼的內容。它建立一個javax.sql.DataSource型別的物件,就像之前一樣。我們已經方便地將其精煉為本質。看起來我們的工作完成了,我們可以繼續了,對吧?嗯,還不完全是。我們仍然有很多工作可以做,以進一步減少配置。這裡顯示的一些程式碼是我們編寫的自定義程式碼。如果我們願意註解程式碼,那麼我們可以讓Spring自行處理,而不是必須顯式地寫出來。要做到這一點,我們需要在檔案中新增context名稱空間並啟用元件掃描。元件掃描掃描帶有某些註解的Bean,並自動註冊它們。類似地,類本身上發現的註解將被處理。這是包含適當XML名稱空間的修訂版XML檔案。


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:context="http://www.springframework.org/schema/context"
 	  xsi:schemaLocation="... http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="org.springframework.samples"/>
    <!-- ...  same as before ... -->
</beans>

我們在org.springframework.samples包中用Spring框架的@Component註解的所有Bean都將被拾取並作為Bean註冊到上下文中,就像我們在XML中使用bean元素配置它們一樣。我們已經用@Component註解了DatabaseCustomerServiceCustomerClient類,這使我們可以從XML配置中刪除這些Bean的等效bean元素。元件掃描非常方便,因為Spring做了很多繁重的工作,儘管它使配置去中心化。

我們知道這個Bean依賴於JdbcTemplateJdbcTemplate已經配置在上下文中了。由於只配置了一個,我們可以簡單地將類上的setter註解為@Autowired,這告訴Spring透過型別解析依賴並注入它。如果上下文中配置了多個例項,在這種情況下會丟擲錯誤。

依賴解析的註解 - 除了@Autowired之外,還有許多方法可以告訴Spring注入哪個Bean。您可以使用JSR 330支援的註解,如@javax.inject.Inject,或JSR 250的@javax.annotation.Resource,或Spring的@Value註解。
package org.springframework.samples;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;

import java.sql.ResultSet;
import java.sql.SQLException;

@Component
public class DatabaseCustomerService implements CustomerService {
    private JdbcTemplate jdbcTemplate;
    private RowMapper<Customer> customerRowMapper = new CustomerRowMapper();

    public Customer getCustomerById(long id) {
        return jdbcTemplate.queryForObject(
            "select * from CUSTOMERS where ID = ?", this.customerRowMapper, id);
    }

    @Autowired
    public void setJdbcTemplate( JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    class CustomerRowMapper implements RowMapper<Customer> {
        public Customer mapRow(ResultSet resultSet, int i)  throws SQLException {
            String fn = resultSet.getString("FIRST_NAME");
            String ln = resultSet.getString("LAST_NAME");
            String email = resultSet.getString("EMAIL");
            long id = resultSet.getInt("ID");
            return new Customer(id, fn, ln, email);
        }
    }
}

最後一個類是使用CustomerService例項的客戶端。我們像以前一樣註冊它,使用@Component註解。它需要對CustomerService例項的引用,就像DatabaseCustomerService例項需要對JdbcTemplate的引用一樣。所以,我們使用了我們老朋友@Autowired

package org.springframework.samples;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.support.GenericXmlApplicationContext;
import org.springframework.stereotype.Component;

@Component
public class CustomerClient {
	private CustomerService customerService ;

	@Autowired
	public void setCustomerService(CustomerService customerService) {
		this.customerService = customerService;
	}

	public void printCustomerInformation ( long customerId ) {
		Customer customer = this.customerService.getCustomerById( customerId );
		System.out.println( customer ) ;
	}
}

我們修訂後的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:jdbc="http://www.springframework.org/schema/jdbc"
       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/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="org.springframework.samples"/>

    <jdbc:embedded-database id="ds" type="H2">
        <jdbc:script location="classpath:setup.sql"/>
    </jdbc:embedded-database>

    <bean class="org.springframework.jdbc.core.JdbcTemplate" id="jdbcTemplate">
        <constructor-arg ref="ds"/>
    </bean>

</beans>

我們使用名稱空間支援快速配置了一個嵌入式javax.sql.DataSource例項。我們使用了元件掃描,讓Spring自動為我們註冊DatabaseCustomerServiceCustomerClient Bean。事情看起來不錯,但我們仍然使用XML來描述JdbcTemplate的配置,而我們可以更方便地用Java來描述它。在這種情況下,XML並不比普通的Java更有說服力;它只提供對等性。如果我們也能對JdbcTemplate例項使用元件掃描就好了。然而,元件掃描只對用@Component註解的Bean有效。由於我們不能為我們可能沒有原始碼的第三方類新增@Component註解,所以元件掃描不是JdbcTemplate例項的選項。

Spring提供了Java配置支援,讓您直接使用Java來描述和配置Bean。Java配置選項提供了兩全其美的好處:它允許您配置任何類,即使是您沒有原始碼的類(就像XML配置選項一樣),並且它仍然是Java為中心的,因此可以利用Java語言的所有型別安全(以及Java IDE中可用的重構工具)。

Java配置處理註冊到上下文的Bean,並查詢用@Bean註解的方法並呼叫它們。方法呼叫的結果作為Bean註冊到應用程式上下文中,就像您使用XML配置物件一樣。Bean的型別是返回物件的型別,而id則取自方法名。由於配置由方法中的Java程式碼提供,您可以進行任何方式的設定,這與FactoryBean讓您做的非常相似。人們通常選擇Java配置選項,因為它允許您將Bean配置儲存在一兩個知名、集中的類中。XML配置和Java配置都提供了一種集中描述應用程式的方式。

配置類就像任何其他Spring Bean一樣。適用於普通Spring Bean的所有規則都適用於配置Bean,除了用@Bean註解的方法。Spring會透過元件掃描拾取您的配置類。如果您想在配置類中使用其他Bean(例如,我們之前使用XML名稱空間配置的javax.sql.DataSource例項),您可以使用所有可用的正常選項來獲取它們,包括@Autowired。讓我們來看看我們示例的配置類。


package org.springframework.samples;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import org.springframework.jdbc.core.JdbcTemplate;

import javax.sql.DataSource;

@Configuration
public class CustomerConfiguration {

	@Autowired private DataSource dataSource;

	@Bean
	public JdbcTemplate jdbcTemplate() {
		return new JdbcTemplate(this.dataSource);
	}
}

這個類使用@Autowired來獲取對嵌入式資料來源的引用。這與之前示例中的用法類似,除了這裡我們在私有欄位變數上使用註解,而不是setter方法。Spring框架可以在類的建構函式、欄位變數或setter方法上使用註解。最後,我們有一個用@Bean註解的方法。這個方法提供了JdbcTemplate例項的定義,這意味著我們可以從檔案中刪除XML配置。我們在這裡沒有這樣做,但您可以在一個類中定義多個@Bean定義方法,並且它們可以透過簡單地呼叫彼此來引用。如果一個用@Bean註解的方法呼叫另一個,返回值將是新建立的物件,或者——如果Bean已經建立——則是在上下文中已註冊的Bean。在上面的類中,我們還在類上有一個@Configuration註解。這個註解告訴Spring將這個類視為一種特殊的元件型別,專門用於配置。本質上,這個Bean享有Spring上下文中註冊的任何Bean的相同服務,*並且*它還應用了額外的服務來啟用Java配置。要使用Java配置,請確保您的類路徑上有CGLIB庫。

最終修訂的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:jdbc="http://www.springframework.org/schema/jdbc"
       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/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="org.springframework.samples"/>

    <jdbc:embedded-database id="ds" type="H2">
        <jdbc:script location="classpath:setup.sql"/>
    </jdbc:embedded-database>   

</beans>

在本文中,我們追蹤了您也可以遵循的步驟,透過Spring的依賴注入能力來獲得更簡潔、更友好的程式碼庫。儘管我們討論了提供比任何其他技術更靈活、更多功能的絕妙技術,但重要的是要記住,這種不可思議的支援至少已經存在了4年。其中大部分已經存在很多年了。人們長期以來一直將這些元件作為其應用程式的基石。依賴注入和控制反轉僅僅是開始。Spring框架提供了大量的簡化庫,用於基於此處建立的元件模型構建的日益增長的大量用例。

當您在Spring的基礎上構建應用程式時,您可以使自己不受部署應用程式的Web伺服器、應用程式伺服器和雲環境的鎖定,同時最大化您在基礎平臺上的投資回報。

獲取 Spring 新聞通訊

透過 Spring 新聞通訊保持聯絡

訂閱

領先一步

VMware 提供培訓和認證,助您加速進步。

瞭解更多

獲得支援

Tanzu Spring 提供 OpenJDK™、Spring 和 Apache Tomcat® 的支援和二進位制檔案,只需一份簡單的訂閱。

瞭解更多

即將舉行的活動

檢視 Spring 社群所有即將舉行的活動。

檢視所有