1. 程式人生 > >Jetty掃盲之實踐(一)

Jetty掃盲之實踐(一)

Jetty作為專案使用的輕量級web容器,使用廣泛。

目前的微服務化也是以該技術為基礎。熟悉如Spring-boot技術的同學不要嘲笑,本文只是技術學習而已。

基本實現

作為web服務,可以通過web.xml的進行配置,主要對servlet進行配置(本文不通過配置檔案而是通過程式碼直接載入Servlet)

<?xml version="1.0" encoding="ISO-8859-1"?>
<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">

    <servlet>
        <servlet-name>JAXRSRestconf</servlet-name>
        <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
        <init-param>
            <param-name>javax.ws.rs.Application</param-name>
            <param-value>com.zte.sunquan.demo.jersey.config.DemoResourceConfig</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>


    <servlet-mapping>
        <servlet-name>JAXRSRestconf</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>
</web-app>

或者通過程式碼直接完成Jetty服務功能

注意我們使用的是org.glassfish.jersey.servlet.ServletContainer,你需要在POM加入相關依賴:

        <!--jersey-->
        <dependency>
            <groupId>org.glassfish.jersey.containers</groupId>
            <artifactId>jersey-container-servlet-core</artifactId>
            <version>2.0</version>
        </dependency>
        <dependency>
            <groupId>org.glassfish.jersey.containers</groupId>
            <artifactId>jersey-container-servlet</artifactId>
            <version>2.0</version>
        </dependency>
        <!-- Jetty -->
        <dependency>
            <groupId>org.eclipse.jetty.aggregate</groupId>
            <artifactId>jetty-all-server</artifactId>
            <version>8.1.16.v20140903</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.jaxrs</groupId>
            <artifactId>jackson-jaxrs-json-provider</artifactId>
            <version>2.9.6</version>
        </dependency>

DemoResourceConfig參考:

package com.zte.sunquan.demo.jersey.config;

import org.glassfish.jersey.server.ResourceConfig;

import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider;
import com.zte.sunquan.demo.jersey.bean.User;


/**
 * Created by sunquan on 2018/7/16.
 */
public class DemoResourceConfig extends ResourceConfig {
    public DemoResourceConfig() {
        //自動掃描
        packages("com.zte.sunquan.demo.jersey");
        register(User.class);
        register(JacksonJsonProvider.class);
    }
}

jersey提供了十分方便的packages,可以對自動進行掃描相應包,並解析包中類上註釋定義的特性,例如在包com.zte.sunquan.demo.jersey.action中定義了類EntranceAction

package com.zte.sunquan.demo.jersey.action;

import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.UriInfo;

import com.google.common.collect.Sets;
import com.zte.sunquan.demo.jersey.bean.User;
import com.zte.sunquan.demo.jersey.exception.DeviceException;
import com.zte.sunquan.demo.jersey.exception.ErrorCode;

/**
 * Created by sunquan on 2018/7/16.
 * http://localhost:28101/demo-jersey/user
 */
@Path("user")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public class EntranceAction {
    public static final String XML = "+xml";
    public static final String JSON = "+json";
    private AtomicInteger count = new AtomicInteger(0);
    private static Map<Long, User> dataStore = new ConcurrentHashMap<>();

    static {
        dataStore.put(1L, new User(1, "sunquan", 28));
    }

    @POST
    public void createUser(User user) {
        System.out.println("add user:" + user);
        if (user != null) {
            dataStore.put(user.getId(), user);
        }
    }

    @PUT
    public void modifyUser(User user) {
        System.out.println("modify user:" + user);
        if (user != null) {
            dataStore.put(user.getId(), user);
        }
    }

    @DELETE
    @Path("{id}")
    public void deleteUser(@PathParam("id") long id) {
        System.out.println("delete user:" + id);
        dataStore.remove(id);
    }

    @GET
    @Path("{id}")
    public User getUserById(@PathParam("id") long id) throws Exception {
        User user = dataStore.get(id);
        return user;
    }

    @GET
    @Path("list")
    public Set<User> getUsers() throws Exception {
        Set<User> users = Sets.newHashSet(dataStore.values());
        return users;
    }

    @GET
    @Path("list/by-name/{name}")
    //@Produces(MediaType.TEXT_PLAIN)
    public User getUserByName(@PathParam("name") String name) throws Exception {
        for (User user : dataStore.values()) {
            if (user.getName().equals(name))
                return user;
        }
        //不存在該名稱使用者
        throw new DeviceException(ErrorCode.PARAM_INVALID);
    }

    //通用藉助URI資訊
    @GET
    @Path("/find")
    public String get(@Context UriInfo ui) {
        MultivaluedMap<String, String> queryParams = ui.getQueryParameters();
        MultivaluedMap<String, String> pathParams = ui.getPathParameters();
        return "success";
    }

    @POST
    @Path("/find2")
    @Consumes(MediaType.APPLICATION_JSON)
    public String get2(@Context UriInfo ui, User user) {
        MultivaluedMap<String, String> queryParams = ui.getQueryParameters();
        MultivaluedMap<String, String> pathParams = ui.getPathParameters();
        return "success";
    }
}

當掃描到EntranceAction後,開始分析其上的註解,熟悉JAX-RS規範知道其是指定了一個可訪問的Restfull介面,

如:

接下來要做的則是構建服務並啟動服務,這裡通過程式碼實現

package com.zte.sunquan.demo.jersey.main;

import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.glassfish.jersey.servlet.ServletContainer;

import com.zte.sunquan.demo.jersey.config.DemoResourceConfig;

/**
 * Created by on 2018/7/16.
 */
public class Main {
    public static void main(String[] args) throws Exception {
        Server server = new Server(28101);
        DemoResourceConfig config = new DemoResourceConfig();
        ServletContainer servletContainer = new ServletContainer(config);
        ServletHolder servlet = new ServletHolder(servletContainer);
        ServletContextHandler handler = new ServletContextHandler(ServletContextHandler.SESSIONS);
        handler.setContextPath("/demo/jesery");
        handler.addServlet(servlet, "/*");
        server.setHandler(handler);
        server.start();
        System.out.println("start...in 28101");
    }
}

啟動服務並監聽28101埠,訪問上文的URL,列印:

期待的打印出現,說明以上Jetty的使用流程沒有問題。

嘗試查詢User列表,注意返回的是一個數組

嘗試查詢id為1的使用者

嘗試查詢使用者名稱為sunquan的使用者

嘗試查詢使用者名稱為sunquan2的使用者(使用者不存在)

返回這種頁面非常不友好。基本這種情況,如果能夠將程式異常捕獲並返回有用的資訊給使用者。

幸運的是ExceptionMapper則匹配了上面的需求:

package com.zte.sunquan.demo.jersey.exception;

import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;

/**
 * Created by sunquan on 2018/7/16.
 */
@Provider
public class DemoExceptionMapper implements ExceptionMapper<Exception> {
    @Override
    public Response toResponse(Exception e) {
        Response.ResponseBuilder ResponseBuilder = null;
        if (e instanceof DeviceException) {
            DeviceException exp = (DeviceException) e;
            ErrorEntity entity = new ErrorEntity(exp.getCode(), exp.getMessage());
            ResponseBuilder = Response.ok(entity, MediaType.APPLICATION_JSON);
        }else if(e instanceof NullPointerException){
            String msg=e.getMessage();
            System.out.println(msg);//特殊處理...
        }
        else {
            ErrorEntity entity = new ErrorEntity(ErrorCode.OTHER_ERR.getCode(), e.getMessage());
            ResponseBuilder = Response.ok(entity, MediaType.APPLICATION_JSON);
        }
        return ResponseBuilder.build();
    }
}

上圖針對DeviceException進行了攔截,並統一轉化為ErrorEntity

@XmlRootElement
public class ErrorEntity {
    private int resp_code;
    private String resp_msg;

    public ErrorEntity(int resp_code, String resp_msg) {
        this.resp_code = resp_code;
        this.resp_msg = resp_msg;
    }

    public ErrorEntity() {
    }

    public int getResp_code() {
        return resp_code;
    }

    public void setResp_code(int resp_code) {
        this.resp_code = resp_code;
    }

    public String getResp_msg() {
        return resp_msg;
    }

    public void setResp_msg(String resp_msg) {
        this.resp_msg = resp_msg;
    }
}

再次訪問:

這樣則使用者友好的多。

嘗試新增使用者wang

最後通過Context註解可以將URL傳入後臺,藉助
MultivaluedMap<String, String> queryParams = ui.getQueryParameters();
MultivaluedMap<String, String> pathParams = ui.getPathParameters();
該介面可以作很多通用性的介面
public String get(@Context UriInfo ui) {
public String get2(@Context UriInfo ui, User user) {