Resteasy with maven
目录
环境说明
- 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
也可以交互方式创建项目,步骤如下:
- 执行命令 mvn archetype:generate
- 在接下来的众多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
- 在接下来的交互中,输入合适的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。目前正确的项目依赖关系如下图所示:
编写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] ------------------------------------------------------------------------
打开一个浏览器窗口测试一下吧!
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处理不同数据格式请求的过程:
因此,只要定义一个这样的方法即可应对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; } }
下面是浏览器的响应:
为什么浏览器获得的是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才会生效。
参考资料
- jaxrs api doc: https://jax-rs-spec.java.net/nonav/2.0/apidocs/