读Spring实战(第四版)概括—装配Bean

很久很久以前读过Spring实战(第三版),因为第三版和第四部差异还是特别明显的,在整体思想上有了比较重大的改变,比如用注解和JavaConfig替换Xml以及现在非常火热的Springboot在书的最后也有提到。OK,开始看书,书本的第一章讲了一下Spring存在的目的(简化Java开发)和Spring的功能,以及Spring3->Spring4增加了哪些功能,那我就从第二章开始概括本书,以给我自己后面快速回忆使用。

在Spring中,对象无需自己查找或者创建与其所关联的其他对象。相反容器负责把需要相互协作的对象引用赋予各个对象。创建应用对象之间协作关系的行为通常被称为装配(wiring),这也是依赖注入(DI)的本质。

1.Spring配置的可选方案

当描述bean如何装配时,Spring具有非常大的灵活性,它提供了三种主要的装配机制:

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

我建议尽可能的使用自动配置的机制。显示配置越少越好。如果必须要使用显示配置,我推荐使用类型安全并且比XML更加强大的JavaConfig,最后在使用XML配置。

2.自动化装配Bean

Spring从两个角度来实现自动化装配:

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

组件扫描和自动装配组合一起就能发挥强大的威力,他们能够将你的显示配置降低到最小。

实现自动化装配有两种方式:(组件扫描默认是不启动的)

1)通过XML启用组件扫描

首先在mvc-config.xml中启动组件扫描功能,并用base-package属性指定扫描范围

<context:component-scan base-package="com.wxh.controller"/>

再通过在需要注解的class上添加@Controller@Service、@Repository、@Component等注解,比如:

@Controller
public class UserController {// ......}

2)通过@ComponentScan注解启用组件扫描

首先在class上使用@ComponentScan启用组件扫描,例如:

@ComponentScan
public class AppConfig {// ......}

此外:@ComponentScan(basePackages="conf")等同于@ComponentScan("conf"),然后通过在需要注解的class上添加@Controller@Service、@Repository、@Component等注解,例如:

@Controller
public class UserController {// ......}

对于@ComponentScan可以通过basePackages或者basePackageClasses指定扫描范围,等同于XML注解中的base-package属性;如果不指定扫描范围,则默认扫描当前类所在包以及子包的所有类。当然Craig Walls建议使用basePackageClasses因为如果代码重构的话这种方式会立马发现错误,下面是basePackageClasses的使用方式(指定基础扫描类):

@ComponentScan(basePackageClasses={
		UserController.class
		// ......
})
public class AppConfig {}

使用@Autowired可以为bean实现自动装配,@Autowired可以使用在构造函数、Setter方法、普通方法和成员变量上。比如下面的用法:

// 用法一:
@Autowired
MessageSend messageSend;
// 用法二:(构造函数也一样,主要是函数参数的依赖)
@Autowired
public void setMessageSend(MessageSend messageSend) {
	this.messageSend = messageSend;
}

设置@Autowired(required=false)时,Spring尝试执行自动装配,但是如果没有匹配的bean则忽略,但是这种情况故意出现空指针异常NullPointerException。@Autowired注解可以使用@Inject替换,@component可以使用@Named注解替换,后者是源于Java依赖注入规范。

3.通过Java代码装配

大部分的场景自动化装配Bean是满足要求的,但是在一些特殊场景下自动化装配Bean是无法满足要求的,比如说要将第三方的组件装配到自己的应用中,因为没有方法将@component或者@Autowired注解放置在它们的类上。但是你仍然可以采用显示装配方式:Java代码装配和XML配置。

首先需要创建配置类,创建配置类的关键是使用@Configuration注解来表明这个类是一个配置类,该类包涵Spring上下文中如何创建Bean的细节。

声明一个简单Bean:在Java的配置类中编写一个带有@Bean注解的方法,比如下面:

@Bean
public UserServiceImpl userService(){
	return new UserServiceImpl();
}

默认情况下Bean的ID是方法名,也可以指定Bean的ID:@Bean(name="userService"),如果有依赖可以使用下面这些的方式来实现:

@Bean
public UserServiceImpl userService(){
	return new UserServiceImpl(userDao());
}
@Bean
public UserDaoImpl userDao(){
	return new UserDaoImpl();
}

上面看起来是调用UserDaoImpl(),其实在配置类中Spring会拦截对这个方法的引用,并返回该方法所创建的bean,而不是每次都对其进行实际调用。当然下面这种方式也是可以的,userService()方法需要userDao作为参数,Spring创建Bean的时候会自动装配一个UserDaoImpl到方法中(我猜测应该和@Autowired意思差不多,当Spring Context下只有一个UserDaoImpl就可以通过匹配原则进行装配),这种方式是被推荐的,如果UserDaoImpl不是在本配置类下配置,任然可以正常使用(比如XML默认的组件扫描等)。

@Bean
public UserServiceImpl userService(UserDaoImpl userDao){
	return new UserServiceImpl(userDao);
}

上面我们通过构造函数的方式实现依赖注入(DI),当然我们也可以用一种更好的方式来实现依赖注入,就是用Setter方法注入,如下所示:

@Bean
public UserServiceImpl userService(UserDaoImpl userDao){
	UserServiceImpl userService = new UserServiceImpl();
	userService.setDao(userDao);
	return userService;
}

上面两种,通过JavaConfig或者自动化装配Bean在SpringBoot会被大量推荐使用,下面的XML模式的装配Bean已经不被推荐使用了。

4.通过XML装配Bean

XML装配Bean的方式,虽然已经不推荐了,但是还是我们最早使用的一种Bean装配的方式,还是需要学习的(很多古老的项目还在用,我目前的公司也是)。下面是一个简单Bean的声明:

<bean id="myFile" class="com.wxh.config.MyFile"></bean>

<Bean>元素类似于配置类中的@Bean,如果没有声明id="myFile",则这个bean的ID则为myFile#0,以此类推是myFile#1、myFile#2......

借助构造器(使用构造函数)注入初始化Bean的方式有两个:

  • <constructor-arg>元素
  • 使用Spring3中引入的c命名空间

如下所示:

<!-- 下面是使用<constructor-arg>元素方式注入Bean -->
<bean id="myFile" class="com.wxh.config.MyFile">
	<constructor-arg ref="fileProperty"></constructor-arg>
</bean>	
<!-- 下面是使用Spring3.0中引入的c-命名空间方式注入Bean -->
<bean id="myFile-c" class="com.wxh.config.MyFile" 
c:fileProperty-ref="fileProperty" c:count="0"></bean>

上面的两种方式第二张会比较简单,配置代码短,但是风格比较诡异,它的一个解读如下所示:

当然不论是<constructor-arg>元素还是Spring3.0 c-命名空间还可以使用构造器参数位置来注入相应的bean:

<bean id="myFile" class="com.wxh.config.MyFile">
	<constructor-arg index="0" ref="fileProperty"></constructor-arg>
	<constructor-arg index="1" value="0"></constructor-arg>
</bean>	
<bean id="myFile-c" class="com.wxh.config.MyFile" c:_0-ref="fileProperty" c:_1-ref="0"></bean>

上面说的都是将对象的引用注入到依赖于它们的对象之中,有时候我们也需要一个字面量来配置对象。如下所示(构造函数参数分别是id(int)和name(String)):

<constructor-arg value="101"></constructor-arg>
<constructor-arg value="wuxinhui"></constructor-arg>

对于c标签的使用就不介绍了,个人觉得不如普通的<constructor-arg>元素好用。

有时候,我们需要使用XML对集合的装配。构造函数如下所示(需要装配StringList<String>):

public MyFile(String name, List<String> type) {
	this.name = name;
	this.type = type;
}

对于XMl配置,我们可以使用<null/>来传递一个空值给构造器

<bean id="myfile" class="com.wxh.config.MyFile">
	<constructor-arg value="my.txt"></constructor-arg>
	<constructor-arg><null/></constructor-arg>
</bean>

不过这种方式在使用的时候容易出现空指针异常,所以用下面的方式更好,使用<list>或者<set>元素来传入初始化值:

<bean id="myfile" class="com.wxh.config.MyFile">
	<constructor-arg value="my.txt"></constructor-arg>
	<constructor-arg>
		<list>
			<value>txt</value>
			<value>doc</value>
			<value>docx</value>
			<!--... 其他格式-->
		</list>
	</constructor-arg>
</bean>
<bean id="myfile" class="com.wxh.config.MyFile">
	<constructor-arg value="my.txt"></constructor-arg>
	<constructor-arg>
		<set>
                        <value>txt</value>
			<value>doc</value>
			<value>docx</value>
			<!--... 其他格式 -->
		</set>
	</constructor-arg>
</bean>

list(java.util.List)和set(java.util.Set)的区别在于set是没有重复值,且无序的。其实就是Java中set和list的区别。

对于类属性的装配:有构造器注入和Setter注入两种;一般强依赖属性使用构造器,可选依赖属性使用Setter注入。对于使用注解来装配属性上面已经阐述过,下面主要说一下Spring XML方式进行属性的注入,如下所示:

<bean id="f" class="com.wxh.config.MyFile">
	<!-- 属性注入 -->
	<property name="name" value="ceshi.docx"></property>
	<!-- 其他属性 -->
</bean>

或者我们也可以使用p标签来简化属性的注入配置(个人比较推荐,语法比c标签清晰很多),如下:

<bean id="f" class="com.wxh.config.MyFile" p:name="wxk.txt"></bean>

使用util-命令空间中的元素来注入字面量(数组、集合等),下面是Spring util-命名空间中的元素:

元  素

描  述

<util:constant>

引用某个类型的public static域,并将其暴露为bean

<util:list>

创建一个java.util.List类型的bean,其中包含值和引用

<util:map>

创建一个java.util.Map类型的bean,其中包含值和引用

<util:properties>

创建一个java.util.Properties类型的bean

<util:property-path>

引用一个bean的属性(或内嵌属性),并将其暴露为bean

<util:set>

创建一个java.utilSet类型的bean,其中包含值和引用

使用事例如下所示:

<!-- 静态变量 -->
<util:constant static-field="com.wxh.config.MyFile.MAX_SIZE" />
<!-- list -->
<util:list id="list" list-class="java.util.LinkedList">
	<value>D-1</value>
	<value>D-2</value>
	<value>D-3</value>
</util:list>
<!-- map -->
<util:map id="map">
	<entry key="数学" value="44" />
	<entry key="语文" value="77" />
</util:map>
<!-- 加载资源文件 -->
<util:properties id="prop" location="classpath:application.property">
</util:properties>
<!-- 暴露指定bean的属性 -->
<util:property-path path="myfile.name" />
<util:set id="school">
	<value>小学</value>
	<value>中学</value>
	<value>大学</value>
</util:set>

5.导入和混合配置

在混合配置中,@Autowired自动装配是考虑全局的所有Bean不管XML配置还是JavaConfig注解配置。一般混合配置的使用方式就是:JavaConfig拆分配置、XML拆分配置、JavaConfig配置中引用XML配置、XML中引用JavaConfig配置

5.1.JavaConfig拆分配置

一般如果Bean比较复杂或者逻辑上比较独立,我们为了逻辑上更加清晰,可以将一个JavaConfig拆分成多个。使用@Import注解进行导入,如下所示:

/** AppConfiguration.java */
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = { "com.wxh.service" })
@Import(InitJavaConfig.class)
public class AppConfiguration {
	// 声明的Bean 。。。。。。
}

/** InitJavaConfig.java */
@Configuration
@ComponentScan(basePackages = { "com.wxh.service" })
public class InitJavaConfig {
	// 声明的Bean 。。。。。。
}

5.2.XML拆分配置

当如果我们使用XML进行Bean的配置,我们有时候为了逻辑更加的清晰,会对XML配置进行拆分。我们可以使用<import>元素来引用其他的XML配置,如下所示可以对拆分的XML进行相互引用:(假如我有一个init-config.xml Bean配置文件)

<import resource="init-config.xml"/>

5.3.JavaConfig配置中引用XML配置

对于使用这种模式的还是比较怪异的,早期我在使用Springboot时,由于不太懂使用注解对Bean配置,用过这种奇特的方式。对于JavaConfig配置中引用XML配置,我们可以使用@ImportResource注解实现,如下使用:

@ContextConfiguration
@Component
@ImportResource("classpath:init-config.xml")
public class AppConfig {
	// .....
}

5.4.XML中引用JavaConfig配置

对于也是比较奇葩,我目前还没遇到过。不过这种引用JavaConfig配置却更加简单了,只需要使用<bean>标签就可以了。使用如下所示:(AppConfig是使用注释配置的Bean的类)

<bean class="com.test.config.AppConfig"></bean>
总结:只要是拆分的模式,我习惯性的还是会创建一个根配置,这个配置会展现多个装配类或者XML文件或者混合模式。大致的使用方式如下所示(混合模式),Root XML中配置:
<bean class="com.test.config.AppConfig"></bean>
<import resource="init-config.xml"/>

猜你喜欢

转载自blog.csdn.net/u013468915/article/details/80385318