Spring基础知识(二):依赖注入

Spring最重要的两个特性就是依赖注入DI和面向切面编程AOP。

一、依赖注入

1、依赖注入与控制反转

这两个概念经常被用来比较,个人理解应该是他们描述的角度的不同的。

1.1、依赖注入

理解依赖注入,首先要知道这里说的依赖指的是什么

正常的程序中肯定是有很多个类一起协同工作的,比如一个A类要用到另一个B类的某个方法,那么则表示B类是A类的一个依赖。

那么A类可以自己new一个B类的实例出来,然后再需要的时候进行调用,但是这样做有很多问题,会导致高度耦合难以测试

而如果使用依赖注入的方式,则可以带来一个最明显的好处就是-松耦合

如果一个对象只通过接口(而不是具体实现或初始化过程)来表明依赖,你们这种依赖就能够在对象本身毫不知情的情况下,用不同的具体实现进行替换。

1.2、控制反转

控制反转的意思我理解的就是,所有的依赖都统一交给Spring的容器去管理,

  • 控制的具体含义就是对依赖的注入
  • 而反转是指,依赖的注入本来由自己控制的东西,交给了容器去控制,实现了主动权的反转。

2、依赖的注入方式

说到依赖的注入方式还是离不开Spring的容器,Spring容器负责创建应用程序中的bean并通过DI来协调这些对象之间的关系。所以想要了解依赖的注入方式,就要先知道bean的装配方式

  • 在XML中进行显式配置
  • 在Java代码中进行显式配置
  • 隐式的bean发现机制和自动装配

以XML配置的方式为例,依赖注入有四种方式:

  1. set注入
  2. 构造器注入
  3. 静态工厂方法注入
  4. 实例工厂方法注入

2.1、set注入

这是最简单的注入方式,假设有一个SpringAction,类中需要实例化一个SpringDao对象,那么就可以定义一个private的SpringDao成员变量,然后创建SpringDao的set方法(这是ioc的注入入口):

1
2
3
4
5
6
7
8
9
10
11
12
13
public class SpringAction {  
//注入对象springDao
private SpringDao springDao;

//一定要写被注入对象的set方法
public void setSpringDao(SpringDao springDao) {
this.springDao = springDao;
}

public void ok(){
springDao.ok();
}
}

随后编写spring的xml文件,<bean>中的name属性是class属性的一个别名,class属性指类的全名,因为在SpringAction中有一个公共属性Springdao,所以要在<bean>标签中创建一个<property>标签指定SpringDao。<property>标签中的name就是SpringAction类中的SpringDao属性名,ref指下面<bean name="springDao"...>,这样其实是spring将SpringDaoImpl对象实例化并且调用SpringAction的setSpringDao方法将SpringDao注入:

1
2
3
4
5
6
<!--配置bean,配置后该类由spring管理-->  
<bean name="springAction" class="com.bless.springdemo.action.SpringAction">
<!--(1)依赖注入,配置当前类中相应的属性-->
<property name="springDao" ref="springDao"></property>
</bean>
<bean name="springDao" class="com.bless.springdemo.dao.impl.SpringDaoImpl"></bean>

2.2、构造器注入

这种方式的注入是指带有参数的构造函数注入,看下面的例子,我创建了两个成员变量SpringDao和User,但是并未设置对象的set方法,所以就不能支持第一种注入方式,这里的注入方式是在SpringAction的构造函数中注入,也就是说在创建SpringAction对象时要将SpringDao和User两个参数值传进来:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class SpringAction {  
//注入对象springDao
private SpringDao springDao;
private User user;

public SpringAction(SpringDao springDao,User user){
this.springDao = springDao;
this.user = user;
System.out.println("构造方法调用springDao和user");
}

public void save(){
user.setName("卡卡");
springDao.save(user);
}
}

在XML文件中同样不用<property>的形式,而是使用<constructor-arg>标签,ref属性同样指向其它<bean>标签的name属性:

1
2
3
4
5
6
7
8
<!--配置bean,配置后该类由spring管理-->  
<bean name="springAction" class="com.bless.springdemo.action.SpringAction">
<!--(2)创建构造器注入,如果主类有带参的构造方法则需添加此配置-->
<constructor-arg ref="springDao"></constructor-arg>
<constructor-arg ref="user"></constructor-arg>
</bean>
<bean name="springDao" class="com.bless.springdemo.dao.impl.SpringDaoImpl"></bean>
<bean name="user" class="com.bless.springdemo.vo.User"></bean>

剩下的两种工厂方法的注入方式没有用过,不详细介绍了。

2.3、自动装配

除了xml的装配方式,实际使用中更多的是使用自动装配的方式。Spring从两个角度来实现自动化装配:

  • 组建扫描(component scanning):Spring会自动发现应用上下文中锁创建的bean
  • 自动装载(autowiring):Spring自动满足bean之间的依赖

这两个分别对应了注解@Component和@Autowiring

  • @Component:这个注解表明该类会作为组件类,并告知Spring要为这个类创建bean
  • @AutoWiring:可以用在构造方法或set方法上,表明注入一个依赖

比如,构造器注入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Component
class A implements aa{
void fun();
}
@Component
class B{
A a;

@AutoWiring
public B(A a){
this.a=a;
}

public void fun(){
a.fun();
}
}

set注入

1
2
3
4
5
6
7
8
9
10
11
12
13
   @Component
class B{
A a;

@AutoWiring
public void setA(A a) {
this.a = a;
}

public void fun(){
a.fun();
}
}

2.4、set注入与构造器注入的区别

http://blog.sina.com.cn/s/blog_ad071a810101g9wx.html

@Component,@Service,@Controller,@Repository

Spring 2.5 中除了提供 @Component 注释外,还定义了几个拥有特殊语义的注释,它们分别是:

  • @Repository
  • @Service
  • @Controller。

在目前的 Spring 版本中,这 3 个注释和 @Component 是等效的,但是从注释类的命名上,很容易看出这 3 个注释分别和持久层、业务层和控制层(Web 层)相对应。

3、Spring的特殊注入

Spring中还有两种比较特殊的注入方式:就是通过Spring配置文件中的lookup-method和replace-method,这其实是两个方法级别的注入,和一般的属性(Property)注入是不一样的,它们注入的是方法(Method)。

3.1、lookup-method注入

lookup method注入是spring动态改变bean里方法的实现。方法执行返回的对象,使用spring内原有的这类对象替换,通过改变方法返回值来动态改变方法。内部实现为使用cglib方法,重新生成子类,重写配置的方法和返回对象,达到动态改变的效果。

有种说法这个也叫方法注入,它不同于set注入和构造注入,主要是使用场景不同:一个singleton的Bean需要引用一个prototype的Bean; 一个无状态的Bean需要引用一个有状态的Bean。容器创建之后就加载了所有的Bean. 而BeanA中需要引用的BeanB是状态个不确定的Bean. 那么我们需要在每次需要BeanB的时候都重新让容器加载一次吗?

cglib为我们动态的构造BeanB的子类, 当我们的BeanA需要BeanB的时候, cglib把这个子类对象给BeanA.

1
2
3
4
5
6
7
8
9
10
11
12
13
public abstract class CommandManager {

public Object process(Object commandState) {
// grab a new instance of the appropriate Command interface
Command command = createCommand();
// set the state on the (hopefully brand new) Command instance
command.setState(commandState);
return command.execute();
}

// okay... but where is the implementation of this method?
protected abstract Command createCommand();
}

配置:

1
2
3
4
5
6
<bean id="command" class="fiona.apple.AsyncCommand" scope="prototype">
</bean>

<bean id="commandManager" class="fiona.apple.CommandManager">
<lookup-method name="createCommand" bean="command"/>
</bean>

注意:由于采用cglib生成之类的方式,所以需要用来动态注入的类,不能是final修饰的;需要动态注入的方法,也不能是final修饰的。

同时,还得注意command的scope的配置,如果scope配置为singleton,则每次调用方法createCommand,返回的对象都是相同的;如果scope配置为prototype,则每次调用,返回都不同。

3.2、replaced-method注入

replaced method注入是spring动态改变bean里方法的实现。需要改变的方法,使用spring内原有其他类(需要继承接口org.springframework.beans.factory.support.MethodReplacer)的逻辑,替换这个方法。通过改变方法执行逻辑来动态改变方法。内部实现为使用cglib方法,重新生成子类,重写配置的方法和返回对象,达到动态改变的效果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class MyValueCalculator {

public String computeValue(String input) {
// some real code...
}
// some other methods...
}

/**
* meant to be used to override the existing computeValue(String)
* implementation in MyValueCalculator
*/
public class ReplacementComputeValue implements MethodReplacer {
public Object reimplement(Object o, Method m, Object[] args) throws Throwable {
// get the input value, work with it, and return a computed result
String input = (String) args[0];
...
return ...;
}
}

配置

1
2
3
4
5
6
7
8
<bean id="myValueCalculator" class="x.y.z.MyValueCalculator">
<!-- arbitrary method replacement -->
<replaced-method name="computeValue" replacer="replacementComputeValue">
<arg-type>String</arg-type>
</replaced-method>
</bean>

<bean id="replacementComputeValue" class="a.b.c.ReplacementComputeValue"/>

注意:由于采用cglib生成之类的方式,所以需要用来动态注入的类,不能是final修饰的;需要动态注入的方法,也不能是final修饰的。

3.3、二者的区别

两者的差别是这样的

  1. 如果需要替换的方法没有返回值,那么只能使用replace-method来替换,而不能用lookup-method来替换。
  2. replace-method必须实现MethodReplacer接口的Bean才能替换,而lookup-method则由BeanFactory自动为我们处理了。

二、依赖注入的时机

在IOC容器中依赖注入的触发有两种情况

  • 用户第一次调用getBean方法时触发
  • 通过设置lazy-init属性设置让容器在初始化阶段完成预实例化

在第一篇中已经分析过,Spring中bean的作用域有五种,通过scope属性来设置,比较常见的就是singleton和prototype,前者就是单例模式。下面分析的lazy-init时机,基本都是针对singleton的bean的。

关于lazy-init,很多地方都遇见过,大概就是等需要这个东西的时候再将它实例化。

1、spring的default-lazy-init参数

ApplicationContext实现的默认行为就是在启动服务器时将所有singleton bean提前进行实例化(也就是依赖注入)。提前实例化意味着作为初始化过程的一部分,ApplicationContext实例会创建并配置所有的singleton bean。

这样可以减少web服务器在运行时的负担,但是同时也会增加Spring的启动时间。

所以在Spring中有一个属性可以来配置是否开启lazy-init,就是在<beans>元素上使用default-lazy-init属性。按照上面的说法,Spring启动的时候没有lazy-init,所以这个属性默认的肯定是false的。

1
2
< beans  default-lazy-init="true" >   
</beans>

设置这个属性以后,就可以实现lazy-init,从而大大减少Spring的启动时间。

2、Spring的lazy-init

2.1、lazy-init

1
2
3
4
5
6
<beans> 
<bean id="service1" type="bean路径" lazy-init="true"/>
<bean id="service2" type="bean路径" lazy-init="false">
<property name="service1" ref="service1"/>
</bean>
</beans>

当 IoC容器启动时,service2会实例化,而service1则不会;但是但容器实例化service2时,service1也被实例化了,为什么呢,因为service2需要它。也就是说lazy-init=”true”的bean,IoC容器启动时不会实例化该bean,只有当容器需要用到时才实例化它。lazy-init有利于容器效率,对于不需要的bean可以先不管。

2.2、< beans>与< bean>的优先级

在同一个文件中<bean/>里面设置的优先级大于<beans/>里设置的优先级

3、Spring的abstract

1
2
<bean id="baseTxService"  class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"  abstract="true"> 
</bean>

bean abstract=”true”时,该bean不会被实例化,上面的bean是个模板

4、非单例bean的实例化

以上说的都是bean为singleton的情况。如果一个bean的scope属性为scope=”pototype”时,即使设置了lazy-init=”false”,容器启动时不实例化bean,而是调用getBean方法实例化的

通过上面的分析可以知道,

  • 如果一个bean被设置为lazy-init
  • 在其他bean实例化的时候没有依赖它
  • 或者bean的属性不是singleton

那么它在容器初始化阶段并不会实例化,而是第一次getBean的时候实例化,下面详细分析一下getBean的源码。

三、Bean实例化的三种方式

在Spring中bean的实例化有三种方式

  1. 使用构造器实例化
  2. 使用静态工厂方法实例化
  3. 使用实例化工厂方法实例化

1、使用构造器实例化

这种实例化的方式可能在我们平时的开发中用到的是最多的,因为在xml文件中配置简单并且也不需要额外的工厂类来实现。

默认无参构造方法:

1
2
<!--applicationContext.xml配置:-->  
<bean id="personService" class="cn.mytest.service.impl.PersonServiceBean"></bean>

如果有参数,有多重参数匹配的方式

  1. 按照参数的顺序和个数来注入
  2. 按照参数类型匹配注入
  3. 按照参数索引顺序注入
  4. 通过参数名称注入 Spring3以上
  5. 通过annotation注入@ConstructorProperties Spring3以上
  6. 用简化的c namespace来进行构造函数注入 spring3.1以上

1.1、按照参数的顺序和个数来注入

1
2
3
4
5
6
public class Foo {  

public Foo(Bar bar, Baz baz) {
// ...
}
}
1
2
3
4
5
6
7
8
9
10
<beans>  
<bean id="foo" class="x.y.Foo">
<constructor-arg ref="bar"/>
<constructor-arg ref="baz"/>
</bean>

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

</beans>

下面几种注入方式

1
2
3
4
5
6
7
8
public class ExampleBean {  
private int years;
private String ultimateAnswer;
public ExampleBean(int years, String ultimateAnswer) {
this.years = years;
this.ultimateAnswer = ultimateAnswer;
}
}

1.2、按照参数类型匹配注入

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

1.3、按照参数索引顺序注入

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

1.4、spring3以上还可以通过参数名称进行注入

1
2
3
4
<bean id="exampleBean" class="examples.ExampleBean">  
<constructor-arg name="years" value="7500000"/>
<constructor-arg name="ultimateanswer" value="42"/>
</bean>

1.5、spring3以上通过annotation注入 @ConstructorProperties

1
2
3
4
5
6
7
8
9
package examples;  

public class ExampleBean {
@ConstructorProperties({"years", "ultimateAnswer"})
public ExampleBean(int years, String ultimateAnswer) {
this.years = years;
this.ultimateAnswer = ultimateAnswer;
}
}

2、使用静态工厂方法实例化

根据这个中实例化方法的名称就可以知道要想通过这种方式进行实例化就要具备两个条件:

  1. 要有工厂类及其工厂方法
  2. 工厂方法是静态的

首先创建工程类及其静态方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
package cn.mytest.service.impl;  

/**
*创建工厂类
*
*/
public class PersonServiceFactory {
//创建静态方法
public static PersonServiceBean createPersonServiceBean(){
//返回实例化的类的对象
return new PersonServiceBean();
}
}

然后再去配置spring配置文件,配置的方法和上面有点不同,这里也是关键所在

1
2
<!--applicationContext.xml配置:-->  
<bean id="personService1" class="cn.mytest.service.impl.PersonServiceFactory" factory-method="createPersonServiceBean"></bean>

3、使用实例化工厂方法实例化

这个方法和上面的方法不同之处在与使用该实例化方式工厂方法不需要是静态的,但是在spring的配置文件中需要配置更多的内容

首先创建工厂类及工厂方法:

1
2
3
4
5
6
7
8
9
10
11
12
package cn.mytest.service.impl;  

/**
*创建工厂类
*/
public class PersonServiceFactory {
//创建静态方法
public PersonServiceBean createPersonServiceBean1(){
//返回实例化的类的对象
return new PersonServiceBean();
}
}

然后再去配置spring配置文件,配置的方法和上面有点不同,这里也是关键所在

1
2
3
4
5
<!--applicationContext.xml配置:-->  

<bean id="personServiceFactory" class="cn.mytest.service.impl.PersonServiceFactory"></bean>

<bean id="personService2" factory-bean="personServiceFactory" factory-method="createPersonServiceBean1"></bean>

这里需要配置两个bean,第一个bean使用的构造器方法实例化工厂类,第二个bean中的id是实例化对象的名称,factory-bean对应的被实例化的工厂类的对象名称,也就是第一个bean的id,factory-method是非静态工厂方法。

四、参考地址

http://blessht.iteye.com/blog/1162131

http://rooock.iteye.com/blog/338847

http://www.cnblogs.com/atwanli/articles/6154920.html

http://www.cnblogs.com/wcyBlog/p/3756624.html

http://blog.csdn.net/dracotianlong/article/details/8989874

http://javatea.iteye.com/blog/1337706

http://glzaction.iteye.com/blog/1286325

http://blog.csdn.net/zhongweijian/article/details/8482131