〔SPRING FRAMEWORK〕Dependencies

@(SPRING FRAMEWORK)java

〔4〕7.4 Dependencies

聲明:mysql

  1. 斜體字:《官檔》原文
  2. 斜體加粗字:《官檔》原文的重點字、詞、句
  3. 正體字+前置〔〕:我的表述行爲,如:〔總結〕、〔分析〕等
  4. <samp style="color:#CCCCCC">灰體字:生詞</samp>
  5. <samp style="color:#FF0066">粉體字:疑問</samp>

[TOC]spring

A typical enterprise application does not consist of a single object
〔總結〕一個典型的程序,是須要多個對象相互協做sql

how you go from defining a number of bean definitions that stand alone to a fully realized application where objects collaborate to achieve a goal.
〔總結〕定義一羣具有相互協做能力的對象,以實現目標程序數據庫

1. Dependency Injection

Dependency injection (DI):is a process whereby objects define their dependencies, that is, the other objects they work with, only through constructor arguments, arguments to a factory method, or properties that are set on the object instance after it is constructed or returned from a factory method. The container then injects those dependencies when it creates the bean. This process is fundamentally the inverse, hence the name Inversion of Control (IoC), of the bean itself controlling the instantiation or location of its dependencies on its own by using direct construction of classes, or the Service Locator pattern.apache

1.1 Constructor-based dependency injection

<samp style="color:blue">關鍵詞:</samp> <constructor-arg>架構

Constructor-based DI is accomplished by the container invoking a constructor with a number of arguments, each <samp style="color:#CCCCCC">representing</samp> a dependency. app

Calling a static factory method with specific arguments to construct the bean is nearly <samp style="color:#CCCCCC">equivalent</samp>, and this discussion treats arguments to a constructor and to a static factory method similarly. less

示例代碼ide

public class SimpleMovieLister {

    // the SimpleMovieLister has a dependency on a MovieFinder
    private MovieFinder movieFinder;

    // a constructor so that the Spring container can inject a MovieFinder
    public SimpleMovieLister(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // business logic that actually uses the injected MovieFinder is omitted...

}

〔總結〕基於構造函數的依賴注入,其核心在構造函數上,即重點須要放在一個疑問點上:<samp style="color: #FF0066">構造函數如何影響依賴注入(依賴注入的成功標準:一個bean對象已完成實例化)?</samp>

  1. 一個規範:順序

示例代碼

package x.y;

public class Foo {

    public Foo(Bar bar, Baz baz) {
        // ...
    }

}

第1種bean引用方式:引用引用類型

〔注意〕bean引用方式是指:容器在實例化化一個bean時,須要讀取配置元數據。由於配置元數據中包含了可實例化一個POJO對象的全部必要數據

<!-- -->
<beans>
    <!-- 順序一致 -->
    <bean id="foo" class="x.y.Foo">
        <constructor-arg ref="bar"/><!-- Foo(Bar bar,***) -->
        <constructor-arg ref="baz"/><!-- Foo(***,Baz baz) -->
    </bean>

    <bean id="bar" class="x.y.Bar"/>

    <bean id="baz" class="x.y.Baz"/>
</beans>

第2種bean引用方式:引用基本數據類型

package examples;

public class ExampleBean {

    // Number of years to calculate the Ultimate Answer
    private int years;

    // The Answer to Life, the Universe, and Everything
    private String ultimateAnswer;

    public ExampleBean(int years, String ultimateAnswer) {
        this.years = years;
        this.ultimateAnswer = ultimateAnswer;
    }

}

基於type屬性

<!--  -->
<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg type="int" value="7500000"/>
    <constructor-arg type="java.lang.String" value="42"/>
</bean>

基於name屬性

<!-- -->
<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg name="years" value="7500000"/>
    <constructor-arg name="ultimateAnswer" value="42"/>
</bean>

基於index屬性

<!-- -->
<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg index="0" value="7500000"/>
    <constructor-arg index="1" value="42"/>
</bean>

1.2 Setter-based dependency injection

<samp style="color:blue">關鍵詞:</samp> no-argument

〔總結〕符合如下任一點,可選擇基於setter的依賴注入

  1. 無參構造函數
  2. 無參工廠方法
public class SimpleMovieLister {

    // the SimpleMovieLister has a dependency on the MovieFinder
    private MovieFinder movieFinder;

    // a setter method so that the Spring container can inject a MovieFinder
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // business logic that actually uses the injected MovieFinder is omitted...

}

〔總結〕對比基於帶參構造函數依賴注入和基於setter依賴注入,能夠發現:基於構造函數的不須要提供setter/getter,基於setter的不須要提供帶參構造函數

<samp style="color:#FF0066">Q: Constructor-based or setter-based DI?</samp>

Since you can <samp style="color:#CCCCCC">mix</samp> constructor-based and setter-based DI, it is a good rule of <samp style="color:#CCCCCC">thumb</samp> to use constructors for <samp style="color:#CCCCCC">mandatory</samp> dependencies and setter methods or configuration methods for <samp style="color:#cccccc">optional</samp> dependencies. Note that use of the @Required annotation on a setter method can be used to make the property a required dependency.

能夠混搭一塊兒用。根據實際開發經驗,以Constructor-based DI爲主,以setter-based DI爲輔。主是強制,輔是可選。

The Spring team generally <samp style="color:#CCCCCC">advocates</samp> constructor injection as it enables one to implement application <samp style="color:#CCCCCC">components</samp> as <samp style="color:#CCCCCC">immutable</samp> objects and to ensure that required dependencies are not null. <samp style="color:#CCCCCC">Furthermore</samp> constructor-injected <samp style="color:#CCCCCC">components</samp> are always returned to client (calling) code in a fully initialized state. As a side note, a large number of constructor arguments is a bad code smell, implying that the class likely has too many responsibilities and should be <samp style="color:#CCCCCC">refactored</samp> to better address <samp style="color:#CCCCCC">proper separation</samp> of concerns.

Constructor-based DI能確保

Setter injection should <samp style="color:#cccccc">primarily</samp> only be used for optional dependencies that can be <samp style="color:#cccccc">assigned</samp> reasonable default values within the class. Otherwise, not-null checks must be performed everywhere the code uses the dependency. One benefit of setter injection is that setter methods make objects of that class <samp style="color:#cccccc">amenable</samp> to reconfiguration or re-injection later. Management through JMX MBeans is therefore a <samp style="color:#cccccc">compelling</samp> use case for setter injection.

Use the DI style that makes the most sense for a particular class. Sometimes, when dealing with third-party classes for which you do not have the source, the choice is made for you. For example, if a third-party class does not expose any setter methods, then constructor injection may be the only available form of DI.

〔總結〕懵逼¬_¬


1.3 Dependency resolution process

〔總結〕執行注入的步驟(詳見《官檔》):

  • ApplicationContext根據配置元數據裝配全部bean對象
  • 當依賴的具體表現形式爲如下時:

    • properties
    • constructor arguments
    • arguments to the static-factory method
      那麼ApplicationContext會根據配置元數據的信息爲POJO對象引用到對應的bean對象上。
  • 要保證在每一個屬性,參數構造函數能設值的時候,必須提供值,這個值:能夠是基本值,也能夠是引用值。
  • 每一個屬性、參數構造函數中值的類型都可以被Spring convert成字符串類型。

<samp style="color:#FF0066">如何解決Circular dependencies ?</samp>

Circular dependencies場景

  1. Class A requires an instance of class B through constructor injection, and class B requires an instance of class A through constructor injection.
  2. If you configure beans for classes A and B to be injected into each other, the Spring IoC container <samp style="color:#cccccc">detects</samp> this circular reference at runtime, and throws a <samp style="color:#ff0066">BeanCurrentlyInCreationException</samp>.

Circular dependencies異常解決方案:
you can configure circular dependencies with setter injection.


1.4 Examples of dependency injection

代碼案例:參見《官檔》




2. Dependencies and configuration in detail

2.1 Straight values

觀察如下三個代碼片斷:

代碼片斷 1 使用最原始的方式

<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <!-- results in a setDriverClassName(String) call -->
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
    <property name="username" value="root"/>
    <property name="password" value="masterkaoli"/>
</bean>

代碼片斷 2 : 使用p-namespace配置元數據

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource"
        destroy-method="close"
        p:driverClassName="com.mysql.jdbc.Driver"
        p:url="jdbc:mysql://localhost:3306/mydb"
        p:username="root"
        p:password="masterkaoli"/>

</beans>

代碼片斷 3:使用java.util.Properties

<bean id="mappings"
    class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">

    <!-- typed as a java.util.Properties -->
    <property name="properties">
        <value>
            jdbc.driver.className=com.mysql.jdbc.Driver
            jdbc.url=jdbc:mysql://localhost:3306/mydb
        </value>
    </property>
</bean>

<samp style="color:#ff0066">〔總結〕</samp>
<samp style="color:#ff0066">1. 閱讀對應《官檔》後,依舊對小標題#Straight values#的具體含義模糊不清,不清楚其牽引下文的意圖是什麼。</samp>
<samp style="color:#ff0066">2. 對比上文給出的3個代碼片斷,可發現:皆在用不一樣的方法進行元數據配置,而且被配置的具體對象是數據庫驅動。</samp>


2.2 References to other beans

關鍵詞:ref

The ref element is the final element inside a ① <constructor-arg/> or ② <property/> definition element.Here you set the value of the specified property of ③ a bean to be a reference to another bean (a collaborator) managed by the container. The referenced bean is a dependency of the bean whose property will be set, and it is initialized on demand as needed before the property is set. (If the collaborator is a singleton bean, it may be initialized already by the container.) All references are ultimately a reference to another object. Scoping and validation depend on whether you specify the id/name of the other object through the bean, local, or parent attributes.
〔總結〕從上文可得

  1. ref屬性在哪用,有什麼用
  2. 略提bean做用域、bean驗證對bean對象的影響。

示例代碼

<ref bean="someBean"/>
<!-- in the parent context -->
<bean id="accountService" class="com.foo.SimpleAccountService">
    <!-- insert dependencies as required as here -->
</bean>
<!-- in the child (descendant) context -->
<bean id="accountService" <!-- bean name is the same as the parent bean -->
    class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="target">
        <ref parent="accountService"/> <!-- notice how we refer to the parent bean -->
    </property>
    <!-- insert other configuration and dependencies as required here -->
</bean>

2.3 Inner beans

<bean id="outer" class="...">
    <!-- instead of using a reference to a target bean, simply define the target bean inline -->
    <property name="target">
        <bean class="com.example.Person"> <!-- this is the inner bean -->
            <property name="name" value="Fiona Apple"/>
            <property name="age" value="25"/>
        </bean>
    </property>
</bean>

〔總結〕inner bean相似於Java的內部類


2.4 Collections

  1. Collection merging
  2. Limitations of collection merging
  3. Strongly-typed collection
Spring Collections 映射 Java Collections
<list> —— List
<set> —— Set
<map> —— Map
<props> —— Properties

示例代碼

<bean id="moreComplexObject" class="example.ComplexObject">
    <!-- results in a setAdminEmails(java.util.Properties) call -->
    <property name="adminEmails">
        <props>
            <prop key="administrator">administrator@example.org</prop>
            <prop key="support">support@example.org</prop>
            <prop key="development">development@example.org</prop>
        </props>
    </property>
    <!-- results in a setSomeList(java.util.List) call -->
    <property name="someList">
        <list>
            <value>a list element followed by a reference</value>
            <ref bean="myDataSource" />
        </list>
    </property>
    <!-- results in a setSomeMap(java.util.Map) call -->
    <property name="someMap">
        <map>
            <entry key="an entry" value="just some string"/>
            <entry key ="a ref" value-ref="myDataSource"/>
        </map>
    </property>
    <!-- results in a setSomeSet(java.util.Set) call -->
    <property name="someSet">
        <set>
            <value>just some string</value>
            <ref bean="myDataSource" />
        </set>
    </property>
</bean>

〔總結〕根據示例代碼,得如下結論:
<map>keyvalue;<set>value也能夠是用如下屬性做爲key或value值:

bean | ref | idref | list | set | map | props | value | null

2.5 Null and empty string values

示例代碼:2種方式爲字符串設置NULL或空值

設空值時, 第1種方式:

<bean class="ExampleBean">
    <property name="email" value=""/>
</bean>

設空值時, 第2種方式:

exampleBean.setEmail("")

設NULL時, 第1種方式:

<bean class="ExampleBean">
    <property name="email">
        <null/>
    </property>
</bean>

設NULL時, 第2種方式:

exampleBean.setEmail(null)

2.6 XML shortcut with the p-namespace

代碼片斷

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">
    
    <!--use standard XML format-->
    <bean name="classic" class="com.example.ExampleBean">
        <property name="email" value="foo@bar.com"/>
    </bean>
    
    <!--use the p_namespace-->
    <bean name="p-namespace" class="com.example.ExampleBean"
        p:email="foo@bar.com"/>
</beans>

代碼片斷 :使用p:spouse-ref=""

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean name="john-classic" class="com.example.Person">
        <property name="name" value="John Doe"/>
        <property name="spouse" ref="jane"/>
    </bean>

    <bean name="john-modern"
        class="com.example.Person"
        p:name="John Doe"
        p:spouse-ref="jane"/>

    <bean name="jane" class="com.example.Person">
        <property name="name" value="Jane Doe"/>
    </bean>
</beans>

2.7 XML shortcut with the c-namespace

代碼片斷

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:c="http://www.springframework.org/schema/c"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="bar" class="x.y.Bar"/>
    <bean id="baz" class="x.y.Baz"/>

    <!-- traditional declaration -->
    <bean id="foo" class="x.y.Foo">
        <constructor-arg ref="bar"/>
        <constructor-arg ref="baz"/>
        <constructor-arg value="foo@bar.com"/>
    </bean>

    <!-- c-namespace declaration -->
    <bean id="foo" class="x.y.Foo" c:bar-ref="bar" c:baz-ref="baz" c:email="foo@bar.com"/>

</beans>

2.8 Compound property names

代碼片斷:確保fred,bob屬性的值不爲空,即確保不拋 NullPointerException異常

<bean id="foo" class="foo.Bar">
    <property name="fred.bob.sammy" value="123" />
</bean>



3. Using depends-on

depends-on的做用:
explicitly force one or more beans to be initialized before the bean using this element is initialized.

使用depends-on的場景:

  1. a static initializer in a class needs to be triggered.
  2. database driver registration

代碼片斷 1

<bean id="beanOne" class="ExampleBean" depends-on="manager"/>
<bean id="manager" class="ManagerBean" />

代碼片斷 2

<bean id="beanOne" class="ExampleBean" depends-on="manager,accountDao">
    <property name="manager" ref="manager" />
</bean>

<bean id="manager" class="ManagerBean" />
<bean id="accountDao" class="x.y.jdbc.JdbcAccountDao" />

〔總結〕參閱現有博客提供的博主,及具體應用場景




4. Lazy-initialized beans

延遲加載(初始化)bean對象。

By default, ApplicationContext implementations <samp style="color:#CCCCCC">eagerly</samp> create and configure all singleton beans as part of the initialization process. Generally, this <samp style="color:#CCCCCC">pre-instantiation</samp> is desirable, because errors in the configuration or surrounding environment are discovered immediately, as opposed to hours or even days later. When this behavior is not desirable, you can prevent pre-instantiation of a singleton bean by marking the bean definition as lazy-initialized. A lazy-initialized bean tells the IoC container to create a bean instance when it is first requested, rather than at startup.

代碼示例

<bean id="lazy" class="com.foo.ExpensiveToCreateBean" lazy-init="true"/>
<bean name="not.lazy" class="com.foo.AnotherBean"/>

When the <samp style="color:#CCCCCC">preceding</samp> configuration is <samp style="color:#CCCCCC">consumed</samp> by an ApplicationContext, the bean named lazy is not <samp style="color:#CCCCCC">eagerly</samp> pre-instantiated when the ApplicationContext is starting up, <samp style="color:#CCCCCC">whereas</samp> the not.lazy bean is eagerly pre-instantiated.

However, when a lazy-initialized bean is a dependency of a singleton bean that is not lazy-initialized, the ApplicationContext creates the lazy-initialized bean at startup, because it must satisfy the singleton’s dependencies. The lazy-initialized bean is injected into a singleton bean elsewhere that is not lazy-initialized.

代碼示例: 控制懶加載級別

<beans default-lazy-init="true">
    <!-- no beans will be pre-instantiated... -->
</beans>

〔總結〕參閱《官檔》,屢次閱讀原文旨意。




5. Autowiring collaborators

  1. 自動裝配的優點?
  2. 自動裝配的劣勢?
  3. 自動裝配的模式?
  4. 自動裝配的限制?
  5. 自動裝配的應用?

〔總結〕Spring有能力自動裝配協做者(即對象之間存在依賴關係)。
自動裝配的優勢:

  1. Autowiring can <samp style="color:#cccccc">significantly reduce</samp> the need to specify properties or constructor arguments.
  2. Autowiring can update a configuration as your objects <samp style="color:#cccccc">evolve</samp>. For example, if you need to add a dependency to a class, that dependency can be satisfied automatically without you needing to <samp style="color:#cccccc">modify</samp> the configuration.

〔理論總結〕使用自動裝配後,配置文件具有自更新能力。主流開發的趨勢之一。

autowire //基於XML

5.1 Limitations and disadvantages of autowiring

  1. <samp style="color:#cccccc">Explicit</samp> dependencies in property and constructor-arg settings always override autowiring. You cannot autowire <samp style="color:#cccccc">so-called</samp> simple properties such as <samp style="color:#cccccc">primitives</samp>, Strings, and Classes (and arrays of such simple properties). This limitation is by-design.
  2. Autowiring is less exact than explicit wiring. Although, as noted in the above table, Spring is careful to avoid guessing in case of <samp style="color:#cccccc">ambiguity</samp> that might have unexpected results, the relationships between your Spring-managed objects are no longer documented explicitly.
  3. Wiring information may not be available to tools that may generate documentation from a Spring container.
  4. Multiple bean definitions within the container may <samp style="color:#cccccc">match</samp> the type specified by the setter method or constructor argument to be autowired. For arrays, collections, or Maps, this is not necessarily a problem. However for dependencies that expect a single value, this <samp style="color:#cccccc">ambiguity</samp> is not <samp style="color:#cccccc">arbitrarily</samp> resolved. If no unique bean definition is available, an exception is thrown.

5.2 <samp style="color:#cccccc">Excluding</samp> a bean from autowiring

禁用某特定bean的自動裝配功能

  1. 用法
  2. 做用

5.2.1 用法

基於XML

autowire-candidate=false

基於註解

@Autowired

5.2.2 做用

unavailable to the autowiring infrastructure.
only affect type-based autowiring.

6. Method injection

主要用於解決如下場景:
在大多數應用場景中,bean對象通常都是單例對象。但在bean對象的依賴狀況下:

  1. 一個單例依賴另外一個單例
  2. 一個非單例依賴另外一個非單例

古老的作法:是將一個bean定義爲另外一個bean的屬性。以此創建依賴關係。

如:A-bean 依賴 B-bean,那麼POJO可定義爲:

public class A{
    public B b;
}

public class B{}

bean之間的依賴關係創建後,會產生一個問題: <samp style="color:#FF0066">兩個bean的生命週期不同,怎麼辦?</samp>
古老的作法是這樣子的,見代碼示例:
【代碼示例說明】:

  1. CommandManager obejct在系統架構中屬於業務邏輯層(SERVICE)
  2. CommadManger object的開放式方法process(Map commandState)的調用依賴Command obejct

根據【1.】和【2.】可得,兩個類之間存在密切的依賴關係。因此Spring提供一種方式:當依賴方(CommandManager)的行爲受制於被依賴方(Command)時,能夠在<u>依賴方</u>內部實現<u>被依賴方</u>的初始化。完成上述行爲的前提的是:依賴方(CommandManager)須要實現接口ApplicationContextAware

// a class that uses a stateful Command-style class to perform some processing
package fiona.apple;

// Spring-API imports
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

//依賴方(CommanderManager)現了ApplicationContextAware
public class CommandManager implements ApplicationContextAware {

    private ApplicationContext applicationContext;

    //process()的執行受制於Command對象
    public Object process(Map commandState) {
        
        Command command = createCommand();
       
        command.setState(commandState);
        
        return command.execute();
    }

    protected Command createCommand() {
        // notice the Spring API dependency!
        return this.applicationContext.getBean("command", Command.class);
    }

    public void setApplicationContext(
            ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}

這種作法須要你放棄Spring framework的IoC特性。以上述代碼來看,bean object有兩個:CommandManagerCommand。須要分析如下幾個問題

  1. 兩個bean object中,<u>依賴方</u>是誰? <u>被依賴方</u>又是誰?
    answer:依賴方:CommandManager; 被依賴方:Command
  2. 被依賴方的bean obejct的生命週期如何獲得保證?
    answer:要保證被依賴方的生命週期隨時不死。那麼依賴方實現接口ApplicationContextAware,目的是爲了調用Spring framework的方法

這種在業務層耦合Spring framework代碼已破化IoC原則,由於在實現接口的那一刻起,IoC原則就已經被破壞了。即這種解決方案不可取。Spring給出了2種可靠的方案:

  1. lookup method injection
  2. arbitrary method replacement

〔經驗之道〕業務層的全部代碼應該知足如下注釋要求

// no more Spring imports!

6.1 Lookup method injection

參閱《官檔》,思考代碼示例邏輯

6.2 Arbitrary method replacement

參閱《官檔》,思考代碼示例邏輯

相關文章
相關標籤/搜索