在了解依赖注入(DI)详情之前,需要先了解Spring的容器。
在基于Spring的应用中,应用对象生存于Spring容器(container)中。Spring容器负责创建对象,装配它们,配置它们并管理它们的整个生命周期,从生存到死亡(在这里,可能就是new到finalize())。
在后面,我们将了解如何配置Spring,从而让它知道该创建、配置和组装哪些对象。但首先,最重要的是了解容纳对象的容器。理解容器将有助于理解对象是如何被管理的。
Spring容器并不是只有一个。Spring自带了多个容器实现,可以归为两种不同的类型。
这篇文章主要把精力集中在应用上下文的使用上。
Spring自带了多种类型的应用上下文。下面罗列的几个是你最有可能遇到的。
例如,如下代码展示了如何加载一个FileSystemXmlApplicationContext:
ApplicationContext context = new FileSystemXmlApplicationContext("D://config.xml");
类似地,你可以使用ClassPathXmlApplicationContext从应用的类路径下加载应用上下文:
ApplicationContext context = new ClassPathXmlApplicationContext("config.xml");
使用FileSystemXmlApplicationContext和使用ClassPathXmlApp-licationContext的区别在于:
如果想从Java配置中加载应用上下文,那么可以使用AnnotationConfig-ApplicationContext:
AnnotationConfigApplicationContext applicationContext =new AnnotationConfigApplicationContext(JavaConfig.class);
应用上下文准备就绪之后,我们就可以调用上下文的getBean()方法从Spring容器中获取bean。
创建应用对象之间协作关系的行为通常称为装配(wiring),这也是依赖注入(DI)的本质。
Spring配置的三种装配机制
Spring从两个角度来实现自动化装配:
使用@Component注解。这个简单的注解表明该类会作为组件类,并告知Spring要为这个类创建bean
例如:
@Component
public class Person {}
Person 的具体内容并不重要。需要注意的就是 Person 类上使用了@Component注解。
不过,组件扫描默认是不启用的 。我们还需要显式配置一下Spring,从而命令它去寻找带有@Component注解的类,并为其创建bean。
例如:
@Configuration
@ComponentScan
public class JavaConfig {}
Configuration注解表明这个类是一个配置类
JavaConfig 类使用了@ComponentScan注解,这个注解能够在Spring中启用组件扫描。
如果没有其他额外配置的话,@ComponentScan默认会扫描与配置类相同的包。假设 JavaConfig 类位于 com.springLearn 包中,因此Spring将会扫描这个包以及这个包下的所有子包,查找带有@Component注解的类。
如果 Person 类也在 com.springLearn包下,这样就能发现 Person ,并且会在Spring中自动为其创建一个bean。
如果想要使用XML来启用组件扫描的话,那么可以使用Spring context命名空间的 < context:component-scan>元素。
例如:
Spring应用上下文中所有的bean都会给定一个ID。Spring会根据类名为其指定一个ID。具体来讲,这个bean所给定的ID是将类名的第一个字母变为小写。 (请记住这句话,非常重要!)
如果想为这个bean设置不同的ID,所要做的就是将期望的ID作为值传递给@Component注解。
例如:
@Component("myPerson")
public class Person {}
到现在为止,我们没有为 @ComponentScan 设置任何属性。这意味着,按照默认规则,它会以配置类所在的包作为基础包(basepackage)来扫描组件。但是,如果想扫描不同的包,那该怎么办呢?或者,如果想扫描多个基础包,那又该怎么办呢?
要满足这样的需求其实也完全没有问题!为了指定不同的基础包,所需要做的就是在@ComponentScan的value属性中指明包的名称:
例如:
@Configuration
@ComponentScan("com.otherPackages")
public class JavaConfig {}
如果你想更加清晰地表明你所设置的是基础包,那么你可以通过basePackages属性进行配置:
@Configuration
@ComponentScan(basePackages="com.otherPackages")
public class JavaConfig {}
注意:basePackages属性使用的是复数形式。如果想要设置多个基础包,只需要将basePackages属性设置为要扫描包的一个数组即可:
@Configuration
@ComponentScan(basePackages={"com.otherPackage1", "com.otherPackage2", ......})
public class JavaConfig {}
除此之外@ComponentScan还提供了另外一种方法,那就是将其指定为包中所包含的类或接口:
@Configuration
@ComponentScan(basePackageClasses={"Person.class", "Foots.class", ......})
public class JavaConfig {}
自动装配就是让Spring自动满足bean依赖的一种方法,在满足依赖的过程中,会在Spring应用上下文中寻找匹配某个bean需求的其他bean。为了声明要进行自动装配,我们可以借助Spring的@Autowired注解。
例如:
@Component
public class Person {
@Autowired
private Foots foots;
}
这种写法等同于下面这种写法:
@Component
public class Person {
private Foots foots;
@Autowired
public Person(Foots foots) {
this.foots = foots;
}
}
这就是@Autowired注解的构造器注入方式。
@Component
public class Person {
private Foots foots;
@Autowired
public void setFoots(Foots foots) {
this.foots = foots;
}
}
@Autowired注解不仅能够用在构造器上,还能用在属性的Setter方法上。
不管是构造器、Setter方法还是其他的方法,Spring都会尝试满足方法参数上所声明的依赖。
假如有且只有一个bean匹配依赖需求的话,那么这个bean将会被装配进来。如果没有匹配的bean,那么在应用上下文创建的时候,Spring会抛出一个异常。
为了避免异常的出现,你可以将@Autowired的为了避免异常的出现,你可以将@Autowired的required属性设置为false:
@Component
public class Person {
@Autowired(required = false)
private Foots foots;
}
将required属性设置为false时,Spring会尝试执行自动装配,但是如果没有匹配的bean的话,Spring将会让这个bean处于未装配的状态。
但是,把required属性设置为false时,需要谨慎对待。如果在你的代码中没有进行null检查的话,这个处于未装配状态的属性有可能会出现NullPointerException。
在上文中,已经用到了JavaConfig类,现在来重温一下。
@Configuration
public class JavaConfig {}
Configuration注解表明这个类是一个配置类
要在JavaConfig中声明bean,我们需要编写一个方法,这个方法会创建所需类型的实例,然后给这个方法添加@Bean注解
@Bean
public Person person(){
return new Person();
}
@Bean注解会告诉Spring这个方法将会返回一个对象,该对象要注册为Spring应用上下文中的bean。方法体中包含了最终产生bean实例的逻辑。
默认情况下,bean的ID与带有@Bean注解的方法名是一样的。在本例中,bean的名字将会是person。如果你想为其设置成一个不同的名字的话,那么可以重命名该方法,也可以通过name属性指定一个不同的名字
@Bean(name = "myPerson")
public Person person(){
return new Person();
}
@Bean
public Person person(Foots foots){
return new Person(foots);
}
在这里,person()方法请求一个Foots作为参数。当Spring调用person()创建Person bean的时候,它会自动装配一个Foots到配置方法之中。然后,方法体就可以按照合适的方式来使用它。借助这种技术,person()方法也能够将Foots注入到Person的构造器中
前提是Person 类必须有对应的构造函数才行。
要在基于XML的Spring配置中声明一个bean,我们要使用springbeans模式中的另外一个元素:< bean>。< bean>元素类似于JavaConfig中的@Bean注解。
< bean class="com.springLearn.Person"/>
因为没有明确给定ID,所以这个bean将会根据全限定类名来进行命名。
在本例中,bean的ID将会是“com.springLearn.Person#0”。其中,“#0”是一个计数的形式,用来区分相同类型的其他bean。如果你声明了另外一个Person,并且没有明确进行标识,那么它自动得到的ID将会是“com.springLearn.Person#1”。
因此,通常来讲更好的办法是借助id属性,为每个bean设置一个你自己选择的名字:
< bean id="person" class="com.springLearn.Person"/>
在Spring XML配置中,只有一种声明bean的方式:使用< bean>元素并指定class属性。Spring会从这里获取必要的信息来创建bean。
构造器注入bean引用使用< constructor-arg>元素,在此之前先多声明一个Foots的Bean:
Spring框架的核心是Spring容器。容器负责管理应用中组件的生命周期,它会创建这些组件并保证它们的依赖能够得到满足,这样的话,组件才能完成预定的任务。
在本文中,在Spring中装配bean的三种主要方式:自动化配置、基于Java的显式配置以及基于XML的显式配置。不管采用什么方式,这些技术都描述了Spring应用中的组件以及这些组件之间的关系。
我个人比较喜欢使用自动化配置,第一简单,第二是避免显式配置所带来的维护成本。但是,如果你确实需要显式配置Spring的话,应该优先选择基于Java的配置,它比基于XML的配置更加强大、类型安全并且易于重构
该文中只学习了DI的一部分,关于高级装配中的profile、条件化的Bean、Bean的作用域、运行时值的注入和SpEL这些内容会在后面的高级装配文章中涉及。
技 术 无 他, 唯 有 熟 尔。
知 其 然, 也 知 其 所 以 然。
踏 实 一 些, 不 要 着 急, 你 想 要 的 岁 月 都 会 给 你。