《Spring 实战第一章学习笔记》
《Spring实战》,买了这本书,一直放着在,没有看,最近一直在做毕设呀玩呀,对Spring的很多东西都忘记了,因此,借在入职之前的这点时间,抽点空再学习下Spring。
本篇博文主要记录Spring实战这本书上的相关Demo。
下面是关于Spring IOC/DI的一个小例子。
先假设有如下的依赖关系:
1、Knight 依赖于 Quest
Knight接口及其实现类BraveKnight具体代码如下所示:
/**
* Created by wuranghao on 2017/4/17.
*/
public interface Knight {
public void embarkOnQuest();
}
/**
* Created by wuranghao on 2017/4/17.
*/
public class BraveKnight implements Knight {
private Quest quest;
public BraveKnight(Quest quest) {
this.quest = quest;
}
public void embarkOnQuest(){
quest.embark();
}
}
2、Quest 依赖于 PrintStream
Quest接口及其实现类QuestImpl具体代码如下所示:
public interface Quest {
public void embark();
}
public class QuestImpl implements Quest {
private PrintStream printStream;
public QuestImpl(PrintStream printStream) {
this.printStream = printStream;
}
public void embark() {
printStream.println("Embarking on quest to slay the dragon");
}
}
那么通过Spring怎么来完成对象的依赖注入呢?
有如下的几种方式均可以做到:
1、采用XML方式:在XML中进行显示配置
2、采用JavaConfig方式:在Java中进行显示配置
3、隐式的bean发现机制和自动装配
下面一一进行介绍。
1、采用XML方式来完成bean的装配
既然是使用XML文件的方式来完成bean 的装配,这里就新建一个配置文件:knight.xml,具体内容如下,该配置文件将BraveKnight、QuestImpl和PrintStream装配到了一起。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="knight" class="com.wrh.knight.BraveKnight">
<constructor-arg ref="quest"></constructor-arg>
</bean>
<bean id="quest" class="com.wrh.quest.QuestImpl">
<constructor-arg value="#{T(System).out}" >
</constructor-arg>
</bean>
</beans>
在knight.xml文件中,首先利用标签将BraveKnight、QuestImpl声明为例Spring中的bean。以BraveKnight来说,利用标签表示了其在构造时传入了对QuestImpl的引用,将其作用构造器参数。同样的道理,在构造Quest实例时,将System.out传入到了QuestImpl的构造器中,注意:这里使用了Spring表达式T(),借助于T()运算符就可以访问类作用域的方法和常量;
那么怎么来测试验证通过knight.xml就完成了相关bean的注入/装配呢,具体代码如下:
public class KnightMain {
public static void main( String[] args ) {
//加载应用上下文
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("knights.xml");
//获取knight的bean
Knight knight = context.getBean(Knight.class);
//使用knight
knight.embarkOnQuest();
}
}
由于Spring通过应用上下文装载bean的定义并把它们组装起来。Spring应用上下文全权负责对象的创建和组装。Spring自带了多种应用上下文的实现,他们之间的主要区别仅仅在于如何加载配置。
这里是使用XML文件进行配置的,因此选择ClassPathXmlApplicationContext,该类加载位于应用程序类路径下的一个或多个XML配置文件。本示例程序的配置文件放置在src/main/resources/knight.xml中,如下图所示。
运行KnightMain程序,则可以在控制台看到相应的输出,这就表明通过XML配置文件的这种方式完成bean的自动注入。
如果你不喜欢XML配置这种方式的话,也可以选择其他方式,例如,Spring还支持使用Java来描述配置,具体看下面的介绍。
2、使用JavaConfig来完成bean的装配
如下代码展现了基于Java的配置,其功能与上面介绍的XML配置的功能完全相同。
/**
* Created by wuranghao on 2017/4/17.
*/
@Configuration
public class KnightConfig {
@Bean
public Knight knight(){
return new BraveKnight(quest());
}
@Bean
public Quest quest(){
return new QuestImpl(System.out);
}
}
从上面的代码中使用了注解@Configuration和@Bean来完成。
那么怎么来测试验证通过KnightConfig就完成了相关bean的注入/装配呢,具体代码如下:
public class KnightMain {
public static void main( String[] args ) {
//加载应用上下文
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(KnightConfig.class);
//获取knight的bean
Knight knight = context.getBean(Knight.class);
//使用knight
knight.embarkOnQuest();
}
}
前面提到,Spring应用上下文全权负责对象的创建和组装,这里选择的应用上下文为:AnnotationConfigApplicationContext。
上面介绍了借助于XML和Java来进行Spring 的装配,除了者2种方式,还有第三种方式:自动化装配bean,具体见下面的介绍。
3、自动化装配bean
Spring从两个角度来实现自动化装配
1)组件扫描:Spring会自动发现应用上下文中所创建的bean
2)自动装配:Spring自动满足bean之间的依赖。
组件扫描和自动装配组合在一起就能发挥强大的作用,这样就能够将显示配置降低到最少。
具体分为如下几个步骤
1)创建可被发现的bean
这里新建一个OtherKnight类,代码如下:
@Component
public class OtherKnight implements Knight {
public void embarkOnQuest() {
System.out.println("ohterKnight ......");
}
}
在OtherKnight类中,值得注意的是在该类上使用了@Component注解,这个简单的注解表明该类会作为组件类,并告知Spring要为这个类创建bean。
Spring应用上下文中所有的bean都会给定一个ID。在上面的代码中并没有指定,则Sprng会根据类名(首字母小写)来进行指定,例如:OtherKnight类所对应的bean为otherKnight。
如果想为这个bean设置不同的ID,你只需要将期望的ID作为值传递给@Component注解,例如:
@Component(“knight”)
2)开启组件扫描并设置组件扫描的基础包
不过,组件扫描默认是不启用的,因此,我们还需要配置一下Spring,从而命令它去寻找带有@Component注解的类,并为其创建bean。具体如下:
@Configuration
@ComponentScan
public class KnightConfig {}
该类是通过Java代码定义了Spring的装配规则,这里并没有像前面一样显示的声明了相关的bean,只是使用了@ComponentScan来开启组建扫描。
值得说明的是:@ComponentScan注解如果没有其他配置的话,则默认会扫描与配置类相同的包(及其子包)。
如果配置类与组件类不在一个包下,则需要指定需要扫描的包名称。例如,本文的实例Demo的目录结构如下:
从上面的目录结构可以看到,KnightConfig类与组件类OhterKnight等不在一个包下,因此需要借助于注解@ComponentScan的相关参数来进行指定。具体如下:
@Configuration
@ComponentScan(basePackages = {"com.wrh.knight","com.wrh.quest"})
public class KnightConfig {}
上面@ComponentScan中的基础包是以String类型表示的,除此之外,@ComponentScan还提供了另外一种方法,那就是指定为包中所包含的类或接口,具体如下所示:
@Configuration
//@ComponentScan(basePackages = {"com.wrh.knight"})
@ComponentScan(basePackageClasses = {Knight.class,Quest.class})
public class KnightConfig {}
测试代码如下:
package com.wrh.knight;
import junit.framework.TestCase;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* Created by wuranghao on 2017/4/20.
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = KnightConfig.class)
public class OtherKnightTest extends TestCase {
@Autowired
private OtherKnight otherKnight;
@Test
public void testEmbarkOnQuest() throws Exception {
otherKnight.embarkOnQuest();
}
}
在测试代码OtherKnightTest类中,需要说明的是:@RunWith注解,需要引入如下的依赖,junit的版本必须在4+,idea默认生成的junit版本为3.8的是不行的。
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
而SpringJUnit4ClassRunner.class需要引入如下的依赖:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>4.3.7.RELEASE</version>
</dependency>
以上,就完成了自动化装配。
回到最开始的那个例子,即有如下两个依赖:
1、Knight 依赖于 Quest
1、Quest 依赖于 PrintStream
如果使用上面刚介绍的自动化装配,使用@Component和@Autowired注解修改代码如下:
@Component
public class BraveKnight implements Knight {
private Quest quest;
@Autowired
public BraveKnight(Quest quest) {
this.quest = quest;
}
public void embarkOnQuest(){
quest.embark();
}
}
@Component
public class QuestImpl implements Quest {
private PrintStream printStream;
@Autowired
public QuestImpl(PrintStream printStream) {
this.printStream = printStream;
}
public void embark() {
printStream.println("Embarking on quest to slay the dragon");
}
}
编写测试程序如下:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {KnightConfig.class})
public class BraveKnightTest extends TestCase {
@Autowired
private Knight knight;
@Test
public void testEmbarkOnQuest() throws Exception {
knight.embarkOnQuest();
}
}
结果可想而知,报错,报错信息如下:
Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name ‘braveKnight’ defined in file [/Users/wuranghao/Downloads/gitProject/springdemo01/target/classes/com/wrh/knight/BraveKnight.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name ‘questImpl’ defined in file [/Users/wuranghao/Downloads/gitProject/springdemo01/target/classes/com/wrh/quest/QuestImpl.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type ‘java.io.PrintStream’ available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
这是因为,QuestImpl所依赖的PrintStream没有可以选择的bean注入到QuestImpl中,思考了很久,没有找到方法来通过注解来产生PrintStream并,唯一能想到的是使用XML配置来产生这个bean,这就涉及到混合配置了:即自动化配置与XML配置的混合应用。
并在此实践过程中,由于存在Knight接口的两个具体实现:BraveKnight/OtherKnight。如果对这两个类都使用@Component进行标示,则会发生歧义性错误。具体错误如下图所示:
小结
以上是读《Spring实战》这本书的一点实践和总结。关于Bean装配的更多细节在后面的博文中陆续进行介绍。