@(SPRING FRAMEWORK)
〔4〕7.4 Dependencies
声明:
- 斜体字:《官档》原文
- 斜体加粗字:《官档》原文的重点字、词、句
- 正体字+前置〔〕:个人表述行为,如:〔总结〕、〔分析〕等
- <samp style=”color:#CCCCCC”>灰体字:生词</samp>
- <samp style=”color:#FF0066″>粉体字:疑问</samp>
[TOC]
A typical enterprise application does not consist of a single object
〔总结〕一个典型的程序,是需要多个对象相互协作
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.
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.
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.
示例代码
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>
- 一个规范:顺序
示例代码
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
的依赖注入
- 无参构造函数
- 无参工厂方法
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场景:
- Class A requires an instance of class B through constructor injection, and class B requires an instance of class A through constructor injection.
- 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.
〔总结〕从上文可得
-
ref
属性在哪用,有什么用 - 略提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
- Collection merging
- Limitations of collection merging
- 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>
的key
、value
;<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
的场景:
- a static initializer in a class needs to be triggered.
- 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
- 自动装配的优势?
- 自动装配的劣势?
- 自动装配的模式?
- 自动装配的限制?
自动装配的应用?
〔总结〕Spring有能力自动装配协作者(即对象之间存在依赖关系)。
自动装配的优点:
- Autowiring can <samp style=”color:#cccccc”>significantly reduce</samp> the need to specify properties or constructor arguments.
- 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
- <samp style=”color:#cccccc”>Explicit</samp> dependencies in
property
andconstructor-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. - 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.
- Wiring information may not be available to tools that may generate documentation from a Spring container.
- 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的自动装配功能
- 用法
- 作用
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
对象的依赖情况下:
- 一个单例依赖另一个单例
- 一个非单例依赖另一个非单例
古老的做法:是将一个bean
定义为另一个bean
的属性。以此建立依赖关系。
如:A-bean 依赖 B-bean,那么POJO可定义为:
public class A{
public B b;
}
public class B{}
bean之间的依赖关系建立后,会产生一个问题: <samp style=”color:#FF0066″>两个bean的生命周期不一样,怎么办?</samp>
古老的做法是这样子的,见代码示例:
【代码示例说明】:
-
CommandManager obejct
在系统架构中属于业务逻辑层(SERVICE)。 -
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
有两个:CommandManager
和Command
。需要分析以下几个问题
- 两个
bean object
中,<u>依赖方</u>是谁? <u>被依赖方</u>又是谁?
answer:依赖方:CommandManager
; 被依赖方:Command
。 - 被依赖方的
bean obejct
的生命周期如何得到保证?
answer:要保证被依赖方的生命周期随时不死。那么依赖方实现接口ApplicationContextAware
,目的是为了调用Spring framework的方法
这种在业务层耦合Spring framework代码已破化IoC原则,因为在实现接口的那一刻起,IoC原则就已经被破坏了。即这种解决方案不可取。Spring给出了2种可靠的方案:
- lookup method injection
- arbitrary method replacement
〔经验之道〕业务层的所有代码应该满足以下注释要求
// no more Spring imports!
6.1 Lookup method injection
参阅《官档》,思考代码示例逻辑
6.2 Arbitrary method replacement
参阅《官档》,思考代码示例逻辑