Sam's Notes | Sam Blog

梦想还是要有的,万一实现了呢

0%

发送 JMS 消息

简单的使用默认destination

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import javax.jms.ConnectionFactory;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.Queue;
import javax.jms.Session;

import org.springframework.jms.core.MessageCreator;
import org.springframework.jms.core.JmsTemplate;

public class JmsQueueSender {

private JmsTemplate jmsTemplate;
private Queue queue;

public void setConnectionFactory(ConnectionFactory cf) {
this.jmsTemplate = new JmsTemplate(cf);
}

public void setQueue(Queue queue) {
this.queue = queue;
}

public void simpleSend() {
this.jmsTemplate.send(this.queue, new MessageCreator() {
public Message createMessage(Session session) throws JMSException {
return session.createTextMessage("hello queue world");
}
});
}
}

Message Converters

Message Converters 提供Java 对象 message‘s 数据间的转换。Spring的默认实现 SimpleMessageConverter 可以支持String 和 TextMessage, byte[] 和 BytesMesssage, java.util.Map 和 MapMessage 之间的转换。
下面是个Map的发送:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public void sendWithConversion() {
Map map = new HashMap();
map.put("Name", "Mark");
map.put("Age", new Integer(47));
jmsTemplate.convertAndSend("testQueue", map, new MessagePostProcessor() {
public Message postProcessMessage(Message message) throws JMSException {
message.setIntProperty("AccountID", 1234);
message.setJMSCorrelationID("123-00001");
return message;
}
});
}

//This results in a message of the form:
//MapMessage={
// Header={
// ... standard headers ...
// CorrelationID={123-00001}
// }
// Properties={
// AccountID={Integer:1234}
// }
// Fields={
// Name={String:Mark}
// Age={Integer:47}
// }
//}

SessionCallback 回调和 ProducerCallback 回调

接收 JMS 消息

同步接收 Synchronous

同步接收 JMS 消息会堵塞, 可设置 receiveTimeout

异步接收 Asynchronous - Message-Driven POJOs

类似于 EJB 里的 Message-Driven Bean (MDB) ,Spring 定义了 Message-Driven POJO (MDP) 来作为 JMS 的接收者。
一个 Message-Driven POJO (MDP) 必须实现 javax.jms.MessageListener (或者 MessageListenerAdapter, SessionAwareMessageListener),而且必须是线程安全,它会被多线程调用。
MDP的一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.TextMessage;

public class ExampleListener implements MessageListener {

public void onMessage(Message message) {
if (message instanceof TextMessage) {
try {
System.out.println(((TextMessage) message).getText());
}
catch (JMSException ex) {
throw new RuntimeException(ex);
}
}
else {
throw new IllegalArgumentException("Message must be of type TextMessage");
}
}

}

对应的配置

1
2
3
4
5
6
7
8
9
<!-- this is the Message Driven POJO (MDP) -->
<bean id="messageListener" class="jmsexample.ExampleListener" />

<!-- and this is the message listener container -->
<bean id="jmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="connectionFactory"/>
<property name="destination" ref="destination"/>
---<property name="messageListener" ref="messageListener" />---
</bean>

事务 transaction

本地事务只需要简单配置 sessionTransacted 就可以激活。发送响应是该本地事务的一部分,但其他所有资源(如数据库操作)的操作都是独立的。

1
2
3
4
5
6
<bean id="jmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="connectionFactory"/>
<property name="destination" ref="destination"/>
<property name="messageListener" ref="messageListener"/>
<property name="sessionTransacted" value="true"/>
</bean>

分布式事务

1
2
3
4
5
6
7
8
9
<bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager"/>

<bean id="jmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="connectionFactory"/>
<property name="destination" ref="destination"/>
<property name="messageListener" ref="messageListener"/>
<property name="transactionManager" ref="transactionManager"/>
</bean>

注解驱动的监听端点

@JmsListener

开启监听端点 注解

@Configuration类中加入@EnableJms来使@JmsListener生效。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Configuration
@EnableJms
public class AppConfig {

@Bean
public DefaultJmsListenerContainerFactory jmsListenerContainerFactory() { //default name
DefaultJmsListenerContainerFactory factory =
new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(connectionFactory());
factory.setDestinationResolver(destinationResolver());
factory.setConcurrency("3-10"); //pool size 3~10 threads
return factory;
}
}

注册监听端点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Configuration
@EnableJms
public class AppConfig implements JmsListenerConfigurer {

@Override
public void configureJmsListeners(JmsListenerEndpointRegistrar registrar) {
SimpleJmsListenerEndpoint endpoint = new SimpleJmsListenerEndpoint();
endpoint.setId("myJmsEndpoint");
endpoint.setDestination("anotherQueue");
endpoint.setMessageListener(message -> {
// processing
});
registrar.registerEndpoint(endpoint);
}
}

端点方法 签名

1
2
3
4
5
6
7
8
@Component
public class MyService {

@JmsListener(destination = "myDestination")
public void processOrder(Order order, @Header("order_type") String orderType) {
...
}
}

响应管理

@SendTo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@JmsListener(destination = "myDestination")
@SendTo("status")
public OrderStatus processOrder(Order order) {
// order processing
return status;
}


//or

@JmsListener(destination = "myDestination")
@SendTo("status")
public Message<OrderStatus> processOrder(Order order) {
// order processing
return MessageBuilder
.withPayload(status)
.setHeader("code", 1234)
.build();
}

运行时响应destination

1
2
3
4
5
6
7
8
9
@JmsListener(destination = "myDestination")
public JmsResponse<Message<OrderStatus>> processOrder(Order order) {
// order processing
Message<OrderStatus> response = MessageBuilder
.withPayload(status)
.setHeader("code", 1234)
.build();
return JmsResponse.forQueue(response, "status");
}

JMS 命名空间

Spring 集成 ActiveMq

JMS

JMS(Java Messaging Service)是Java平台上有关面向消息中间件(MOM)的技术规范.

  • 组成
    JMS提供者
    连接面向消息中间件的,JMS接口的一个实现。提供者可以是Java平台的JMS实现,也可以是非Java平台的面向消息中间件的适配器。

JMS客户
生产或消费基于消息的Java的应用程序或对象。

JMS生产者(Message Producer)
创建并发送消息的JMS客户。

JMS消费者(Message Consumer)
接收消息的JMS客户。

JMS消息
包括可以在JMS客户之间传递的数据的对象

JMS队列
一个容纳那些被发送的等待阅读的消息的区域。与队列名字所暗示的意思不同,消息的接受顺序并不一定要与消息的发送顺序相同。一旦一个消息被阅读,该消息将被从队列中移走。

JMS主题
一种支持发送消息给多个订阅者的机制。

  • 对象模型
    连接工厂。连接工厂(ConnectionFactory)是由管理员创建,并绑定到JNDI树中。客户端使用JNDI查找连接工厂,然后利用连接工厂创建一个JMS连接。

JMS连接。JMS连接(Connection)表示JMS客户端和服务器端之间的一个活动的连接,是由客户端通过调用连接工厂的方法建立的。

JMS会话。JMS会话(Session)表示JMS客户与JMS服务器之间的会话状态。JMS会话建立在JMS连接上,表示客户与服务器之间的一个会话线程。

JMS目的。JMS目的( Destination ),又称为消息队列,是实际的消息源。可以是 queue 或 topic 。

JMS生产者和消费者。生产者(Message Producer)和消费者(Message Consumer)对象由Session对象创建,用于发送和接收消息。

  • 消息模型
    点对点(Point-to-Point)。在点对点的消息系统中,消息分发给一个单独的使用者。点对点消息往往与队列(javax.jms.Queue)相关联。
    发布/订阅(Publish/Subscribe)。发布/订阅消息系统支持一个事件驱动模型,消息生产者和消费者都参与消息的传递。生产者发布事件,而使用者订阅感兴趣的事件,并使用事件。该类型消息一般与特定的主题(javax.jms.Topic)关联。

Spring JMS

Spring 提供类似JDBC的 JMS集成框架来简单得使用 JMS API。

JmsTemplate

JmsTemplate 是 JMS 核心包的中心类。它简化了JMS的使用,因为它处理了发送或同步接收消息时资源的创建和释放。

Connections

JmsTemplate的需要一个ConnectionFactory的引用。ConnectionFactory是JMS规范的一部分,并作为JMS切入点。

  • SingleConnectionFactory
    SingleConnectionFactory会在所有 createConnection() 调用时返回相同的 Connection 。

  • CachingConnectionFactory
    CachingConnectionFactory继承SingleConnectionFactory,并增加了会话,MessageProducer,和MessageConsumers的缓存

Destination Management

Message Listener Containers

用来从JMS消息队列接收消息并驱动已经注入的 MessageListener,负责所有线程消息的接收和分发到监听器的进程,是MDP和消息提供者之间的中介,并注册接收的消息,参与事务,资源获取和释放,异常转换等等。使程序开发人员可以编写和接收消息相关的(可能比较复杂)业务逻辑。
有以下两种监听器

  • SimpleMessageListenerContainer
    简单,但不兼容JavaEE的JMS规范

  • DefaultMessageListenerContainer
    最常用的。XA transaction(JtaTransactionManager), 可以自定义缓存,可以回复(默认是每5秒,可以自己实现BackOff来制定更细的粒度,参考ExponentialBackOff

Transaction management

Spring提供了JmsTransactionManager来为单独的ConnectionFactory管理事务。
JmsTransactionManager 来管理本地事务和资源。

JtaTransactionManager 来处理分布式事务。

spring提供完整的对标准 Java web services 的支持。有2种方式 SpringBeanAutowiringSupport 和 SimpleJaxWsServiceExporter 。

SpringBeanAutowiringSupport没实验成功 :( , 下面只说 SimpleJaxWsServiceExporter 的方式。我是直接混合springMVC使用的。

  • EndPoint

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    @Component("UserService") // auto scan
    @WebService(serviceName="UserService")
    public class UrUserEndpoint
    {

    @Autowired
    IUrUserService userService;

    @WebMethod
    public UrAbsUserEntity getUser(String username){

    return userService.findByUsername(username);
    }

    }
  • xml config

    1
    2
    3
    <bean class="org.springframework.remoting.jaxws.SimpleJaxWsServiceExporter">
    <property name="baseAddress" value="http://localhost:8081/"/> //注意,如果是直接混合springMVC使用的,不能和servlet容器端口冲突
    </bean>
  • java config

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    @Configuration
    public class ApplicationConfiguration
    {
    @Bean
    public SimpleJaxWsServiceExporter jaxms()
    {
    SimpleJaxWsServiceExporter jaxws = new SimpleJaxWsServiceExporter();
    jaxws.setBaseAddress("http://localhost:8081/"); //注意,如果是直接混合springMVC使用的,不能和servlet容器端口冲突
    //此处可以有更多设置


    return jaxws;
    }

    }
  • wsdl
    启动容器,在浏览器中访问 http://localhost:8081/UserService?wsdl , 就能看到wsdl的内容了

CORS

跨域资源共享(CORS): 访问不同域站点上的资源;比如a.com上的某页面可以访问b.com上的资源,c.com上的某页面可以访问a.com上资源。

不转载了,链接: CORS(跨域资源共享)简介

Spring framework 对 CORS 的支持

controller上对类和方法的配置

Spring通过@CrossOrigin注解来实现对CORS的功能支持。

  1. 方法级

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    @RestController
    @RequestMapping("/account")
    public class AccountController {

    @CrossOrigin
    @RequestMapping("/{id}")
    public Account retrieve(@PathVariable Long id) {
    // ...
    }

    @RequestMapping(method = RequestMethod.DELETE, path = "/{id}")
    public void remove(@PathVariable Long id) {
    // ...
    }
    }
  2. 类级

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    @CrossOrigin(origins = "http://domain2.com", maxAge = 3600)
    @RestController
    @RequestMapping("/account")
    public class AccountController {

    @RequestMapping("/{id}")
    public Account retrieve(@PathVariable Long id) {
    // ...
    }

    @RequestMapping(method = RequestMethod.DELETE, path = "/{id}")
    public void remove(@PathVariable Long id) {
    // ...
    }
    }
  3. 混合
    Spring结合类和方法的注解内容生成。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    @CrossOrigin(maxAge = 3600)
    @RestController
    @RequestMapping("/account")
    public class AccountController {

    @CrossOrigin("http://domain2.com")
    @RequestMapping("/{id}")
    public Account retrieve(@PathVariable Long id) {
    // ...
    }

    @RequestMapping(method = RequestMethod.DELETE, path = "/{id}")
    public void remove(@PathVariable Long id) {
    // ...
    }
    }

全局配置

默认是GET,HEAD和POST有效

  • java 配置
  1. /**路径, 全部默认属性

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @Configuration
    @EnableWebMvc
    public class WebConfig extends WebMvcConfigurerAdapter {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
    registry.addMapping("/**");
    }
    }
  2. 自定义路径和属性

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    @Configuration
    @EnableWebMvc
    public class WebConfig extends WebMvcConfigurerAdapter {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
    registry.addMapping("/api/**")
    .allowedOrigins("http://domain2.com")
    .allowedMethods("PUT", "DELETE")
    .allowedHeaders("header1", "header2", "header3")
    .exposedHeaders("header1", "header2")
    .allowCredentials(false).maxAge(3600);
    }
    }
  • XML 配置
  1. /**路径, 全部默认属性

    1
    2
    3
    <mvc:cors>
    <mvc:mapping path="/**" />
    </mvc:cors>
  2. 自定义属性

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <mvc:cors>

    <mvc:mapping path="/api/**"
    allowed-origins="http://domain1.com, http://domain2.com"
    allowed-methods="GET, PUT"
    allowed-headers="header1, header2, header3"
    exposed-headers="header1, header2" allow-credentials="false"
    max-age="123" />

    <mvc:mapping path="/resources/**"
    allowed-origins="http://domain1.com" />

    </mvc:cors>

电脑是不会错的

  • 电脑是不会错的
  • 要出错都是你的代码错了,so去查你的代码
  • 如果发现电脑错了,参考第二条

二分法

分析处理问题,二分法挺好

数据字典

统一项目组的名词用语(中文,英文,简写)

阶段性项目小结

每周codereview

每周/每月 分享沙龙

项目目录

  • 一级目录: 共通/base, 业务别
  • 二级目录: 文件类型别,如 controller, view, entity;业务内也可以提取共通;

团队文化

  • 你的价值不仅仅体现在计划内的工作
  • 付出与得到
  • 用心,科学偷懒

使用情景

Spring Framework是基于STOMP提供的WebSocket服务,适合与对时间延迟非常敏感并且需要高频得交换大量数据的场合。比如但不限于这样的应用:金融,游戏,在线合作等。REST和HTTP API可以和WebSocket混合使用。 Spring Framework允许@Controller@RestController同时有HTTP request操作和WebSocket消息操作。

Spring MVC Test Framework建立在Servlet API mock objects基础上,他不需要一个运行的Servlet容器,
不需要,不需要,不需要!
他使用DispatcherServlet来提供完整Spring MVC的支持,使用TestContext framework来加载实际的Spring各个配置。

Server-Side Tests

Spring MVC Test的目的:提供一种有效的利用DispatcherServlet所伴生的requests和responses来测试controller的方式。
例如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration("test-servlet-context.xml")
public class ExampleTests {

@Autowired
private WebApplicationContext wac;

private MockMvc mockMvc;

@Before
public void setup() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
}

@Test
public void getAccount() throws Exception {
this.mockMvc.perform(get("/accounts/1").accept(MediaType.parseMediaType("application/json;charset=UTF-8")))
.andExpect(status().isOk())
.andExpect(content().contentType("application/json"))
.andExpect(jsonPath("$.name").value("Lee"));
}

}

Setup Options

  • webAppContextSetup

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    @RunWith(SpringJUnit4ClassRunner.class)
    @WebAppConfiguration
    @ContextConfiguration("my-servlet-context.xml")
    public class MyWebTests {

    @Autowired
    private WebApplicationContext wac;

    private MockMvc mockMvc;

    @Before
    public void setup() {
    this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
    }

    // ...

    }
  • standaloneSetup(简单controller实例)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public class MyWebTests {

    private MockMvc mockMvc;

    @Before
    public void setup() {
    this.mockMvc = MockMvcBuilders.standaloneSetup(new AccountController()).build();
    }

    // ...

    }

如何选择?
webAppContextSetup 方式:加载并缓存完整的Spring MVC配置,

standaloneSetup 方式:更像一个单元测试

他们就像集成测试Vs单元测试。

Performing Requests

1
2
3
4
5
6
7
8
9
mockMvc.perform(post("/hotels/{id}", 42).accept(MediaType.APPLICATION_JSON));

mockMvc.perform(fileUpload("/doc").file("a1", "ABC".getBytes("UTF-8")));

mockMvc.perform(get("/hotels?foo={foo}", "bar"));

mockMvc.perform(get("/hotels").param("foo", "bar"));

mockMvc.perform(get("/app/main/hotels/{id}").contextPath("/app").servletPath("/main"))

测试预期

测试预期结果可以在Requests后面加一个或多个 .andExpect(..)

1
2
3
4
mockMvc.perform(post("/persons"))
.andDo(print()) // static import from MockMvcResultHandlers, can print all the available result data
.andExpect(status().isOk())
.andExpect(model().attributeHasErrors("person"));

如果需要直接访问结果来验证一些其他数据,在测试预期最后加上 .andReturn()

1
MvcResult mvcResult = mockMvc.perform(post("/persons")).andExpect(status().isOk()).andReturn();

HtmlUnit Integration TODO

Client-Side REST Tests

使用RestTemplate进行客户端的REST测试。

1
2
3
4
5
6
7
8
RestTemplate restTemplate = new RestTemplate();

MockRestServiceServer mockServer = MockRestServiceServer.createServer(restTemplate);
mockServer.expect(requestTo("/greeting")).andRespond(withSuccess("Hello world", MediaType.TEXT_PLAIN));

// use RestTemplate ...

mockServer.verify();

PetClinic Example

Spring官方提供的完整案例Github

Spring TestContext Framework

TODO 设计原理和实现

TestContextManager : Spring TestContext Framework的主入口,管理单例的TestContext和所有注册的TestExecutionListeners。
TestContext
TestExecutionListener
ContextLoader

Context management

每个TestContext提供 context 管理, 测试实例无权配置ApplicationContext, 实例仅能获得一个ApplicationContext的引用。特别的是AbstractJUnit4SpringContextTests和AbstractTestNGSpringContextTests实现了ApplicationContextAware而且有权修改ApplicationContext。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class MyTest {

@Autowired
private ApplicationContext applicationContext;

// class body...
}


@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration
public class MyWebAppTest {
@Autowired
private WebApplicationContext wac;

// class body...
}

注入mocks

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@WebAppConfiguration
@ContextConfiguration
public class WacTests {

@Autowired
WebApplicationContext wac; // cached

@Autowired
MockServletContext servletContext; // cached

@Autowired
MockHttpSession session;

@Autowired
MockHttpServletRequest request;

@Autowired
MockHttpServletResponse response;

@Autowired
ServletWebRequest webRequest;

//...
}

执行SQL scripts

  • 代码
    下列都可以用来执行
    org.springframework.jdbc.datasource.init.ScriptUtils
    org.springframework.jdbc.datasource.init.ResourceDatabasePopulator
    org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests
    org.springframework.test.context.testng.AbstractTransactionalTestNGSpringContextTests

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    @Test
    public void databaseTest {
    ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
    populator.addScripts(
    new ClassPathResource("test-schema.sql"),
    new ClassPathResource("test-data.sql"));
    populator.setSeparator("@@");
    populator.execute(this.dataSource);
    // execute code that uses the test schema and data
    }
  • @Sql

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    //1
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration
    @Sql("/test-schema.sql")
    public class DatabaseTests {

    @Test
    public void emptySchemaTest {
    // execute code that uses the test schema without any test data
    }

    @Test
    @Sql({"/test-schema.sql", "/test-user-data.sql"})
    public void userTest {
    // execute code that uses the test schema and test data
    }
    }



    //1
    @Test
    @Sql(scripts = "/test-schema.sql", config = @SqlConfig(commentPrefix = "`"))
    @Sql("/test-user-data.sql")
    public void userTest {
    // execute code that uses the test schema and test data
    }


    //3
    @Test
    @SqlGroup({
    @Sql(scripts = "/test-schema.sql", config = @SqlConfig(commentPrefix = "`")),
    @Sql("/test-user-data.sql")
    )}
    public void userTest {
    // execute code that uses the test schema and test data
    }


    //4
    @Test
    @Sql(
    scripts = "create-test-data.sql",
    config = @SqlConfig(transactionMode = ISOLATED)
    )
    @Sql(
    scripts = "delete-test-data.sql",
    config = @SqlConfig(transactionMode = ISOLATED),
    executionPhase = AFTER_TEST_METHOD
    )
    public void userTest {
    // execute code that needs the test data to be committed
    // to the database outside of the test's transaction
    }

TestContext Framework支持类

Spring JUnit Runner

1
2
3
4
5
6
7
8
9
@RunWith(SpringJUnit4ClassRunner.class)
@TestExecutionListeners({})
public class SimpleTest {

@Test
public void testMethod() {
// execute test logic...
}
}

如果要使用MockitoJUnitRunner等第三方runners, 可以使用Spring JUnit Rules。

Spring JUnit Rules

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Optionally specify a non-Spring Runner via @RunWith(...)
@ContextConfiguration
public class IntegrationTest {

@ClassRule
public static final SpringClassRule SPRING_CLASS_RULE = new SpringClassRule();

@Rule
public final SpringMethodRule springMethodRule = new SpringMethodRule();

@Test
public void testMethod() {
// execute test logic...
}
}

Annotations

Spring Testing Annotations

  • @ContextConfiguration
    类级别注解,用来声明如何加载和配置ApplicationContext。可以使用XML配置文件和被@Configuration注解的类。
    例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    // 1) xml
    @ContextConfiguration("/test-config.xml")
    public class XmlApplicationContextTests {
    // class body...
    }

    // 2) @Configuration Class
    @ContextConfiguration(classes = TestConfig.class)
    public class ConfigClassApplicationContextTests {
    // class body...
    }

    // 3) `ApplicationContextInitializer` Class
    @ContextConfiguration(initializers = CustomContextIntializer.class)
    public class ContextInitializerTests {
    // class body...
    }

    // 4) ContextLoader
    @ContextConfiguration(locations = "/test-context.xml", loader = CustomContextLoader.class)
    public class CustomLoaderXmlApplicationContextTests {
    // class body...
    }
  • @WebAppConfiguration
    类级别注解,用来声明如何加载WebApplicationContext。默认"file:src/main/webapp"为web App根目录。必须协同 @ContextConfiguration才生效

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    @ContextConfiguration
    @WebAppConfiguration
    public class WebAppTests {
    // class body...
    }




    @ContextConfiguration
    @WebAppConfiguration("classpath:test-web-resources")
    public class WebAppTests {
    // class body...
    }
  • @ContextHierarchy
    类级别注解,声明多层级的@ContextConfiguration

    1
    2
    3
    4
    5
    6
    7
    @ContextHierarchy({
    @ContextConfiguration("/parent-config.xml"),
    @ContextConfiguration("/child-config.xml")
    })
    public class ContextHierarchyTests {
    // class body...
    }
    1
    2
    3
    4
    5
    6
    7
    8
    @WebAppConfiguration
    @ContextHierarchy({
    @ContextConfiguration(classes = AppConfig.class),
    @ContextConfiguration(classes = WebConfig.class)
    })
    public class WebIntegrationTests {
    // class body...
    }
  • @ActiveProfiles
    类级别注解,声明ApplicationContext使用哪些profiles。

    1
    2
    3
    4
    5
    @ContextConfiguration
    @ActiveProfiles("dev")
    public class DeveloperTests {
    // class body...
    }
    1
    2
    3
    4
    5
    @ContextConfiguration
    @ActiveProfiles({"dev", "integration"})
    public class DeveloperIntegrationTests {
    // class body...
    }
  • @TestPropertySource
    类级别注解,指定加载properties文件或手动增加PropertySources的内容

    1
    2
    3
    4
    5
    @ContextConfiguration
    @TestPropertySource("/test.properties")
    public class MyIntegrationTests {
    // class body...
    }
    1
    2
    3
    4
    5
    @ContextConfiguration
    @TestPropertySource(properties = { "timezone = GMT", "port: 4242" })
    public class MyIntegrationTests {
    // class body...
    }
  • @DirtiesContext
    类或方法级别注解,根据不同策略,Spring TestContext会刷新Spring的上下文(就是重新创建ApplicationContext)
    有各种策略

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    // BEFORE_CLASS
    @DirtiesContext(classMode = BEFORE_CLASS)
    public class FreshContextTests {
    // some tests that require a new Spring container
    }



    //default class mode : `AFTER_CLASS`
    @DirtiesContext
    public class ContextDirtyingTests {
    // some tests that result in the Spring container being dirtied
    }



    // BEFORE_EACH_TEST_METHOD
    @DirtiesContext(classMode = BEFORE_EACH_TEST_METHOD)
    public class FreshContextTests {
    // some tests that require a new Spring container
    }



    // AFTER_EACH_TEST_METHOD
    @DirtiesContext(classMode = AFTER_EACH_TEST_METHOD)
    public class ContextDirtyingTests {
    // some tests that result in the Spring container being dirtied
    }



    // BEFORE_METHOD
    @DirtiesContext(methodMode = BEFORE_METHOD)
    @Test
    public void testProcessWhichRequiresFreshAppCtx() {
    // some logic that requires a new Spring container
    }



    //default method mode `AFTER_METHOD`
    @DirtiesContext
    @Test
    public void testProcessWhichDirtiesAppCtx() {
    // some logic that results in the Spring container being dirtied
    }
  • @TestExecutionListeners

  • @Commit

  • @Rollback

  • @BeforeTransaction

  • @AfterTransaction

  • @Sql

  • @SqlConfig

  • @SqlGroup

Standard Annotation Support

Spring TestContext Framework 支持下列标准的注解

  • @Autowired
    
  • @Qualifier
  • @Resource (javax.annotation) if JSR-250 is present
  • @Inject (javax.inject) if JSR-330 is present
  • @Named (javax.inject) if JSR-330 is present
  • @PersistenceContext (javax.persistence) if JPA is present
  • @PersistenceUnit (javax.persistence) if JPA is present
  • @Required
  • @Transactional

Spring JUnit Testing Annotations

组合SpringJUnit4ClassRunner, Spring’s JUnit rules, 或 Spring’s JUnit support classes使用的时候,Spring TestContext Framework 还支持下列标准的注解:

  • @IfProfileValue
    类级别或方法级别注解,校验具体的环境变量,匹配才进行测试,否则就会忽略。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @IfProfileValue(name="java.vendor", value="Oracle Corporation")
    @Test
    public void testProcessWhichRunsOnlyOnOracleJvm() {
    // some logic that should run only on Java VMs from Oracle Corporation
    }


    @IfProfileValue(name="test-groups", values={"unit-tests", "integration-tests"})
    @Test
    public void testProcessWhichRunsForUnitOrIntegrationTestGroups() {
    // some logic that should run only for unit and integration test groups
    }
  • @ProfileValueSourceConfiguration

  • @Timed

    1
    2
    3
    4
    @Timed(millis=1000)
    public void testProcessWithOneSecondTimeout() {
    // some logic that should not take longer than 1 second to execute
    }
  • @Repeat

    1
    2
    3
    4
    5
    @Repeat(10)
    @Test
    public void testProcessRepeatedly() {
    // ...
    }