1. 程式人生 > 程式設計 >Spring基礎篇(1)——Spring框架核心介紹

Spring基礎篇(1)——Spring框架核心介紹

DI(Dependency Injection),依賴注入

和我們常聽說的另一個概念IOC(控制反轉)其實歸根結底實現的功能是相同的,只是同樣的功能站在不同的角度來闡述罷了。我們需要知道的是,什麼叫依賴注入,為什麼要依賴注入。搞清這兩點,我想對Spring的學習在思想上就算是上道了。

在沒用使用Spring的時候——也就是沒有依賴注入的時候,java應用程式的類與類之間要實現相互的功能協作是比較費勁的,某個類(A)要實現它的功能如果需要依賴另一個類(B)的協作的話,就需要在A類中主動創建出B類的物件,才能使用B類的方法完成功能(這裡看官就不要去糾結靜態方法之類的情況了)。這等於是A類需要負責B類物件整個生命週期的管理。在極度簡單的情況下,在一個類中new出另一個類的物件似乎並沒有什麼問題,但是複雜的應用程式類與類的協作關係往往是多邊的,我們並不知道一個類功能的實現會依賴多少個另類物件來協作,所以在類中自行建立物件並且管理物件的整個生命週期,會造成程式碼的高度耦合以及不可想象的複雜度。那麼,試想,如果我們能將物件的生命週期交給第三方元件來管理,當某個類需要另外的物件時第三方元件就直接創建出來交給它,這樣,類就可以只專注於自己功能的實現,而不用去管理其他類物件的生命週期,這樣類的功能就單純了很多。

是的,你一定已經明白了,Spring(容器)就是這個第三方元件。我們只需要告訴Spring(容器)有哪些物件需要管理就行了,不用去關心Spring框架是如何建立物件的。這樣,當某個類A需要類B物件時,如果類B已經宣告交給了Sping容器管理,那麼在程式執行到類A需要類B時,Spring容器就通過依賴注入的方式,將類B物件注入到類A中協助完成業務功能。通過第三方元件的依賴注入,物件無需再自行的建立和管理類與類之間的依賴關係了。物件的建立依賴注入的方式也有多種,譬如介面注入,構造方法注入,setter方法注入等等。說到這裡,你對依賴注入應該有比較直白的認知了。至於為什麼要依賴注入,上文已經說得很明白了,就是為了減少程式碼中元件之間的耦合度,我們還是先通過簡單示例來直觀感受下依賴注入比自己管理物件的好處吧——

public class Man implements Human {
    private QQCar car;
    public Man() {
        this.car = new QQCar();
    }
    @Override
    public void xiabibi() {
    }
    public void driveCar(){
        car.drive();
    }
}
複製程式碼

介面Car暫有兩個實現:賓士車和QQ車,在以上Man類和QQCar類高度耦合的程式碼中,老司機通過構造器只建立了QQ車物件,所以只能開QQ車,那麼老司機想開賓士怎麼辦呢,你讓他重新建立賓士車的物件嗎?這樣高度耦合的程式碼似乎是毫無辦法的,那麼,我們通過注入物件的方式對上述程式碼做一番改進:

public class Man implements Human {
    private Car car;
    public Man(Car car) {
        this.car = car;
    }
    @Override
    public void xiabibi() {
    }

    public void driveCar() {
        car.drive();
    }
}
複製程式碼

以上程式碼根據多型特性,通過構造器介面注入的方式遮蔽掉了具體的物件實現,這樣,老司機就能想開什麼車就開什麼車了。這就是依賴注入帶來的好處。 

IoC:Inverse of Control(控制反轉)

  • 讀作 “反轉控制”,更好理解,不是什麼技術,而是一種設計思想,就是將原本在程式中手動建立物件的控制權,交由Spring框架來管理。
  • 正控:若要使用某個物件,需要自己去負責物件的建立
  • 反控:若要使用某個物件,只需要從 Spring 容器中獲取需要使用的物件,不關心物件的建立過程,也就是把建立物件的控制權反轉給了Spring框架
  • 好萊塢法則: Don’t call me,I’ll call you

一個例子

控制反轉顯然是一個抽象的概念,我們舉一個鮮明的例子來說明。在現實生活中,人們要用到一樣東西的時候,第一反應就是去找到這件東西,比如想喝新鮮橙汁,在沒有飲品店的日子裡,最直觀的做法就是:買果汁機、買橙子,然後準備開水。值得注意的是:這些都是你自己“主動”創造的過程,也就是說一杯橙汁需要你自己創造。

然而到了今時今日,由於飲品店的盛行,當我們想喝橙汁時,第一想法就轉換成了找到飲品店的聯絡方式,通過電話等渠道描述你的需要、地址、聯絡方式等,下訂單等待,過一會兒就會有人送來橙汁了。

請注意你並沒有“主動”去創造橙汁,橙汁是由飲品店創造的,而不是你,然而也完全達到了你的要求,甚至比你創造的要好上那麼一些。

Spring IoC 闡述

這就是一種控制反轉的理念,上述的例子已經很好的說明瞭問題,我們再來描述一下控制反轉的概念:控制反轉是一種通過描述(在 Java 中可以是 XML 或者註解)並通過第三方(Spring)去產生或獲取特定物件的方式。

  • 好處:降低物件之間的耦合,我們不需要理解一個類的具體實現,只需要知道它有什麼用就好了(直接向 IoC 容器拿)

主動建立的模式中,責任歸於開發者,而在被動的模式下,責任歸於 IoC 容器,基於這樣的被動形式,我們就說物件被控制反轉了。(也可以說是反轉了控制)

Spring IoC 和 依賴注入的例子(通過 xml 檔案配置的方式裝配 bean)

  1. 新建一個空的 Java 專案,命名為【spring】
  2. 新建一個名為【lib】的目錄,並新增進必要的 jar 包,匯入專案

  1. 在 Packge【pojo】下新建一個【Source】類:
package pojo;

public class Source {  
    private String fruit;
    private String sugar;
    private String size;
}
複製程式碼
  1. Packge【pojo】下新建一個【JuiceMaker】類:
package pojo;

public class JuiceMaker {

    // 唯一關聯了一個 Source 物件
    private Source source = null;

    public String makeJuice(){
        String juice = "xxx使用者點了一杯" + source.getFruit() + source.getSugar() + source.getSize();
        return juice;
    }
}
複製程式碼
  1. 在 xml 檔案中配置 JuiceMaker 物件:
  • 注意:這裡要使用 ref 來注入另一個物件
<?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 name="source" class="pojo.Source">
        <property name="fruit" value="橙子"/>
        <property name="sugar" value="多糖"/>
        <property name="size" value="超大杯"/>
    </bean>
    <bean name="juickMaker" class="pojo.JuiceMaker">
        <property name="source" ref="source" />
    </bean>
</beans>
複製程式碼
  1. 在 Packge【test】下新建一個【TestSpring】類:
package test;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import pojo.JuiceMaker;
import pojo.Source;

public class TestSpring {

    @Test
    public void test(){
        ApplicationContext context = new ClassPathXmlApplicationContext(
                new String[]{"applicationContext.xml"}
        );

        Source source = (Source) context.getBean("source");
        System.out.println(source.getFruit());
        System.out.println(source.getSugar());
        System.out.println(source.getSize());

        JuiceMaker juiceMaker = (JuiceMaker) context.getBean("juickMaker");
        System.out.println(juiceMaker.makeJuice());
    }
}
複製程式碼
  1. 執行測試程式碼:

總結: IoC 和 DI 其實是同一個概念的不同角度描述,DI 相對 IoC 而言,明確描述了“被注入物件依賴 IoC 容器配置依賴物件”

AOP(Aspect Oriented Programming),面向切面程式設計。

如果說 IoC 是 Spring 的核心,那麼面向切面程式設計就是 Spring 最為重要的功能之一了,在資料庫事務中切面程式設計被廣泛使用。

AOP 即 Aspect Oriented Program 面向切面程式設計

首先,在面向切面程式設計的思想裡面,把功能分為核心業務功能,和周邊功能。

  • 所謂的核心業務,比如登陸,增加資料,刪除資料都叫核心業務
  • 所謂的周邊功能,比如效能統計,日誌,事務管理等等

周邊功能在 Spring 的面向切面程式設計AOP思想裡,即被定義為切面

在面向切面程式設計AOP的思想裡面,核心業務功能和切面功能分別獨立進行開發,然後把切面功能和核心業務功能 "編織" 在一起,這就叫AOP

AOP 的目的

AOP能夠將那些與業務無關,卻為業務模組所共同呼叫的邏輯或責任(例如事務處理、日誌管理、許可權控制等)封裝起來,便於減少系統的重複程式碼,降低模組間的耦合度,並有利於未來的可拓展性和可維護性。

AOP 當中的概念:

  • 切入點(Pointcut) 在哪些類,哪些方法上切入(where)
  • 通知(Advice) 在方法執行的什麼實際(when:方法前/方法後/方法前後)做什麼(what:增強的功能)
  • 切面(Aspect) 切面 = 切入點 + 通知,通俗點就是:在什麼時機,什麼地方,做什麼增強!
  • 織入(Weaving) 把切面加入到物件,並創建出代理物件的過程。(由 Spring 來完成)

AOP 程式設計

  1. 在 Packge【service】下建立 【ProductService】類:
package service;

public class ProductService {
    public void doSomeService(){
        System.out.println("doSomeService");
    }
}
複製程式碼
  1. 在 xml 檔案中裝配該 bean:
<bean name="productService" class="service.ProductService" />
複製程式碼
  1. 在【TestSpring】中編寫測試程式碼,執行:

  1. 在 Packge【aspect】下準備日誌切面 【LoggerAspect】類:
package aspect;

import org.aspectj.lang.ProceedingJoinPoint;

public class LoggerAspect {
    
    public Object log(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("start log:" + joinPoint.getSignature().getName());
        Object object = joinPoint.proceed();
        System.out.println("end log:" + joinPoint.getSignature().getName());
        return object;
    }
}
複製程式碼
  1. 在 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:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
   http://www.springframework.org/schema/beans
   http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
   http://www.springframework.org/schema/aop
   http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
   http://www.springframework.org/schema/tx
   http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
   http://www.springframework.org/schema/context
   http://www.springframework.org/schema/context/spring-context-3.0.xsd">

    <bean name="productService" class="service.ProductService" />
    <bean id="loggerAspect" class="aspect.LoggerAspect"/>

    <!-- 配置AOP -->
    <aop:config>
        <!-- where:在哪些地方(包.類.方法)做增加 -->
        <aop:pointcut id="loggerCutpoint"
                      expression="execution(* service.ProductService.*(..)) "/>

        <!-- what:做什麼增強 -->
        <aop:aspect id="logAspect" ref="loggerAspect">
            <!-- when:在什麼時機(方法前/後/前後) -->
            <aop:around pointcut-ref="loggerCutpoint" method="log"/>
        </aop:aspect>
    </aop:config>
</beans>
複製程式碼
  1. 再次執行 TestSpring 中的測試程式碼,程式碼並沒有改變,但是在業務方法執行之前和執行之後,都分別輸出了日誌資訊: