那么既然说到了分布式的开发,那么所有的开发者一定都会立刻联想到一个词:“慢”,对于传统的WEB服务开发(AXIS、XFire、CXF等),而且在编写的时候需要编写一大堆的客户端代码,这样对于整个程序的开发的复杂度还是非常高的,可是在实际的开发之中,如果你作为一个架构师,一定不可能将你一个项目的所有的子系统都设计为一个,中间一定会拆分成若干个子系统,于是这个若干个子系统之间如果要想进行数据的交互处理,只能够使用RPC。
在整个系统的处理过程里面,对于Restful的实现要比传统的任何的WEB服务的操作都方便。所有的处理都是以资源路径的形式出现的,回顾一下,如果现在使用的是普通开发架构:
现在我的所有资源都保存在了“/pages/back/message”,则此时对于路径可能就分为:
· 增加路径:/pages/back/message/add.action;
· 修改路径:/pages/back/message/edit.action;
· 删除路径:/pages/back/message/rm.action;
· 修改路径:/pages/back/message/list.action;
如果基于Restful风格,那么对于路径操作就没有必要如此的复杂:
· 增加数据:POST请求模式,使用“/message”;
· 修改数据:PUT请求模式,使用“/message”;
如果要想开发Restful架构的WEB服务,那么需要使用jesey组件。这个组件主要实现WEB Service的开发模式。在这个组件里面整合了JAXB操作标准,可以自动的将VO类对象转换为JSON或者是XML结构。
1、 建立一个新的WEB项目:RestProject;
· 由于此时还没有整合Spring,所以建立项目的时候一定要建立好web.xml文件;
2、 将jesey的开发包设置到项目之中;
· 版本:jersey-archive-1.19.1.zip。
3、 在web.xml文件里面追加如下的配置信息,主要是为了接收WEB服务使用的;
· 处理的Servlet:com.sun.jersey.spi.container.servlet.ServletContainer
<servlet> <servlet-name>restService</servlet-name> <servlet-class> com.sun.jersey.spi.container.servlet.ServletContainer </servlet-class> <init-param> <param-name>com.sun.jersey.config.property.packages</param-name> <param-value>cn.mldn.resources</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>restService</servlet-name> <url-pattern>/rest/*</url-pattern> </servlet-mapping> |
---|
Restful风格实现的WEB服务代码,都需要使用Annotation的方式来进行处理,所以一定要配置好扫描包;
4、 在cn.mldn.resources包中建立HelloResource程序,这个程序主要就是进行最简单的打印信息的操作;
package cn.mldn.resources; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; @Path("/hello") public class HelloResource { @GET @Produces(MediaType.TEXT_PLAIN) public String say() { return "www.mldn.cn" ; } } |
---|
在整个的开发过程之中,程序的编写几乎没有任何的难点,包括如下几个说明:
· “@Path("/hello")”:表示此服务的访问路径;
· “@GET”:表示此服务的路径将基于GET请求的模式来进行处理;
· “@Produces(MediaType.TEXT_PLAIN)”:表示的响应的结果,以文本的方式返回。
5、 将项目发布到Tomcat之中;
· 路径:http://localhost/RestProject/rest/hello
整个这样的方式所实现的WEB服务不仅简单而且性能要高。
如果要是实现基于Restful架构风格的WEB服务,那么对于接收与返回的数据重点在于JSON结构上,也就是说只有返回的类型为JSON才可以被所有的操作所正常解析。
所以本次将使用JAXB实现VO与JSON的转换处理操作。
1、 定义一个MessageResource程序类;
package cn.mldn.resources; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; @Path("/message") public class MessageResource { @GET @Path("/json") @Produces(MediaType.APPLICATION_JSON) public String getJson() { return "{mid:10,title:'Java开发'}" ; } } |
---|
但是以上的实现操作之中都是基于字符串的过程,这样在很大程度上来讲并不方便。最好的做法是利用VO类转换。
2、 定义Message.java类
package cn.mldn.vo; import java.io.Serializable; import javax.xml.bind.annotation.XmlRootElement; @SuppressWarnings("serial") @XmlRootElement public class Message implements Serializable { private Integer mid ; private String title ; public Integer getMid() { return mid; } public void setMid(Integer mid) { this.mid = mid; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } } |
---|
如果需要将VO对象自动转换为JSON结构,那么一定要使用“@XmlRootElement”注解操作。
但是千万要记住一点,此时如果要是存在有级联关系(一般别这么写),那么就必须针对于级联设置元素名称。
package cn.mldn.vo; public class Member { private String name ; private Integer age ; } |
---|
随后在资源里面对其进行定义:
@GET @Path("/list") @Produces(MediaType.APPLICATION_JSON) public Message getList() { Message vo = new Message() ; vo.setMid(10); vo.setTitle("Java开发"); vo.setMembers(new ArrayList<Member>()); for (int x = 0 ; x < 10 ; x ++) { Member mem = new Member() ; mem.setName("mldn - " + x); mem.setAge(x); vo.getMembers().add(mem) ; } return vo ; } |
---|
这个时候在进行数据输出操作中,如果不想使用默认的属性名称作为数组的名称,那么可以直接在Message类中进行定义:
@XmlElement(name="allMessages") public List<Member> getMembers() { return members; } |
---|
随后在生成数据的时候就会自动的使用“allMessages”作为所有Message的数组的key的信息。
在Jesey里面是可以注入一些操作对象的,使用“@Context”注解即可。
范例:注入对象
@Context private Request request ; @Context private UriInfo uriInfo ; @Context private ServletContext context ; @GET @Path("/info") @Produces(MediaType.TEXT_PLAIN) public String info() { System.out.println("REQUEST : " + this.request.getMethod()); System.out.println("UriInfo : " + this.uriInfo.getPath()); System.out.println("ServletContext : " + this.context.getRealPath("/")); return "www.mldn.cn" ; } |
---|
REQUEST : GET UriInfo : message/info ServletContext : C:\apache-tomcat-8.0.36\webapps\RestProject\ |
在整个Restful架构的处理过程之中,依然可以与所使用的容器结合在一起使用。
在Restful风格的架构里面对于参数的接收有许多的方式,而且使用Restful实现的WEB服务架构最大的好处在于其可以直接以WEB的方式运行。
可以接收以地址重写的方式传递的参数内容。
1、 定义Echo操作:
package cn.mldn.resources; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; @Path("/myparam") public class ParamResource { @GET @Path("echo") @Produces(MediaType.TEXT_PLAIN) public String echo(@QueryParam("msg") String msg) { return "ECHO : " + msg ; } } |
---|
随后运行的时候要使用地址重写的方式传递:
· 地址:http://localhost/RestProject/rest/myparam/echo?msg=mldn
此注解表示参数是通过表单设置完成的。
范例:定义一个使用“@FormParam”接收的参数方法
@POST @Path("show") @Produces(MediaType.TEXT_PLAIN) public String show(@FormParam("info") String info) { return "[Echo] : " + info ; } |
---|
既然此处使用的是表单提交的方式处理,所以使用POST请求模式完成。
范例:定义表单
<form action="<%=basePath%>rest/myparam/show" method="post"> 消息:<input type="text" name="info" id="info"> <input type="submit" value="提交"> </form> |
---|
只要是表单提交的操作处理,都建议使用此种模式完成。
“@PathParam”对于它的使用环境比较多,而且如果需要传递多个参数,也一定要使用“@PathParam”完成。
1、 直接以路径的形式设置内容:
@GET @Path("/{msg}") @Produces(MediaType.TEXT_PLAIN) public String msg(@PathParam("msg") String msg) { return "ECHO : " + msg ; } |
---|
访问地址:http://localhost/RestProject/rest/myparam/mldn/
2、 传递多个参数:
对于多个参数的传递的处理操作,可以直接利用PathSegment完成,但是它也是基于PathParam的方式处理的,并且最为重要的是所有的参数之间使用“;”分割。
范例:使用PathSegment来进行参数接收
@GET @Path("/query/{condition}") @Produces(MediaType.TEXT_PLAIN) public String segment(@PathParam("condition") PathSegment condition) { System.out.println("*******************" + condition.getPath()); MultivaluedMap<String, String> map = condition.getMatrixParameters() ; Iterator<Map.Entry<String,List<String>>> iter = map.entrySet().iterator() ; while (iter.hasNext()) { Map.Entry<String,List<String>> me = iter.next() ; System.out.println("key = " + me.getKey() + "、value = " + me.getValue()); } return "mldn" ; } |
---|
*******************restful-mldn key = cp、value = [1] key = ls、value = [10] key = type、value = [1,2,3] |
访问路径:http://localhost/RestProject/rest/myparam/query/restful-mldn;cp=1;ls=10;type=1,2,3
可以发现,所有的参数最终接收之后都变为了List集合的形式。
实际上在这里面操作的时候也存在有另外一种小小的注解,因为以上的操作是将所有的参数都设置在一起了,虽然可以进行传递,但是在进行取得内容的时候还是比较麻烦的。所以在Jesey里面提供有一个“@MatrixParam”注解分开接收。
范例:分开接收参数内容
@GET @Path("/more/{condition}") @Produces(MediaType.TEXT_PLAIN) public String msegment( @PathParam("condition") PathSegment condition , @MatrixParam("cp") int currentPage , @MatrixParam("ls") int lineSize) { System.out.println("*******************" + condition.getPath()); System.out.println("currentPage = " + currentPage); System.out.println("lineSize = " + lineSize); return "mldn" ; } |
---|
以上的操作在进行参数传递的时候依然可以正常接收:
· 路径:http://localhost/RestProject/rest/myparam/more/restful-mldn;cp=2;ls=100
在restful架构里面如果要传递的参数的结构较多,则也可以使用正则匹配参数路径。
范例:定义具体的参数接收
@GET @Path("{region:.+}/beijing/{district:\\w+}") @Produces(MediaType.TEXT_PLAIN) public String city( @PathParam("region") List<PathSegment> region , @PathParam("district") String district) { Iterator<PathSegment> iter = region.iterator() ; while (iter.hasNext()) { System.out.println("*** " + iter.next().getPath()); } System.out.println("### district = " + district); return "city" ; } |
---|
对于参数的传递前面可以随便写,但是如果要想触发本操作,那么必须有“beijing”;
· 路径:http://localhost/RestProject/rest/myparam/China/BeiJing/beijing/chaoyangqu
如果现在需要进行多层的内容接收,则对于后续的定义也应该使用“.+”的形式完成,同时使用List集合;
@GET @Path("{region:.+}/beijing/{district:.+}") @Produces(MediaType.TEXT_PLAIN) public String city( @PathParam("region") List<PathSegment> region , @PathParam("district") List<PathSegment> district) { System.out.println("*** region = " + region); System.out.println("### district = " + district); return "city" ; } |
---|
此时“/beijing”前后内容可以随便编写,多少级都没有关系;
· 地址:http://localhost/RestProject/rest/myparam/China/BeiJing/beijing/chaoyangqu/maquanying/ditie
之所以会提供这么多风格的参数的操作,主要还是为了进行资源的统一利用。也就是说希望一个路径可以按照最简短的方式实现最多的功能。
如果在实际的开发之中,即便是进行了WEB服务的开发,那么依然需要业务层处理,依然需要数据层的操作处理,而这些肯定是Spring的强项,所以必须要将jesey组件与Spring整合。
1、 为项目添加Spring开发支持;
2、 在web.xml文件里面一定要配置好jesey相关内容,而一旦增加了Spring之后,那么也会自动出现一个监听器;
3、 随后要修改applicationContext.xml文件,需要配置Annotation声明注入;
<context:annotation-config/> <context:component-scan base-package="cn.mldn"/> |
---|
4、 定义资源类:
package cn.mldn.resources; import java.util.ArrayList; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; import cn.mldn.vo.Member; import cn.mldn.vo.Message; @Path("/mymsg") @Component // 表示Spring组件 // 表示每一次请求发生时,都会生成一个新的Jesey服务实例 @Scope("prototype") // WEB服务是不需要保存用户状态的 public class SpringMessage { @GET @Path("/list") @Produces(MediaType.APPLICATION_JSON) public Message getList() { Message vo = new Message() ; vo.setMid(10); vo.setTitle("Java开发"); vo.setMembers(new ArrayList<Member>()); for (int x = 0 ; x < 10 ; x ++) { Member mem = new Member() ; mem.setName("mldn - " + x); mem.setAge(x); vo.getMembers().add(mem) ; } return vo ; } } |
---|
5、 启动服务器,来观察当前的服务接口是否可用;
· http://localhost/RestProject/rest/mymsg/list
6、 编写客户端,执行数据的取得:
package cn.mldn.test; import com.sun.jersey.api.client.Client; import com.sun.jersey.api.client.WebResource; public class TestMsg { public static void main(String[] args) { Client client = Client.create() ; WebResource wr = client.resource("http://localhost/RestProject/rest/mymsg/list") ; System.out.println(wr.get(String.class)); } } |
---|
这个时候对于整个的WEB服务的编写与实现可以发现非常的容易理解。