“Resteasy with maven”的版本间的差异

来自软件实验室
跳转至: 导航搜索
同时响应多种请求格式
响应多种请求格式的方法
 
(未显示同一用户的4个中间版本)
第223行: 第223行:
 
</dependency>
 
</dependency>
 
</nowiki>
 
</nowiki>
== 同时响应多种请求格式 ==
+
== 响应多种请求格式的方法 ==
 
不同的客户端可能青睐不同的数据格式,比如Javascript喜欢json,Java喜欢xml,ruby喜欢YAML,你可以针对每种数据格式写一个响应方法,比如:
 
不同的客户端可能青睐不同的数据格式,比如Javascript喜欢json,Java喜欢xml,ruby喜欢YAML,你可以针对每种数据格式写一个响应方法,比如:
  
 
  <nowiki>
 
  <nowiki>
 +
@GET
 +
@Path("hello")
 +
@Produces({MediaType.APPLICATION_XML)
 +
public Student sayHelloByXML(){
 +
return new Student("zhangsan",95);
 +
}
 +
 +
@GET
 +
@Path("hello")
 +
@Produces({MediaType.APPLICATION_JSON)
 +
public Student sayHelloByJSON(){
 +
return new Student("zhangsan",95);
 +
}
 +
 
</nowiki>
 
</nowiki>
  
第302行: 第316行:
 
[[file:resteasy_xml_output.png]]
 
[[file:resteasy_xml_output.png]]
  
 +
为什么浏览器获得的是xml格式的数据呢?我们看一下http的请求头部信息(参见[[chrome观察HTTP请求的方法]]):
 +
<nowiki>
 +
GET /helloRest/hello/ HTTP/1.1
 +
Host: localhost:8080
 +
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
 +
Accept-Encoding: gzip, deflate, sdch
 +
Accept-Language: zh-CN,zh;q=0.8
 +
Upgrade-Insecure-Requests: 1
 +
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.80 Safari/537.36
 +
 +
</nowiki>
 +
 +
注意到Accept这一行,表示浏览器能够接受的数据格式列表。可以看出,默认情况下,浏览器最喜欢html格式的数据,其次是xhtml+xml,接着是xml......。本例中,由于sayHello这个service只能提供xml或者json格式,因此返回的数据格式是xml格式(浏览器说可以理解xml格式的数据)而不是json格式。
 +
 +
下面是观察到的HTTP响应的头部:
 +
<nowiki>
 +
HTTP/1.1 200 OK
 +
Connection: keep-alive
 +
Content-Length: 114
 +
Content-Type: application/xml
 +
Date: Tue, 15 Dec 2015 04:46:07 GMT
 +
Server: WildFly/9
 +
X-Powered-By: Undertow/1
 +
</nowiki>
 
=== 写一个客户端应用验证一下 ===
 
=== 写一个客户端应用验证一下 ===
 
可以写一个测试用例来验证sayHello能够响应两种数据格式的请求:
 
可以写一个测试用例来验证sayHello能够响应两种数据格式的请求:
第310行: 第348行:
 
===模型类最佳实践 ===
 
===模型类最佳实践 ===
 
如上面的模型类所示,json的注解放到field上面,xml的注解放到get方法上面。
 
如上面的模型类所示,json的注解放到field上面,xml的注解放到get方法上面。
 +
 +
== 思考 ==
 +
* 为什么将应用部署到wildfly时,可以只定义一个空的Application实现,即getClasses和getSingltons无需实现?
 +
* @ApplicationPath是否可以在web.xml中定义servlet-mapping替代?
  
 
== 进一步的问题 ==
 
== 进一步的问题 ==
 
* 为什么wildfly:deploy把war文件部署到了standalone/data/content目录下,而不是standalone/deployments目录下?eclipse下面运行wildfly是部署到deployments目录下的。
 
* 为什么wildfly:deploy把war文件部署到了standalone/data/content目录下,而不是standalone/deployments目录下?eclipse下面运行wildfly是部署到deployments目录下的。
 
* eclpse的maven支持难道真的有点问题?为什么不能自动刷新加入的依赖关系,通常需要close project & reopen project才会生效。
 
* eclpse的maven支持难道真的有点问题?为什么不能自动刷新加入的依赖关系,通常需要close project & reopen project才会生效。
 +
 +
== 参考资料 ==
 +
* jaxrs api doc: https://jax-rs-spec.java.net/nonav/2.0/apidocs/

2015年12月16日 (三) 16:22的最新版本

环境说明

  • maven 3.3.9
  • wildfly 9.0.2
  • resteasy:wildfly自带,版本3.0.11.Final

使用maven创建项目

使用下列命令创建示例项目:

mvn archetype:generate  -DgroupId=cn.edu.sdut.r314  -DartifactId=helloRest  -DarchetypeArtifactId=maven-archetype-webapp  -DinteractiveMode=false

也可以交互方式创建项目,步骤如下:

  1. 执行命令 mvn archetype:generate
  2. 在接下来的众多archetype中,输入webapp筛选缩小范围,目前列出了86个符合webapp的archetype,我们这里使用第22个archetype,创建一个简单的webapp的框架:
Choose a number or apply filter (format: [groupId:]artifactId, case sensitive contains): 703: webapp
Choose archetype:
1: remote -> br.com.ingenieux:elasticbeanstalk-docker-dropwizard-webapp-archetype (A Maven Archetype for Publishing Dropwizard-based Services on AWS' Elastic Beanstalk Service)
......
21: remote -> org.apache.marmotta:marmotta-archetype-webapp (Web Application bundle (WAR file) containing Apache Marmotta)
22: remote -> org.apache.maven.archetypes:maven-archetype-webapp (An archetype which contains a sample Maven Webapp project.)
......
86: remote -> uk.ac.rdg.resc:edal-ncwms-based-webapp (-)
Choose a number or apply filter (format: [groupId:]artifactId, case sensitive contains): : 22


  1. 在接下来的交互中,输入合适的groupId(包名),artifactId(项目名)即可成功创建项目框架。

项目导入到eclipse

为了方便项目文件的编辑,可以将maven创建的项目导入到eclipse中进行管理。

由于我们用不到jsp文件,将src/main/webapp目录下的index.jsp删除即可。

修改pom.xml

初始的pom.xml文件是这个样子的:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>cn.edu.sdut.r314</groupId>
	<artifactId>helloRest</artifactId>
	<packaging>war</packaging>
	<version>1.0-SNAPSHOT</version>
	<name>helloRest Maven Webapp</name>
	<url>http://maven.apache.org</url>
	<dependencies>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>3.8.1</version>
			<scope>test</scope>
		</dependency>
	</dependencies>
	<build>
		<finalName>helloRest</finalName>
	</build>
</project>

可以看出,maven-archetype-webapp这个archetype创建的项目默认已经添加了junit的依赖。虽然我们这个示例程序暂时没有涉及单元测试的内容,我们还是保留junit的依赖设置,方便以后增加单元测试的内容。本实例当然需要增加resteasy的依赖设置,另外由于我们要把应用部署到wildfly,因此增加了部署wildfly应用的plugin,修改后的pom.xml文件如下所示:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>cn.edu.sdut.r314</groupId>
	<artifactId>helloRest</artifactId>
	<packaging>war</packaging>
	<version>1.0-SNAPSHOT</version>
	<name>helloRest Maven Webapp</name>
	<url>http://maven.apache.org</url>
	<dependencies>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>3.8.1</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.jboss.resteasy</groupId>
			<artifactId>resteasy-jaxrs</artifactId>
			<version>3.0.11.Final</version>
			<scope>provided</scope>
		</dependency>
	</dependencies>
	<build>
		<finalName>helloRest</finalName>
		<plugins>
			<plugin>
				<groupId>org.wildfly.plugins</groupId>
				<artifactId>wildfly-maven-plugin</artifactId>
				<version>1.1.0.Alpha4</version>
			</plugin>
		</plugins>

	</build>
</project>

由于wildfly 9.0.2内置了resteasy 3.0.11.Final,因此我们的示例程序对resteasy的依赖声明为provided scope,即告诉wildfly,我们使用wildfly自带的resteasy实现(参见wildfly的modules/system/layers/base/org/jboss/resteasy目录)。如果去掉scope声明,则使用我们指定的resteasy实现。换言之,如果resteasy的scope为provided,则打包后的应用不包含resteasy-jaxrs.jar;如果resteasy的scope省略,则打包后的应用会包含resteasy-jaxrs.jar。

在完成这个示例后,可以分别观察删除和保留resteasy-jaxrs的<scope>provided</scope>时,target/helloRest/WEB-INF目录的不同布局:当删除scope声明时,WEB-INF会生成一个lib目录,将resteasy-jaxrs及其依赖包包含进来;而scope声明为provided时,target/helloRest/WEB-INF下面不会生成lib目录。

创建一个空的web.xml

由于wildfly已经集成了resteasy,已经不需要在web.xml声明servlet映射等了,只需要放一个空的web.xml在webapp/WEB-INF目录即可:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
      version="3.0">
</web-app>

编写服务器端的service

由于maven-archetype-webapp没有正确的创建java目录和包,需要我们手工创建:在src/main目录下创建java/cn/edu/sdut/r314目录(可以通过在eclipse创建相应的包名的方式创建此目录),我们服务器端的service即安放在这个目录下。同时,在src/创建test/java,将来的单元测试放在这里。

继续编写服务器端的service代码之前,请确保eclipse已经根据maven更新了项目的依赖包。由于eclipse的某个bug(或者设置问题?),有的时候eclipse并不能及时的根据修改后的pom.xml及时的更新项目的依赖库,导致代码自动提示功能失效。如果遇到此种情况,可以尝试close project然后reopen。目前正确的项目依赖关系如下图所示:

Maven-resteasy-dep.png

编写BaseApplication.java

代码如下:

package cn.edu.sdut.r314;

import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;

@ApplicationPath("")
public class BaseApplication extends Application {

}


编写模型类Student.java

package cn.edu.sdut.r314;

public class Student {
	private String name;
	private int javaGrade; // java课程成绩
	
	public Student(String name, int javaGrade) {
		super();
		this.name = name;
		this.javaGrade = javaGrade;
	}
	
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getJavaGrade() {
		return javaGrade;
	}
	public void setJavaGrade(int javaGrade) {
		this.javaGrade = javaGrade;
	}
}


编写HelloService.java

代码如下:

package cn.edu.sdut.r314;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

@Path("")
public class HelloService {

	@GET
	@Path("hello")
	@Produces(MediaType.APPLICATION_JSON)
	public Student sayHello(){
		return new Student("zhangsan",95);
	}
}

部署

ok,首先将wildfly启动起来,然后在项目的根目录下执行如下指令部署应用:

mvn clean package wildfly:deploy

如果一切顺利,会显示:

[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 3.645 s
[INFO] Finished at: 2015-12-14T20:29:08+08:00
[INFO] Final Memory: 25M/197M
[INFO] ------------------------------------------------------------------------


打开一个浏览器窗口测试一下吧!

Resteasy maven json output.png

json格式问题

但是,且慢!在返回的json对象中,可以看到javaGrade不太符合json的一般写法java_grade。这就需要在Student类上面增加jackson的注解把javaGrade映射为java_grade:

	@JsonProperty("java_grade")  
	private int javaGrade; // java课程成绩

但是默认情况下JsonProperty所属的包com.fasterxml.jackson.annotation并没有导入到项目的编译路径,因此需要在pom.xml文件增加如下的依赖设置:

		<dependency>
			<groupId>org.jboss.resteasy</groupId>
			<artifactId>resteasy-jackson2-provider</artifactId>
			<version>3.0.11.Final</version>
			<scope>provided</scope>
		</dependency>

响应多种请求格式的方法

不同的客户端可能青睐不同的数据格式,比如Javascript喜欢json,Java喜欢xml,ruby喜欢YAML,你可以针对每种数据格式写一个响应方法,比如:

	@GET
	@Path("hello")
	@Produces({MediaType.APPLICATION_XML)
	public Student sayHelloByXML(){
		return new Student("zhangsan",95);
	}

	@GET
	@Path("hello")
	@Produces({MediaType.APPLICATION_JSON)
	public Student sayHelloByJSON(){
		return new Student("zhangsan",95);
	}


但是,这样写代码好麻烦啊!幸运的是,JAX-RS API已经帮我们想到了这一点,下面是JAX-RS处理不同数据格式请求的过程:

文件:Jaxrs-request-dispatch.png

因此,只要定义一个这样的方法即可应对XML,JSON两种格式的数据请求:

package cn.edu.sdut.r314;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

@Path("")
public class HelloService {

	@GET
	@Path("hello")
	@Produces({MediaType.APPLICATION_XML,MediaType.APPLICATION_JSON})
	public Student sayHello(){
		return new Student("zhangsan",95);
	}
}

当然,模型类Student也要做相应的修改:

package cn.edu.sdut.r314;

import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import com.fasterxml.jackson.annotation.JsonProperty;

@XmlRootElement(name = "user")
public class Student {
	private String name;

	@JsonProperty("java_grade")  
	private int javaGrade; // java课程成绩

    public Student() {}
	
	public Student(String name, int javaGrade) {
		super();
		this.name = name;
		this.javaGrade = javaGrade;
	}
	
    @XmlElement
	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

    @XmlElement
	public int getJavaGrade() {
		return javaGrade;
	}

	public void setJavaGrade(int javaGrade) {
		this.javaGrade = javaGrade;
	}
}


下面是浏览器的响应:

Resteasy xml output.png

为什么浏览器获得的是xml格式的数据呢?我们看一下http的请求头部信息(参见chrome观察HTTP请求的方法):

GET /helloRest/hello/ HTTP/1.1
Host: localhost:8080
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Encoding: gzip, deflate, sdch
Accept-Language: zh-CN,zh;q=0.8
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.80 Safari/537.36


注意到Accept这一行,表示浏览器能够接受的数据格式列表。可以看出,默认情况下,浏览器最喜欢html格式的数据,其次是xhtml+xml,接着是xml......。本例中,由于sayHello这个service只能提供xml或者json格式,因此返回的数据格式是xml格式(浏览器说可以理解xml格式的数据)而不是json格式。

下面是观察到的HTTP响应的头部:

HTTP/1.1 200 OK
Connection: keep-alive
Content-Length: 114
Content-Type: application/xml
Date: Tue, 15 Dec 2015 04:46:07 GMT
Server: WildFly/9
X-Powered-By: Undertow/1

写一个客户端应用验证一下

可以写一个测试用例来验证sayHello能够响应两种数据格式的请求:


模型类最佳实践

如上面的模型类所示,json的注解放到field上面,xml的注解放到get方法上面。

思考

  • 为什么将应用部署到wildfly时,可以只定义一个空的Application实现,即getClasses和getSingltons无需实现?
  • @ApplicationPath是否可以在web.xml中定义servlet-mapping替代?

进一步的问题

  • 为什么wildfly:deploy把war文件部署到了standalone/data/content目录下,而不是standalone/deployments目录下?eclipse下面运行wildfly是部署到deployments目录下的。
  • eclpse的maven支持难道真的有点问题?为什么不能自动刷新加入的依赖关系,通常需要close project & reopen project才会生效。

参考资料