以前使用过Spring框架,但是始终是学了前面的,忘了后面的,对其中的配置、原理等等也是一头雾水;于是最近开始学习《Spring实战》,然后总结一下每一章的内容(笔记内容可能与书本内容有所出入,另外以下内容基本都会有相应的测试代码);
在学习之前,需要先创建一个maven项目,配置一下Spring相关依赖(这里是Spring 4.7版本)
<dependencies>
<dependencies>
<!-- Spring核心依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>4.3.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>4.3.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.7.RELEASE</version>
</dependency>
<!-- Spring test依赖:方便做单元测试和集成测试 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>4.3.7.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
1.Spring配置的可选方案
Spring容器装配Bean有以下三种方案:
- XML显式配置;
- 在Java中显式配置;
- 隐式的bean发现机制和自动装配;
这三种方案都是先声明组件类或bean,再将bean注册到容器中,然后Spring根据容器中各个bean的依赖关系进行装配;
选择配置方案时:
- 尽量选择自动装配;
- 如果一定要使用显式配置,最好使用基于Java的配置(Java Config);
- 除非基于Java的配置无法实现,最后使用XML配置;
2.自动化装配
Spring从两个角度进行自动化装配,将显式配置降低到最少
- 组件扫描:Spring会自动发现应用上下文中所创建的Bean;
- 自动装配:Spring自动满足bean之间的依赖;
2.1创建可发现的Bean
以CD为例,CD需要放入CD播放器才有作用,因此可以认为CD对象依赖于CD播放器;
注: 2.1节的类文件均放在同一个包下
首先创建CompactDisc接口,定义CD的概念
//接口定义了CD播放器对CD的操作,将CD播放器的任意实现与CD的耦合降低到了最低
public interface CompactDisc{
void play();
}
创建一个CompactDisc的实现
@Component
public class SgtPeppers{
private String title="Sgt.Pepper's Lonely Hearts Club Band";
private String artist="The Beatles";
public void play(){
sout("Playing"+title+"by"+artist);
}
}
分析:
SgtPeppers的内容并不重要,只需关注@Component注解,它表明该类会作为组件类,并告知Spring要为这个类创建bean而无需显式配置它的bean;但是需要注意的是,组件扫描默认不启用,下面需要配置Spring的组件扫描
@Configuration
//@Configuration表明它是一个配置类
@ComponentScan
public class CDPlayerConfig{
}
分析:
- 若未指定包,@ComponentScan默认扫描当前包及其子包下的带@Component注解的类;
- 若使用XML启用组件扫描,可以使用Spring context命名空间的context:component-scan元素,如下:
<beans>
<!--base-package指定了查询的包-->
<context:component-scan base-package="soundsystem.autoconfig"/>
</beans>
组件类和组件扫描都配置好了,下面对功能进行测试,判断CompactDisc是否被创建出来
//测试扫描组件是否能发现CompactDisc
@RunWith(SpringJUnit4ClassRunner.class)
//使用SpringJUnit4ClassRunner.class,测试时自动创建Spring的应用上下文
@ContextConfiguration(classes = CDPlayerConfig.class)
//这一注解表明在CDPlayerConfig中加载配置
public class CDPlayerTest {
//@Autowired注解将容器中的CompactDisc注入到代码中
@Autowired
private CompactDisc cd;
@Test
public void cdShouldNotBeNull(){
assertNotNull(cd);
}
}
最终结果如下:
值得注意的是:
Spring进行测试时,必须使用@RunWith(SpringJUnit4ClassRunner.class)和@ContextConfiguration(classes = CDPlayerConfig.class)注解,功能如注释所言,不然在测试时会出现AssertError异常和空指针异常;
总结:
- 创建接口和实现类,并对实现类使用@Component注解,表明它是组件类;
- 创建配置类(记得加上@Configuration表明它是一个配置类),使用@ComponentScan启用组件扫描;至此,创建Bean的工作已经完成;
- (可忽略) 测试类时需要加上@RunWith(SpringJUnit4ClassRunner.class)和@ContextConfiguration(classes = 配置类类名.class)注解,然后注入容器中的类,使用它的方法;
2.2为组件扫描的bean命名
Spring应用上下文会为所有bean设置一个ID:默认设置为bean的名字,开头字母小写;或者由开发者设置;设置bean的方法如下:
//两种方法均可,我比较推荐方法一
//在编写时,@Named好像不能用,可能是版本问题???希望有大佬指教
//方法一
@Component("lonelyHeartsClub")
public class SgtPeppers implements CompactDisc {
/*
具体代码已省略
*/
}
//方法二
@Named("lonelyHeartsClub")
public class SgtPeppers implements CompactDisc {
/*
具体代码已省略
*/
}
在这里,我使用方法一的代码进行测试:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = CDPlayerConfig.class)
public class SgtPeppersTest {
//这里为了证明能够从容器中获取重新命名的bean,使用上下文方法获取bean
//这里是基于CDPlayerConfig类加载Spring上下文
AnnotationConfigApplicationContext annotationConfigApplicationContext=
new AnnotationConfigApplicationContext(CDPlayerConfig.class);
//通过bean的名称获取相关bean
CompactDisc cd=(SgtPeppers)annotationConfigApplicationContext
.getBean("lonelyHeartsClub");
@Test
public void testNotNull(){
assertNotNull(cd);
}
}
测试结果如下:
2.3设置组件扫描的基础包
@ComponentScan默认扫描当前包,若将配置类放置在单独的包中,就需要设置扫描其他包,方法如下:
//1.扫描单个包
@Configuration
@ComponentScan("soundsystem")
public class CDPlayerConfig {
}
//2.扫描单个包
@Configuration
@ComponentScan(basePackages = "soundsystem")
public class CDPlayerConfig {
}
//3.扫描多个包,类型不安全,重构代码后,指定的包有可能出错
@Configuration
@ComponentScan(basePackages = {"soundsystem","video"})
public class CDPlayerConfig {
}
//4.指定类或接口,这些类所在的包将会作为组件扫描的基础包
@Configuration
@CoponentScan(basePackageClasses = {CDPlayer.class,DVDPlayer.class})
public class CDPlayerConfig {
}
2.4通过为bean添加注解实现自动装配
自动装配是让Spring满足bean依赖的方法,上一节所做的只是组件扫描,但是很多对象只有依赖其他对象才能完成业务,因此需要使用自动装配,将组件和它们的依赖装配在一起;
前面已经提到CD和CDPlayer的依赖,下面创建MediaPlayer接口及CDPlayer类
/**接口MediaPlayer**/
public interface MediaPlayer {
void play();
}
/**CDPlayer实现接口MediaPlayer**/
@Component
public class CDPlayer implements MediaPlayer{
private CompactDisc cd;
@Autowired
public CDPlayer(CompactDisc cd) {
this.cd = cd;
}
public void play(){
cd.play();
}
}
@Autowired表示注入bean,前面已经在容器中注册了CompactDisc的bean,那么这里便会直接注入CompactDisc;(注意:这个类仍需要使用@Component注解)
除了构造器注入参数,还能使用Setter方法甚至普通方法,只要有CompactDisc cd参数,便可以注入
@Component
public class CDPlayer implements MediaPlayer{
/**...**/
@Autowired
public void setCD(CompactDisc cd) {
this.cd = cd;
}
/**或者使用更普通的方法**/
@Autowired
public void f (CompactDisc cd) {
this.cd = cd;
}
/**...**/
}
注意: 对于@Autowired注解
- 默认required=true,即注入时,bean必须存在,否则注入失败,报错;
- reqired=false表示忽略当前要注入的bean,如果有则直接注入,否则跳过,不报错;若代码中没有控制检查,有可能报NullPointerException;
- 如果存在多个bean满足依赖关系,抛出BeanCreateException;
另外,也可以使用@Named+@Inject替代@Component+@Autowired
@Named
public class CDPlayer implements MediaPlayer{
/**...**/
@Inject
public void setCD(CompactDisc cd) {
this.cd = cd;
}
/**...**/
}
下面验证自动装配
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = CDPlayerConfig.class)
public class CDPlayerTest {
//SgtPeppers为组件
@Autowired
private CompactDisc cd;
//CDPlayer为组件
@Autowired
private MediaPlayer mediaPlayer;
@Test
public void play(){
mediaPlayer.play();
}
}
测试结果
测试类中,仅仅声明了CompactDisc 和MediaPlayer并注入,由于MediaPlayer的实现类CDPlayer实现一个注入CompactDisc的方法,在Spring运行时,直接自动装配,因此在测试类的代码层面,两个对象没有任何依赖,却实现了play方法;
2.5总结
自动装配方法的大致流程及注意事项如下:
- 确定业务逻辑中相互依赖的对象,创建相关接口及其实现类(实现类需要使用@Component注解,表明它是组件类);
- 创建配置类(使用@Configuration),启用组件扫描(@ComponentScan),@ComponentScan确定需要扫描的包有默认和四种指定名称的方式(直接指定,base-packages指定单个包,base-packages使用数组指定多个包,直接指定一个或多个配置类扫描类所在的包);
- 在组件类中对方法使用@Autowired注解进行依赖注入;
- (测试) 加上@RunWith(SpringJUnit4ClassRunner.class)和@ContextConfiguration(classes = 配置类类名.class)对于指定名称的bean,使用AnnotationConfigApplicationContext加载配置类,获取bean,然后就可以测试了;
以上就是Spring自动化装配的流程,下一节将详细讲述Spring通过Java装配bean的流程;