Java EE 7 Tutorial分析

title: Java EE 7 Tutorial分析
date: 2016-12-10 16:47:25
catagory: web
tags: javaee


分析要求##

install Java EE7.0 JDK
learn Java EE 7 Tutorial,run and analysis Javae EE7.0 Samples,include

  • Servlet 3.1
  • The Annotations Servlet Sample Application
  • The Absolute Ordering of Web Fragments Servlet …
  • The File Upload Servlet Sample Application
  • JAX-RS 2.0
  • The Asynchronous Chat JAX-RS Sample Application
  • JSON Processing 1.0
  • The JAX-RS JSONP Sample Application
  • WebSocket 1.0
  • The Echo WebSocket Sample Application
  • The Auction WebSocket Sample Application

第一章:java ee7 總覽##

JavaEE 7提供了一個完整、全面、集成的堆棧來幫助你構建企業和Web應用程序。

javaEE 容器###

主要就是JSF(JavaServer Faces)和EJB(Enterprise Java Bean)兩大部分,JSF依賴于EJB,并且是重量級的,JSF使用了一大堆組件控制頁面,跟Struts2的標簽差不多;EJB目前做得不錯,相比Spring,EJB完全不需要作任何配置,內部包含JPA規范,可以和Hibernate無縫接入,但是學習曲線依然很大,并且對服務器有要求,用tomcat做服務器還需要和JBoss搭配,新手學習可以使用Glassfish。

WEB 容器###

這部分內容比較多,JavaEE 7新添加的為下圖棕黃色的部分,即WebSocket、Concurrency Utilities、Batch、JSON-P,新添加部分主要是為HTML5提供更好的伸縮性。

名詞 概念###

JWS:即Java Web Service,指與webservice相關的JavaEE技術部分,webservice是一種基于XML的獨立的、跨平臺的、互操作的應用程序,XML又包含XSD、DTD、XPath等相關技術,這個撇開不說。webservice平臺元素主要有SOAP(簡易對象訪問協議)、UDDI(通用描述、發現及整理)、WSDL(WS描述語言)。

JAX:即Java Xml,類似地JAXB(Java Xml Binding)

目前JWS主要有:

  • JAX-WS 全稱JavaTM API forXML-Based Web Services 又叫JAX-RPC(遠程調用),顧名思義就是基于Web Services
  • JAX-RS 全稱JavaTM API forRESTful Web Services 即使用REST風格
  • JAXB
  • JAXR
  • SAAJ
  • STAX

網上說關于JAX-WS與JAX-RS有這么說的: 兩者是不同風格的SOA架構。前者以動詞為中心,指定的是每次執行函數。而后者以名詞為中心,每次執行的時候指的是資源。

感覺這個說法比較靠譜,JAX-WS是面向消息的,每次請求的時候指定了請求的方法。JAX-RS是面向資源的。后則將網絡上的東西當做一種資源,每次請求都是對該資源進行操作,比如對資源的增刪查改。

CDI:即Contexts Dependency Injection,和Spring的IOC差不多的東西,就是可以在組件中通過注解注入上下文、請求和響應等。

JTA:即Java Transaction API,使用過Hibernate和EJB的應該知道,就是事務處理,JTA依賴于所處的容器,如果不是分布式開發的話,我們一般使用本地事務,即是數據庫本身的事務處理。

PA:即Java Persistence API,就是最常用的持久化技術,原本屬于EJB中的部分,EJB3.0之后分離出來,作為一個獨立的規范。作為一種ORM技術,JPA提供了基本的統一標準。

MS:即Java Message Service,和JDBC類似,提供了一個統一的API供其他廠商實現,主要用于客戶機信息的交互,JMS主要有點到點和訂閱/發布兩種方式

第二章:servlet 3.1技術##

本章主要分兩部分來介紹servlet技術。

第一部分講解servlet的一些知識,主要參考17章:java servlet technology。主要包含以下內容:

3.0新特性:

  • 開發的簡易型
  • 新增注解支持
  • 可插拔性和可擴展性
  • Web 片段是將 web 應用程序邏輯分區為 servlet、servlet-mapping、servlet-filter、filter-mapping、servlet-listener 之類的元素及其子元素
  • 異步支持
  • 將新的 API 添加到 ServletRequest 和 ServletResponse,用于掛起、恢復和查詢請求的狀況、啟用禁用和查詢響應的狀況。開發人員可以分別通過 requestSuspended(), requestResumed() 和 requestCompleted() 方法使用請求的 resume、suspend 和 complete 方法通知事件
  • 安全性增強
  • Servlet3.0方案建議提供通過編程實現登錄和注銷功能。HTTPServletRequest 中添加的新 API 可以啟用這項功能。HTTPServletRequest 的 login 方法使應用程序或者框架強制進行以容器為中介的驗證。HTTPServletRequest 和 HTTPSession 的 logout 方法允許應用程序重置請求的驗證狀態
  • 其它雜項變化

3.1新特性:

  • 無阻塞 I/O
  • 協議升級
  • 安全性增強

第二部分分析javaee 7 simples 下面的個例子。分為以下6個模塊:

  • 最基本的操作實例
  • cookies
  • error-mapping
  • file-upload
  • servlet3中引入的注解的例子
  • servlet-filters
  • event-listeners
  • 注解與web.xml并存或分片處理的例子
  • metadata-complete
  • web-fragment
  • 異步請求處理
  • async-servlet
  • nonblocking
  • 安全驗證
  • form-based-security
  • servlet-security
  • 資源打包
  • resource-packaging
  • 協議操作
  • protocol-handler

第一部分:servlet 3.1###

2.1.1 what is a servlet?

A servlet is a Java? technology-based Web component, managed by a container,that generates dynamic content. —— from JSR315

The service() method is given an? implementation in the HTTPServlet?base class, where the doGet() and doPost() methods are called.

2.1.2 Servlet Lifecycle

2.1.3 writing service methods

servletrequest:

ServletRequest接口中封裝了客戶請求信息,如客戶請求方式、參數名和參數值、客戶端正在使用的協議, 以及發出客戶請求的遠程主機信息等。

ServletResponse:

Defines an object to assist a servlet in sending a response to the client.

2.1.4 filtering requests and responses

Filters are Java components—very similar to servlets—that you can use to intercept and process requests before they are sent to the servlet, or to process responses after the servlet has completed, but before the response goes back to the client.

  • init()
  • doFilter()
  • destory()

2.1.5 invoking other web resources

web組件能夠直接或者間接的調用其他web資源。一個web組件間接地調用其他web資源通過把一個指向另一個web組件的url嵌入到返回客戶端的內容中,當他被執行的時候,一個web組件直接的調用另一個資源或者包換另一個資源的內容或者只想另一個資源的請求。

RequestDispatcher

include(request,response)
forward()

2.1.6 accessing the web context

web組件執行的context是一個實現servletContext接口的對象,你可以取出該對象通過getServletContext方法。web context子宮訪問以下內容的訪問方法:

  • Initialization parameters
  • Resources associated with the web context
  • Object-valued attributes
  • Logging capabilities
setAttribute(String name, Object object) 
 //Binds an object to a given attribute name in this ServletContext.
getAttribute(String name)
//Returns the servlet container attribute with the given name, or null if there is no attribute by that name.
getRequestDispatcher(String path) 
//Returns a RequestDispatcher object that acts as a wrapper for the resource located at the given path. 
 log(String msg) 
//Writes the specified message to a servlet log file, usually an event log.  
getRealPath(String path) 
//Gets the real path corresponding to the given virtual path.

2.1.7 maintaining client state

http協議是無狀態的。為了支持需要獲取狀態的應用,java servlet 技術提供了一個管理sessions的api和允許多種實現sessions的機制。

getSession():返還和request關聯的session對象,沒有的話,創建一個。
getSession(false): 返回返還和request關聯的session對象,沒有的話,返回null
isNew()//如果客戶端還沒有返回具有該sessionid的session,為true

2.1.8 uploading files with java servlet technology

@MultipartConfig :支持一下屬性

  • location:文件系統中目錄的絕對路徑
  • fileSizeThreshold:在文件被暫時存儲在disk上,文件的自己額大小,默認是0字節。
  • MaxFileSize:允許上傳文件的最大大小
  • maxRequestSize:最大允許一個multipart/form-data請求的數量。
@MultipartConfig(location="/tmp", fileSizeThreshold=1024*1024,
maxFileSize=1024*1024*5, maxRequestSize=1024*1024*5*5)
  • Collection<Part> getParts()
  • Part getPart(String name)

2.1.9 Asynchronous Processing in Servlets

使用asyncSupported屬性

@WebServlet(urlPatterns={"/asyncservlet"}, asyncSupported=true)
public class AsyncServlet extends HttpServlet { ... }

javax.servlet.AsyncContext提供方法,這些方法包含你需要異步處理的功能。

public void doGet(HttpServletRequest req, HttpServletResponse resp) {
...
AsyncContext acontext = req.startAsync();
...
}

2.1.10 Nonblocking I/O

java ee 對于servlets和filters提供非阻塞i/o ,當在異步模式下處理請求。下面的步驟總結了怎么使用非阻塞io來處理請求和寫回復,在service方法中。

  1. Put the request in asynchronous mode as described in Asynchronous Processing.
  2. Obtain an input stream and/or an output stream from the request and response
    objects in the service method.
  3. Assign a read listener to the input stream and/or a write listener to the output
    stream.
  4. Process the request and the response inside the listener's callback methods.

第二部分: Examples of the servlet

2.2.1 Example 1:session-cookie-config

這個例子展示了session的獲取和cookie的獲取。

首先定義一個監聽器,來初始化SessionCookieConfig對象。

@WebListener()
public class ConfigListener implements ServletContextListener {

    /**
     * Receives notification that the web application initialization
     * process is starting.
     *
     * @param sce The servlet context event
     */
    public void contextInitialized(ServletContextEvent sce) {
        SessionCookieConfig scc =
            sce.getServletContext().getSessionCookieConfig();
        scc.setName("MYJSESSIONID");
        scc.setPath("/myPath");
        scc.setDomain("mydomain");
        scc.setComment("myComment");
        scc.setSecure(true);
        scc.setHttpOnly(true);
        scc.setMaxAge(123);
    }

    public void contextDestroyed(ServletContextEvent sce) {
        // Do nothing
    }
}

當接收到get請求的時候,我們通過

@WebServlet(urlPatterns = "/")
public class CreateSession extends HttpServlet {

    @Override
    public void doGet(HttpServletRequest req, HttpServletResponse res)
            throws ServletException, IOException {

        req.getSession(true);

        String sessionCookie = res.getHeader("Set-Cookie");
        if (sessionCookie == null) {
            throw new ServletException("Missing Set-Cookie response header");
        }

        // name
        if (sessionCookie.indexOf("MYJSESSIONID=") == -1) {
            throw new ServletException("Missing session id");
        }

        // comment
       /* if (sessionCookie.indexOf("Comment=myComment") == -1) {
            throw new ServletException("Missing cookie comment");
        }*/

        // domain
        if (sessionCookie.indexOf("domain=mydomain") == -1) {
            throw new ServletException("Missing cookie domain");
        }

        // path
        if (sessionCookie.indexOf("path=/myPath") == -1) {
            throw new ServletException("Missing cookie path");
        }

        // secure
        if (sessionCookie.indexOf("Secure") == -1) {
            throw new ServletException("Missing Secure attribute");
        }

        // http-only
        if (sessionCookie.indexOf("HttpOnly") == -1) {
            throw new ServletException("Missing HttpOnly attribute");
        }

        // max-age
        if (sessionCookie.indexOf("Max-Age=123") == -1) {
            throw new ServletException("Missing max-age");
        }

        res.getWriter().println(sessionCookie);
    }
}

2.2.2 Example 2:注解的使用####

TestServlet分析:
Servlet3.1規范大量使用注解來聲明Servlet中,過濾器,監聽器和安全性。配置文件web.xml中現在是可選的。

servlet:

import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebInitParam;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * This class illustrate WebServlet annotation
 *
 * @author Shing Wai Chan
 */
@WebServlet(name = "TestServlet", urlPatterns = {"/"},
            initParams = {@WebInitParam(name = "message", value = "my servlet")})
public class TestServlet extends HttpServlet {

    private String listenerMessage = null;

    @Override
    public void init(ServletConfig config) throws ServletException {
        super.init(config);
        listenerMessage = (String) config.getServletContext().getAttribute("listenerMessage");
    }

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse res)
            throws IOException, ServletException {
        PrintWriter writer = res.getWriter();
        writer.write("Hello, " + getInitParameter("message") + ", ");
        writer.write(req.getAttribute("filterMessage") + ", ");
        writer.write(listenerMessage + ".\n");
    }
}

攔截器

@WebFilter(filterName = "TestFilter", urlPatterns = {"/"},
           initParams = {@WebInitParam(name = "mesg", value = "my filter")})
public class TestFilter implements Filter {

    String mesg = null;

    public void init(FilterConfig filterConfig) throws ServletException {
        mesg = filterConfig.getInitParameter("mesg");
    }

    public void doFilter(ServletRequest req, ServletResponse res,
            FilterChain chain) throws IOException, ServletException {
        req.setAttribute("filterMessage", mesg);
        chain.doFilter(req, res);
    }

    public void destroy() {
    }
}

監聽器

@WebListener()
public class TestServletContextListener implements ServletContextListener {

    public void contextInitialized(ServletContextEvent sce) {
        ServletContext context = sce.getServletContext();
        String msg = "my listener";
        context.setAttribute("listenerMessage", msg);
    }

    public void contextDestroyed(ServletContextEvent sce) {
    }
}

運行結果:


2.2.3 Example 3:文件上傳####

解釋:
一個file是一個part,并且filename可以自己寫方法解析,也可以直接調用方法part.getSubmittedFileName()。
寫文件到本地,可以直接調用write()方法,也可以自己實現。總的來說是一個很方便的功能。

@WebServlet標注使用URL模式屬性來定義的servlet映射。

@MultipartConfig注釋指示該servlet的期望請求被使用的multipart / form-data的MIME類型進行。

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Collection;

import javax.servlet.ServletException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;

/**
 * @author Kinman Chung
 * @author Daniel Guo
 */
@WebServlet(name = "TestServlet", urlPatterns = {"/TestServlet"})
@MultipartConfig()
public class TestServlet extends HttpServlet {

    /**
     * Processes requests for both HTTP
     * <code>GET</code> and
     * <code>POST</code> methods.
     *
     * @param request servlet request
     * @param response servlet response
     * @throws ServletException if a servlet-specific error occurs
     * @throws IOException if an I/O error occurs
     */
    protected void processRequest(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
     request.setCharacterEncoding("utf-8");
     Collection<Part> parts= request.getParts();
     System.out.println(parts.size());
     for(Part part:parts){
     System.out.println(getFileName(part));
     String name=getFileName(part);
     if(name==null||name.equals("")){
     //do nothing
     }else{
     part.write("e:/a/" +getFileName(part)); 
     }
     }
       request.getRequestDispatcher("getParts.jsp").forward(request, response);
    }

    private String getFileName(Part part) {
        String header = part.getHeader("Content-Disposition");
        String fileName = header.substring(header.indexOf("filename=\"") + 10, header.lastIndexOf("\""));

        return fileName;
    }
    private void writeTo(String fileName, Part part) throws IOException, FileNotFoundException {
        InputStream in = part.getInputStream();
        File file = new File("e:/a/" + fileName);
        OutputStream out = new FileOutputStream("e:/a/" + fileName);
        byte[] buffer = new byte[1024];
        int length = -1;
        while ((length = in.read(buffer)) != -1) {
            out.write(buffer, 0, length);
        }

        in.close();
        out.close();
    }
    // <editor-fold defaultstate="collapsed" desc="HttpServlet methods. Click on the + sign on the left to edit the code.">
    /**
     * Handles the HTTP
     * <code>GET</code> method.
     *
     * @param request servlet request
     * @param response servlet response
     * @throws ServletException if a servlet-specific error occurs
     * @throws IOException if an I/O error occurs
     */
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        processRequest(request, response);
    }

    /**
     * Handles the HTTP
     * <code>POST</code> method.
     *
     * @param request servlet request
     * @param response servlet response
     * @throws ServletException if a servlet-specific error occurs
     * @throws IOException if an I/O error occurs
     */
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        processRequest(request, response);
    }

    /**
     * Returns a short description of the servlet.
     *
     * @return a String containing servlet description
     */
    @Override
    public String getServletInfo() {
        return "Short description";
    }// </editor-fold>
}

運行結果:


2.2.4 Example 4:web模塊化####

原本一個web應用的任何配置都需要在web.xml中進行,因此會使得web.xml變得很混亂,而且靈活性差,因此Servlet 3.0可以將每個Servlet、Filter、Listener打成jar包,然后放在WEB-INF\lib中;注意各自的模塊都有各自的配置文件,這個配置文件的名稱為 web-fragment.xml ;

步驟如下:

  1. 編寫Servlet,并編譯;
  2. 將此編譯class文件及所在包通過jar包命令打成jar包;
  3. 將此jar包用winrar打開,并將其中的META-INF中的manifest刪除并添加 web-fragment.xml;
  4. 將此jar包放入WEB-INF\lib中即可;

web-fragment.xml注意點:

  1. 根元素為<web-fragment>;
  2. <name></name>表示模塊名稱;
  3. <ordering></ordering>是此模塊的加載順序;
  4. <before><others/></before>表示第一個加載;
  5. <after><name>A</name></after>表示比A后面加載;
  6. 可以在里面部署listener、filter、servlet

針對本例子而言,
為了更好地進行適配,減少配置,一個web-fragment是可指定的,并包括在一個庫或框架jar文件中的web.xml的一部分或全部。如果有很多個web-fragment jars時,那么人們可能會喜歡指定處理Web-fragment.xml之和注釋的順序。這個很重要。例如,過濾器可以為在web.xml中指定的順序被執行,類似于監聽。在Servlet3.1中,引入了web.xml 中的的標簽和web-fragment.xml中的標簽。

Web Fragments的順序被指定在以下優先級:

  • (1)在web.xml中如果存在
  • (2)如果存在于每一個web-fragment.xml
  • (3)其他未指定

在web.xml的 中提供了一種方法,以指定加載web的fragment.xml之和web fragments的注釋處理的順序。代碼如下:

<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="3.0" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
    <absolute-ordering>
        <name>A</name>
        <others/>
        <name>B</name>
    </absolute-ordering>
</web-app>

另外,在上述例子中,web fragment A 將被第一個處理,web fragment B 被最后處理。名稱A和B在web-fragment.xml之中的元素指定的(見下面的例子)。

排序是在web-fragment.xml中被指定的。如果在web.xml中沒有,會查找web-fragment.xml中的。
僅僅在web-fragment.xml存在一個的jar包,代碼如下

<web-fragment>
            <name>A</name>
            ...
            <ordering>
                <before>
                    <others/>
                </before>
            </ordering>
</web-fragment> 

在這種情況下,web-fragment A將首先被處理。
下面是在web-fragment.xml存在兩個的示例,代碼如下:
web-fragment A

<web-fragment>
            <name>A</name>
            ...
            <ordering>
                <before>
                    <others/>
                </before>
            </ordering>
</web-fragment> 

web-fragment B

<web-fragment>
            <name>B</name>
            ...
            <ordering>
                <before>
                    <others/>
                </before>
            </ordering>
</web-fragment> 

這時web-fragment A和web-fragment B會首先被處理。在這種情況下,人們只能保證web-fragment A和web-fragment B在其他web-fragment之前處理。但是A和B的順序并不確定,在這種情況下這是隨機的。
有兩個包含 的jars 存在于web-fragment.xml之中,如下

web-fragment A

<web-fragment>
            <name>A</name>
            ...
            <ordering>
                <before>
                    <others/>
                </before>
            </ordering>
</web-fragment> 

web-fragment B

<web-fragment>
            <name>B</name>
            ...
            <ordering>
                <after>
                    <name>A</name>
                </after>
                <before>
                    <others/>
                </before>
            </ordering>
</web-fragment> 

在這種情況下,A將首先被處理,其次是B,然后其他web fragments。如果想有一個確定的順序,那么建議使用在web.xml中的absolute-ordering。
如何存放web fragments?如果一個框架被打包為一個jar文件,并在部署描述符的形式的元數據信息,那么Web fragments需要被放置在jar文件的META-INF/文件夾。

另一方面,如果一個框架,優先使用web fragment.xml這種方法,而且它增強了Web應用程序的web.xml,該框架必須在Web應用程序中被放置在的WEB-INF/ lib目錄中。

運行結果:


第三章:JAX-RS 2.0 技術##

當JAX-RS 1.0在2008年第一次由JSR-311——特別是其領導者Marc Hadley和Paul Sandoz——公之于眾的時候,它就成為了第一個基于POJO/Annotation的、用于創建健壯的Web應用的框架。

五年后,Java EE 7已經發布并且它包含了最新的JAX-RS 2.0版本,JSR-339的實現由Marek Potociar和Santiago Pericas-Geertsen發起。與Java EE 7的核心主題相一致,JAX-RS 2.0添加了一些期待已久的特性,這些特性主要圍繞Oracle所稱的“簡化API”。

本章主要介紹rest和jax-rs技術。分兩個部分。

第一部分介紹相關知識和概念,主要參考第31章內容。主要包含以下內容:

  • 客戶端API
  • 異步
  • HATEOAS(超媒體)
  • 注解
  • 驗證
  • 過濾器與處理程序
  • 內容協商

第二部分主要介紹一些simples。

  • async-chat
  • message-board

第一部分:JAX-RS: Advanced Topics####

  • rest:

    REpresentational State Transfer:代表性狀態傳輸、具象狀態傳輸

    REST定義了應該如何正確地使用Web標準,例如HTTP和URI。REST并非標準,而是一種開發 Web 應用的架構風格,可以將其理解為一種設計模式。

  • jax-rs:

    Java API forRESTful WebServices旨在定義一個統一的規范,使得 Java 程序員可以使用一套固定的接口來開發 REST 應用,避免了依賴于第三方框架。是一個Java編程語言的應用程序接口,支持按照表象化狀態轉變 (REST)架構風格創建Web服務Web服務。

    與傳統的 servlet 模型相比,JAX-RS 提供了一種可行的、更為簡便、移植性更好的方式來在 Java 內實現 RESTful 服務。使用注釋讓您能夠輕松提供 Java 資源的路徑位置并將 Java 方法綁定到 HTTP 請求方法。一種可移植的數據綁定架構提供了一些本機的 Java 類型支持并允許進行序列化/反序列化處理的完全定制。javax.ws.rs.core.Application 子類的擴展以及 web.xml 內的相應清單表明了用最少的部署描述符配置就能進行輕松部署。

    JAX-RS 的具體實現由第三方提供,例如 Sun 的參考實現 Jersey、Apache 的 CXF 以及 JBoss 的 RESTEasy。

3.1.1 注解####

注釋
新的注釋已經嵌入,例如支持新的注入。
JAX-RS提供注解如下:


  • @Path,標注資源類或方法的相對路徑

  • @GET,@PUT,@POST,@DELETE,標注方法是用的HTTP請求的類型,分別對應 4 種 HTTP 方法,用于對資源進行創建、檢索、更新和刪除的操作。

  • 若要創建資源,應該使用 POST 方法;

  • 若要檢索某個資源,應該使用 GET 方法;

  • 若要更改資源狀態或對其進行更新,應該使用 PUT 方法;

  • 若要刪除某個資源,應該使用 DELETE 方法。

  • @Produces,標注返回的MIME媒體類型

  • @Consumes,標注可接受請求的MIME媒體類型

  • @PathParam,@QueryParam,@HeaderParam,@CookieParam,@MatrixParam,@FormParam,分別標注方法的參數來自于HTTP請求的不同位置,

  • @PathParam來自于URL的路徑,

  • @QueryParam來自于URL的查詢參數,

  • @HeaderParam來自于HTTP請求的頭信息,

  • @CookieParam來自于HTTP請求的Cookie。

具體例子如下:

  • 獲取path參數:
@Path("/employees/{firstname}.{lastname}@{domain}.com")
public class EmpResource {
@GET
@Produces("text/xml")
public String getEmployeelastname(@PathParam("lastname") String lastName) {
...
}
}

在這個例子中,@path注解定義了url變量(firstname lastname domain),request請求中pathparm標簽從url中獲取lastname屬性。你也可以使用正則表達式,比如你想要求lastname只包含大小寫字母,你可以定義如下。在這里,如果lastname沒有匹配正則表達式,一個404 response將會被返回。@Path("/employees/{firstname}.{lastname[a-zA-Z]*}@{domain}.com")

  • 獲取query參數:
@Path("/employees/")
@GET
public Response getEmployees(
@DefaultValue("2003") @QueryParam("minyear") int minyear,
@DefaultValue("2013") @QueryParam("maxyear") int maxyear)
{...}
這段代碼定義了連個參數minyear和maxyear。請求如下:`GET /employees?maxyear=2013&minyear=2003`我們可以得到這兩個參數。
  • 獲取表單數據

使用@formparm注解來從html forms中抽取參數。

<FORM action="http://example.com/employees/" method="post">
<p>
<fieldset>
Employee name: <INPUT type="text" name="empname" tabindex="1">
Employee address: <INPUT type="text" name="empaddress" tabindex="2">
Manager name: <INPUT type="text" name="managername" tabindex="3">
</fieldset>
</p>
</FORM>

獲取指定參數:

@POST
@Consumes("application/x-www-form-urlencoded")
public void post(@FormParam("managername") String managername) {
// Store the value
...
}

獲取一個鍵值對map:

@POST
@Consumes("application/x-www-form-urlencoded")
public void post(MultivaluedMap<String, String> formParams) {
// Store the message
}
  • 獲取java類型
@GET
public String getParams(@Context UriInfo ui) {
MultivaluedMap<String, String> queryParams = ui.getQueryParameters();
MultivaluedMap<String, String> pathParams = ui.getPathParameters();
}
@GET
public String getHeaders(@Context HttpHeaders hh) {
MultivaluedMap<String, String> headerParams = hh.getRequestHeaders();
MultivaluedMap<String, Cookie> pathParams = hh.getCookies();
}

3.1.2 resource類和resource方法####

Web 資源作為一個 Resource 類來實現,對資源的請求由 Resource 方法來處理。

Resource 類或 Resource 方法被打上了 Path 標注,Path 標注的值是一個相對的 URI 路徑,用于對資源進行定位,路徑中可以包含任意的正則表達式以匹配資源。和大多數 JAX-RS 標注一樣,Path 標注是可繼承的,子類或實現類可以繼承超類或接口中的 Path 標注。

Resource 類是 POJO,使用 JAX-RS 標注來實現相應的 Web 資源。

Resource 類分為根 Resource 類和子 Resource 類,區別在于子 Resource 類沒有打在類上的 @Path 標注。

Resource 類的實例方法打上了@Path 標注,則為 Resource 方法或子 Resource 定位器,子 Resource 定位器上沒有任何 @GET、@POST、@PUT、@DELETE 或者自定義的 @HttpMethod

@Path("/")   
public class BookkeepingService {   
    ......   
    @Path("/person/") //資源方法;若無@POST,則為子資源定位器  
    @POST   
    @Consumes("application/json")   
    public Response createPerson(Person person) { //JSON 格式的請求體被自動映射為實體參數person  
        ......   
    }   
  
    @Path("/person/")   
    @PUT   
    @Consumes("application/json")   
    public Response updatePerson(Person person) {   
        ......   
    }   
  
    @Path("/person/{id:\\d+}/") //正則表達式  
    @DELETE   
    public Response deletePerson(@PathParam("id")   
    int id) {   
        ......   
    }   
  
    @Path("/person/{id:\\d+}/")   
    @GET   
    @Produces("application/json")   
    public Person readPerson(@PathParam("id")   
    int id) {   
        ......   
    }   
  
    @Path("/persons/")   
    @GET   
    @Produces("application/json")   
    public Person[] readAllPersons() { //數組類型的返回值被自動映射為 JSON 格式的響應體——?  
        ......   
    }   
  
    @Path("/person/{name}/")   
    @GET   
    @Produces("application/json")   
    public Person readPersonByName(@PathParam("name")   
    String name) {   
        ......   
}   

Resource 方法合法的參數類型包括:

  1. 原生類型
  2. 構造函數接收單個字符串參數,或者包含擁有一個static的valueOf(String)方法
  3. List<T>,Set<T>,SortedSet<T>(T 為以上的 2 種類型)
  4. 用于映射請求體的實體參數

Resource 方法合法的返回值類型包括:

  1. void:狀態碼 204 和空響應體
  2. Response:Response 的 status 屬性指定了狀態碼,entity 屬性映射為響應體return Response.status(Status.OK).entity(JsonUtils.toString(result)).build();
  3. GenericEntity:GenericEntity 的 entity 屬性映射為響應體,entity 屬性為空則狀態碼為 204,非空則狀態碼為 200
  4. 其它類型:返回的對象實例映射為響應體,實例為空則狀態碼為 204,非空則狀態碼為 200

對于錯誤處理,Resource 方法可以拋出非受控異常 WebApplicationException 或者返回包含了適當的錯誤碼集合的 Response 對象。

3.1.3 內容協商與數據綁定####

Web 資源可以有不同的表現形式,服務端與客戶端之間需要一種稱為內容協商(Content Negotiation)的機制:

作為服務端,Resource 方法的@Produces 標注用于指定響應體的數據格式(MIME 類型),@Consumes 標注用于指定請求體的數據格式;

作為客戶端,Accept 請求頭用于選擇響應體的數據格式,Content-Type 請求頭用于標識請求體的數據格式。

服務器端:

@GET  
@Path(value="/{emailAddress:.+@.+\\.[a-z]+}")  
@Produces(value={"text/xml", "application/json"})  
public ContactInfo getByEmailAddress(@PathParam(value="emailAddress")   
    String emailAddress) {  
    ...  
}     
  
  
@POST  
@Consumes(value={"text/xml", "application/json"})  
public void addContactInfo(ContactInfo contactInfo) {  
    ...  
} 

3.1.4 bean驗證####

一個基于注釋的機制來識別參數的meta-data。例如“@NotNull shares”代表“shares”參數不允許為null。你同樣可以提供傳統的注釋,比如保證特定的數據格式,例如郵編或者電話號碼。

@POST
@Path("/createUser")
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public void createUser(@NotNull @FormParam("username") String username,
@NotNull @FormParam("firstName") String firstName,
@NotNull @FormParam("lastName") String lastName,
@Email @FormParam("email") String email) {
...
}

也可以用到一個resource class中,如下:

@Path("/createUser")
public class CreateUserResource {
@NotNull
@FormParam("username")
private String username;
@NotNull
@FormParam("firstName")
private String firstName;
@NotNull
@FormParam("lastName")
private String lastName;
@Email
@FormParam("email")
private String email;
...
}

3.1.5 客戶端api####

JAX-RS 1.0是一個嚴格的客戶端API,一些實現對客戶端提供了各種等級的支持,但是通常開發人員會安裝像Apache軟件基Jakarte公共組件中的HttpClient 或 WizTools的 REST Client。

JAX-RS 2.0 為客戶端調用Web服務添加了一個"構建" 工具:

  Client client=ClientFactory.newClient();
  String shares=client.target("http://.../portfolio/123")
      .pathParam("identifier", "IBM")
      .queryParameter("identifierType", "ticker")
      .request("text/plain).get(String.class");

我們看,這個方法首先包含了一個客戶端(client),然后使用了構建模式來構造URL的所有參數,同時,允許開發人員不需要使用各種各樣的URL構造器來規劃URL。

3.1.6 異步####

異步:
在JAX-RS 1.0中,一個要調用的客戶端需要等待服務器傳回的響應。2.0嵌入了異步支持。這讓一個客戶端能夠發起一個REST的調用,并且在響應完成的時候得到一個Future或者一個InvocationCallback作為通知。

3.1.7 HATEOAS####

根據嚴格的RESTafarian規范,如果你沒有使用HATEOAS,你就不是在做REST!HATEOAS(作為應用狀態引擎的超媒體)要求REST的生產者和客戶在“每個調用返回一組鏈接”上達成共識,以便進行下一步。如果你認為REST是一個應用版本的Web頁,那么HATEOAS就可以認為是包含一組Web頁面的鏈接。

JAX-RS 2.0提供了Link和Target類,來把超鏈接嵌入到一個服務器端的響應當中,并把它們響應到客戶端去。

第二部分:Examples of the JAX-RS 2.0

3.2.1 Example 1 async-war

分析:

chatApplication

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

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

@ApplicationPath("/chat")
public class ChatApplication extends Application {

    @Override
    public Set<Class<?>> getClasses() {
        final Set<Class<?>> classes = new HashSet<Class<?>>();
        classes.add(ChatResource.class);

        return classes;
    }
}
@Path("/")
@Singleton
public class ChatResource {

    /**
     * This queue synchronizes produces/consumes operations. It contains {@link AsyncResponse async responses} into
     * which post message should be written.
     */
    private final BlockingQueue<AsyncResponseWrapper> suspended = new ArrayBlockingQueue<AsyncResponseWrapper>(10);

    /**
     * Internal response wrapper which bundles response with id.
     */
    private static class AsyncResponseWrapper {
        private final AsyncResponse asyncResponse;
        private final String id;

        private AsyncResponseWrapper(AsyncResponse asyncResponse, String id) {
            this.asyncResponse = asyncResponse;
            this.id = id;
        }

        public AsyncResponse getAsyncResponse() {
            return asyncResponse;
        }

        public String getId() {
            return id;
        }
    }

    /**
     * Handle a HTTP get message asynchronously (suspend response in order to release the container thread instead of
     * blocking it on the blocking queue).
     *
     * @param asyncResponse Suspended asynchronous response (injected).
     * @param requestId Header identifying the header.
     */
    @GET
    public void getMessage(@Suspended final AsyncResponse asyncResponse, final @HeaderParam("request-id") String requestId) {
        new Thread(new Runnable() {

            @Override
            public void run() {
                try {
                    // Put actual response to the queue. This response will be later taken and resumed with
                    // the message.
                    suspended.put(new AsyncResponseWrapper(asyncResponse, requestId));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

    /**
     * Handle a HTTP POST method asynchronously (suspend response in order to release the container thread instead of
     * blocking it on the blocking queue).
     *
     * @param postAsyncResponse Suspended asynchronous response (injected).
     * @param message Message to be sent.
     */
    @POST
    public void postMessage(@Suspended final AsyncResponse postAsyncResponse, final String message) {

        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    // Take one response from the queue and resume it with the message. If no message is in the queue now
                    // then this method will block the thread until the response is put into queue (by GET http method).
                    final AsyncResponseWrapper responseWrapper = suspended.take();
                    responseWrapper.getAsyncResponse().resume(Response.ok()
                            .entity(message).header("request-id", responseWrapper.getId()).build());

                    // Now resume response connected with the request invoking this post method just reply that the message
                    // was delivered.
                    postAsyncResponse.resume("Sent!");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

    /**
     * Get the string representation of internal async response queue <code>suspended<code/>.
     * <p/>
     * This resource is requested in regular intervals by clients.
     *
     * @return Plain text with list of request-id of async responses.
     */
    @GET
    @Path("queue")
    @Produces("text/html")
    public String getResponseQueue() {
        StringBuffer sb = new StringBuffer();
        boolean addSeparator = false;
        for (AsyncResponseWrapper asyncResponseWrapper : suspended) {
            if (addSeparator) {
                sb.append(", ");
            } else {
                addSeparator = true;
            }
            sb.append(asyncResponseWrapper.getId());
        }
        return sb.toString();
    }
}

3.2.2 Example 2: message-board

詳情見源代碼

第四章:json processing 1.0 總覽##

主要參照第19章:json processing

第一部分:json processing 介紹

4.1.1 json####

{
"firstName": "Duke",
"lastName": "Java",
"age": 18,
"streetAddress": "100 Internet Dr",
"city": "JavaTown",
"state": "JA",
"postalCode": "12345",
"phoneNumbers": [
{ "Mobile": "111-111-1111" },
{ "Home": "222-222-2222" }
]
}

http header:
Content-Type: application/json

4.1.2 json processing in the java ee platform####

4.1.3 using the object model api####

構建json對象

JsonObject model = Json.createObjectBuilder()
.add("firstName", "Duke")
.add("lastName", "Java")
.add("age", 18)
.add("streetAddress", "100 Internet Dr")
.add("city", "JavaTown")
.add("state", "JA")
.add("postalCode", "12345")
.add("phoneNumbers", Json.createArrayBuilder()
.add(Json.createObjectBuilder()
.add("type", "mobile")
.add("number", "111-111-1111"))
.add(Json.createObjectBuilder()
.add("type", "home")
.add("number", "222-222-2222")))
.build();

第二部分:Examples of json processing###

jsonobject例子:

@Path("/object")
public class ObjectResource {
    private static final JsonBuilderFactory bf = Json.createBuilderFactory(null);

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public JsonObject doGet() {
        return bf.createObjectBuilder()
            .add("firstName", "John")
            .add("lastName", "Smith")
            .add("age", 25)
            .add("address", bf.createObjectBuilder()
                .add("streetAddress", "21 2nd Street")
                .add("city", "New York")
                .add("state", "NY")
                .add("postalCode", "10021"))
            .add("phoneNumber", bf.createArrayBuilder()
                .add(bf.createObjectBuilder()
                    .add("type", "home")
                    .add("number", "212 555-1234"))
                .add(bf.createObjectBuilder()
                    .add("type", "fax")
                    .add("number", "646 555-4567")))
            .build();
    }

    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    public void doPost(JsonObject structure) {
        System.out.println(structure);
    }

}

jsonarray例子:

@Path("/array")
public class ArrayResource {
    private static final JsonBuilderFactory bf = Json.createBuilderFactory(null);

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public JsonArray doGet() {
        return bf.createArrayBuilder()
                .add(bf.createObjectBuilder()
                    .add("type", "home")
                    .add("number", "212 555-1234"))
                .add(bf.createObjectBuilder()
                    .add("type", "fax")
                    .add("number", "646 555-4567"))
                .build();
    }

    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    public void doPost(JsonArray structure) {
        System.out.println(structure);
    }

}

jsonStructure

@Path("/structure")
public class StructureResource {
    private static final JsonBuilderFactory bf = Json.createBuilderFactory(null);

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public JsonStructure doGet() {
        return bf.createObjectBuilder()
            .add("firstName", "John")
            .add("lastName", "Smith")
            .add("age", 25)
            .add("address", bf.createObjectBuilder()
                .add("streetAddress", "21 2nd Street")
                .add("city", "New York")
                .add("state", "NY")
                .add("postalCode", "10021"))
            .add("phoneNumber", bf.createArrayBuilder()
                .add(bf.createObjectBuilder()
                    .add("type", "home")
                    .add("number", "212 555-1234"))
                .add(bf.createObjectBuilder()
                    .add("type", "fax")
                    .add("number", "646 555-4567")))
            .build();
    }

    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    public void doPost(JsonStructure structure) {
        System.out.println(structure);
    }

}

JsonGenerator例子

@Path("/generator")
public class GeneratorResource {

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public StreamingOutput doGet() {
        return new StreamingOutput() {
            public void write(OutputStream os) {
                writeWikiExample(os);
            }
        };
    }

    // Writes wiki example JSON in a streaming fashion
    private void writeWikiExample(OutputStream os) {
        try(JsonGenerator gene = Json.createGenerator(os)) {
            gene.writeStartObject()
                .write("firstName", "John")
                .write("lastName", "Smith")
                .write("age", 25)
                .writeStartObject("address")
                    .write("streetAddress", "21 2nd Street")
                    .write("city", "New York")
                    .write("state", "NY")
                    .write("postalCode", "10021")
                .writeEnd()
                .writeStartArray("phoneNumber")
                    .writeStartObject()
                        .write("type", "home")
                        .write("number", "212 555-1234")
                    .writeEnd()
                    .writeStartObject()
                        .write("type", "fax")
                        .write("number", "646 555-4567")
                    .writeEnd()
                .writeEnd()
            .writeEnd();
        }
    }

}

JsonParser例子

@Path("/parser")
public class ParserResource {

    @GET
    @Produces("text/plain")
    public StreamingOutput doGet() {
        return new StreamingOutput() {
            public void write(OutputStream os) throws IOException {
                writeTwitterFeed(os);
            }
        };
    }

    /**
     * Parses JSON from twitter search REST API
     *
     * ... { ... "from_user" : "xxx", ..., "text: "yyy", ... } ...
     *
     * then writes to HTTP output stream as follows:
     *
     * xxx: yyy
     * --------
     */
    private void writeTwitterFeed(OutputStream os) throws IOException {
        URL url = new URL("http://search.twitter.com/search.json?q=%23java");
        try(InputStream is = url.openStream();
            JsonParser parser = Json.createParser(is);
            PrintWriter ps = new PrintWriter(new OutputStreamWriter(os, "UTF-8"))) {

            while(parser.hasNext()) {
                Event e = parser.next();
                if (e == Event.KEY_NAME) {
                    if (parser.getString().equals("from_user")) {
                        parser.next();
                        ps.print(parser.getString());
                        ps.print(": ");
                    } else if (parser.getString().equals("text")) {
                        parser.next();
                        ps.println(parser.getString());
                        ps.println("---------");
                    }
                }
            }
        }
 }

}

第四章:WebSocket 1.0分析##

作為Html5新特性之一的WebSocket組件,在實時性有一定要求的WEB應用開發 中還是有一定用武之地,高版本的IE、Chrome、FF瀏覽器都支持Websocket,標準的Websocket通信是基于RFC6455實現服務器 端與客戶端握手與消息接發的。如果對Websocket通信不是太理解,可以查看RFC文檔即可,簡單說就是通過發送HTTP請求,實現雙方握手,將無狀 態的HTTP通信協議進一步升級成有狀態的通信協議,同時Websocket還支持子協議選項與安全傳輸。標準的websocket連接URL以ws開 頭,如果是基于TLS的則以wss開頭。

第一部分:java api for websocket###

參考第18章:java api for websocket

4.1.1 websocket endpoint

在websocket應用中,服務器發布WebSocket端點和客戶端使用端點的URI來連接到服務器。在連接建立后,websocket協議是對成的。當連接建立后,客戶端和服務器可以在任何時候互發消息。客戶端通常只連接一個服務器。一個服務器接受多臺客戶端的請求。

websocket 協議有兩部分:握手和傳輸

來自于一個客戶端握手的例子:

GET /path/to/websocket/endpoint HTTP/1.1
Host: localhost
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: xqBt3ImNzJbYqRINxEFlkg==
Origin: http://localhost
Sec-WebSocket-Version: 13

服務器回應客戶端的例子:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: K7DJLdLooIwIG/MOpvWFB3y3FE8=

server把一個已知的操作賦值給sec-websocket-key的頭部來生成sec-websocket-accept的值,客戶端吧相同的操作給sec-websocket-key頭部賦值,如果來自于服務器的值和result匹配,那么連接成功建立。客戶端和服務器之間可以互發消息,在一次成功的連接之后。

websocket java api 可以創建兩種endpoints。

  • programmatic endpoints
  • annotated endpoints.

The process for creating and deploying a WebSocket endpoint follows.

  1. Create an endpoint class.
  2. Implement the lifecycle methods of the endpoint.
  3. Add your business logic to the endpoint.
  4. Deploy the endpoint inside a web application.
public class EchoEndpoint extends Endpoint {
@Override
public void onOpen(final Session session, EndpointConfig config) {
session.addMessageHandler(new MessageHandler.Whole<String>() {
@Override
public void onMessage(String msg) {
try {
session.getBasicRemote().sendText(msg);
} catch (IOException e) { ... }
}
});
}
}

annotated endpoints

@ServerEndpoint("/echo")
public class EchoEndpoint {
@OnMessage
public void onMessage(Session session, String msg) {
try {
session.getBasicRemote().sendText(msg);
} catch (IOException e) { ... }
}
}

4.1.2 send and receive messages

websocket endpoints 可以發送和接受文本和二進制消息。另外也可以發送ping和pong消息。

發消息:

  1. 從連接中獲取session對象
  2. 使用session對象獲取remoteEndPoint對象
  3. 使用remoteEndPoint對象發送消息到節點。
  • sendText()
  • sendBinary()
  • sendPing()
  • sendPong()
@ServerEndpoint("/echoall")
public class EchoAllEndpoint {
@OnMessage
public void onMessage(Session session, String msg) {
try {
for (Session sess : session.getOpenSessions()) {
if (sess.isOpen())
sess.getBasicRemote().sendText(msg);
}
} catch (IOException e) { ... }
}
}

接收消息:

@ServerEndpoint("/receive")
public class ReceiveEndpoint {
@OnMessage
public void textMessage(Session session, String msg) {
System.out.println("Text message: " + msg);
}
@OnMessage
public void binaryMessage(Session session, ByteBuffer msg) {
System.out.println("Binary message: " + msg.toString());
}
@OnMessage
public void pongMessage(Session session, PongMessage msg) {
System.out.println("Pong message: " +
msg.getApplicationData().toString());
}
}

4.1.3 using endodes and decoders

java對象轉換成websocket message;

  1. 實現以下接口
  • Encoder.Text<T> for text messages
  • Encoder.Binary<T> for binary messages
  1. Add the names of your encoder implementations to the encoders optional
    parameter of the ServerEndpoint annotation.
  2. Use the sendObject(Object data) method of the RemoteEndpoint.Basic or
    RemoteEndpoint.Async interfaces to send your objects as messages. The container looks for an encoder that matches your type and uses it to convert the object to a WebSocket message.

websocket message對象轉換為java object:

  1. 實現以下接口:
  • Decoder.Text<T> for text messages
  • Decoder.Binary<T> for binary messages
  1. Add the names of your decoder implementations to the decoders optional
    parameter of the ServerEndpoint annotation.
  2. Use the OnMessage annotation in the endpoint to designate a method that takes your custom Java type as a parameter. When the endpoint receives a message that can be decoded by one of the decoders you specified, the container calls the method annotated with @OnMessage that takes your custom Java type as a parameter if this method exists.

第二部分:Examples for websocket###

the example:auction(拍賣會)

auction:拍賣會實體類

public class Auction {

    /*
     * Current state of the auction
     */
    private AuctionState state;

    /*
     * ID of the auction used for communication
     */
    private final String id;

    /*
     * Assigns id to newly created Auction object
     */
    private static int idCounter = 0;

    /*
     * Auction Item
     */
    private final AuctionItem item;

    /*
     * List of remote clients (Peers)
     */
    private final List<Session> arcList = new ArrayList<>();

    /*
     * Timer that sends pre-auction time broadcasts
     */
    private Timer auctionRunningTimer;

    /*
     * Value of the highest bid
     */
    private double bestBid;

    private String bestBidderName;

    /*
     * Separator used to separate different fields in the communication
     * datastring
     */
    public static final String SEPARATOR = ":";

    public enum AuctionState {
        PRE_AUCTION, AUCTION_RUNNING, AUCTION_FINISHED
    }

    public Auction(AuctionItem item) {
        this.item = item;

        this.state = AuctionState.PRE_AUCTION;
        this.id = Integer.toString(Auction.idCounter);
        bestBid = item.getPrice();
        idCounter++;
    }

    synchronized void addArc(Session arc) {
        arcList.add(arc);
    }

    public synchronized void removeArc(Session arc) {
        arcList.remove(arc);
    }

    /*
     * New user logs into the auction.
     */
    public void handleLoginRequest(AuctionMessage messsage, Session arc) {

        arc.getUserProperties().put("name", messsage.getData());
        synchronized (id) {
            if (state != AuctionState.AUCTION_FINISHED) {
                if (!getRemoteClients().contains(arc)) {
                    this.addArc(arc);
                }
                try {
                    item.setPrice(bestBid);
                    arc.getBasicRemote().sendObject(new AuctionMessage.LoginResponseMessage(id, item));
                } catch (IOException | EncodeException e) {
                    Logger.getLogger(Auction.class.getName()).log(Level.SEVERE, null, e);
                }

                if(state == AuctionState.PRE_AUCTION){
                    startAuctionTimeBroadcast();
                }
            } else {
                try {
                    arc.getBasicRemote().sendObject(new AuctionMessage.LoginResponseMessage(id, item));
                    if(bestBidderName!= null && bestBidderName.equals(messsage.getData())){
                        arc.getBasicRemote().sendObject(new AuctionMessage.ResultMessage(id, String.format("Congratulations, You won the auction and will pay %.0f.", bestBid)));
                    }else{
                        arc.getBasicRemote().sendObject(new AuctionMessage.ResultMessage(id, String.format("You did not win the auction. The item was sold for %.0f.", bestBid)));
                    }
                } catch (IOException | EncodeException e) {
                    Logger.getLogger(Auction.class.getName()).log(Level.SEVERE, null, e);
                }
            }
        }
    }

    public void handleBidRequest(AuctionMessage message, Session arc) {
        synchronized (id) {
            if (state == AuctionState.AUCTION_RUNNING) {
                Double bid = Double.parseDouble((String)message.getData());
                if (bid > bestBid) {
                    bestBid = bid;

                    bestBidderName = (String) arc.getUserProperties().get("name");
                    sendPriceUpdateMessage();
                    stopAuctionTimeBroadcast();
                    startAuctionTimeBroadcast();
                }
            }
        }
    }

    private void sendPriceUpdateMessage() {
        AuctionMessage.PriceUpdateResponseMessage purm = new AuctionMessage.PriceUpdateResponseMessage(id, "" + bestBid);
        for (Session arc : getRemoteClients()) {
            try {
                arc.getBasicRemote().sendObject(purm);
            } catch (IOException | EncodeException e) {
                Logger.getLogger(Auction.class.getName()).log(Level.SEVERE, null, e);
            }
        }
    }

    public void switchStateToAuctionFinished() {
        synchronized (id) {
            state = AuctionState.AUCTION_FINISHED;
        }
        stopAuctionTimeBroadcast();
        sendAuctionResults();
    }

    private void sendAuctionResults() {
        Session bestBidder = null;

        if(bestBidderName != null){
            for (Session session : getRemoteClients()) {
                if(session.getUserProperties().get("name").equals(bestBidderName)){
                    bestBidder = session;
                }
            }
        }

        if (bestBidder!= null) {
            AuctionMessage.ResultMessage winnerMessage = new AuctionMessage.ResultMessage(id, String.format("Congratulations, You won the auction and will pay %.0f.", bestBid));
            try {
                bestBidder.getBasicRemote().sendObject(winnerMessage);
            } catch (IOException | EncodeException e) {
                Logger.getLogger(Auction.class.getName()).log(Level.SEVERE, null, e);
            }
        }

        AuctionMessage.ResultMessage loserMessage = new AuctionMessage.ResultMessage(id, String.format("You did not win the auction. The item was sold for %.0f.", bestBid));
        for (Session arc : getRemoteClients()) {
            if (arc != bestBidder) {
                try {
                    arc.getBasicRemote().sendObject(loserMessage);
                } catch (IOException | EncodeException e) {
                    Logger.getLogger(Auction.class.getName()).log(Level.SEVERE, null, e);
                }
            }
        }
    }

    private void startAuctionTimeBroadcast() {
        synchronized (id) {
            state = AuctionState.AUCTION_RUNNING;
        }
        auctionRunningTimer = new Timer();
        auctionRunningTimer.schedule(new AuctionTimeBroadcasterTask(this, item.getBidTimeoutS()), 0, 1000);

    }

    private void stopAuctionTimeBroadcast() {
        auctionRunningTimer.cancel();
    }

    public String getId() {
        return id;
    }

    public List<Session> getRemoteClients() {
        return Collections.unmodifiableList(arcList);
    }

    public AuctionItem getItem() {
        return item;
    }
}

auctionItem代碼:

public class AuctionItem {

    /*
     * Name of the item.
     */
    private final String name;

    /*
     * Description of the item.
     */
    private final String description;

    /*
     * Current price of the item.
     */
    private double price;

    /*
     * Timeout which is applied for one bid.
     */
    private final int bidTimeoutS;

    public AuctionItem(String name, String description, double price, int bidTimeoutS) {
        this.name = name;
        this.description = description;
        this.price = price;
        this.bidTimeoutS = bidTimeoutS;
    }

    @Override
    public String toString() {
        return name + Auction.SEPARATOR + description + Auction.SEPARATOR + price + Auction.SEPARATOR + "0" + Auction.SEPARATOR + bidTimeoutS + " seconds";
    }

    public int getBidTimeoutS() {
        return bidTimeoutS;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price){
        this.price = price;
    }

    public String getName() {
        return name;
    }
}

auctionserver類:

@ServerEndpoint(value = "/auction",
        decoders = {
                AuctionMessageDecoder.class,
        },
        encoders = {
                AuctionMessageEncoder.class
        }
)
public class AuctionServer {

    /*
     * Set of auctions (finished, running, to be started auctions).
     */
    private static final Set<Auction> auctions = Collections.unmodifiableSet(new HashSet<Auction>() {{
        add(new Auction(new AuctionItem("Swatch", "Nice Swatch watches, hand made", 100, 20)));
        add(new Auction(new AuctionItem("Rolex", "Nice Rolex watches, hand made", 200, 20)));
        add(new Auction(new AuctionItem("Omega", "Nice Omega watches, hand made", 300, 20)));
    }});

    @OnClose
    public void handleClosedConnection(Session session) {
        for (Auction auction : auctions) {
            auction.removeArc(session);
        }
    }

    @OnMessage
    public void handleMessage(AuctionMessage message, Session session){
        String communicationId;

        switch (message.getType()){
            case AuctionMessage.LOGOUT_REQUEST:
                handleClosedConnection(session);
                break;
            case AuctionMessage.AUCTION_LIST_REQUEST:
                StringBuilder sb = new StringBuilder("-");

                for (Auction auction : auctions) {
                    sb.append(auction.getId()).append("-").append(auction.getItem().getName()).append("-");
                }

                try {
                    session.getBasicRemote().sendObject((new AuctionMessage.AuctionListResponseMessage("0", sb.toString())));
                } catch (IOException | EncodeException e) {
                    Logger.getLogger(AuctionServer.class.getName()).log(Level.SEVERE, null, e);
                }
                break;
            case AuctionMessage.LOGIN_REQUEST:
                communicationId = message.getCommunicationId();
                for (Auction auction : auctions) {
                    if (communicationId.equals(auction.getId())) {
                        auction.handleLoginRequest(message, session);
                    }
                }
                break;
            case AuctionMessage.BID_REQUEST:
                communicationId = message.getCommunicationId();
                for (Auction auction : auctions) {
                    if (communicationId.equals(auction.getId())) {
                        auction.handleBidRequest(message, session);
                        break;
                    }
                }
                break;
        }

    }
}

AuctionTimeBroadcasterTask類:

public class AuctionTimeBroadcasterTask extends TimerTask {

    private Auction owner;
    private int timeoutCounter;

    public AuctionTimeBroadcasterTask(Auction owner, int timeoutCounter) {
        this.owner = owner;
        this.timeoutCounter = timeoutCounter;
    }

    @Override
    public void run() {
        if (timeoutCounter < 0) {
            owner.switchStateToAuctionFinished();
        } else {
            if (!owner.getRemoteClients().isEmpty()) {
                AuctionMessage.AuctionTimeBroadcastMessage atbm = new AuctionMessage.AuctionTimeBroadcastMessage(owner.getId(), timeoutCounter);

                for (Session arc : owner.getRemoteClients()) {
                    try {
                        arc.getBasicRemote().sendText(atbm.toString());
                    } catch (IOException ex) {
                        Logger.getLogger(AuctionTimeBroadcasterTask.class.getName()).log(Level.SEVERE, null, ex);
                    }
                }
            }
        }
        timeoutCounter--;
    }
}

decoder:

public class AuctionMessageDecoder implements Decoder.Text<AuctionMessage> {

    @Override
    public AuctionMessage decode(String s) {
        String[] tokens = s.split(":");

        return new AuctionMessage(tokens[0], tokens[1], tokens[2]);
    }

    @Override
    public boolean willDecode(String s) {
        return s.startsWith(AuctionMessage.BID_REQUEST) ||
                s.startsWith(AuctionMessage.AUCTION_LIST_REQUEST) ||
                s.startsWith(AuctionMessage.LOGIN_REQUEST) ||
                s.startsWith(AuctionMessage.LOGOUT_REQUEST);
    }

    @Override
    public void init(EndpointConfig endpointConfig) {
        // do nothing.
    }

    @Override
    public void destroy() {
        // do nothing.
    }
}

encoder:

public class AuctionMessageEncoder implements Encoder.Text<AuctionMessage> {

    @Override
    public String encode(AuctionMessage object) throws EncodeException {
        return object.toString();
    }

    @Override
    public void init(EndpointConfig endpointConfig) {
        // do nothing.
    }

    @Override
    public void destroy() {
        // do nothing.
    }
}

java ee7 總結##

Java EE 7 擴展了 Java EE 6,利用更加透明的 JCP 和社區參與來引入新的功能,如圖 1(本圖引用自 Java 官網)所示,主要包括加強對 HTML5 動態可伸縮應用程序的支持、提高開發人員的生產力和滿足苛刻的企業需求。

  1. 提高開發人員的生產力
    通過一個緊密集成的平臺簡化了應用架構,減少樣板代碼和加強對注釋的使用來提高效率,另外借助標準 RESTful Web 服務對客戶端的支持提高了應用程序的可移植性。
  2. 加強對 HTML 5 動態可伸縮應用程序的支持
    基于其可擴展的基礎架構,Java EE 7 推動了對 HTML 5 應用的構建和支持。在新的平臺中,借助具有行業標準的 JSON 簡化了數據分析和交換,并通過低延遲和雙向通信的 WebSockets 減少了響應時間。以及利用改進的 JAX-RS 2.0 更好地支持異步的、可擴展的、高性能的 RESTful 服務,從而更好地支持多用戶的并發操作。
  3. 滿足苛刻的企業需求
    為更好地滿足企業的需求,Java EE 7 提供了許多新功能:
    細化批處理作業,形成可管理的區塊,以實現不間斷的 OLTP 性能;
    簡化多線程并發任務的定義,以提高可擴展性;
    以及提供具有選擇性和靈活性的事務應用程序等。
    Java EE 7 開發的開放性,使得 Java 社區、供應商、組織和個人都能參與其中。19 個來自世界各地的用戶組,包括來自北美、南美、歐洲和亞洲,都參與了“采用 JSR”計劃,提供了寶貴的反饋意見和代碼示例以驗證 Java 規范 (JSR) 的 API。
    在最新發布的 Java EE 平臺中都大大簡化了訪問集裝箱服務的 API,同時大大拓寬了服務范圍。Java EE 7 繼續秉承了簡化性和高效性的趨勢,并進一步拓寬了平臺范圍。下面就針對 Java EE 7 的三大新特性進行詳細的剖析。
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,606評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,582評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,540評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,028評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,801評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,223評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,294評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,442評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,976評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,800評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,996評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,543評論 5 360
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,233評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,926評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,702評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,991評論 2 374

推薦閱讀更多精彩內容