Spring6 初始

2023-07-19 12:00:12

Spring6 初始

@

每博一文案:

人生的態度是:抱有最大的希望。
盡最大的努力,做最壞的打算。
                                  —————— 柏拉圖《理想國》

1. 初始 Spring6

閱讀以下程式碼:

package com.powernode.oa.controller;

import com.powernode.oa.service.UserService;
import com.powernode.oa.service.impl.UserServiceImpl;

public class UserController {

    private UserService userService = new UserServiceImpl();

    public void login(){
        String username = "admin";
        String password = "123456";
        boolean success = userService.login(username, password);
        if (success) {
            // 登入成功
        } else {
            // 登入失敗
        }
    }
}
package com.powernode.oa.service.impl;

import com.powernode.oa.bean.User;
import com.powernode.oa.dao.UserDao;
import com.powernode.oa.dao.impl.UserDaoImplForMySQL;
import com.powernode.oa.service.UserService;

public class UserServiceImpl implements UserService {

    private UserDao userDao = new UserDaoImplForMySQL();

    public boolean login(String username, String password) {
        User user = userDao.selectByUsernameAndPassword(username, password);
        if (user != null) {
            return true;
        }
        return false;
    }
}
package com.powernode.oa.dao.impl;

import com.powernode.oa.bean.User;
import com.powernode.oa.dao.UserDao;

public class UserDaoImplForMySQL implements UserDao {
    public User selectByUsernameAndPassword(String username, String password) {
        // 連線MySQL資料庫,根據使用者名稱和密碼查詢使用者資訊
        return null;
    }
}

可以看出,UserDaoImplForMySQL中主要是連線MySQL資料庫進行操作。如果更換到Oracle資料庫上,則需要再提供一個UserDaoImplForOracle,如下:

package com.powernode.oa.dao.impl;

import com.powernode.oa.bean.User;
import com.powernode.oa.dao.UserDao;

public class UserDaoImplForOracle implements UserDao {
    public User selectByUsernameAndPassword(String username, String password) {
        // 連線Oracle資料庫,根據使用者名稱和密碼查詢使用者資訊
        return null;
    }
}

很明顯,以上的操作正在進行功能的擴充套件,新增了一個新的類UserDaoImplForOracle來應付資料庫的變化,這裡的變化會引起連鎖反應嗎?當然會,如果想要切換到Oracle資料庫上,UserServiceImpl類程式碼就需要修改,如下:

package com.powernode.oa.service.impl;

import com.powernode.oa.bean.User;
import com.powernode.oa.dao.UserDao;
import com.powernode.oa.dao.impl.UserDaoImplForOracle;
import com.powernode.oa.service.UserService;

public class UserServiceImpl implements UserService {

    //private UserDao userDao = new UserDaoImplForMySQL();
    private UserDao userDao = new UserDaoImplForOracle();

    public boolean login(String username, String password) {
        User user = userDao.selectByUsernameAndPassword(username, password);
        if (user != null) {
            return true;
        }
        return false;
    }
}

1.1 OCP開閉原則

這樣一來就違背了開閉原則OCP。開閉原則是這樣說的:在軟體開發過程中應當對擴充套件開放,對修改關閉

也就是說,如果在進行功能擴充套件的時候,新增額外的類是沒問題的,但因為功能擴充套件而修改之前執行正常的程式(程式碼),這是忌諱的,不被允許的。因為一旦修改之前執行正常的程式,就會導致專案整體要進行全方位的重新測試。這是相當麻煩的過程。導致以上問題的主要原因是:程式碼和程式碼之間的耦合度太高。如下圖所示:

可以很明顯的看出,上層是依賴下層的。UserController依賴UserServiceImpl,而UserServiceImpl依賴UserDaoImplForMySQL,這樣就會導致下面只要改動上面必然會受牽連(跟著也會改),所謂牽一髮而動全身。這樣也就同時違背了另一個開發原則:依賴倒置原則。

1.2 依賴倒置原則DIP

依賴倒置原則(Dependence Inversion Principle),簡稱DIP,主要倡導面向抽象程式設計,面向介面程式設計,不要面向具體程式設計,讓上層不再依賴下層,下面改動了,上面的程式碼不會受到牽連。這樣可以大大降低程式的耦合度,耦合度低了,擴充套件力就強了,同時程式碼複用性也會增強。(軟體七大開發原則都是在為解耦合服務

你可能會說,上面的程式碼已經面向介面程式設計了呀:

確實已經面向介面程式設計了,但物件的建立是:new UserDaoImplForOracle()顯然並沒有完全面向介面程式設計,還是使用到了具體的介面實現類。什麼叫做完全面向介面程式設計?什麼叫做完全符合依賴倒置原則呢?請看以下程式碼:

如果程式碼是這樣編寫的,才算是完全面向介面程式設計,才符合依賴倒置原則。那你可能會問,這樣userDao是null,在執行的時候就會出現空指標異常呀。你說的有道理,確實是這樣的,所以我們要解決這個問題。解決空指標異常的問題,其實就是解決兩個核心的問題:

  • 第一個問題:誰來負責物件的建立。【也就是說誰來:new UserDaoImplForOracle()/new UserDaoImplForMySQL()】
  • 第二個問題:誰來負責把建立的物件賦到這個屬性上。【也就是說誰來把上面建立的物件賦給userDao屬性】

如果我們把以上兩個核心問題解決了,就可以做到既符合OCP開閉原則,又符合依賴倒置原則。

很榮幸的通知你:Spring框架可以做到。

在Spring框架中,它可以幫助我們new物件,並且它還可以將new出來的物件賦到屬性上。換句話說,Spring框架可以幫助我們建立物件,並且可以幫助我們維護物件和物件之間的關係。比如:

Spring可以new出來UserDaoImplForMySQL物件,也可以new出來UserDaoImplForOracle物件,並且還可以讓new出來的dao物件和service物件產生關係(產生關係其實本質上就是給屬性賦值)。

很顯然,這種方式是將物件的建立權/管理權交出去了,不再使用寫死的方式了。同時也把物件關係的管理權交出去了,也不再使用寫死的方式了。像這種把物件的建立權交出去,把物件關係的管理權交出去,被稱為控制反轉。

1.3 控制反轉IoC

控制反轉(Inversion of Control,縮寫為IoC),是物件導向程式設計中的一種設計思想,可以用來降低程式碼之間的耦合度,符合依賴倒置原則。

控制反轉的核心是:將物件的建立權交出去,將物件和物件之間關係的管理權交出去,由第三方容器來負責建立與維護。簡單的說:就是可以幫你建立物件(new 物件)這個步驟:不需要你手動的 new 物件了。

控制反轉常見的實現方式:依賴注入(Dependency Injection,簡稱DI)

通常,依賴注入的實現由包括兩種方式:

  • set方法注入
  • 構造方法注入

而Spring框架就是一個實現了IoC思想的框架。

IoC可以認為是一種全新的設計模式,但是理論和時間成熟相對較晚,並沒有包含在GoF中。(GoF指的是23種設計模式)

2. Spring 初始

Spring 是一個開源框架,它由Rod Johnson 建立。它是為了解決企業應用開發的複雜性而建立的

從簡單性,可測試性和鬆耦合的角度而言,任何Java應用都可以從 Spring 中收益。

Spring 是一種輕量級的控制反轉(IOC) 和麵向切面(ADP) 的容器框架。

Spring 最初的出現是為了解決EJB隆腫的設計,以及難以測試等問題。

Spring 為此簡化開發而生,讓程式設計師只需關注核心業務的實現,儘可能的不再關注非業務邏輯程式碼(事務控制,安全紀錄檔等)

注意:Spring5版本之後是8個模組。在Spring5中新增了WebFlux模組。

  1. Spring Core模組

這是Spring框架最基礎的部分,它提供了依賴注入(DependencyInjection)特徵來實現容器對Bean的管理。核心容器的主要元件是 BeanFactory,BeanFactory是工廠模式的一個實現,是任何Spring應用的核心。它使用IoC將應用設定和依賴從實際的應用程式碼中分離出來。

  1. Spring Context模組

如果說核心模組中的BeanFactory使Spring成為容器的話,那麼上下文模組就是Spring成為框架的原因。

這個模組擴充套件了BeanFactory,增加了對國際化(I18N)訊息、事件傳播、驗證的支援。另外提供了許多企業服務,例如電子郵件、JNDI存取、EJB整合、遠端以及時序排程(scheduling)服務。也包括了對模版框架例如Velocity和FreeMarker整合的支援

  1. Spring AOP模組

Spring在它的AOP模組中提供了對面向切面程式設計的豐富支援,Spring AOP 模組為基於 Spring 的應用程式中的物件提供了事務管理服務。通過使用 Spring AOP,不用依賴元件,就可以將宣告性事務管理整合到應用程式中,可以自定義攔截器、切點、紀錄檔等操作。

  1. Spring DAO模組

提供了一個JDBC的抽象層和異常層次結構,消除了煩瑣的JDBC編碼和資料庫廠商特有的錯誤程式碼解析,用於簡化JDBC。

  1. Spring ORM模組

Spring提供了ORM模組。Spring並不試圖實現它自己的ORM解決方案,而是為幾種流行的ORM框架提供了整合方案,包括Hibernate、JDO和iBATIS SQL對映,這些都遵從 Spring 的通用事務和 DAO 異常層次結構。

  1. Spring Web MVC模組

Spring為構建Web應用提供了一個功能全面的MVC框架。雖然Spring可以很容易地與其它MVC框架整合,例如Struts,但Spring的MVC框架使用IoC對控制邏輯和業務物件提供了完全的分離。

  1. Spring WebFlux模組

Spring Framework 中包含的原始 Web 框架 Spring Web MVC 是專門為 Servlet API 和 Servlet 容器構建的。反應式堆疊 Web 框架 Spring WebFlux 是在 5.0 版的後期新增的。它是完全非阻塞的,支援反應式流(Reactive Stream)背壓,並在Netty,Undertow和Servlet 3.1+容器等伺服器上執行。

  1. Spring Web模組

Web 上下文模組建立在應用程式上下文模組之上,為基於 Web 的應用程式提供了上下文,提供了Spring和其它Web框架的整合,比如Struts、WebWork。還提供了一些面向服務支援,例如:實現檔案上傳的multipart請求。

2.1 Spring特點

  1. 輕量

    1. 從大小與開銷兩方面而言Spring都是輕量的。完整的Spring框架可以在一個大小隻有1MB多的JAR檔案裡釋出。並且Spring所需的處理開銷也是微不足道的。
    2. Spring是非侵入式的:Spring應用中的物件不依賴於Spring的特定類。
  2. 控制反轉

    1. Spring通過一種稱作控制反轉(IoC)的技術促進了鬆耦合。當應用了IoC,一個物件依賴的其它物件會通過被動的方式傳遞進來,而不是這個物件自己建立或者查詢依賴物件。你可以認為IoC與JNDI相反——不是物件從容器中查詢依賴,而是容器在物件初始化時不等物件請求就主動將依賴傳遞給它。
  3. 面向切面

    1. Spring提供了面向切面程式設計的豐富支援,允許通過分離應用的業務邏輯與系統級服務(例如審計(auditing)和事務(transaction)管理)進行內聚性的開發。應用物件只實現它們應該做的——完成業務邏輯——僅此而已。它們並不負責(甚至是意識)其它的系統級關注點,例如紀錄檔或事務支援。
  4. 容器

    1. Spring包含並管理應用物件的設定和生命週期,在這個意義上它是一種容器,你可以設定你的每個bean如何被建立——基於一個可設定原型(prototype),你的bean可以建立一個單獨的範例或者每次需要時都生成一個新的範例——以及它們是如何相互關聯的。然而,Spring不應該被混同於傳統的重量級的EJB容器,它們經常是龐大與笨重的,難以使用。
  5. 框架

    1. Spring可以將簡單的元件設定、組合成為複雜的應用。在Spring中,應用物件被宣告式地組合,典型地是在一個XML檔案裡。Spring也提供了很多基礎功能(事務管理、持久化框架整合等等),將應用邏輯的開發留給了你。

所有Spring的這些特徵使你能夠編寫更乾淨、更可管理、並且更易於測試的程式碼。它們也為Spring中的各種模組提供了基礎支援。

2.2 Spring6 的下載:

官網地址:https://spring.io/

官網地址(中文):http://spring.p2hp.com/

開啟Spring官網後,可以看到Spring Framework,以及通過Spring Framework衍生的其它框架:

我們即將要學習的就是Spring Framework。

怎麼下載呢?

  • 第一步:進入github

  • 第二步:找到下圖位置,點選超連結

  • 第三步:找到下圖位置,點選超連結

  • 第四步:按照下圖步驟操作

  • 第五步:繼續在springframework目錄下找下圖的spring,點開之後你會看到很多不同的版本

  • 第六步:選擇對應的版本

  • 第七步:點選上圖的url

點選spring-5.3.9-dist.zip下載spring框架。

將下載的zip包解壓:

docs:spring框架的API幫助檔案

libs:spring框架的jar檔案(用spring框架就是用這些jar包

schema:spring框架的XML組態檔相關的約束檔案

2.3 Spring的jar檔案

開啟libs目錄,會看到很多jar包:

spring-core-5.3.9.jar:位元組碼(這個是支撐程式執行的jar包

spring-core-5.3.9-javadoc.jar:程式碼中的註釋

spring-core-5.3.9-sources.jar:原始碼

我們來看一下spring框架都有哪些jar包:

JAR檔案 描述
spring-aop-5.3.9.jar 這個jar 檔案包含在應用中使用Spring 的AOP 特性時所需的類
spring-aspects-5.3.9.jar 提供對AspectJ的支援,以便可以方便的將面向切面的功能整合進IDE中
spring-beans-5.3.9.jar 這個jar 檔案是所有應用都要用到的,它包含存取組態檔、建立和管理bean 以及進行Inversion ofControl / Dependency Injection(IoC/DI)操作相關的所有類。如果應用只需基本的IoC/DI 支援,引入spring-core.jar 及spring-beans.jar 檔案就可以了。
spring-context-5.3.9.jar 這個jar 檔案為Spring 核心提供了大量擴充套件。可以找到使用Spring ApplicationContext特性時所需的全部類,JDNI 所需的全部類,instrumentation元件以及校驗Validation 方面的相關類。
spring-context-indexer-5.3.9.jar 雖然類路徑掃描非常快,但是Spring內部存在大量的類,新增此依賴,可以通過在編譯時建立候選物件的靜態列表來提高大型應用程式的啟動效能。
spring-context-support-5.3.9.jar 用來提供Spring上下文的一些擴充套件模組,例如實現郵件服務、檢視解析、快取、定時任務排程等
spring-core-5.3.9.jar Spring 框架基本的核心工具類。Spring 其它元件要都要使用到這個包裡的類,是其它元件的基本核心,當然你也可以在自己的應用系統中使用這些工具類。
spring-expression-5.3.9.jar Spring表示式語言。
spring-instrument-5.3.9.jar Spring3.0對伺服器的代理介面。
spring-jcl-5.3.9.jar Spring的紀錄檔模組。JCL,全稱為"Jakarta Commons Logging",也可稱為"Apache Commons Logging"。
spring-jdbc-5.3.9.jar Spring對JDBC的支援。
spring-jms-5.3.9.jar 這個jar包提供了對JMS 1.0.2/1.1的支援類。JMS是Java訊息服務。屬於JavaEE規範之一。
spring-messaging-5.3.9.jar 為整合messaging api和訊息協定提供支援
spring-orm-5.3.9.jar Spring整合ORM框架的支援,比如整合hibernate,mybatis等。
spring-oxm-5.3.9.jar 為主流O/X Mapping元件提供了統一層抽象和封裝,OXM是Object Xml Mapping。物件和XML之間的相互轉換。
spring-r2dbc-5.3.9.jar Reactive Relational Database Connectivity (關係型資料庫的響應式連線) 的縮寫。這個jar檔案是Spring對r2dbc的支援。
spring-test-5.3.9.jar 對Junit等測試框架的簡單封裝。
spring-tx-5.3.9.jar 為JDBC、Hibernate、JDO、JPA、Beans等提供的一致的宣告式和程式設計式事務管理支援。
spring-web-5.3.9.jar Spring整合MVC框架的支援,比如整合Struts等。
spring-webflux-5.3.9.jar WebFlux是 Spring5 新增的新模組,用於 web 的開發,功能和 SpringMVC 類似的,Webflux 使用當前一種比較流程響應式程式設計出現的框架。
spring-webmvc-5.3.9.jar SpringMVC框架的類庫
spring-websocket-5.3.9.jar Spring整合WebSocket框架時使用

這裡我們不是使用上面的方式下載的: 而是通過:Maven 自動化管理工具:

  <!--        spring6 框架-->
        <!--spring contest 倉庫-->
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>6.0.3</version>
        </dependency>

注意:如果你只是想用Spring的IoC功能,僅需要引入:spring-context即可。將這個jar包新增到classpath當中。

如果採用maven只需要引入context的依賴即可。

<!--Spring6的正式版釋出之前,這個倉庫地址是需要的-->
<repositories>
  <repository>
    <id>repository.spring.milestone</id>
    <name>Spring Milestone Repository</name>
    <url>https://repo.spring.io/milestone</url>
  </repository>
</repositories>

<dependencies>
  <!--        spring6 框架-->
        <!--spring contest 倉庫-->
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>6.0.3</version>
        </dependency>
</dependencies>

3. 第一個Spring 程式的編寫

  • 開啟IDEA建立Empty Project:spring6

設定該專案的 JDK為 17

注意的是: 對應 Spring6 來說:最低支援的是 JDK17 ,低於 17 的版本是不行的,所以如果你要想使用 Spring6 框架的話,必須安裝 JDK > = 17 的版本才行。

第一步:設定好該專案中的Maven本地路徑資訊:

第二步:新增spring context的依賴,pom.xml設定如下

<?xml version="1.0" encoding="UTF-8"?>
<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.raibowsea</groupId>
    <artifactId>spring6-003-dependency-injection</artifactId>
    <version>1.0-SNAPSHOT</version>
    <!--    將專案的打包方式為 jar Java專案的方式-->
    <packaging>jar</packaging>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
    </properties>

    <!--    匯入相關的依賴倉庫-->
    <dependencies>
        <!--        spring6 框架-->
        <!--spring contest 倉庫-->
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>6.0.3</version>
        </dependency>
        <!-- junit4 -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.2</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>2.19.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-slf4j2-impl</artifactId>
            <version>2.19.0</version>
        </dependency>
    </dependencies>

</project>

注意:打包方式jar。

當加入spring context的依賴之後,會關聯引入其他依賴:

spring aop:面向切面程式設計

spring beans:IoC核心

spring core:spring的核心工具包

spring jcl:spring的紀錄檔包

spring expression:spring表示式

第三步: bean : User

在:Spring 框架中,每一個管理的類,都是 bean ,在 xml 中基本上面向 bean 編寫的。

package com.rainbowsea.spring6.bean;


/**
 * 這是一個bean ,封裝了使用者的資訊,Spring 可以幫助我們建立User物件嗎?
 *
 */
public class User {

    // Spring 是怎麼範例化物件的?
    // 預設情況下Spring 是通過反射機制,呼叫類的無引數構造方法來範例化物件的
    // 所以,對於反射來說,我們來要有,一定要有私有的構造器
    // Class<?> clazz = Class.forName("com.rainbowsea.spring6.bean.User");
    //        Object o = clazz.newInstance();

    public User () {
        System.out.println("User 的無引數構造器構造方法的執行");
    }
}

第四步:編寫spring的組態檔:beans.xml。該檔案放在類的根路徑下。

上圖是使用IDEA工具自帶的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 組態檔中進行 bean 的設定:

**注意: **

  • id屬性:代表物件的唯一標識。可以看做一個人的身份證號。
  • class屬性:用來指定要建立的java物件的類名,這個類名必須是全限定類名(帶包名)。
<?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="userBean2" class="com.rainbowsea.spring6.bean.User"></bean>
    
<!--    id 屬性: 代表物件的唯一標識,可以看做一個人的身份證號:-->
<!--    class 屬性: 用來指定要建立的 java 物件的類名,這個類名必須是全限定類名(帶包名)-->
</beans>

第五步:編寫測試程式

這裡我們使用: JUit4 的測試:

package com.rainbowsea.spring6;

import com.rainbowsea.spring6.bean.User;
import com.rainbowsea.spring6.bean.UserDao;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;


import java.text.SimpleDateFormat;
import java.util.Date;


public class FirstSpringTest {

    @Test
    public void testFirstSpringCode() {
        // 第一步:獲取到Spring 容器物件
        // ApplicationContext 翻譯為: 應用上下文,其實就是
        // ApplicationContext 是一個介面:
        // ApplicationContest 介面下有很多實現類,其中有一個實現類叫做:ClassPathXmlApplicationContext
        // ClassPathXmlApplicationContext 專門從類路徑當中載入Spring 組態檔的一個類
        // 下面這行程式碼只要執行,就相當與啟動了/Spring 容器,解析Spring.xml 檔案.
        // 面向介面程式設計,左邊的是介面,右邊的類
         ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");

        /**
         * 原始碼分析:
         *     public ClassPathXmlApplicationContext(String... configLocations) throws BeansException {
         *         this(configLocations, true, (ApplicationContext)null);
         *     }
         *     可變引數,說明可以傳入多個引數西資訊;
         *
         */


        // 第二步: 根據bean 的id從Spring 的容器中獲取到這個物件
        // 注意:這個name ,儘量使用 copy 的方式,防止發生錯誤
        Object userBean = applicationContext.getBean("userBean");

        System.out.println(userBean);
    }
}

4. 第一個Spring程式詳細剖析

<bean id="userBean" class="com.powernode.spring6.bean.User"/>
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
Object userBean = applicationContext.getBean("userBean");

4.1 bean標籤的id屬性可以重複嗎?

注意: 在 Spring 當中的 xml 中對應設定的 bean 中的 id屬性的值是不可以重複的,它就像人的身份證號一樣的,不可重複。

package com.powernode.spring6.bean;

public class Vip {
}
<?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="userBean" class="com.powernode.spring6.bean.User"/>
    <bean id="userBean" class="com.powernode.spring6.bean.Vip"/>
        <--這裡我們的 id 重複了,測試-->
</beans>

執行測試程式:

通過測試得出:在spring的組態檔中id是不能重名。

4.2 底層是怎麼建立物件的,是通過反射機制呼叫無引數構造方法嗎?

package com.powernode.spring6.bean;

public class User {
    public User() {
        System.out.println("User的無引數構造方法執行");
    }
}

在User類中新增無引數構造方法,如上。

執行測試程式:

通過測試得知:建立物件時確實呼叫了無引數構造方法。

如果提供一個有引數構造方法,不提供無引數構造方法會怎樣呢?

package com.powernode.spring6.bean;

public class User {
    /*public User() {
        System.out.println("User的無引數構造方法執行");
    }*/

    public User(String name){
        System.out.println("User的有引數構造方法執行");
    }
}

執行測試程式:

通過測試得知:spring是通過呼叫類的無引數構造方法來建立物件的,所以要想讓spring給你建立物件,必須保證無引數構造方法是存在的。

Spring是如何建立物件的呢?原理是什麼?

// dom4j解析beans.xml檔案,從中獲取class的全限定類名
// 通過反射機制呼叫無引數構造方法建立物件
Class clazz = Class.forName("com.powernode.spring6.bean.User");
Object obj = clazz.newInstance();

4.3 把建立好的物件儲存到一個什麼樣的資料結構當中了呢?

4.4 spring組態檔的名字必須叫做beans.xml嗎?

ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");

通過以上的java程式碼可以看出,這個spring組態檔名字是我們負責提供的,顯然spring組態檔的名字是隨意的。

  1. 像這樣的beans.xml檔案可以有多個嗎?

再建立一個spring組態檔,起名:spring.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="vipBean" class="com.powernode.spring6.bean.Vip"/>
</beans>

package com.powernode.spring6.test;

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

public class Spring6Test {

    @Test
    public void testFirst(){
        // 初始化Spring容器上下文(解析beans.xml檔案,建立所有的bean物件)
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml","spring.xml");

        // 根據id獲取bean物件
        Object userBean = applicationContext.getBean("userBean");
        Object vipBean = applicationContext.getBean("vipBean");

        System.out.println(userBean);
        System.out.println(vipBean);
    }
}

執行測試程式:

通過測試得知,spring的組態檔可以有多個,在ClassPathXmlApplicationContext構造方法的引數上傳遞檔案路徑即可。這是為什麼呢?通過原始碼可以看到:

4.5 在組態檔中設定的類必須是自定義的嗎,可以使用JDK中的類嗎,例如:java.util.Date?

<?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="userBean" class="com.powernode.spring6.bean.User"/>
    <!--<bean id="userBean" class="com.powernode.spring6.bean.Vip"/>-->

    <bean id="dateBean" class="java.util.Date"/>
</beans>

package com.powernode.spring6.test;

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

public class Spring6Test {

    @Test
    public void testFirst(){
        // 初始化Spring容器上下文(解析beans.xml檔案,建立所有的bean物件)
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml","spring.xml");

        // 根據id獲取bean物件
        Object userBean = applicationContext.getBean("userBean");
        Object vipBean = applicationContext.getBean("vipBean");
        Object dateBean = applicationContext.getBean("dateBean");

        System.out.println(userBean);
        System.out.println(vipBean);
        System.out.println(dateBean);
    }
}

執行測試程式:

通過測試得知,在spring組態檔中設定的bean可以任意類,只要這個類不是抽象的,並且提供了無引數構造方法。

4.6 getBean()方法呼叫時,如果指定的id不存在會怎樣?

執行測試程式:

通過測試得知,當id不存在的時候,會出現異常。

4.7 getBean()方法返回的型別是Object,如果存取子類的特有屬性和方法時,還需要向下轉型,有其它辦法可以解決這個問題嗎?

User user = applicationContext.getBean("userBean", User.class);
  1. ClassPathXmlApplicationContext是從類路徑中載入組態檔,如果沒有在類路徑當中,又應該如何載入組態檔呢?

<?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="vipBean2" class="com.powernode.spring6.bean.Vip"/>
</beans>
ApplicationContext applicationContext2 = new FileSystemXmlApplicationContext("d:/spring6.xml");
Vip vip = applicationContext2.getBean("vipBean2", Vip.class);
System.out.println(vip);

沒有在類路徑中的話,需要使用FileSystemXmlApplicationContext類進行載入組態檔。注意: 如果在類的路徑當中,讀取到的是類路徑下的子目錄下的 xml 檔案資訊,需要指明對應的 類路徑下的子目錄,不然無法讀取到其中的 xml 檔案資訊的。

這種方式較少用。一般都是將組態檔放到類路徑當中,這樣可移植性更強。

4.8 ApplicationContext的超級父介面BeanFactory。

BeanFactory beanFactory = new ClassPathXmlApplicationContext("spring.xml");
Object vipBean = beanFactory.getBean("vipBean");
System.out.println(vipBean);

BeanFactory是Spring容器的超級介面。ApplicationContext是BeanFactory的子介面。

5. Spring6啟用Log4j2紀錄檔框架

第一步:引入Log4j2的依賴

<!--log4j2的依賴-->
<dependency>
  <groupId>org.apache.logging.log4j</groupId>
  <artifactId>log4j-core</artifactId>
  <version>2.19.0</version>
</dependency>
<dependency>
  <groupId>org.apache.logging.log4j</groupId>
  <artifactId>log4j-slf4j2-impl</artifactId>
  <version>2.19.0</version>
</dependency>

第二步:在類的根路徑下提供log4j2.xml組態檔(檔名固定為:log4j2.xml,檔案必須放到類根路徑下。)

<?xml version="1.0" encoding="UTF-8"?>

<configuration>

    <loggers>
        <!--
            level指定紀錄檔級別,從低到高的優先順序:
                ALL < TRACE < DEBUG < INFO < WARN < ERROR < FATAL < OFF
        -->
        <root level="DEBUG">
            <appender-ref ref="spring6log"/>
        </root>
    </loggers>

    <appenders>
        <!--輸出紀錄檔資訊到控制檯-->
        <console name="spring6log" target="SYSTEM_OUT">
            <!--控制紀錄檔輸出的格式-->
            <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss SSS} [%t] %-3level %logger{1024} - %msg%n"/>
        </console>
    </appenders>

</configuration>

第三步:使用紀錄檔框架

Logger logger = LoggerFactory.getLogger(FirstSpringTest.class);
logger.info("我是一條紀錄檔訊息");

6. 總結:

  1. 開閉原則是這樣說的:在軟體開發過程中應當對擴充套件開放,對修改關閉
  2. 依賴倒置原則(Dependence Inversion Principle),簡稱DIP,主要倡導面向抽象程式設計,面向介面程式設計,不要面向具體程式設計,讓上層不再依賴下層,下面改動了,上面的程式碼不會受到牽連。這樣可以大大降低程式的耦合度,耦合度低了,擴充套件力就強了,同時程式碼複用性也會增強。(軟體七大開發原則都是在為解耦合服務
  3. 控制反轉的核心是:將物件的建立權交出去,將物件和物件之間關係的管理權交出去,由第三方容器來負責建立與維護。簡單的說:就是可以幫你建立物件(new 物件)這個步驟:不需要你手動的 new 物件了。
  4. 通過Maven 構建工具類 進行一個匯入 Spring6 的架構如下:對應的 maven 設定
  <!--        spring6 框架-->
        <!--spring contest 倉庫-->
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>6.0.3</version>
        </dependency>
  1. 第一個 Spring6 的框架設計:
ApplicationContext applicationContext2 = new FileSystemXmlApplicationContext("d:/spring6.xml");
Vip vip = applicationContext2.getBean("vipBean2", Vip.class);
System.out.println(vip);
  1. Spring6 編寫的注意事項:
    1. 標籤當中的 id 是唯一的不可以重複
    2. 對應的.xml 檔案的命名可以不用自行命名。

7. 最後:

「在這個最後的篇章中,我要表達我對每一位讀者的感激之情。你們的關注和回覆是我創作的動力源泉,我從你們身上吸取了無盡的靈感與勇氣。我會將你們的鼓勵留在心底,繼續在其他的領域奮鬥。感謝你們,我們總會在某個時刻再次相遇。」