RESTFul 快速实现

一.第一个Java RESTFul 服务

本节讲述基于JavaSE环境的Jersey 官文文档中提供的示例simple-service,并在此基础上扩展自定义的RESTFul 资源服务

1.1 环境准备

准备开发RESTFul 服务的环境 包括JDK Maven 和 IDEA

1.2 创建服务

1.从Maven 原型创建项目

Jersey 官方文档中的提供的例子simple-service 是一个Maven 原型项目,在控制台执行如下命令来生成我们想要的simole-service 项目,项目的存储路径可以自行选择

mvn archetype:generate -DarchetypeArtifactId=jersey-quickstart-grizzly2 -DarchetypeGroupId=org.glassfish.jersey.archetypes 
-DinteractiveMode=false -DgroupId=com.example -DartifactId=simple-service -Dpackage=com.example -DarchetypeVersion=2.26

控制台命令成功执行之后,会在当前目录下创建simple-service目录。该目录包含了simple-service 项目的源代码

simple-service

2.项目入口类分析
因为这是一个javaSE项目 所以需要一个入口类来启动服务并加载项目资源

package com.example;

import org.glassfish.grizzly.http.server.HttpServer;
import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
import org.glassfish.jersey.jdkhttp.JdkHttpServerFactory;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.simple.SimpleContainer;
import org.glassfish.jersey.simple.SimpleContainerFactory;
import java.io.IOException;
import java.net.URI;

/**
 * Main class.
 *
 */
public class Main {
    // 服务器路径
    public static final String BASE_URI = "http://localhost:8080/myapp/";
    public static HttpServer startServer() {
        // 加载资源
        final ResourceConfig rc = new ResourceConfig().packages("com.example");

        //创建和启动grizzly http 服务器
        return GrizzlyHttpServerFactory.createHttpServer(URI.create(BASE_URI), rc);
    }
    public static com.sun.net.httpserver.HttpServer startServerByHTTPServer(){
        final ResourceConfig rc = new ResourceConfig().packages("com.example");
        return JdkHttpServerFactory.createHttpServer(URI.create(BASE_URI),rc);
    }
    /**
     * 启动服务器
     * @param args
     * @throws IOException
     */
    public static void main(String[] args) throws IOException {
        final HttpServer server = startServer();
        System.out.println(String.format("Jersey app started with WADL available at "
                + "%sapplication.wadl\nHit enter to stop it...", BASE_URI));
        System.in.read();
        server.stop();
    }
}

Main类定义了HTTP服务器的路径即http://localhost:8080/myapp/ .在其构造器中映射了源代码所在的包名为 new ResourceConfig().packages("com.example"); 这意味着 服务器启动时会自动扫描该包下的所有类,根据该包中所含类的REST资源路径的注解,在内存中做好映射。这样一来客户端请求指定路径后服务端就可以根据映射,分派请求给相应的资源类实例的相应的方法了

2.资源类分析
套用Web 开发环境中典型的三层逻辑,资源类位于逻辑分层的最高层----API层 其下为Service 层和数据访问层,在三层逻辑中,API层用于对外公布接口

package com.example;

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

/**
 * Root resource (exposed at "myresource" path)
 */
@Path("myresource")
public class MyResource {

    /**
     * Method handling HTTP GET requests. The returned object will be sent
     * to the client as "text/plain" media type.
     *
     * @return String that will be returned as a text/plain response.
     */
    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String getIt() {
        return "Got it!";
    }
}

1.3 扩展服务

1.增加设备实体类(在com.domain 包下新建一个Device 类)

package com.domain;

import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement(name="device")
public class Device {

    private String deviceIP;
    private int deviceStatus;

    public Device(){

    }

    public Device(String deviceIP){
        this.deviceIP = deviceIP;
    }

    public String getDeviceIP() {
        return deviceIP;
    }

    public void setDeviceIP(String deviceIP) {
        this.deviceIP = deviceIP;
    }

    @XmlAttribute
    public int getDeviceStatus() {
        return deviceStatus;
    }

    public void setDeviceStatus(int deviceStatus) {
        this.deviceStatus = deviceStatus;
    }
}
package com.dao;

import com.domain.Device;
import com.sun.org.apache.bcel.internal.generic.RETURN;

import java.util.concurrent.ConcurrentHashMap;

public class DeviceDao {

    private ConcurrentHashMap<String,Device> fakeDB = new ConcurrentHashMap<>();

    public DeviceDao(){
        fakeDB.put("127.0.0.1",new Device("127.0.0.1"));
        fakeDB.put("192.168.4.74",new Device("192.168.4.74"));
    }

    public Device getDevice(String IP){
        return fakeDB.get(IP);
    }

    public Device updateDevice(Device device){
        String IP = device.getDeviceIP();
        fakeDB.put(IP,device);
        return fakeDB.get(IP);
    }
}

该类标注了JAXB 标准定义的@XmlRootElement 和 @XmlAttribute 注解 以便将Device类和XML格式的设备数据相互转化并在服务器和客户端之间传输

2.增加设备资源类
创建了设备实体类之后,我们需要一个资源来公布设备的REST API

package com.example;

import com.dao.DeviceDao;
import com.domain.Device;

import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import java.util.List;
import java.util.Map;

@Path(value = "device")
public class DeviceResource {

    private DeviceDao deviceDao;

    public DeviceResource(){
        deviceDao = new DeviceDao();
    }

    @GET
    @Produces({MediaType.APPLICATION_JSON,MediaType.APPLICATION_XML})
    public Device get(@QueryParam("ip") final String deviceIP){
        Device device = null;
        if (deviceIP != null){
            device = deviceDao.getDevice(deviceIP);
        }
        return device;
    }

    @PUT
    @Produces({MediaType.APPLICATION_JSON,MediaType.APPLICATION_XML})
    public Device put(final Device device){
        Device result = null;
        if (device != null){
            result = deviceDao.updateDevice(device);
        }
        return result;
    }

}

@PUT是标注处理put请求 @Produces({MediaType.APPLICATION_JSON,MediaType.APPLICATION_XML}) 是标注返回实体的类型,支持JSON 和XML 数据格式

3.测试和运行服务

package com.example;

import com.domain.Device;
import org.glassfish.grizzly.http.server.HttpServer;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.MediaType;
import java.util.HashMap;
import java.util.Map;

public class DeviceResourceTest {
    private HttpServer httpServer;
    private WebTarget target;
    @Before
    public void setUp() throws Exception{
        httpServer = Main.startServer();
        final Client client = ClientBuilder.newClient();
        target = client.target(Main.BASE_URI);
    }
    @After
    public void tearDown() throws Exception{
        httpServer.shutdown();
    }
    @Test
    public void testGetDevice(){
        final String targetIP = "127.0.0.1";
        final Device device = target.path("device").queryParam("ip",targetIP).request().get(Device.class);
        Assert.assertEquals(targetIP,device.getDeviceIP());
    }
    @Test
    public void testPutDevice(){
        Device device = new Device();
        device.setDeviceIP("192.168.5.5");
        device.setDeviceStatus(2);
        Entity<Device> entity = Entity.entity(device, MediaType.APPLICATION_XML_TYPE);
        Device result = target.path("device").request().put(entity,Device.class);
        Assert.assertEquals("192.168.5.5",result.getDeviceIP());
    }

    @Test
    public void testPost(){
        Device device = new Device();
        device.setDeviceIP("192.168.5.5");
        device.setDeviceStatus(2);
        Entity<Device> entity = Entity.entity(device, MediaType.TEXT_PLAIN_TYPE);
        String result = target.path("device").request().post(entity,String.class);
        Assert.assertEquals(result,"SUCCESS");
    }
}

打开控制台 在项目的目录下运行 :mvn clean test 如果测试通过 即断言验证成功

二.第一个Servlet 容器 服务

1.1 创建和分析Web 服务

simple-service-webapp项目也是Jersey官方文档中的例子,同样是个Maven原型

mvn archetype:generate -DarchetypeArtifactId=jersey-quickstart-webapp 
                -DarchetypeGroupId=org.glassfish.jersey.archetypes -DinteractiveMode=false 
                -DgroupId=com.example -DartifactId=simple-service-webapp -Dpackage=com.example
                -DarchetypeVersion=2.26

在 控制台下 执行该命令 就可以获取源代码了

simple-service-webapp

通过maven 将项目打包 部署至tomcat运行即可

三.REST 服务类型

1.REST 服务分为四种类型

四种类型
  • 类型一:当服务中没有Application 子类时 容器会查找Servlet的子类来做入口,如果Servlet 的子类也不存在,则REST服务类型为类型一
  • 类型二:当服务类中没有Application 子类,但存在Servlet的子类时,则REST 服务类型为类型二
  • 类型三:服务中定义了Application 的子类 而且这个Application 的子类使用了@ApplicationPath注解 则REST服务类型为类型三
  • 类型四:如果服务中定义了Application 的子类 但是这个Application 的子类没有使用@ApplicationPath注解 则REST服务类型为类型四

2.REST 服务类型一

类型一相应的逻辑是服务中同时不存在Application的子类和Servlet的子类,因此需要为REST服务动态生成一个名为javax.ws.rs.core.Application 的Servlet 实例,并自动探测匹配资源,需要在 web.xml 下配置

<?xml version="1.0" encoding="UTF-8"?>
<!-- This web.xml file is not required when using Servlet 3.0 container, 
    see implementation details http://jersey.java.net/nonav/documentation/latest/jax-rs.html -->
<web-app version="2.5" 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_2_5.xsd">
    <servlet>
        <servlet-name>Jersey Web Application</servlet-name>
        <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
        <init-param>
            <param-name>jersey.config.server.provider.packages</param-name>
            <param-value>com.example</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>Jersey Web Application</servlet-name>
        <url-pattern>/webapi/*</url-pattern>
    </servlet-mapping>
</web-app>

3.REST 服务类型二
类型二 相应的逻辑不存在Application 的子类 ,但存在Servlet的子类,因此需要有个类继承自HttpServlet

package com.example;

import javax.servlet.annotation.WebInitParam;
import javax.servlet.annotation.WebServlet;

import org.glassfish.jersey.servlet.ServletContainer;

/**
 * 类型二:不存在Application子类,存在Servlet的子类,ServletContainer 继承自HttpServlet
 */
@WebServlet(initParams = @WebInitParam(name = "jersey.config.server.provider.packages", value = "com.example"),
urlPatterns = "/webapi/*", 
loadOnStartup = 1)
public class AirServlet extends ServletContainer {
    private static final long serialVersionUID = 1L;
}

四.REST 服务类型三
类型三 相应的逻辑存在Application 的子类并且定义了@ApplicationPath 注解

package com.example;

import javax.ws.rs.ApplicationPath;
import org.glassfish.jersey.server.ResourceConfig;

@ApplicationPath("/webapi/*")
public class AirResourceConfig extends ResourceConfig {
    public AirResourceConfig() {
        packages("com.example");
    }
}

五.REST 服务类型四
类型四 不存在Servlet 子类 也不存在或者不允许使用@ApplicationPath 注解

package com.example;

import java.util.HashSet;
import java.util.Set;

import javax.ws.rs.core.Application;

public class AirApplication extends Application {
    @Override
    public Set<Class<?>> getClasses() {
        final Set<Class<?>> classes = new HashSet<Class<?>>();
        classes.add(MyResource.class);
        return classes;
    }
}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,100评论 18 139
  • Spring Boot 参考指南 介绍 转载自:https://www.gitbook.com/book/qbgb...
    毛宇鹏阅读 46,358评论 6 343
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,568评论 25 707
  • “并不是那些记忆 在我心里维系着你 你也并不因一种美好愿望的力量 而属于我” ——里尔克《室内肖像》 日光茂盛时 ...
    水槛阅读 323评论 5 5
  • 时间过了这么久,我还是很喜欢你。 我喜欢那种一扭头就看见你的感觉。 我喜欢那种一见你就想笑的感觉。 我...
    7f1eb9ef276f阅读 310评论 2 3