这篇文章并不是对Spring内容整体的泛泛而谈,而是以详略得当,风趣幽默的方式将Spring最基础的知识——依赖注入向大家娓娓道来。
由于在这里图片上传非常痛苦(我必须把每个图片都一一上传),所以我强烈推荐您联系这个邮箱:
[email protected]索要word电子稿或者jar包、源码。我会在第一时间回复您。
如果这篇文章有什么吸引你的地方然而描述的不够详细,相信你总能从Craig的著作中找到你想要的。
Spring是一个开源框架,最早由Rod Johnson创建,任何java程序都能在简单性、可测试性和松耦合性等方面从Spring中获益。
Spring使用Bean表示应用组件(说白了,Bean就是实现了特定功能java类),我们用Spring Bean表示装载在Spring容器中的java Bean。
为降低java开发的复杂性,Spring采取了以下四种关键策略:
1.基于POJO的轻量级和最小入侵性编程
2.通过依赖注入和面向接口实现松耦合
3.基于切面和惯例进行声明式编程
4.通过切面和模板减少样板式代码
容器是Spring框架的核心。Spring容器创建对象、装配它们、配置它们管理它们的整个生命周期,从创建到销毁。并不存在单一的Spring容器,Spring自带了几种容器实现,可分为两种类型:Bean工厂和应用上下文。其中应用上下文是给予Bean工厂之上构建的,提供例如从属性文件解析文本信息等面向应用的服务,因此应用上下文要比Bean工厂更受欢迎。所以,我们在接下来的学习中只与应用上下文共事。
File—>New—>Project 新建Spring项目
点击Next,因为我们不需要模板,所以取消掉Creat project from template前的对勾。然后一直点击Next直到Finish为止。这样我们就创建了一个Spring项目。
新建一个名为lib的文件夹,将所需要的工具包(请自行从网上下载Spring的相关jar包)放在该文件夹下,然后将lib文件夹放在项目之中。
点击File—>project Structure—>Libraries的“+”号将lib文件夹创建为Library。
在File—>project Structure—>Modules板块引入新创建的Library即可。
(1)ClassPathXmlApplicationContext——从类路径的XML配置文件中加载上下文的定义,把应用上下文定义文件当做类资源。
例如:
ApplicationContext context= new ClassPathApplicationContext(“foo.xml”);
(2)FileSystemXmlApplicationContext——读取文件系统下的XML配置文件并加载上下文定义
例如:
ApplicationContext context= new FileSystemXmlApplicationContext (“c:/foo.xml”);
(3)XmlWebApplicationContext——读取Web应用下的XML配置文件并装载至上下文。
我们可以通过应用上下文对象的getBean()方法来根据Bean的id获得Spring容器中的Bean(假设我们已经将一些Bean放在了容器中,至于如何放置则是我们下一章要学习的内容)。要注意getBean()返回的是一个Object类型的对象,需要进行转换,例如:
Performer kenny=(Performer)applicationContext.getBean(“kenny”);
如果项目存在多个Spring XML配置文件,在不同的配置文件中声明的bean又需要相互调用,那么需要将他们封装到同一个上下文中:Spring上下文的构造函数中可以以字符串数组作为参数传入,其中数组的每一个元素就是要加载的配置文件的相应路径。
String[]configFiles={"springIdol2/config/stuffByAnnotation.xml","springIdol2/config/stuffByAnnotation.xml"};
//将需要加载的文件统一成一个数组,所有XML文件中声明的Bean才共用一个spring上下文
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(configFiles);
我们每新建一个Spring XML配置文件,IDEA就会有心的问一下要不要把这个文件加载到我们的Spring上下文中,如果你已经想好在加载配置文件的时候同时加载新创建的这个配置文件,我推荐你选择同意,这样会得到IDEA Spring帮手更多友善的提示。但如果你需要做测试或者其他用途,不想让两个配置文件中的内容相互影响,就不要理会Spring的这个选项,或者选择第二项,否则即使你不加载这个XML文件,IDEA也会认为它在Spring上下文中(这就会导致有的时候程序本身是没有问题的但是IDEA总是提示程序有错)。
在Spring中,对象无需自己负责查找或创建与其相关联的对象,相反容器负责把需要相互协作的对象的引用赋予各个对象。创建应用对象之间协作关系的行为通常被称为装配,这也是依赖注入的本质。
IDEA会自动为你生成文件头,当我们需要一个新的命名空间时,IDEA也会自动的为你在内引入相关的声明。
1.创造一个简单的Bean(该Bean没有成员变量,只有默认的构造器)
例如:
2.通过构造器注入(该Bean拥有成员变量并在构造器进行初始化)
例如:
<bean id="jack" class="springIdol.stuffImpl.PoeticJuggler">
<constructor-arg value="15"/>
<constructor-arg ref="sonnet29"/>
bean>
注意:前两种方法都是相当于调用类的构造器来创建实例。
3.工厂方法创建Bean(该Bean作为整个项目的单例对象模式,没有公开的构造方法)
例如:
类定义部分
public class Stage {
private Stage(){
System.out.println("你好");
}
private static class StageSingletonHolder{
static Stage instance=new Stage();
}
public static Stage getInstance(){
return StageSingletonHolder.instance;
}
}
xml配置部分
"theStage" class="springIdol.stuffImpl.Stage" factory-method="getInstance"/>
元素的factory-method属性,允许我们调用一个指定的静态方法。从而代替构造方法来创建一个类的实例。
所有的Spring Bean默认都是单例(这里的单例不等同于单例模式的单例,因为Spring有关单例的概念仅限于Spring上下文范围内),当容器分配一个Bean时,无论是通过装配还是调用容器的getBean()方法,它总是返回Bean的同一个实例。
我们可以通过设置元素的scope属性(默认值为singleton)为prototype来使我们每次请求时都获得惟一的Bean实例。例如:
的init-method属性指定了在初始化Bean之后要调用的方法,类似的,destory-method属性指定了Bean从容器中移除之前要调用的方法。注意:所要调用的方法并非创造这个bean的方法。
如果上下文定义中的很多Bean拥有相同名字的初始化和销毁方法,我们可以在元素中使用default-init-method和default-destroy-method方法为应用上下文所有的Bean设置一个共同的初始化/销毁方法。
通常,JavaBean的属性是私有的,同时拥有一组存取器方法,以setXXX()和getXXX()形式存在。Spring可以借助属性的set方法来配置属性的值,以实现setter方式的注入。注意:这同时意味着如果Bean的定义中某属性不存在setter方法,那么Spring将无法通过这种方式对该属性进行注入。
<bean id="kenny" class="springIdol.stuffImpl.Instrumentalist">
<property name="song" value="你是我的家"/>
<property name="age" value="25"/>
<property name="instrument" ref="violin"/>
bean>
<bean id="violin" class="springIdol.stuffImpl.Violin"/>
<!—内部类会影响Spring XML的可读性-->
<bean id="kenny" class="springIdol.stuffImpl.Instrumentalist">
<property name="song" value="你是我的家"/>
<property name="age" value="25"/>
<property name="instrument">
<bean class="springIdol.stuffImpl.Violin"/>
property>
bean>
Spring提供了另一种Bean属性的装配方式——使用命名空间p来装配属性:
<bean id="kenny" class="springIdol.stuffImpl.Instrumentalist"
p:song="你是我的家"
p:instrument-ref="violin"
>
bean>
Spring会自动在元素中为你引入:
xmlns:p=”http://www.springframework.org/schema/p”
选择还是命名空间p完全取决于你,因为它们是完全等价的。
<bean id="hank" class="springIdol.stuffImpl.OneManBand">
<property name="instruments">
<list>
<ref bean="violin"/>
<ref bean="cymbal"/>
list>
property>
bean>
<property name="instruments">
<map>
<entry key="VIOLIN" value-ref="violin2"/>
<entry key="CYMBAL" value-ref="cymbal"/>
map>
property>
中的元素由一个键和一个值组成、键和值可以是简单类型也可以是其他Bean的引用。
属性 用途
key 指定map中entry的键为String
key-ref 指定map中entry的键为Spring上下文中其他Bean的引用
value 指定map中entry的值为String
value-ref 指定map中entry的值为Spring上下文中其他Bean的引用
XML配置文件:
<property name="instruments">
<props>
<prop key="VIOLIN">小提琴prop>
<prop key="CYMBAL">锣鼓prop>
props>
property>
遍历properties集合:
public void perform() {
Iterator
在某些情况下,如果我们不能完全确定属性值会为null,那么最好显式的为该属性装配一个null值。
<property name="instruments">
<null/>
property>
如果我们为属性装配的值只有在运行期才能知道的情况下,则需要Spring表达式(Spring Expression Language ,SPEL)的帮助。SPEL表达式在#{}中书写。
<property name="age" value="#{1e4}"/>
<property name="song" value="#{'《Greensleeves》'}"/>
id="carl" class="springIdol.stuffImpl.Instrumentalist">
<property name="song" value="#{kenny.song}"/>
<property name="instrument" value="#{violin}"/>
甚至还可以使用Bean的方法获得属性、对获得的属性使用额外的方法进行渲染:
<property name="song" value="#{songSelector.getSong().toUpperCase()}"/>
但是如果songSelector.getSong()返回值为空,由于它要调用toUpperCase()方法,所以就会抛出空指针异常。为了避免这种情况,我们使用“.?”代替“.”表示如果不为空则执行后面的方法。
在SPEL中,使用T()运算符会调用类作用域的方法和常量
<property name="age" value="#{T(java.lang.Math).random()}"/>
运算符类型 运算符
算术运算 +、-、*、/、%(求余运算)、^(乘方)
关系运算 lt、gt、eq、le(小于等于)、ge(大于等于)
逻辑运算 and、or、not
条件运算 ?:(ternary)、?:(Elvis)
正则表达式 matches
“+”运算符既可以进行加法运算还可以执行字符串的链接
关系运算会返回布尔值;逻辑运算用来组合关系运算
"instrument" value="#{songSelector.getSong()=='《Hey Jude》'?violin:cymbal}"/>
另一个常见的三元运算符使用场景是检查一个值是否为null。如果为null,则装配
一个默认值。
"song" value="#{kenny.song!=null?kenny.song:'Greensleeves'}"/>
但是kenny.song的引用重复出现了两次,SPEL提供了三目运算符的变体来简化表达式:
"song" value="#{kenny.song?:'Greensleeves'}"/>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
<util:list id="cities">
<bean class="springIdol.stuffImpl.City" p:name="Chicago" p:state="IL" p:population="10"/>
<bean class="springIdol.stuffImpl.City" p:name="NewYork" p:state="Bl" p:population="100"/>
<bean class="springIdol.stuffImpl.City" p:name="Jal" p:state="NM" p:population="20"/>
util:list>
<bean id="city" class="springIdol.Initial.Initial4">
<property name="chosenCity" value="#{cities[T(java.lang.Math).random()*cities.size()]}"/>
<property name="bigCities" value="#{cities.?[population gt 25 or population lt 15].![name+','+state]}"/>
bean>
beans>
Spring提供了几种技巧可以帮助我们减少XML的配置数量。
1.自动装配:有助于减少甚至消除配置元素和元素,让Spring自动识别如何装配Bean的依赖关系。
2.自动检测:比自动装配更进了一步,让Spring能够自动识别哪些类需要被配置成Spring Bean,从而减少对元素的使用。
当自动装配和自动检测一起使用的时候,他们可以显著地减少Spring的XML配置数量。
我们通过设置的autowire属性来让Spring帮助我们自动装配这个Bean的属性。值得注意的是,Spring为这些属性自动装配的前提是这些属性有对应的setter方法或在构造器中对这些属性进行了初始化,再次强调,如果你想为非构造器里初始化的属性注入值,那么它必须拥有setter方法,即使这些属性是公有的。
自动装配类型 作用
byName 把与Bean属性具有相同名字的(id)的Bean自动装配到Bean的对应属性中,如果未匹配到,则不装配。
byType 把与Bean属性具有相同类型的Bean自动装配到Bean的对应属性中,如果未匹配到,则不装配。
constructor 把与Bean的构造器入参具有相同类型的其他Bean自动装配到Bean构造器的对应入参中。
autodetect 首先尝试使用constructor进行自动装配,如果失败,再尝试使用ByType进行自动装配。
<bean id="mySong" class="springIdol2.stuffImpl.Sonnet29"/>
<bean id="instrument" class="springIdol2.stuffImpl.Violin"/>
<bean id="kenny" class="springIdol2.stuffImpl.Instrumentalist" autowire="byName">
<property name="song" value="《uptown funk》"/>
bean>
下面的装配与byName的格式完全相同,就不在举例子了,我们把重点放在他们的区别和优缺点上。
byType装配存在一个局限性:如果Spring寻找到了多个Bean,它们的类型与需要自动装配的属性的类型都匹配(尤其是将属性是父类或借口的实例),Spring不会去擅自猜测那个Bean更适合你,而会选择抛出异常。
Spring为我们提供了两种方法去解决这个问题:
1.自动装配标识一个首选Bean
可以使用的primary属性来设置首选Bean。默认情况下,所有满足条件候选的Bean这个属性都是true,所以我们需要设置的并不是那个首选bean,而是将所有非首选bean的primary属性设置为false。
id="violin" class="springIdol2.stuffImpl.Violin"/>
id="cymbal" class="springIdol2.stuffImpl.Cymbal" primary="false"/>
2.取消其他Bean的候选资格
如果我们希望直接排除在选择时直接排除某些Bean,可以将这些Bean的autowire-candidate属性设置为false,这将会直接无情地排除这些Bean的候选资格。
id="cymbal" class="springIdol2.stuffImpl.Cymbal" autowire-candidate="false"/>
constructor利用构造器为Bean装配属性。constructor和byType具有相同的局限性,同时,如果一个类具有多个构造器,它们都满足自动装配条件时,Spring也不会和你玩猜猜乐的游戏——究竟哪个构造器更适合使用。
如果你正在为自动装配Bean,又不知道以哪种方式一筹莫展,现在不必担心了,我们可以设置autowire属性为autodetect。由Spring来决定。Spring会首先尝试使用constructor来自动装配,如果没有发现与构造器相匹配的Bean时,Spring将尝试使用byType自动装配。(当然话说回来了,它还是没有解决有多个候选Bean的问题)
从Spring2.5开始,最有趣的一种装配Spring Bean的方式是使用注解自动装配Bean的属性。使用注解自动装配和在XML中使用autowire属性自动装配并没有太大的区别。但是使用注解方式允许更细粒度的自动装配。
Spring容器默认禁用注解装配,所以在使用基于注解的自动装配前,我们需要在Spring配置中启动它。
<context:annotation-config/>
至于命名空间的引入,IDEA会自动为你效劳。
注解方式与XML方式的一个最大的区别在于你需要依次为你想让Spring为你自动注入的属性加上注解(说实话,这一点都不麻烦,特别是在我们学习了马上要讲的自动扫描技术之后。相信我,你会爱上注解这个“新”技术)
这里说一句题外话,IDEA的智能代码小帮手患有大小写敏感症,在第一次使用@Autowired注解时,当你写出“@a”不会得到任何提示,而“@A”才可获得弥足珍贵的提示信息。
@Autowired可以标注需要自动装配Bean引用的任何方法前(无论是setter、constructor还是其它自定义的方法)
@Autowired
public void setInstrument(Instrument instrument) {
this.instrument = instrument;
}
@Autowired可以直接标注到成员变量前,这时@Autowired甚至不会受限于private关键字,这意味着我们可以删除它的setter方法(但这不意味着别的方式下public的成员变量就不需要setter方法)。
@Autowired
private Instrument instrument;
当Spring发现我们在适当的位置使用了@Autowired注解,就会尝试着对该属性(或者方法)执行byType自动装配。
@Autowired是这样的方便我们的开发,但它也存在着两个阻碍我们工作场景的问题:如果没有匹配到Bean或者匹配到多个Bean, @Autowired就会遇到一些麻烦。
默认情况下,@Autowired具有强契约性。当找不到可装配的对象时,@Autowired不像传统方式配置的那些属性是天生的乐观派,它接受不了null这个无稽的事实,就会抛出令人讨厌的NoSuchBeanDefinitionException异常。嗯,他是够任性的。
属性不一定要装配,null值也是可以接受的,在这种情况下,可以设置@Autowired的required属性为false来告诉@Autowired这个属性的自动装配是可选的:
@Autowired(required = false)
private Instrument instrument;
注意,required属性可以用于@Autowired注解所使用的任何地方。但是当使用构造器装配时,只有一个构造器可以将@Autowired的required属性设置为true。此外,当@Autowired标注多个构造器时,Spring会从所有满足装配条件的构造器中选择入参最多的那个构造器。
另一方面,如果有多个Bean符合条件,也是个麻烦事。我们采取缩小范围的方式来解决这个问题。
1. 以ID作为条件选择Bean(缩小选择范围至唯一)
@Autowired(required = false)
@Qualifier(“violin”)
private Instrument instrument;
这样,Spring就会选择id为violin的Bean来进行注入。
2.缩小选择范围
我们也可以在Bean的定义中使用@Qualifier注解来给它限定一个范围
@Qualifier(“stringed”)
public class Violin implements Instrument {
… …
}
或者(stringed代表弦类乐器)
<bean id="violin" class="springIdol2.stuffImpl.Violin">
<qualifier value="stringed"/>
bean>
然后在注入时额外使用一个@Qualifier注解
@Autowired(required = false)
@Qualifier(“stringed”)
private Instrument instrument;
3.创建自定义限定器
这个方法实际上就是我们通过模板自定义一个注解来替换@Qualifier(“… …”)(比如我们自定义一个@hello注解替换@Qualifier(“stringed”)),但如果你想了解更多,推荐你去谷歌,这里不再过多讲解。
@Inject与@Autowired用法和作用非常相近,@Inject则是为统一各种依赖注入框架编程模型的规范问题的产物。我们只关心@Inject和@Autowired的区别:
@Inject没有required属性,因此@Inject标注的依赖关系必须存在。
与其直接注入一个引用,不如要求@Inject注入一个Provider。Provider接口可以实现Bean引用的延迟注入以及注入Bean的多个实例等功能。这个这里也不做过多的介绍。
@Named()之于@Inject相当于@ Qualifier之于@Autowired。实际上,@Named注解就是一个使用@Qualifier注解所标注的注解。@Qualifier注解帮助我们缩小匹配Bean选择的范围(默认使用ID),而@Named通过Bean的ID来标识可选择的Bean。
@Autowired注解可以帮助我们自动装配其他Bean的引用,而@Value注解则可以帮我们装配基本类型合String类型的值。例如:
@Value(“《我爱你中国》”)
private String song;
@Value如果只能装配简单类型的值也挺无聊的,@Value真正的强大之处在于它可以装配一个SpEL表达式。在前面的章节中我们知道SpEL表达式返回的结果可以是任意类型,所以,@Value可以根据SpEL表达式进行的动态求算来装配任何类型的值。
@Value(“#{violin}”)
private Instrument instrument;
在之前的学习中,我们使用了注解的方式帮助我们实现了为我们自定义的Spring Bean属性的自动注入——和
元素除了完成与一样的工作外,还允许Spring检测Bean和定义Bean。这意味着不使用元素,Spring应用中的大多数Bean都能实现定义和装配。
元素将扫描base-package属性标识的包及其子包,并将类名前有如下注解(构造型注解)的类注册为Spring Bean:
注解名称 相关描述
@Component 通用的构造型注解,标识该类为Spring组件
@Controller 标识该类定义为SpringMVC的controller
@Repository 标识该类定义为数字仓库
@Service 标识该类定义为服务
其中@Component标注可以任意自定义注解,我们在后面SpringMVC的章节中会对其他注解进行介绍。例如:
@Component("violin")
public class Violin implements Instrument {
public Violin() {
System.out.println("小提琴准备好了");
}
@Override
public void play(){
System.out.println("同时演奏小提琴");
}
}
其中violin代表这个Bean的id。
试想,如果想通过注解的方式让所有实现Instrument接口的类都注册为Spring Bean,我们不得不依次查看这些乐器:小提琴,架子鼓,钢琴… …然后依次为其添加@Component注解,这是相当不方便的。
Spring组件的组件扫描相当灵活,我们可以在中使用和子元素随意调整扫描行为:我们可以使用直接告知哪些类需要注册为Spring Bean,使用告知哪些类不需要注册为Spring Bean。例如:
<context:component-scan base-package="springIdol2.stuffImpl">
<context:include-filter type="assignable" expression="springIdol2.stuff.Instrument"/>
<context:exclude-filter type="assignable" expression="springIdol2.stuffImpl.Cymbal"/>
context:component-scan>
这两个子元素的expression属性定义了对哪些Bean需要注册哪些Bean不需要注册。type属性定义组件扫描方式:
过滤器类型 描述
annotation 过滤器扫描使用指定注解所标注的哪些类。通过expression属性指定要扫描的注解
assignable 过滤器扫描派生于expression属性所指派类型的那些类
aspectj 过滤器扫描与expression属性所指定的AspectJ表达式所匹配的哪些类
custom 使用自定义的org.springframework.core.type.TypeFilter实现类,该类由expression属性指定
regex 过滤器扫描类的名称与expression属性所指定的正则表达式所匹配的那些类
这样,我们甚至摆脱了注解带来的极其微小的限制。
对于He-Man XML Haters俱乐部的正式成员,他们热衷于消除那些可恶的尖括号,Spring也之提供了基于java的配置。
我们仍然需要及少量的XML来启动java配置:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframe work.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="springIdol2">context:component-scan>
beans>
在基于Java的配置里使用@Configuration注解java类就等价于XML配置中的元素,@Bean注解的java方法相当于XML配置中的,该注解告知Spring将其所注解的方法返回的对象在Spring上下文中注册为Spring Bean。该方法名作为Spring Bean的id。参数注入显得格外简单,直接实例化我们的对象以最传统的java方式为这些属性赋值就可以了。
package springIdol3.config;
import org.springframework.context.annotation.Bean;
import springIdol.stuff.Performer;
import springIdol.stuffImpl.Instrumentalist;
import springIdol2.stuffImpl.Cymbal;
/**
* Created by hukaihe on 2016/3/4.
*/
@org.springframework.context.annotation.Configuration
//上面的注解会告诉Spring这个类将包含一个或多个Spring Bean的定义
public class Configuration {
@Bean
public Cymbal cymbal(){
return new Cymbal();
}
@Bean
//方法名为该bean的id
public Performer kenny(){
Instrumentalist kenny=new Instrumentalist();
kenny.setSong("我爱你中国");
kenny.setInstrument(cymbal());
return kenny;
}
}