Sping in action 第四版

第一章 Spring 之旅《Spring in action 第

2020-02-16  本文已影响0人  平头下雨不愁

一、关于 Spring

  1. Spring 是一个开源框架,是为了解决企业级的应用开发的复杂性而创建,有着简化 Java 开发,可测试,松耦合等特点。

  2. 几个重要概念
    POJO(Plain Old Java object):简单老式 Java 对象
    DI(Dependency Injection):依赖注入
    AOP(Aspect-Oriented Programming):面向切面编程

  3. 为降低 Java 开发的复杂性,Spring 采取以下四种关键性策略。

二、源码编译

执行命令(./gradlew build)
如遇到环境配置和编译出错,请移步这里。《Spring in action 第四版》源码下载和 Java 环境配置

三、代码分析

  1. Quest.java 和 Knight.java 声明了两个接口。
package sia.knights;

public interface Quest {

  void embark();

}
package sia.knights;

public interface Knight {

  void embarkOnQuest();

}
  1. 类 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();
  }

}
  1. 包含 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 作为构造函数参数。

  1. 测试类 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() 方法是否被调用了一次。

  1. 测试类 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 对象用来缓存所有发送到输出流的数据。

  1. 测试类 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() 方法。

完!
上一篇 下一篇

猜你喜欢

热点阅读