Spring基礎篇——通過Java註解和XML配置裝配bean

来源:https://www.cnblogs.com/chenbenbuyi/archive/2018/02/23/8457700.html
-Advertisement-
Play Games

自動化裝配的確有很大的便利性,但是卻並不能適用在所有的應用場景,比如需要裝配的組件類不是由自己的應用程式維護,而是引用了第三方的類庫,這個時候自動裝配便無法實現,Spring對此也提供了相應的解決方案,那就是通過顯示的裝配機制——Java配置和XML配置的方式來實現bean的裝配。 Java配置類裝 ...


  自動化裝配的確有很大的便利性,但是卻並不能適用在所有的應用場景,比如需要裝配的組件類不是由自己的應用程式維護,而是引用了第三方的類庫,這個時候自動裝配便無法實現,Spring對此也提供了相應的解決方案,那就是通過顯示的裝配機制——Java配置和XML配置的方式來實現bean的裝配。

  Java配置類裝配bean

  我們還是藉助上篇博文中的老司機開車的示例來講解。Car介面中有開車的drive方法,該介面有兩個實現——QQCar和BenzCar

package spring.impl;

import spring.facade.Car;

public class QQCar implements Car {
    @Override
    public void drive() {
        System.out.println("開QQ車");
    }
}

  既然是通過Java代碼來裝配bean,那就是不是我們上一篇講的通過組件掃描的方式來發現應用程式中的bean的自動裝配機制了,而是需要我們自己通過配置類來聲明我們的bean。我們先通過@Configuration註解來創建一個Spring的配置類,該類中包含了bean的創建細節——

import org.springframework.context.annotation.Configuration;
import spring.facade.Car;
import spring.impl.QQCar;

/**
 * @Configuration 表明該類是Spring的一個配置類,該類中會包含應用上下文創建bean的具體細節
 * @Bean 告訴Spring該方法會返回一個要註冊成為應用上下文中的bean的對象
 */
@Configuration
public class CarConfig {

    @Bean
    public Car laoSiJi() {
        return new QQCar();
    }
}

  以上類中創建的bean實例預設情況下和方法名是一樣的,我們也可以通過@Bean註解的name屬性自定義ID,例如 @Bean(name = "chenbenbuyi") ,那麼在獲取bean的時候根據你自己定義的ID獲取即可。接著我們測試——

package spring.test;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import spring.config.CarConfig;
import spring.facade.Car;

public class CarTest {
    @Test
    public void carTest() {
        ApplicationContext context = new AnnotationConfigApplicationContext(CarConfig.class);
        //根據ID從容器容獲取bean
        Car car = (Car) context.getBean("chenbenbuyi");
        car.drive();
    }
}

  以上測試能夠成功輸出,這就表明我們能夠獲取到QQCar的實例對象的,而這也是最簡單的基於Java配置類來裝配bean的示例了。但是你可能會說,明明是我們自己創建的Car的實例,怎麼就成了Spring為我們創建的呢?好吧,我們把@Bean註解拿開,測試當然是無法通過,會拋NoSuchBeanDefinitionException異常。這裡,你可能需要好好理解控制反轉的思想了:因為現在對於bean創建的控制權我們是交給了Spring容器的,如果沒有@Bean註解,方法就只是一個普通方法,方法體返回的實例對象就不會註冊到應用上下文(容器)中,也就說,Spring不會為我們管理該方法返回的實例對象,當我們在測試類中向容器伸手要對象的時候,自然就找不到。

  上述示例過於簡單,現在,我們要更進一步,給簡單的對象添加依賴,來完成稍微複雜一點的業務邏輯。車是需要老司機來開的,於是我們同上篇一樣定義一個Man類,Man的工作就是開車——

package spring.impl;

import spring.facade.Car;

public class Man {

    private Car car;public Man(Car car) {
        this.car = car;
    }

    public void work() {
        car.drive();
    }
}

  Car的對象實例是通過構造器註入,而Car的實例對象在配置類中通過方法laoSiJi()返回,所以我們在配置類中可以直接調用laoSiJi方法獲取bean註入到Man的實例對象——

package spring.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import spring.facade.Car;
import spring.impl.BenzCar;
import spring.impl.Man;

@Configuration
public class CarConfig {

    @Bean
    public Car laoSiJi() {
        return new BenzCar();
    }

    @Bean
    public Man work() {
        return new Man(laoSiJi());
    }
}

  測試類中通過上下文對象的getBean("work")方法就可以獲取到Man的實例對象,從而完成對老司機開車的測試。或許,你會覺得,work方法是通過調用laoSiJi方法才獲取的Car的實例的,實際上並非如此。因為有了@Bean註解Spring會攔截所有對該註解方法的調用,直接返回該方法創建的bean,也即容器中的管理的bean。也就是說,laoSiJi方法返回的bean交給了Spring容器管理後,當其他地方需要實例對象的時候,是直接從容器中獲取的第一次調用方法產生的實例對象,而不會重覆的調用laoSiJi方法。我們可以如下測試——

package spring.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import spring.facade.Car;
import spring.impl.BenzCar;
import spring.impl.Man;

@Configuration
public class CarConfig {

    @Bean
    public Car laoSiJi() {
        System.out.println("方法調用");
        return new BenzCar();
    }

    @Bean
    public Man work() {
        return new Man(laoSiJi());
    }

    @Bean   
    public Man work2() {
        return new Man(laoSiJi());
    }
}

  如上測試你會發現,雖然我定義了兩個方法來獲取Man實例,但是控制台只輸出了一次調用列印,即證明方法只在最初返回bean的時候被調用了一次,而後的實例獲取都是直接從容器中獲取的。這也就是預設情況下Spring返回的實例都是單例的原因:一旦容器中註冊了實例對象,應用程式需要的時候,就直接給予,不用重覆創建。當然,很多情況下我們不會如上面的方式去引入依賴的bean,而可能會通過參數註入的方式,這樣你就可以很靈活的使用不同的裝配機制來滿足對象之間的依賴關係,比如下麵這種自動裝配的方式給Man的實例註入依賴的Car對象——

package spring.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import spring.facade.Car;
import spring.impl.Man;

@Configuration
@ComponentScan("spring.impl")
public class CarConfig {

    @Bean
    public Man work(Car car) {
        return new Man(car);
    }
}

  當然,如果你喜歡去簡就繁,也可以通過XML配置文件配置依賴的bean。下麵再來看看XML的方式如何裝配bean。

  XML配置文件裝配bean

  使用XML配置文件的方式裝配bean,首要的就是要創建一個基於Spring配置規範的XML文件,該配置文件以<beans>為根元素(相當於Java配置的@Configuration註解),包含一個或多個<bean>元素(相當於配置類中@Bean註解)。針對上文的汽車示例,如果改成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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">
    
    <!--通過類的全限定名來聲明要創建的bean-->
    <bean class="spring.impl.BenzCar"></bean>
</beans>

  然後,從基於XML的配置文件中載入上下文定義,我們就能根據ID獲取到對應的bean了——

package spring.test;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import spring.facade.Car;

public class CarTest {
    @Test
    public void carTest() {
        ApplicationContext context = new ClassPathXmlApplicationContext("resource/applicationContext.xml");
        //XML的方式如果沒有明確給定ID,預設bean的ID會根據類的全限定名來命名,以#加計數序號的方式命名。
        Car car = (Car)context.getBean("spring.impl.BenzCar#0");
        car.drive();
    }
}

  當然,示例中使用自動化的命名ID看起來逼格滿滿,但其實並不實用,如果需要引用bean的實例就有點操蛋了,實際應用中當然還是要藉助<bean>的id屬性來自定義命名。

  構造器註入

  給<bean>元素設置id屬性,在構建另外的對象實例的時候,就可以很方便的引用,譬如上面基於Java的配置中的構造器註入,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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="car" class="spring.impl.BenzCar"></bean>
    <bean id="man" class="spring.impl.Man">
        <!--通過Man的構造器註入Car的實例對象-->
        <constructor-arg ref="car"></constructor-arg>
    </bean>
</beans>

  而有時候我們並不一定都是將對象的引用裝配到依賴對象中,也可以簡單的註入字面值——

package spring.impl;

import spring.facade.Car;

public class Man {

    private Car car;
    private String str;
public Man(String str ,Car car) { this.car = car; this.str = str; } public void work() { System.out.println(str); car.drive(); } }
<?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="car" class="spring.impl.BenzCar"></bean>
    <bean id="man" class="spring.impl.Man">
        <!--分別註入字面值和對象的應用-->
        <constructor-arg value="陳本布衣"></constructor-arg>
        <constructor-arg ref="car"></constructor-arg>
    </bean>
</beans>

  接著,我們繼續對已有代碼做些改動,將註入的參數改為Car的List集合——

 public Man(List<Car> cars) {
        this.cars = cars;
 }

   那麼配置文件就可以這樣配置——

<?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="benzCar" class="spring.impl.BenzCar"></bean>
    <bean id="qqCar" class="spring.impl.QQCar"></bean>
    <bean id="man" class="spring.impl.Man">
        <!--通過<list>子元素實現List集合對象的裝配-->
        <constructor-arg>
            <list>
                <ref bean="benzCar"/>
                <ref bean="qqCar"/>
            </list>
        </constructor-arg>
    </bean>
</beans>

  如果是需要註入集合中的字面值,寫法如下——

<?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="benzCar" class="spring.impl.BenzCar"></bean>
    <bean id="qqCar" class="spring.impl.QQCar"></bean>
    <bean id="man" class="spring.impl.Man">
        <!--通過<list>子元素實現List集合字面值的裝配-->
        <constructor-arg>
            <list>
                <value>這裡直接填寫字面值</value>
                <value>陳本布衣</value>
            </list>
        </constructor-arg>
    </bean>
</beans>

  我們可以採用同樣的方式裝配Set集合,只是Set集合會忽略掉重覆的值,而且順序也不保證。此處不做演示。

  屬性註入

  構造器註入是一種強依賴註入,而很多時候我們並不傾向於寫那種依賴性太強的代碼,而屬性的Setter方法註入作為一種可選性依賴,在實際的開發中是應用得非常多的。上面Man類如果要通過屬性註入的方式註入Car的實例,就該是這樣子——

package spring.impl;

import spring.facade.Car;

public class Man {

    private Car car;

    public void setCar(Car car) {
        this.car = car;
    }

    public void work() {
        car.drive();
    }
}
<?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="benzCar" class="spring.impl.BenzCar"></bean>
    <bean id="qqCar" class="spring.impl.QQCar"></bean>
    <bean id="man" class="spring.impl.Man">
        <!--通過屬性註入的方式註入Car的實例-->
        <property name="car" ref="benzCar"></property>
    </bean>
</beans>

  以上示例中,XML配置文件中屬性註入的屬性名必須要和Java類中Setter方法對應的屬性名一致。而對於字面量的註入,和上面構造器的方式類似,只不過使用的元素名換成了<property>而已,下麵僅做展示——

    <bean id="man" class="spring.impl.Man">
        <property name="str" value="字面量的註入"></property>
        <property name="list">
            <list>
                <value>集合的字面量註入1</value>
                <value>集合的字面量註入2</value>
            </list>
        </property>
    </bean>
<bean id="benzCar" class="spring.impl.BenzCar"></bean>
    <bean id="qqCar" class="spring.impl.QQCar"></bean>
    <bean id="man" class="spring.impl.Man">
        <!--屬性註入的方式註入集合-->
        <property name="cars">
            <list>
                <ref bean="qqCar"></ref>
                <ref bean="benzCar"></ref>
            </list>
        </property>
    </bean>

   三種裝配方式的混合使用

   在同一個應用程式中,Spring常見的這三種裝配方式我們可能都會用到,而對於不同的裝配方式,他們之間如何實現相互引用從而整合到一起的呢?我們先看看Java配置類的引用問題。試想如果Java配置類中的bean數量過多,我們可能會考慮拆分。在本文的示例中,Man類實例的創建必須通過構造器註入Car的實例,如果把兩個實例的產生分成兩個配置類,那麼在依賴註入的配置類中可以通過@Import註解引入被依賴的配置類——

package spring.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import spring.facade.Car;
import spring.impl.Man;

@Configuration
@Import(CarConfig.class) //通過@Import註解引入產生Car實例的配置類
public class ManConfig {
    @Bean
    public Man work(Car car) {
        return new Man(car);
    }
}

  但是如果Car的實例不是通過Java類配置的,而是通過XML方式配置的方式配置,我們只需通過@ImportResource註解將配置bean的XML文件引入即可,只不過這個時候要保證XML中被依賴的bean的id要和Java配置類中的形參保持一致——

package spring.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;
import spring.facade.Car;
import spring.impl.Man;

@Configuration
@ImportResource("classpath:resource/applicationContext.xml")
public class ManConfig {
    @Bean
    public Man work(Car car) {
        return new Man(car);
    }
}

   而如果bean是採用XML進行裝配,如果需要裝配的bean過多,我們當然還是會根據業務拆分成不同的配置文件,然後使用<improt>元素進行不同XML配置文件之間的引入,形如: <import resource="classpath:xxx.xml" /> ;而如果要在XML中引入Java配置,只需將Java配置類當成普通的bean在XML中進行聲明即可,但是在測試的時候要註意開啟組件掃描,因為載入XML配置的上下文對象只會載入XML配置文件中的bean定義,無法讓基於Java配置類產生bean的裝配機制自動生效——

 

<?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"
> <!--開啟組件掃描,在測試的時候配置類才能向容器中註冊類中聲明的bean--> <context:component-scan base-package="spring"/> <!--XML中引入Java配置類:將配置類聲明為bean--> <bean class="spring.config.CarConfig"></bean> <bean id="man" class="spring.impl.Man"> <constructor-arg ref="laoSiJi"></constructor-arg> </bean> </beans>

  最後說一點,不管是Java配置還是XML配置,有個通常的做法就是創建一個比所有配置都更高層次的根配置類/文件,該配置不聲明任何的bean,只用來將多個配置組合在一起,從而讓配置更易於維護和擴展。好了,以上便是兩種bean的裝配方式的簡單講解,如有紕漏,歡迎指正,不勝感激。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 前面我們曾有篇文章中提到過關於用tensorflow訓練手寫28 28像素點的數字的識別,在那篇文章中我們把手寫數字圖像直接碾壓成了一個784列的數據進行識別,但實際上,這個圖像是28 28長寬結構的,我們這次使用CNN捲積神經網路來進行識別。 捲積神經網路我的理解是部分模仿了人眼的功能。 我們在看 ...
  • 在Java5.0之前,協調對共用對象的訪問可以使用的機制只有synchronized和volatile。我們知道synchronized關鍵字實現了內置鎖,而volatile關鍵字保證了多線程的記憶體可見性。在大多數情況下,這些機制都能很好地完成工作,但卻無法實現一些更高級的功能,例如,無法中斷一個正 ...
  • 目錄: Python之路第一篇——認識Python ...
  • 本來覺得沒什麼可寫的,因為網上這玩意一搜一大把,不過爬蟲畢竟是python的一個大亮點,不說說感覺對不起這玩意基礎點來說,python2寫爬蟲重點需要兩個模塊,urllib和urllib2,其實還有re先介紹下模塊的一些常用功能urllib.urlopen('http://xxx.xxx.xxx') ...
  • 此文章會講述簡單標簽處理器,因為經典自定義標簽處理器沒有簡單標簽處理器方便使用,故在此不進行描述。 參考:慕課網的《JSP自定義標簽》視頻; 《Servlet、JSP和Spring MVC初學指南》的第六章; IBM的《利用 JSP 2 提供的 SimpleTagSupport 開發自定義標簽》; ...
  • python小白嘗試寫游戲.. 學了點pygame不知道那什麼練手好,先拿貪吃蛇開刀吧. 一個游戲可以粗略的分為兩個部分: 數據(變數) 處理數據(函數,方法) 設計變數 首先預想下,畫面的那些部分需要存儲在變數里 整個畫面上只會有矩形,而且這些矩形整整齊齊,大小相等,原本一個矩形需要四個變數表示位 ...
  • 思路:用ArrayList存放母音字母(因為有個contains方法),並記錄母音的序號和放進棧裡面(反轉類似棧先進後出),裡面用到string與char數組的轉換 ...
  • 面向對象的意義在於: -將日常生活中習慣的思維方式引入程式設計中 -將需求中的慨念直觀的映射到解決方案中 -以模塊為中心構建可復用的軟體系統 -提高軟體產品的可維護性和可擴展性 其中類和對象是面向對象中的兩個基本概念 -類:指的一類事物,裡面包括不同對象實體,是一個抽象的概念,比如:程式語言 -對象 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...