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 的三大新特性进行详细的剖析。

推荐阅读更多精彩内容