Spring核心——Bean的依赖注入
依赖注入
在设计模式与IoC这篇文章中,介绍了Spring基础的三大支柱的两项内容——IoC、Bean。本篇将继续围绕着Bean的创建时的注入方式来介绍Spring的核心思想与设计模式。
天底下所有面向对象的语言都不可能只用一个类来解决问题,即使是最简单的应用程序都存在类与类之间的依存关系。如下面这个人人都理解的组合例子:
class Foo{
   private Other other;
   public Foo(){
      other = new Other();
   }
}
class Other{}
在设计模式上关于类的组合与继承的适用性不属于本篇的讨论范围,但是从Spring框架非侵入式的设计思路来看,组合才是使用Spring的正确姿势。
官方将这种组合的关系叫做“依赖注入(DI——Dependency injection)”。从名字上来看这也是一种依托Ioc容器很自然的实现方式——所有的Bean都放置在容器中,然后通过一些配置来告诉容器bean与bean之间的依存关系。一个类除了在内部块中通过new关键字实现一个组合关系,也可以通过构造方法传参或接口方法设置。
由于IoC容器不可能去修改一个类内部的代码,所以类与类的组合方式通过构造方法(Constructor)和set方法(Setter)来实现。此外,Ioc可以根据接口(interface)来注入对应的实现类(class extands interface),所以从设计模式的角度来说,依赖注入的方式很好的规避了标准组合模式中new关键字违反依赖倒置原则的问题。
构造方法注入
直接通过构造方法注入组合数据。
class:
package x.y;
public class A {
    private B b;
    private C c;
    public Foo(B b, C c) {
       this.b = b;
       this.c = c;
    }
}
public class B {}
public class C {}
xml:
<beans>
    <bean id="a" class="x.y.A">
        <constructor-arg ref="b"/>
        <constructor-arg ref="c"/>
    </bean>
<bean id="b" class="x.y.B"/>
<bean id="c" class="x.y.C"/>
</beans>
如果是源生类型的参数,可以通过指定类型来注入数据:
package x.y;
public class A {
    private int b;
    private String c;
    public Foo(int b, String c) {
       this.b = b;
       this.c = c;
    }
}
<bean id="a" class="x.y.A">
    <constructor-arg type="int" value="1"/>
    <constructor-arg type="java.lang.String" value="42"/>
</bean>
也可以通过索引的方式:
<bean id="a" class="x.y.A">
    <constructor-arg index="0" value="1"/>
    <constructor-arg index="1" value="42"/>
</bean>
配合@ConstructorProperties注解,还可以直接使用名称来注入:
package x.y;
public class A {
    private int b;
    private String c;
    @ConstructorProperties({"b", "c"})
    public Foo(int b, String c) {
       this.b = b;
       this.c = c;
    }
}
<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg name="b" value="1"/>
    <constructor-arg name="c" value="42"/>
</bean>
在Debug模式下不用这个注解也可以实现按名字注入,但是千万别这样做。
Set方法注入
package x.y;
public class A {
    private B b;
    private C c;
    private String value;
    public void setA(A a){this.a = a;}
    public void setB(B b){this.b = b;}
    public void setB(String value){this.value = value;}
}
public class B {}
public class C {}
<bean id="a" class="x.y.A">
    <property name="b" ref="b"/>
    <property name="c" ref="c"/>
    <property name="value" value="1"/>
</bean>
<bean id="b" class="x.y.B"/>
<bean id="c" class="x.y.C"/>
使用 Constructor还是Setter?
2种注入方法在使用的过程中我们应该如何选取呢?Spring官方给出的答案是如果注入的数据或bean是一个“必要依赖”那么使用构造方法注入,如果属于配置性的非必须数据,使用Set方法注入。但是在实际应用时,会发现绝大部分注入方式都是通过Setter实现的,包括一些很流行的开源工具,例如下面的druid:
<bean id="ds" class="com.mchange.v2.c3p0.ComboPooledDataSource">
     <property name="driverClass" value="com.mysql.jdbc.Driver"/>
     <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/c3p0jdbctemplate"/>
     <property name="user" value="admin"/>
     <property name="password" value="123456"/>
</bean>
话说你不提供账户和密码能链接到数据库吗?这算必要依赖还是配置性依赖?所以也不用死守这些规则。下面是一些关键性的建议:
数据配置类使用constructor注入的方法来实现,因为这样可以将bean设置为一个不可变对象(immutable objects)。这样结合单例模式能够很好实现享元模式共享数据,结合原型模式可以创建“浅对比”对象(变更则替换)。
如果构造函数要传入的参数太多,证明你的类要完成的责任太多,这个时候用Setter当然比较合理,但是建议回头去看看类当中是不是有可以拆分的功能。给你们推荐一个学习java的平台659270626,趁年轻,使劲拼,给未来的自己一个交代!
Setter注入主要用于可选的依赖关系,如果没有设置值,类应该提供默认值。所以Setter方法应该检查传入值的有效性(not null、not blank等)。
如果出现了循环依赖,其实可以通过一个bean使用setter注入另外一个bean使用constructor注入来解决,不过最好检查一下代码为什么会循环,这是设计模式上的大忌。
最有一个建议最重要。如果用第三方类,别人给什么你只能用什么,没得选。
注入参数
在XML配置中,用来设定注入方式和注入数据的XML标签很多,详细内容就不一一复述了,常规用法可以到官网 Dependencies and configuration in detail  一节了解。这里仅仅说明一些要点:
父子Bean。Ioc容器提供Bean的父子关系配置。父子关系Bean可以进行数据合并,但是很少看见什么地方有实际应用。
<idref>标签和<ref>标签的差异:1)前者只能通过id引入,后者可以通过id或name引入;2)前者可以直接用value属性替换,但是value属性的效率会差很多;3)前者只能适用与当前配置文件或当前容器,后者可以引入任何位置的内容。
当需要设置一个null值时,用<null>标签代替value=""。在执行代码时直接传入一个null。
自动装配
这里所说的自动装配是通过<bean>上的autowire属性实现的功能,与@Autowired注解并不是一回事,但是他的一些参数会影像@Autowired注解的行为。
在有@Autowired注解的情况下,autowire属性现在用得很少。基本上他实现的结果和@Autowired差不多,就是让Ioc容器根据bean的类型或者bean名称等自动将容器中其他能对应得上的bean注入到对于的构造方法或者set方法中。详情了解 Autowiring collaborators。
方法注入
如果每一个Bean都是单例模式,那么我们通过常规的XML配置引用的手段就可以实现所有的依赖组合关系。但是每个bean都有不同的生命周期,常规配置方法很难实现某些应用不同生命周期bean的依赖关系。
第一种方式是通过继承 ApplicationContextAware 类,继承后可以直接使用 applicationContext 的 getBean 接口来获取任何一个 bean。
package x.y;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
public class BeanManagerFoo implements ApplicationContextAware {
private ApplicationContext applicationContext;
public <T> T getBean(Class<T> type){
		return applicationContext.getBean(type);
	}
public Object getBean(String id){
		return springContext.getBean(id);
	}
public void setApplicationContext(
            ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}
第二种方法是使用Lookup Method。
Lookup Method的实现思路是使用CGLIB生成了动态代理类并放置到Ioc中代替源生的类。看下面的例子。
首先实现我们的抽象类,抽象的要求至少有一个抽象方法:
package x.y;
public abstract class A {
    public String getName() {
        B b = this.createB();
        return b.getName();
    }
protected abstract B createB();
}
public class B {
    private String name = "B class";
    public String getName(http://www.my516.com){
        return this.name;
    }
}
然后通过<lookup-method>标签来指定获取bean的方式:
<bean id="b" class="x.y.B" scope="prototype" />
<bean id="a" class="x.y.A">
    <lookup-method name="createB" bean="b"/>
</bean>
现在,在调用A.getName方法时都会创建一个新的B类实例。需要注意scope属性,如果修改为singleton则每次都获取同一个B实例。
使用动态代理由于是字节码级别的变换,所有有很多限制需要注意:方法和类都不能用fina关键字;测试用例需要自己实现代理模式,否则抽象类没有实现;
第三种方法是使用委派模式,即我们执行A.compute方法时,实际执行的是被委派的B.reimplement方法。
先定义2个基础类——Origin、Replace:
package x.y
public class Origin {
    public int compute(int in1, int in2) {
        return in1+in2;
    }
}
public class Replace {
    public int reimplement(Object o, Method m, Object[] args) {
        int in1 = (int)args[0];
        int in2 = (int)args[1];
        return in1+in2;
    }
}
然后定义Spring配置:
<bean id="origin" class="x.y.Origin">
    <replaced-method name="compute" replacer="replace">
        <arg-type>int</arg-type>
        <arg-type>int</arg-type>
    </replaced-method>
</bean>
<bean id="replace" class="x.y.Replace"/>
这个时候,在任何时候执行“origin”这个bean的compute方法,实际上都是执行的Replace::reimplement方法。
上面<arg-type/>的参数用全称或简写都可以,例如java.lang.String,使用String,Str都是指向这个类型。
使用委派模式的好处是限制少、灵活,并且不会用到CGLIB这种重量级工具。但是委派之后委派方法的真实参数和被委派方法的参数完全不一样,开发时需要时时刻刻紧跟委派类的结构来修改代码。一旦委派类发生任何修改而没有相应的调整被委派类,可能会出现意想不到的问题。
---------------------
Spring核心——Bean的依赖注入的更多相关文章
- Spring进阶之路(1)-Spring核心机制:依赖注入/控制反转
		
原文地址:http://blog.csdn.net/wangyang1354/article/details/50757098 我们经常会遇到这样一种情景,就是在我们开发项目的时候经常会在一个类中调用 ...
 - (转)Spring读书笔记-----Spring核心机制:依赖注入
		
Java应用(从applets的小范围到全套n层服务端企业应用)是一种典型的依赖型应用,它就是由一些互相适当地协作的对象构成的.因此,我们说这些对象间存在依赖关系.加入A组件调用了B组件的方法,我们就 ...
 - Spring核心机制:依赖注入
		
转载:http://www.cnblogs.com/chenssy/ Java应用(从applets的小范围到全套n层服务端企业应用)是一种典型的依赖型应用,它就是由一些互相适当地协作的对象构成的.因 ...
 - Spring读书笔记-----Spring核心机制:依赖注入
		
spring框架为我们提供了三种注入方式,分别是set注入,构造方法注入,接口注入.今天就和大家一起来学习一下 依赖注入的基本概念 依赖注入(Dependecy Injection),也称为IoC(I ...
 - Spring学习-spring核心机制-IOC依赖注入
		
转载自:http://www.cnblogs.com/chenssy/archive/2012/11/11/2765266.html 今天复习一下spring两大特性之一:IOC依赖注入,看了一下大佬 ...
 - 30个类手写Spring核心原理之依赖注入功能(3)
		
本文节选自<Spring 5核心原理> 在之前的源码分析中我们已经了解到,依赖注入(DI)的入口是getBean()方法,前面的IoC手写部分基本流程已通.先在GPApplicationC ...
 - Spring核心之IoC——依赖注入
		
在J2EE开发平台中,Spring是一种优秀的轻量级企业应用解决方案.Spring倡导一切从实际出发,它的核心技术就是IOC(控制反转)和AOP(面向切面编程)技术.本文用的Spring版本为spri ...
 - 采用Spring管理Bean和依赖注入
		
1. 实例化spring容器和从容器获取Bean对象 实例化Spring容器常用的两种方式: 方法一: 在类路径下寻找配置文件来实例化容器 [推荐使用] ApplicationContext ctx ...
 - Srping - bean的依赖注入(Dependency injection)
		
目录 1 概述 2 两种基本的依赖注入方式 2.1 构造函数方式 2.2Setter方式 3 其他依赖注入功能 3.1 <ref/>标签引用不同范围的bean 3.2 内部bean 3.3 ...
 
随机推荐
- Codeforces Round #578 (Div. 2) E. Compress Words (双哈希)
			
题目:https://codeforc.es/contest/1200/problem/E 题意:给你n个单词,你需要把他合成成一个句子,相邻的两个单词,相邻部分相同的话可以把其中一个的删掉 思路:因 ...
 - 如何利用nginx实现负载均衡(总结)
			
如何利用nginx实现负载均衡(总结) 一.总结 一句话总结: 推荐使用nginx七层(应用层)负载均衡的实现:配置那是相当的简单 1.nginx配置实例? |||-begin #这里的域名要和下面p ...
 - Where should I put <script> tags in HTML markup?
			
Where should I put <script> tags in HTML markup? When embedding JavaScript in an HTML document ...
 - Docker容器数据卷-Volume详解
			
Docker中的数据可以存储在类似于虚拟机磁盘的介质中,在Docker中称为数据卷(Data Volume).数据卷可以用来存储Docker应用的数据,也可以用来在Docker容器间进行数据共享.数据 ...
 - 解决修改 Linux 下的 PHP 环境变量不生效的方法
			
这个问题出现服务器有多个 PHP 版本,php -v和phpinfo()显示两个不同的版本 最近真的,都给朋友解决问题了... phpinfo查看的 php 版本是 7.2.6,到 bash 去使用p ...
 - linux 命令参数列表过长以及find用法
			
1.在一个目录下删除大批量的文件时,当使用 rm -rf 或者rm *会提示参数列表过长 通过修改命令为 :find . -name "*" | xargs rm -rf '* ...
 - github 上的PHP资源大全
			
依赖管理 ——用于依赖管理的包和框架Composer/Packagist : 一个包和依赖管理器Composer Installers: 一个多框架Composer库安装器Pickle: 可以在任意 ...
 - <读书笔记>JavaScript系列之7种创建对象(面向对象)
			
写在前面: 以下三选一: 阅读博文JavaScript 对象详解. 阅读<JavaScript权威指南>第6章. 阅读<JavaScript高级程序设计>第6章. 注意:只需要 ...
 - 【题解】A Horrible Poem
			
题目大意 给出一个由小写英文字母组成的字符串 S,再给出 q 个询问,要求回答 S 某个子串的最短循环节. 如果字符串 B 是字符串 A 的循环节,那么 A 可以由 B 重复若干次得到. 输入格式 第 ...
 - Aspnetcore下面服务器热更新与配置热加载
			
原文:Aspnetcore下面服务器热更新与配置热加载 Asp.net的热更新方案Appdomain在aspnetcore中不被支持了 新的方案如下: 配置文件更新选项 reloadOnChange ...