Sam's Notes | Sam Blog

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

0%

以下全是linux系统上的操作,在opensuse 13.2 上实际操作。

准备

  • JDK 1.6 以上
  • JAVA_HOME 配置好
  • 官网

安装

  • 1 下载 activemq gzip 文件

    1
    $ wget http://ftp.meisei-u.ac.jp/mirror/apache/dist/activemq/5.12.0/apache-activemq-5.12.0-bin.tar.gz
  • 2 解压缩

    1
    $ tar zxvf activemq-x.x.x.tar.gz
  • 3 确认权限
    确认activemq 是否有执行权限

    1
    2
    $ cd [activemq_install_dir]/bin
    $ chmod 755 activemq

启动

进入安装目录的bin路径,启动activemq

1
2
$ cd [activemq_install_dir]/bin
$ ./activemq start

正常输出

1
2
3
4
INFO: Loading '[activemq_install_dir]/bin/env'
INFO: Using java '/usr/lib64/jvm/java/bin/java'
INFO: Starting - inspect logfiles specified in logging.properties and log4j.properties to get details
INFO: pidfile created : '[activemq_install_dir]/data/activemq.pid' (pid 'xxxx')

监控

网页监控 http://localhost:8161/admin
默认用户密码 admin/admin

可以在 conf/jetty-real.properties 中修改

端口

  • 网页监控
    端口: 8161
    配置: [activemq_install_dir]/conf/jetty.xml

  • activeMq 服务
    openwire: 61616
    amqp : 5672
    stomp : 61613
    mqtt : 1883
    ws : 61614
    配置: [activemq_install_dir]/conf/activemq.xml

停止

1
2
$ cd [activemq_install_dir]/bin
$ ./activemq stop

配置

[activemq_install_dir]/conf/activemq.xml

集群

集群

web demo

官网说 启动 activemq 后, http://localhost:8161/demo直接能访问, 其实5.12.0版本是不行的,不行的,不行的。

需要在 conf/jetty 中增加配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<bean class="org.eclipse.jetty.webapp.WebAppContext">
<property name="contextPath" value="/admin" />
<property name="resourceBase" value="${activemq.home}/webapps/admin" />
<property name="logUrlOnStart" value="true" />
</bean>


<!-- 需要增加 -->
<bean class="org.eclipse.jetty.webapp.WebAppContext">
<property name="contextPath" value="/demo" />
<property name="resourceBase" value="${activemq.home}/webapps-demo/demo/" />
<property name="logUrlOnStart" value="true" />
</bean>


...

重启activemq, http://localhost:8161/demo可以访问了, enjoy it !

activemq集合贴

几个消息总线(龟速更新。。。)

ActiveMQ

是Apache出品,最流行的,能力强劲的开源消息总线。ActiveMQ 是一个完全支持JMS1.1和J2EE 1.4规范的JMS Provider实现。

Kafka

由LinkedIn开发的一个分布式的消息系统,开源贡献给了Apache

Apollo

ActiveMQ的升级版,重新改写了内核,速度飞快,功能目前还不完全。

RabbitMQ

RabbitMQ是实现AMQP(高级消息队列协议)的消息中间件。Erlang语言编写的。

常用Message Queue对比 (转载 info 郭俊)

  • RabbitMQ

RabbitMQ是使用Erlang编写的一个开源的消息队列,本身支持很多的协议:AMQP,XMPP, SMTP, STOMP,也正因如此,它非常重量级,更适合于企业级的开发。同时实现了Broker构架,这意味着消息在发送给客户端时先在中心队列排队。对路由,负载均衡或者数据持久化都有很好的支持。

  • Redis

Redis是一个基于Key-Value对的NoSQL数据库,开发维护很活跃。虽然它是一个Key-Value数据库存储系统,但它本身支持MQ功能,所以完全可以当做一个轻量级的队列服务来使用。对于RabbitMQ和Redis的入队和出队操作,各执行100万次,每10万次记录一次执行时间。测试数据分为128Bytes、512Bytes、1K和10K四个不同大小的数据。实验表明:入队时,当数据比较小时Redis的性能要高于RabbitMQ,而如果数据大小超过了10K,Redis则慢的无法忍受;出队时,无论数据大小,Redis都表现出非常好的性能,而RabbitMQ的出队性能则远低于Redis。

  • ZeroMQ

ZeroMQ号称最快的消息队列系统,尤其针对大吞吐量的需求场景。ZeroMQ能够实现RabbitMQ不擅长的高级/复杂的队列,但是开发人员需要自己组合多种技术框架,技术上的复杂度是对这MQ能够应用成功的挑战。ZeroMQ具有一个独特的非中间件的模式,你不需要安装和运行一个消息服务器或中间件,因为你的应用程序将扮演这个服务器角色。你只需要简单的引用ZeroMQ程序库,可以使用NuGet安装,然后你就可以愉快的在应用程序之间发送消息了。但是ZeroMQ仅提供非持久性的队列,也就是说如果宕机,数据将会丢失。其中,Twitter的Storm 0.9.0以前的版本中默认使用ZeroMQ作为数据流的传输(Storm从0.9版本开始同时支持ZeroMQ和Netty作为传输模块)。

  • ActiveMQ

ActiveMQ是Apache下的一个子项目。 类似于ZeroMQ,它能够以代理人和点对点的技术实现队列。同时类似于RabbitMQ,它少量代码就可以高效地实现高级应用场景。

  • Kafka/Jafka

Kafka是Apache下的一个子项目,是一个高性能跨语言分布式发布/订阅消息队列系统,而Jafka是在Kafka之上孵化而来的,即Kafka的一个升级版。具有以下特性:快速持久化,可以在O(1)的系统开销下进行消息持久化;高吞吐,在一台普通的服务器上既可以达到10W/s的吞吐速率;完全的分布式系统,Broker、Producer、Consumer都原生自动支持分布式,自动实现负载均衡;支持Hadoop数据并行加载,对于像Hadoop的一样的日志数据和离线分析系统,但又要求实时处理的限制,这是一个可行的解决方案。Kafka通过Hadoop的并行加载机制统一了在线和离线的消息处理。Apache Kafka相对于ActiveMQ是一个非常轻量级的消息系统,除了性能非常好之外,还是一个工作良好的分布式系统

薪水之外,工程师最应该关系的问题

你们的企业文化是什么?

可以问问企业从开发到测试都喜欢什么工具,询问开发流程,询问工程和其他团队之间的关系。建议问“在应对项目时,你们公司会给开发人员什么级别的自主性?”

如何衡量我?

你的雇主如何定义你的“成功”?不同公司的评判标准不同,要满足你觉得不舒服的目标会让你的生活苦不堪言。

有什么成长计划?

询问是否有一个针对软件工程师的成长计划,询问一下,多少外部聘请 vs 公司内部晋升。

你们的发展计划?

你需要了解他们的发展计划,建议可以问这样的问题,如“你们的资金消耗率(公司的负现金流)是多少?”

我会喜欢你们的人吗?

聊到目前的团队成员,试着和公司的内部人士聊天(面试官以外),以便于知道公司内部管理人员大致的情形, “他们好合作吗,他们做事征求意见吗,他们提供反馈吗,他或她投资团队成员并帮助他们成长吗?”

发送 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