Java Web系列:Spring依賴注入基礎

王剛發表於2016-01-15

一、Spring簡介

1.Spring簡化Java開發

Spring Framework是一個應用框架,框架一般是半成品,我們在框架的基礎上可以不用每個專案自己實現架構、基礎設施和常用功能性元件,而是可以專注業務邏輯。因此學習Spring Framework在架構和模式方面的結構和原理,對我們在架構和模組級別的理解幫助極大。Spring Framework(參考1)的宗旨是簡化Java開發,主要的手段如下:

(1)在架構上解耦:通過DI(依賴注入)管理型別依賴,通過AOP分離關注點,減少重複程式碼。

(2)在設計上廣泛採用DIP(依賴倒置)和ISP(介面隔離)等原則和Facade(外觀)等模式:提供簡化的呼叫介面並封裝了眾多出色的第三方元件。

(3)在語言層面上採用註解:通過配置檔案和Annotation(參考.NET Attribute)簡化應用配置。

2.Spring Framework的架構和模組:

Spring Framework本身的架構是典型的鬆散分層,外層可以按需引用全部內層,內層不能引用外層。Spring的基礎元件如下圖所示:

從圖中可以看出,開始的模組只有從core\beans\aop\context四個元件,後來新增了context-support【1.2】擴充套件模組、expression【3.0】擴充套件模組和beans-groovy【4.0】擴充套件模組。

Spring上述模組的基礎上,內建和封裝了眾多的實用的通用元件,主要的元件如圖所示:

 從圖中可以看出,spring-oxm、spring-jdbc和spring-web是眾多模組依賴的核心,spring-oxm提供了Object和XML的對映支援。

二、基礎知識

1.DIP:DIP(依賴倒置原則)是DI(依賴注入)的核心(參考2)。

(1)高層模組不應該依賴於低層模組。兩者都應該依賴於抽象。

(2)抽象不應該依賴於細節。細節應該依賴於抽象。

說人話就是:將對具體類的引用轉換成對其介面的引用,具體類只引用介面(引用==依賴,介面==介面或抽象類)。事實上我們呼叫具體類的時候在頭腦裡也是隻關心其提供的API而非實現,DIP則通過在設計和重構階段在技術手段上保證瞭解耦。

2.DI:DI(依賴注入)讓我們不必手寫工廠程式碼來管理介面和實現類的對映、物件的建立和生命週期的管理。

(1)介面注入:必須實現特定的介面才可以,侵入性太強,現在已經無人關心和使用。

(2)建構函式注入:依賴體現在建構函式的引數上。

(3)屬性注入:依賴體現在屬性上。

由於在實現時,可以將型別註冊為自己的相容型別,這樣依賴注入就可以直接替代new例項化物件,這樣理解和使用依賴注入工具還不如不使用或手寫工廠了。依賴注入工具在實現時肯定會實現成一個支援不同配置和不同生命週期的物件工廠,但即使沒有提供一套新增依賴倒置原則限制的API,也不意味著我們把它當成new的替代品。如同對映工具雖然在實現時可以任意對映,但不是用來取代賦值的,而是用來處理領域實體和檢視模型等有實際對應關係的物件之間的對映。

(1)依賴配置:依賴配置是依賴注入實現的基礎。依賴注入工具都至少支援程式碼配置和檔案配置。Java中可以通過Annotation(.NET中通過Attribute)簡化配置。

(2)物件工廠:根據配置返回一個或多個物件。這是核心功能。

(3)生命週期管理:一般提供至少4種級別的支援:作用域、單例、執行緒、HTTP請求範圍。

大多數依賴注入工具在支援依賴倒置原則的基礎上,在技術手段上實現了更多的功能,如型別的相容轉換、對依賴命名、在配置時直接傳入物件等。

三、Spring依賴注入的要點

Bean在Spring中就是POJO(.NET的POCO)。

Spring依賴注入需要掌握的核心是3個型別BeanDefinitionBeanFactoryApplicationContext

1.BeanFactory

BeanFactory是spring中依賴注入的核心介面,其設計主要採用了ISP(介面隔離原則),通過多層次的介面繼承即保證了單個介面的內聚又保證了整個體系的簡潔。這裡我們要關注的核心是DefaultListableBeanFactory

如圖所示,檢視XmlBeanFactory程式碼,可以看到XmlBeanFactory只是通過XmlBeanDefinitionReader載入了BeanDefinition配置,XmlBeanDefinitionReader負責將配置解析到BeanDefinition。DefaultListableBeanFactory是真正的實現類,其中定義了型別為Map<String, BeanDefinition>的beanDefinitionMap列表用於儲存依賴配置。

2.BeanDefinition:

BeanDefinition定義了配置後設資料,無論使用java code、xml、Annotation還是Groovy指令碼方式,不同的配置方式通過不同的BeanDefinitionReader解析為BeanDefinition。

3.ApplicationContext

ApplicationContext的核心都是將物件工廠功能委託給BeanFactory的實現類DefaultListableBeanFactory。目前最常用的是基於註解的AnnotationConfigApplicationContextAnnotationConfigWebApplicationContext

四、Spring依賴注入快速上手

1.使用Java配置代替xml配置

Java配置的核心是@Configuration和@Bean。定義生命週期使用@Scope,需要引入其他配置檔案時使用@Import

(1)@Configuration:應用了@Configuration註解的POCO成為了配置類。相當於xml配置檔案。

(2)@Bean:配置類中應用了@Bean註解的方法成為了配置項。相當於xml中的Bean節點。

package me.test.spring_ioc;

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

public class App {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext container = new AnnotationConfigApplicationContext(AppConfig.class);
        String message = container.getBean(IAppService.class).Test();
        System.out.println(message);
        container.close();
    }
}

@Configuration
class AppConfig {
    @Bean
    public IAppService IAppService() {
        return new AppService(new Repository<SimpleEntity>());
    }
}

class SimpleEntity {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

interface IAppService {
    String Test();
}

interface IRepository<T> {
    String Test();
}

class AppService implements IAppService {

    private IRepository<SimpleEntity> _repo;

    public AppService(IRepository<SimpleEntity> repo) {
        _repo = repo;
    }

    @Override
    public String Test() {
        return this._repo.Test();
    }

}

class Repository<T> implements IRepository<T> {

    @Override
    public String Test() {
        return this.getClass().getName();
    }

}

如果是Web應用程式,應該使用AnnotationConfigWebApplicationContext,在JSP中可通過WebApplicationContextUtils獲取ApplicationContext物件。

<%@page import="swp.IAppService"%>
<%@page import="org.springframework.web.context.WebApplicationContext"%>
<%@page
    import=" org.springframework.web.context.support.WebApplicationContextUtils"%>
<html>
<body>
    <%
        WebApplicationContext context = WebApplicationContextUtils
                .getRequiredWebApplicationContext(this.getServletContext());
        String message = context.getBean(IAppService.class).print();
        out.print(message);
    %>
</body>
</html>

2.基於Annotation的自動裝配

自動裝配主要使用@ComponentScan@Component@Autowired

(1)@ComponentScan:作用在配置類上,啟用元件掃描。掃描並註冊標註了@Component(@Controller\@Service\@Repository)的型別。@Configuration已經應用了@Component註解。

(2)@Autowired:按型別自動裝配。@Autowired和使用@Inject(JSR-330)或@Resource(JSR-250)的效果是類似的。@Autowired和@Inject預設按型別注入,@Resource預設按名稱注入。

package me.test.spring_ioc;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;

public class App {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext container = new AnnotationConfigApplicationContext(AppConfig.class);
        String message = container.getBean(IAppService.class).Test();
        System.out.println(message);
        container.close();
    }
}

@Configuration
@ComponentScan
class AppConfig {

    /*@Bean
    public IAppService IAppService() {
        return new AppService(new Repository<SimpleEntity>());
    }*/

}

class SimpleEntity {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

interface IAppService {
    String Test();
}

interface IRepository<T> {
    String Test();
}

@Component
class AppService implements IAppService {

    private IRepository<SimpleEntity> _repo;

    @Autowired
    public AppService(IRepository<SimpleEntity> repo) {
        _repo = repo;
    }

    @Override
    public String Test() {
        return this._repo.Test();
    }

}

@Component
class Repository<T> implements IRepository<T> {

    @Override
    public String Test() {
        return this.getClass().getName();
    }

}

 

小結:

你至少應該知道的:

(1)BeanFactory和BeanDefinition

(2)DefaultListableBeanFactory和ApplicationContext

(3)AnnotationConfigApplicationContext和AnnotationConfigWebApplicationContext

(4)@Configuraton和@Bean

Spring的依賴注入在配置上是基於物件而不是型別,最先支援的是xml而不是註解,到現在也沒有比較方便的程式碼配置機制。雖然現在已經從xml過渡到了註解方式,但基於物件的基礎仍然是影響很多方面的缺點。

參考

1.http://docs.spring.io/spring-framework/docs/current/spring-framework-reference/html/overview.html

2.https://en.wikipedia.org/wiki/Dependency_inversion_principle

3.http://www.ibm.com/developerworks/cn/java/j-guice.html

4.https://www.ibm.com/developerworks/cn/webservices/ws-springjava/

5.http://www.ibm.com/developerworks/cn/opensource/os-cn-spring-iocannt/

6.https://www.ibm.com/developerworks/cn/java/j-lo-spring25-ioc/

7.http://www.ibm.com/developerworks/cn/java/j-lo-beanannotation/

8.http://www.ibm.com/developerworks/cn/java/j-guice.html

9.http://www.yiibai.com/spring/spring-dependency-checking-with-required-annotation.html

相關文章