第一章 Spring 之旅《Spring in action 第
一、关于 Spring
-
Spring 是一个开源框架,是为了解决企业级的应用开发的复杂性而创建,有着简化 Java 开发,可测试,松耦合等特点。
-
几个重要概念
POJO(Plain Old Java object):简单老式 Java 对象
DI(Dependency Injection):依赖注入
AOP(Aspect-Oriented Programming):面向切面编程 -
为降低 Java 开发的复杂性,Spring 采取以下四种关键性策略。
- 基于 POJO 的轻量级和最小侵入性编程
- 通过依赖注入和面向接口实现松耦合
- 基于切面和惯例进行声明式编程
- 通过切面和模板减少样板式代码
二、源码编译
执行命令(./gradlew build)
如遇到环境配置和编译出错,请移步这里。《Spring in action 第四版》源码下载和 Java 环境配置
三、代码分析
- Quest.java 和 Knight.java 声明了两个接口。
package sia.knights;
public interface Quest {
void embark();
}
package sia.knights;
public interface Knight {
void embarkOnQuest();
}
- 类 SlayDragonQuest 和 BraveKnight 分别实现了 Quest 和 Knight 接口。
package sia.knights;
import java.io.PrintStream;
public class SlayDragonQuest implements Quest {
private PrintStream stream;
public SlayDragonQuest(PrintStream stream) {
this.stream = stream;
}
public void embark() {
stream.println("Embarking on quest to slay the dragon!");
}
}
package sia.knights;
public class BraveKnight implements Knight {
private Quest quest;
public BraveKnight(Quest quest) {
this.quest = quest;
}
public void embarkOnQuest() {
quest.embark();
}
}
- 包含 main 函数的类 KightMain 和配置文件 knight.xml。
<?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">
<!--(1)-->
<bean id="knight" class="sia.knights.BraveKnight">
<constructor-arg ref="quest" />
</bean>
<!--(2)-->
<bean id="quest" class="sia.knights.SlayDragonQuest">
<constructor-arg value="#{T(System).out}" />
</bean>
</beans>
package sia.knights;
import org.springframework.context.support.
ClassPathXmlApplicationContext;
public class KnightMain {
public static void main(String[] args) throws Exception {
ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext(
"META-INF/spring/knight.xml");//(3)
Knight knight = context.getBean(Knight.class);//(4)
knight.embarkOnQuest();
context.close();
}
}
(1)配置类 sia.knights.BraveKnight 为应用上下文的 bean,构造函数的参数是一个引用。
(2)配置类 sia.knights.SlayDragonQuest 为应用上下文的 bean,构造函数的参数是一个值。
(3)表示从文件 META-INF/spring/knight.xml 加载应用上下文。
(4)从应用上下文获取一个类 Knight 的 bean。
以上分析可知,配置文件 knight.xm 配置了两个 bean,产生 id = knight 的 bean 时,系统自动传入 id = quest 的 bean 作为构造函数参数。
- 测试类 BraveKnightTest
package sia.knights;
import static org.mockito.Mockito.*;
import org.junit.Test;
import sia.knights.BraveKnight;
import sia.knights.Quest;
public class BraveKnightTest {
@Test //(1)
public void knightShouldEmbarkOnQuest() {
Quest mockQuest = mock(Quest.class); //(2)
BraveKnight knight = new BraveKnight(mockQuest);
knight.embarkOnQuest();
verify(mockQuest, times(1)).embark(); //(3)
}
}
(1)单元测试会自动运行使用 @test 注解的方法。
(2)使用 Mockito 框架生成一个 Mock object,用于模拟实际的对象, 并且能够校验对这个 Mock object 的方法调用是否符合预期。
(3)校验 mockQuest.embark() 方法是否被调用了一次。
- 测试类 KnightJavaConfigInjectionTest 和 Java 配置。
package sia.knights;
import java.io.PrintStream;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import sia.knights.BraveKnight;
import sia.knights.Knight;
import sia.knights.Quest;
import sia.knights.SlayDragonQuest;
@Configuration //(1)
public class KnightConfig {
@Bean //(2)
public Knight knight() {
return new BraveKnight(quest());
}
@Bean
public Quest quest() {
return new SlayDragonQuest(stream());
}
@Bean
public PrintStream stream() {
return new FakePrintStream();
}
}
package sia.knights;
import static org.junit.Assert.*;
import org.junit.After;
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;
import org.springframework.test.context.support.AnnotationConfigContextLoader;
import sia.knights.Knight;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=KnightConfig.class,loader=AnnotationConfigContextLoader.class) //(3)
public class KnightJavaConfigInjectionTest {
@Autowired // (4)
Knight knight;
@Autowired
FakePrintStream printStream;
@After //(5)
public void clearPrintStream() {
printStream.clear();
}
@Test
public void shouldInjectKnightWithSlayDragonQuest() {
knight.embarkOnQuest();
assertEquals(
"Embarking on quest to slay the dragon!\n",
printStream.getPrintedString()); //(6)
}
}
(1)@Configuration 注解用于定义配置类,被注解的类内部包含有一个或多个被@Bean注解的方法,用于 bean 定义。
(2)被 @Bean 注解修饰的方法用于产生 bean。
(3)@ContextConfiguration 注解包含两个参数,classes=KnightConfig.class 指定 Java config 类,loader=AnnotationConfigContextLoader.class 指定类加载器。
(4) @Autowired 注解,为 knight 自动装配一个 bean,而不用手动赋值。
(5)@After 注解的方法,在测试结束后会自动运行该方法。
(6)断言测试输出与预期字符串是否一致。printStream 对象用来缓存所有发送到输出流的数据。
- 测试类 KnightXMLInjectionTest 和 KnightXMLInjectionTest-context.xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="knight" class="sia.knights.BraveKnight">
<constructor-arg ref="quest" />
</bean>
<bean id="quest" class="sia.knights.SlayDragonQuest">
<constructor-arg ref="fakePrintStream" />
</bean>
<!--(1)-->
<bean id="minstrel" class="sia.knights.Minstrel">
<constructor-arg ref="fakePrintStream" />
</bean>
<bean id="fakePrintStream" class="sia.knights.FakePrintStream" />
<!--(2)-->
<aop:config>
<aop:aspect ref="minstrel">
<aop:pointcut id="embark"
expression="execution(* *.embarkOnQuest(..))"/>
<aop:before pointcut-ref="embark"
method="singBeforeQuest"/>
<aop:after pointcut-ref="embark"
method="singAfterQuest"/>
</aop:aspect>
</aop:config>
</beans>
package sia.knights;
import static org.junit.Assert.*;
import org.junit.After;
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;
import sia.knights.Knight;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class KnightXMLInjectionTest {
@Autowired
Knight knight;
@Autowired
FakePrintStream printStream;
@After
public void clearPrintStream() {
printStream.clear();
}
@Test
public void shouldInjectKnightWithSlayDragonQuest() {
knight.embarkOnQuest();
assertEquals(
"Fa la la, the knight is so brave!\n" +
"Embarking on quest to slay the dragon!\n" +
"Tee hee hee, the brave knight did embark on a quest!\n",
printStream.getPrintedString());
}
}
(1)把 Minstrel 声明为一个 bean。
(2)把 Minstrel bean 声明为一个切面,并声明在调用 embarkOnQuest() 方法前后,分别调用 Minstrel 的 singBeforeQuest() 和 singAfterQuest() 方法。