随着Java平台企业版(Java EE),Java企业应用程序的开发从未如此简单或更快。在Java EE 7平台的目的是向开发人员提供了一套强大的API,同时缩短开发时间,降低了应用的复杂性,并提高应用程序的性能。
在Java EE 7平台引入了一个简化的编程模型。随着Java EE 7的技术,XML部署描述符现在是可选的。相反,开发人员可以简单地输入信息作为注释直接到Java源文件,以及Java EE服务器将配置组件在部署和运行。这些注解通常用于,否则将在一个部署描述符中提供一个节目数据嵌入。使用注释,规范信息直接把你的代码下一个程序单元,它的影响。
本文分析了Servlet 3.1、JAX-RS 2.0、JSON Processing 1.0、WebSocket 1.0相关技术的几个示例,以及相关技术下的应用。
原理 新特性 开发人员现在越来越多地认识到需要分布式事务,并利用速度,安全性和服务器端技术的可靠性便携式应用。在信息技术的世界中,企业应用程序必须设计,建设和生产用更少的钱,以更快的速度,并以更少的资源。 Java EE平台是通过Java进程(JCP),开发了负责所有的Java技术。有关方面组成的专家小组已经创建Java规范请求(JSR)来定义各种Java EE技术。 Java社区在JCP程序的工作有助于确保Java技术的标准,稳定性和跨平台的兼容性。
Java EE平台使用简化的编程模型。 XML部署描述符是可选的。相反,开发人员可以简单地输入信息作为注释直接插入Java源文件,以及Java EE服务器将配置组件在部署和运行。这些注解通常用于嵌入,否则将在部署来布置一个节目数据描述符。使用注释,你把规范的信息在你的代码下一个程序单元的影响。
在Java EE平台,依赖注入可以应用于所有资源组件的需求,从而有效地隐藏资源的创建和查询应用程序代码。依赖注入可以在企业JavaBeans(EJB)中使用容器,Web容器和应用程序客户端。依赖注入允许Java EE容器自动插入引用其他所需的组件或 资源,使用注释。
Java持久性API提供了在企业Bean,Web组件和应用程序的客户管理关系数据的对象/关系映射。它也可以在Java SE应用程序所使用的,Java EE的环境之外。
主要包括加强对 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应用程序模型从Java编程语言和Java虚拟机。事实证明,便携性,安全性和开发人员的生产力提供形成所述应用模型的基础。
Java EE的旨在支持实现客户,员工,供应商的企业服务的应用程序,合作伙伴和其他人谁作出要求或贡献的企业。这样应用本质上是复杂,从各种可能访问数据源和应用程序分发到各种客户端。为了更好地控制和管理这些应用中,业务功能,以支持这些不同的用户在中间层进行。中间层代表这是密切企业的信息化控制的环境部门。中间层通常运行在专门的服务器硬件,并具有访问企业的全程服务。
在Java EE应用程序模型定义的架构实施服务能够提供可扩展性,可访问性和可管理性多层应用程序及分布式多层应用程序需要企业级的应用。这种模式划分所需的工作 实现的多层服务分为以下几个部分:
(1)业务和表示逻辑由开发商实施
(2)由Java EE平台提供的标准的系统服务,可以依靠在平台上,以提供所述硬系统级解决方案开发多层服务的问题。
分布式多层应用程序 Java EE平台使用,为企业的分布式多层应用模型和应用程序。应用逻辑被分成部件根据功能,和该应用程序组件,使Java EE应用程序被安装在根据层的多层Java EE的环境中各种机械该应用程序组件所属。
在下面的列表中。在图1-1所示的Java EE应用程序部分呈现的Java EE组件。
(1)客户端层组件的客户端机器上运行。
(2)在Java EE服务器上运行的Web层组件。
(3)业务层组件的Java EE服务器上运行。
(4)企业信息系统(EIS)层软件的EIS服务器上运行。
尽管Java EE应用程序可以由图1-1中,Java EE的显示所有层多层应用程序通常被认为是三层应用因为它们分布在三个地方:客户端机器上,Java EE服务器机,数据库或传统的机器在后端。三层以这种方式运行的应用程序扩展标准双层客户机和服务器 模型通过将多线程应用程序服务器的客户端应用程序之间和后端存储。
Web层 Java EE Web组件使用JavaServer创建或者servlet或网页Faces技术和/或JSP技术(JSP页面)。
Servlet是Java编程语言类动态处理请求并构建响应。 JSP页面是基于文本的文档为servlet执行,但允许更自然的方法来创建静态内容。
JavaServer Faces技术建立在servlet和JSP技术,并提供了用于网络应用程序用户界面组件的框架。
静态的HTML页面和小应用程序捆绑在一起的应用程序中的Web组件装配,但不被视为Web组件由Java EE规范。
服务器端实用工具类也可以绑定Web组件,像HTML页面,不被视为Web组件。
Web层,就像客户层,可能包括一个JavaBeans组件来管理用户输入和发送输入到企业Bean运行在业务层进行处理。
源码解析 Servlet 3.1 Annotations Servlet Servlet3.1规范大量使用注解来声明Servlet中,过滤器,监听器和安全性。配置文件web.xml中现在是可选的。 源码如下:
package sample; import java.io.IOException ;import java.io.*;import javax.servlet.*;import javax.servlet.annotation.*;import javax.servlet.http.*; @WebServlet (name="testServlet" , urlPatterns={"/hello" }, initParams={ @WebInitParam (name="simpleParam" , value="paramValue" ) } ) public class TestServlet extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException , IOException { PrintWriter out = response.getWriter(); String simpleParam = getServletConfig().getInitParameter("simpleParam" ); out.println("Hello World " +simpleParam); out.close(); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException , IOException { doGet(request,response); } }
在上面的代码,我们已经注册了的TestServlet下的URL模式“/你好”(注意复数形式可以有一个以上的)。此外,我们已经设置了一个名为“simpleParam”的初始参数。不需要web.xml中运行这个servlet。
你可以声明以及器过滤器使用注释,想在这个exampleYou可以声明以及过滤器在这个例子中使用注释,代码如下:
import java.io.*;import javax.servlet.*;import javax.servlet.annotation.*; @WebFilter (urlPatterns={"/*" }, initParams={ @WebInitParam (name="simpleParam" , value="paramValue" ) }) public class TestFilter implements Filter { @Override public void doFilter (ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { StringWriter sw = new StringWriter(); PrintWriter writer = new PrintWriter(sw); writer.println("===============" ); writer.println("Filter intercepted!" ); writer.println("===============" ); writer.flush(); filterConfig.getServletContext(). log(sw.getBuffer().toString()); chain.doFilter(request, response); } private FilterConfig filterConfig = null ; public void init (FilterConfig filterConfig) throws ServletException { this .filterConfig = filterConfig; } @Override public void destroy () { } }
上述过滤器将拦截发到网络环境中的所有请求。 下边介绍另一个有用的注释是@WebListener注释可以用来标记一个Java类为WebListener,代码如下:
package sample;import javax.servlet.*;@javax .servlet.annotation.WebListenerpublic class SessionListener implements ServletContextListener { @Override public void contextDestroyed (ServletContextEvent sce) { System.out.println("Context destroyed!" ); } @Override public void contextInitialized (ServletContextEvent sce) { System.out.println("Context created!" ); } }
Absolute Ordering of Web Fragments 为了更好地进行适配,减少配置,在Servlet的3.1h中引入了web-fragment的概念。一个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 > ... <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目录中。
File Upload File Upload示例应用程序由一个单一的servlet和HTML表单。这使得上载文件到servlet。这个例子包括两个字段,文件和目标非常简单的HTML表单。输入类型,文件,使得用户能够浏览本地文件系统,选择该文件。
当选择了文件时,它被发送给服务器作为POST请求的一部分。在这一过程中,下面有两个强制性限制应用于具有输入类型的文件的形式。
(1)该enctype属性必须设置为multipart / form-数据的值。
(2)它的方法必须是POST。
当以这种方式指定的形式时,整个请求被发送到服务器编码形式。然后servlet使用它自己的方式来处理,以处理该请求传入的文件数据,并提取从流的一个文件。目的地是路径某个位置的文件会被保存在电脑上。
在按下上传按钮 表格的下方张贴数据到servlet,它保存在文件中指定的目的地。 index.html中的HTML格式如下:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html lang ="en" xmlns ="http://www.w3.org/1999/xhtml" xml:lang ="en" > <head > <title > File Upload</title > <meta http-equiv ="Content-Type" content ="text/html; charset=UTF-8" /> </head > <body > <form method ="post" action ="upload" enctype ="multipart/form-data" > File: <input type ="file" name ="file" id ="file" /> <br /> Destination: <input type ="text" value ="/tmp" name ="destination" /> <br /> <input type ="submit" value ="Upload" name ="upload" id ="upload" /> </form > </body > </html >
当客户端需要发送数据到服务器作为POST请求方法用于的要求,上传文件或提交填妥的表格时等部分。相反,GET请求方法发出URL和headers 仅给服务器,而POST请求还包括消息正文。这允许随机长度的数据键入要发送到服务器。
在POST请求中的报头字段通常指示消息正文的互联网媒体类型。当提交表单时,浏览器流的内容,联合各个部分,与每个部分代表一个形式下一个字段。部分被命名为输入元素后,相互之间用命名边界字符串分隔。 从文件上传表单提交的数据看,选择sample.txt的作为将要上传到tmp目录上的本地文件,代码如下:
POST /fileupload/upload HTTP/1.1 Host: localhost:8080 Content-Type: multipart/form-data; boundary= Content-Length: 441 Content-Disposition: form-data; name ="file" ; filename="sample.txt" Content-Type: text /plain Data from sample file Content-Disposition: form-data; name ="destination" /tmp Content-Disposition: form-data; name ="upload" Upload
该servlet FileUploadServlet.java开头如下:
@WebServlet (name = "FileUploadServlet" , urlPatterns = {"/upload" })@MultipartConfig public class FileUploadServlet extends HttpServlet { private final static Logger LOGGER = Logger .getLogger(FileUploadServlet .class .getCanonicalName());
@WebServlet标注使用URL模式属性来定义的servlet映射。
@MultipartConfig注释指示该servlet的期望请求被使用的multipart / form-data的MIME类型进行。
processRequest方法从请求检索目的地和文件的一部分,然后调用 getFileName方法来检索从文件部分的文件名。该方法然后创建一个- FileOutputStream并将该文件复制到指定的目的地。该方法捕获的错误处理部和处理一些最常见的原因,一个文件就不会被发现,其中的processRequest和getFileName方法是这样的:
protected void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{ response.setContentType("text/html;charset=UTF-8" ); final String path = request.getParameter("destination" ); final Part filePart = request.getPart("file" ); final String fileName = getFileName(filePart); OutputStream out = null ; InputStream filecontent = null ; final PrintWriter writer = response.getWriter(); try { out = new FileOutputStream(new File(path + File.separator + fileName)); filecontent = filePart.getInputStream(); int read = 0 ; final byte [] bytes = new byte [1024 ]; while ((read = filecontent.read(bytes)) != -1 ) { out.write(bytes, 0 , read); } writer.println ("New file " + fileName + " created at " + path); LOGGER.log (Level.INFO, "File{0}being uploaded to {1}" , new Object [] {fileName, path}); } catch (FileNotFoundException fne) { writer.println ("You either did not specify a file to upload or are " + "trying to upload a file to a protected or nonexistent " + "location." ); writer.println ("<br/> ERROR: " + fne.getMessage()); LOGGER.log (Level.SEVERE, "Problems during file upload. Error: {0}" , new Object [] {fne.getMessage()}); } finally { if (out != null ) { out.close(); } if (filecontent != null ) { filecontent.close(); } if (writer != null ) { writer.close(); } } } private String getFileName(final Part part){ final String partHeader = part.getHeader("content-disposition" ); LOGGER.log (Level.INFO, "Part Header = {0}" , partHeader); for (String content : part.getHeader("content-disposition" ).split (";" )) { if (content.trim ().startsWith("filename" )) { return content.substring( content.indexOf('=' ) + 1 ).trim ().replace("\"" , "" ); } } return null ;
JAX-RS 2.0 Asynchronous Chat JAX-RS 该示例应用用程序有三个部分。
(1)客户和地址实体类。这些类模型的数据应用和含有JAXB注解。
(2)客户示例应用程序:CustomerService类。此类包含JAX-RS资源方法 上表示为XML或JSON数据的客户实例执行操作使用JAXB。
(3)CustomerBean会话bean充当辅助bean的Web客户端。CustomerBean使用JAX-RS客户端API调用的CustomerService的方法。
客户和地址实体类 地址实体类:
@Entity @Table (name = "CUSTOMER_ADDRESS" )@XmlRootElement (name = "address" )@XmlAccessorType (XmlAccessType.FIELD)public class Address { @Id @GeneratedValue (strategy = GenerationType.AUTO) private Long id; @XmlElement (required = true ) protected int number; @XmlElement (required = true ) protected String street; @XmlElement (required = true ) protected String city; @XmlElement (required = true ) protected String province; @XmlElement (required = true ) protected String zip; @XmlElement (required = true ) protected String country; public Address () { } public Long getId () { return id; } public void setId (Long id) { this .id = id; } public int getNumber () { return number; } public void setNumber (int number) { this .number = number; } public String getStreet () { return street; } public void setStreet (String street) { this .street = street; } public String getCity () { return city; } public void setCity (String city) { this .city = city; } public String getProvince () { return province; } public void setProvince (String province) { this .province = province; } public String getZip () { return zip; } public void setZip (String zip) { this .zip = zip; } public String getCountry () { return country; } public void setCountry (String country) { this .country = country; } }
@XmlRootElement(name = “address”)标注这个类映射到地址XML元素。
@XmlAccessorType(XmlAccessType.FIELD)注解指定这个类的所有字段默认绑定到XML。
@XmlElement(required=true)注解指定一个元素必须出现在XML中表示。
客户实体类:
@Entity @Table (name = "CUSTOMER_CUSTOMER" )@NamedQuery ( name = "findAllCustomers" , query = "SELECT c FROM Customer c " + "ORDER BY c.id" ) @XmlRootElement (name = "customer" )@XmlAccessorType (XmlAccessType.FIELD)public class Customer { @Id @GeneratedValue (strategy = GenerationType.AUTO) @XmlAttribute (required = true ) protected int id; @XmlElement (required = true ) protected String firstname; @XmlElement (required = true ) protected String lastname; @XmlElement (required = true ) @OneToOne protected Address address; @XmlElement (required = true ) protected String email; @XmlElement (required = true ) protected String phone; public Customer () { } public int getId () { return id; } public void setId (int id) { this .id = id; } public String getFirstname () { return firstname; } public void setFirstname (String firstname) { this .firstname = firstname; } public String getLastname () { return lastname; } public void setLastname (String lastname) { this .lastname = lastname; } public Address getAddress () { return address; } public void setAddress (Address address) { this .address = address; } public String getEmail () { return email; } public void setEmail (String email) { this .email = email; } public String getPhone () { return phone; } public void setPhone (String phone) { this .phone = phone; } }
Customer类包含相同的JAXB注解,除了为@XmlAttribute(required=true)标注,它的属性映射到代表类的XML元素的属性。
客户类包含一个属性,其类型为另一个实体,Address类。这种机制允许你定义在Java代码中的层次关系无需编写.xsd文件自己的实体之间。
JAXB生成前两个类用以下XML模式定义:
<?xml version ="1.0" encoding="utf-8" standalone="yes" ?> <x s:schema version ="1.0" xmlns:xs ="http://www.w3.org/2001/XMLSchema" > <x s:element name="address" type ="address" /> <x s:element name="customer" type ="customer" /> <x s:complexType name="address" > <x s:sequence > <x s:element name="id" type ="xs:long" minOccurs="0" /> <x s:element name="number" type ="xs:int" /> <x s:element name="street" type ="xs:string" /> <x s:element name="city" type ="xs:string" /> <x s:element name="province" type ="xs:string" /> <x s:element name="zip" type ="xs:string" /> <x s:element name="country" type ="xs:string" /> </x s:sequence > </x s:complexType > <x s:complexType name="customer" > <x s:sequence > <x s:element name="firstname" type ="xs:string" /> <x s:element name="lastname" type ="xs:string" /> <x s:element ref="address" /> <x s:element name="email" type ="xs:string" /> <x s:element name="phone" type ="xs:string" /> </x s:sequence > <x s:attribute name="id" type ="xs:int" use="required" /> </x s:complexType > </x s:schema >
CustomerService类 CustomerService类在创建一个客户类的createCustomer方法资源的基础上,并返回给客户类一个新的URI资源,代码如下
@Stateless @Path ("/Customer" )public class CustomerService { public static final Logger logger = Logger.getLogger(CustomerService.class.getCanonicalName()); @PersistenceContext private EntityManager em; private CriteriaBuilder cb; @PostConstruct private void init () { cb = em.getCriteriaBuilder(); } @POST @Consumes ( {MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON}) public Response createCustomer (Customer customer) { try { long customerId = persist(customer); return Response.created(URI.create("/" + customerId)).build(); } catch (Exception e) { logger.log(Level.SEVERE, "Error creating customer for customerId {0}. {1}" , new Object[] {customer.getId(), e.getMessage()}); throw new WebApplicationException(e, Response.Status.INTERNAL_SERVER_ERROR); } } private long persist (Customer customer) { try { Address address = customer.getAddress(); em.persist(address); em.persist(customer); } catch (Exception ex) { logger.warning ("Something went wrong when persisting the customer" ); } return customer.getId () ; }
返回到客户端的响应具有一个新创建的URI资源。返回类型是从与状态码的响应的属性映射的实体主体通过响应的状态属性指定。
WebApplicationException客户示例应用程序的RuntimeException用来包裹适当的HTTP错误状态代码,例如404,406,415或500。
@Consumes ({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})和@Produces ({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
注释设置请求和响应媒体类型使用适当的MIME客户。这些注释可以应用于一个资源的方法,资源类,或甚至一个实体提供者。如果不使用这些注释,JAX-RS允许使用任何媒体类型 (”*/*”)
。
下面的代码 fragments显示了getCustomer的实现和findbyId方法。该getCustomer方法使用@Produces注释和返回一个客户对象,它被转换成XML或JSON表示根据接收由客户指定的headers。
@GET @Path ("{id}" )@Produces ( {MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})public Customer getCustomer(@PathParam ("id" ) String customerId) { Customer customer = null ; try { customer = findById(customerId); } catch (Exception ex) { logger.log(Level.SEVERE, "Error calling findCustomer() for customerId {0}. {1}" , new Object [] {customerId, ex.getMessage()}); } return customer; } private Customer findById(String customerId) { Customer customer = null ; try { customer = em.find(Customer.class , customerId); return customer; } catch (Exception ex) { logger.log(Level.WARNING, "Couldn't find customer with ID of {0}" , customerId); } return customer; }
CustomerBean类 使用JAX-RS客户端API来编写客户端为客户示例应用程序。CustomerBean类调用JAX-RS客户端API测试,启动CustomerService Web服务:
@Named @Stateless public class CustomerBean { protected Client client; private static final Logger logger = Logger.getLogger(CustomerBean.class.getName()); @PostConstruct private void init () { client = ClientBuilder.newClient(); } @PreDestroy private void clean () { client.close(); } public String createCustomer (Customer customer) { if (customer == null ) { logger.log(Level.WARNING, "customer is null." ); return "customerError" ; } String navigation; Response response = client.target ("http://localhost:8080/customer/webapi/Customer" ) .request(MediaType.APPLICATION_XML) .post(Entity.entity(customer, MediaType.APPLICATION_XML), Response.class); if (response.getStatus() == Status.CREATED.getStatusCode()) { navigation = "customerCreated" ; } else { logger.log(Level.WARNING, "couldn''t create customer with " + "id {0}. Status returned was {1}" , new Object[] {customer.getId(), response.getStatus()}); navigation = "customerError" ; } return navigation; } public String retrieveCustomer (String id) { String navigation; Customer customer = client.target ("http://localhost:8080/customer/webapi/Customer" ) .path(id) .request(MediaType.APPLICATION_XML) .get(Customer.class); if (customer == null ) { navigation = "customerError" ; } else { navigation = "customerRetrieved" ; } return navigation; } public List<Customer> retrieveAllCustomers() { List<Customer> customers = client.target ("http://localhost:8080/customer/webapi/Customer" ) .path("all" ) .request(MediaType.APPLICATION_XML) .get(new GenericType<List<Customer>>() {}); return customers; } }
不难看出,此客户端使用了POST和GET方法。 HTTP状态代码表示
success:201 POST 200 GET204 DELETE
JSON Processing 1.0 JAX-RS JSONP JAX-RS可自动读取并使用JAXB写入XML,但它也可以读写JSON数据。 JSON是从获得的数据交换一个简单的基于文本的格式JavaScript的。对于前述示例,一个产品的XML表示是
<?xml version="1.0" encoding="utf-8"?> <product > <id > 1</id > <name > Mattress</name > <description > Queen size mattress</description > <price > 500</price > </product >
用json格式表示为:
{ "id ":"1" , "name ":"Mattress" , "description ":"Queen size mattress" , "price ":500 }
添加格式的应用程序/ JSON或MediaType.APPLICATION_JSON到 @Produces注释资源的方法来生产使用JSON数据响应:
@GET @Path ("/get" )@Produces ({"application/xml" ,"application/json" })public Product getProduct () { ... }
这个例子中,默认响应是XML,但反应是一个JSON对象,如果客户端发出包含这个头的GET请求:
方法还可以接受JSON数据和JAXB注释类:
@POST @Path ("/create" )@Consumes ({"application/xml" ,"application/json" })public Response createProduct (Product prod) { ...
则必须包含request post:
Content-Type : application/json
根据JAX-RS2.0规范,为JSON处理JSR-353的Java API的支持是强制性的要求,意味着消息reader(s)/writer(s)为以下几种类型的存在:JsonStructure,JsonArray和的JSONObject。在Apache CXF提供JsrJsonpProvider提供者的形式,例如一个支持被Apache CXF的JAX-RS扩展模块提供商(cxf-rt-rs-extension-providers)代码如下。
<jaxrs:providers > <bean class ="org.apache.cxf.jaxrs.provider.jsrjsonp.JsrJsonpProvider" /> </jaxrs:providers >
单独加入JsrJsonpProvider提供商(或与其他提供者的组合)允许JAX-RS资源,以本身使用JsonStructure,JsonArray的JSONObject对象作为输入参数或返回值。 例如: GET
@Path ("/books" )@Produces (MediaType.APPLICATION_JSON)public JsonArray getBooks () { } @GET @Path ("/books/{bookId}" )@Produces (MediaType.APPLICATION_JSON)public JsonObject getBook (@PathParam ("bookId" ) Long id) { } @POST @Path ("/books" )@Consumes (MediaType.APPLICATION_JSON)public Response addBook (@Context final UriInfo uriInfo, JsonObject obj) { }
WebSocket 1.0 作为HTML5新特性之一的WebSocket组件,在实时性有一定要求的WEB应用开发 中还是有一定用武之地,高版本的IE、Chrome、FF浏览器都支持Websocket,标准的Websocket通信是基于RFC6455实现服务器 端与客户端握手与消息接发的。如果对Websocket通信不是太理解,可以查看RFC文档即可,简单说就是通过发送HTTP请求,实现双方握手,将无状 态的HTTP通信协议进一步升级成有状态的通信协议,同时Websocket还支持子协议选项与安全传输。标准的websocket连接URL以ws开 头,如果是基于TLS的则以wss开头。
Java EE平台包括的WebSocket(JSR356),这使的Java API 您可以创建,配置和在Web应用程序部署的WebSocket端点。该 在JSR356中指定的WebSocket客户端API,您还可以访问远程的WebSocket端点从任何Java应用程序。
Echo WebSocket websocket回声服务器 package com.websocket.demo;import java.io.IOException;import java.nio.ByteBuffer;import javax.websocket.OnMessage;import javax.websocket.OnOpen;import javax.websocket.Session;import javax.websocket.server.ServerEndpoint;@ServerEndpoint (value = "/echo" )public class EchoExample { @OnMessage public void echoTextMessage (Session session, String msg, boolean last) { try { if (session.isOpen()) { System.out.println("received from client message = " + msg); session.getBasicRemote().sendText(msg, last); } } catch (IOException e) { try { session.close(); } catch (IOException e1) { } } } @OnOpen public void openConn (Session session) throws IOException { session.getBasicRemote().sendText("hello web socket" ); } @OnMessage public void echoBinaryMessage (Session session, ByteBuffer bb, boolean last) { System.out.println("send binary message..." ); try { if (session.isOpen()) { System.out.println("byte buffer lenghth : " + bb.array().length); System.out.println("byte buffer content: " + ((bb.array()[0 ]) & 0xff )); System.out.println("byte buffer content: " + ((bb.array()[1 ]) & 0xff )); System.out.println("byte buffer content: " + ((bb.array()[2 ]) & 0xff )); session.getBasicRemote().sendBinary(bb, last); } } catch (IOException e) { try { session.close(); } catch (IOException e1) { } } } }
web.xml配置 <listener> <listener-class>org.apache .tomcat .websocket .server .WsContextListener </listener-class> </listener>
ServerApplicationConfig接口 package com.config.websocket.client; import java.util.HashSet ;import java.util.Set ;import javax.websocket.Endpoint ;import javax.websocket.server.ServerApplicationConfig ;import javax.websocket.server.ServerEndpointConfig ;public class ScanWebSocketSeverConfig implements ServerApplicationConfig { @Override public Set <ServerEndpointConfig > getEndpointConfigs(Set <Class <? extends Endpoint >> scanned) { Set <ServerEndpointConfig > result = new HashSet <ServerEndpointConfig >(); /* if (scanned.contains(EchoWsChatSever .class)) { result .add(ServerEndpointConfig .Builder .create(EchoWsChatSever .class, "/echo" ).build()); }*/ return result ; } @Override public Set <Class <?>> getAnnotatedEndpointClasses(Set <Class <?>> scanned) { Set <Class <?>> results = new HashSet <Class <?>>(); for (Class <?> clazz : scanned) { if (clazz.getPackage().getName().startsWith("com.websocket." )) { System .out .println("find end point : " + clazz.getName()); results.add(clazz); } } return results; } }
echo.html <html > <head > <title > Web Socket Echo Test</title > <script > var ws = null ; var count = 0 ; function setConnected (connected ) { document .getElementById('connect' ).disabled = connected; document .getElementById('disconnect' ).disabled = !connected; document .getElementById('echo' ).disabled = !connected; } function connect ( ) { var target = document .getElementById('target' ).value; if (target == '' ) { alert('Please select server side connection implementation.' ); return ; } if ('WebSocket' in window ) { ws = new WebSocket(target); } else if ('MozWebSocket' in window ) { ws = new MozWebSocket(target); } else { alert('WebSocket is not supported by this browser.' ); return ; } ws.onopen = function ( ) { setConnected(true ); log('Info: WebSocket connection opened.' ); }; ws.onmessage = function (event ) { log('Received: ' + event.data); if (event.data instanceof ArrayBuffer ) { var bytes = new Uint8Array (event.data); alert(bytes.length + " : " + bytes[0 ]); } }; ws.onclose = function (event ) { setConnected(false ); log('Info: WebSocket connection closed, Code: ' + event.code + (event.reason == "" ? "" : ", Reason: " + event.reason)); }; } function disconnect ( ) { if (ws != null ) { ws.doClose(); ws = null ; } setConnected(false ); } function echo ( ) { if (ws != null ) { var message = document .getElementById('message' ).value; log('Sent: ' + message); ws.send(JSON .stringify({'textMessage' : message})); count++ } else { alert('WebSocket connection not established, please connect.' ); } } function log (message ) { var echomsg = document .getElementById('echomsg' ); var p = document .createElement('p' ); p.style.wordWrap = 'break-word' ; p.appendChild(document .createTextNode(message)); echomsg.appendChild(p); while (echomsg.childNodes.length > 25 ) { echomsg.removeChild(console .firstChild); } echomsg.scrollTop = console .scrollHeight; } document .addEventListener("DOMContentLoaded" , function ( ) { var noscripts = document .getElementsByClassName("noscript" ); for (var i = 0 ; i < noscripts.length; i++) { noscripts[i].parentNode.removeChild(noscripts[i]); } }, false ); </script > </head > <body > <div > <h4 > URL - ws://localhost:8080/websocket/echo</h4 > <input id ="target" type ="text" size ="40" style ="width: 350px" /> </div > <div > <button id ="connect" onclick ="connect();" > Connect</button > <button id ="disconnect" disabled ="disabled" onclick ="disconnect();" > Disconnect</button > </div > <div > <textarea id ="message" style ="width: 350px" > Here is a message!</textarea > </div > <div > <button id ="echo" onclick ="echo();" disabled ="disabled" > Echo message</button > </div > <div id ="echomsg" > </div > </body > </html >
运行 打包部署到tomcat之后,启动chrom浏览器,输入地址:http://localhost:8080/websocket/echo.html
Auction WebSocket
WebSocketDeviceServlet 类 买入类或者 拍卖类发起 WebSocket 长连接后,服务端接受请求的是 WebSocketDeviceServlet 类,跟传统 HttpServlet 不同的是,WebSocketDeviceServlet 类实现 createWebSocketInbound 方法,类似 SocketServer 的 accept 方法,新生产的 WebSocketInbound 实例对应客户端 HTTP 长连接,处理与客户端交互功能。 WebSocketDeviceServlet 服务端代码示例如下:
public class WebSocketDeviceServlet extends org .apache .catalina .websocket .WebSocketServlet { private static final long serialVersionUID = 1 L; @Override protected StreamInbound createWebSocketInbound(String subProtocol, HttpServletRequest request) { WebSocketDeviceInbound newClientConn = new WebSocketDeviceInbound (request); WebSocketDeviceInboundPool .addMessageInbound(newClientConn); return newClientConn; } }
WebSocketServlet 是 WebSocket 协议的后台监听进程,和传统 HTTP 请求一样,WebSocketServlet 类似 Spring/Struct 中的 Servlet 监听进程,只不过通过客户端 ws 的前缀指定了其监听的协议为 WebSocket。 WebSocketDeviceInboundPool 实现了类似 JDBC 数据库连接池的客户端 WebSocket 连接池功能,并统一处理 WebSocket 服务端对单个客户端/多个客户端(同组 买家类拍卖物品)的消息推送,详见 WebSocketDeviceInboundPool 代码类解释。
WebSocketDeviceInboundl 类 WebSocketDeviceInbound 类为每个 买家类和 卖家类拍卖物品验证登录后,客户端建立的 HTTP 长连接的对应后台服务类,类似 Socket 编程中的 SocketServer accept 后的 Socket 进程,在 WebSocketInbound 中接收客户端发送的实时位置信息等消息,并向客户端(卖家类拍卖物品)发送下属 买家类拍卖物品实时位置信息及位置分析结果数据,输入流和输出流都是 WebSocket 协议定制的。WsOutbound 负责输出结果,StreamInbound 和 WsInputStream 负责接收数据:
public class WebSocketDeviceInbound extends MessageInbound { private final HttpServletRequest request; private DeviceAccount connectedDevice; public DeviceAccount getConnectedDevice () { return connectedDevice; } public void setConnectedDevice (DeviceAccount connectedDevice) { this .connectedDevice = connectedDevice; } public HttpServletRequest getRequest () { return request; } public WebSocketDeviceInbound (HttpServletRequest request) { this .request = request; DeviceAccount connectedDa = (DeviceAccount)request.getSession(true ).getAttribute("connectedDevice" ); if (connectedDa == null ) { String deviceId = request.getParameter("id" ); DeviceAccountDao deviceDao = new DeviceAccountDao(); connectedDa = deviceDao.getDaById(Integer.parseInt(deviceId)); } this .setConnectedDevice(connectedDa); } @Override protected void onOpen (WsOutbound outbound) { / } @Override protected void onClose (int status) { WebSocketDeviceInboundPool.removeMessageInbound(this ); } @Override protected void onBinaryMessage (ByteBuffer message) throws IOException { throw new UnsupportedOperationException("Binary message not supported." ); } @Override protected void onTextMessage (CharBuffer message) throws IOException { WebSocketDeviceInboundPool.processTextMessage(this , message.toString()); } public void sendMessage (BaseEvent event) { String eventStr = JSON.toJSONString(event); try { this .getWsOutbound().writeTextMessage(CharBuffer.wrap(eventStr)); } catch (IOException e) { e.printStackTrace(); } } }
connectedDevice 是当前连接的 A/卖家类客户端拍卖物品类实例,在这里做为成员变量以便后续处理交互。
sendMessage 函数向客户端发送数据,使用 Websocket WsOutbound 输出流向客户端推送数据,数据格式统一为 JSON。
onTextMessage 函数为客户端发送消息到服务器时触发事件,调用 WebSocketDeviceInboundPool 的 processTextMessage 统一处理 买家类拍卖物品的登入,更新位置,离线等消息。
onClose 函数触发关闭事件,在连接池中移除连接。
WebSocketDeviceInbound 构造函数为客户端建立连接后,WebSocketServlet 的 createWebSocketInbound 函数触发,查询 买家类/卖家类拍卖物品在后台数据库的详细数据并实例化 connectedDevice 做为 WebSocketDeviceInbound 的成员变量,WebSocketServlet 类此时将新的 WebSocketInbound 实例加入自定义的 WebSocketDeviceInboundPool 连接池中,以便统一处理 A/B 拍卖物品组员关系及位置分布信息计算等业务逻辑。
WebSocketDeviceInboundl 类 WebSocketInboundPool 类: 由于需要处理大量 买家类 卖家类拍卖物品的实时消息,服务端会同时存在大量 HTTP 长连接,为统一管理和有效利用 HTTP 长连接资源,项目中使用了简单的 HashMap 实现内存连接池机制,每次拍卖物品登入新建的 WebSocketInbound 都放入 WebSocketInbound 实例的连接池中,当拍卖物品登出时,从连接池中 remove 对应的 WebSocketInbound 实例。
此外,WebSocketInboundPool 类还承担 WebSocket 客户端处理 买家类和 卖家类拍卖物品间消息传递的作用,在客户端发送 买家类拍卖物品登入、登出及位置更新消息的时候,服务端 WebSocketInboundPool 进行位置分布信息的计算,并将计算完的结果向同时在线的 卖家类拍卖物品推送。
代码如下:
public class WebSocketDeviceInboundPool { private static final ArrayList<WebSocketDeviceInbound> connections = new ArrayList<WebSocketDeviceInbound>(); public static void addMessageInbound (WebSocketDeviceInbound inbound ) { DeviceAccount da = inbound.getConnectedDevice(); System.out .println("新上线拍卖物品 : " + da.getDeviceNm()); connections.add(inbound); } public static ArrayList<DeviceAccount> getOnlineDevices ( ) { ArrayList<DeviceAccount> onlineDevices = new ArrayList<DeviceAccount>(); for (WebSocketDeviceInbound webClient: connections) { onlineDevices.add(webClient.getConnectedDevice()); } return onlineDevices; } public static WebSocketDeviceInbound getGroupBDevices (String group ) { WebSocketDeviceInbound retWebClient = null ; for (WebSocketDeviceInbound webClient: connections) { if (webClient.getConnectedDevice().getDeviceGroup().equals(group ) && webClient.getConnectedDevice().getType().equals("B" )) { retWebClient = webClient; } } return retWebClient; } public static void removeMessageInbound (WebSocketDeviceInbound inbound ) { System.out .println("拍卖物品离线 : " + inbound.getConnectedDevice()); connections.remove(inbound); } public static void processTextMessage (WebSocketDeviceInbound inbound, String message ) { BaseEvent receiveEvent = (BaseEvent)JSON.parseObject(message.toString(), BaseEvent.class); DBEventHandleImpl dbEventHandle = new DBEventHandleImpl(); dbEventHandle.setReceiveEvent(receiveEvent); dbEventHandle.HandleEvent(); if (receiveEvent.getEventType() == EventConst.EVENT_MATCHMATIC_RESULT || receiveEvent.getEventType() == EventConst.EVENT_GROUP_DEVICES_RESULT || receiveEvent.getEventType() == EventConst.EVENT_A_REPAIRE) { String clientDeviceGroup = ((ArrayList<DeviceAccount>) receiveEvent.getEventObjs()).get (0 ).getDeviceGroup(); WebSocketDeviceInbound bClient = getGroupBDevices(clientDeviceGroup); if (bClient != null ) { sendMessageToSingleClient(bClient, dbEventHandle.getReceiveEvent()); } } } } public static void sendMessageToAllDevices (BaseEvent event ) { try { for (WebSocketDeviceInbound webClient : connections) { webClient.sendMessage(event ); } } catch (Exception e) { e.printStackTrace(); } } public static void sendMessageToSingleClient (WebSocketDeviceInbound webClient, BaseEvent event ) { try { webClient.sendMessage(event ); } catch (Exception e) { e.printStackTrace(); } } }
addMessageInbound 函数向连接池中添加客户端建立好的连接。
getOnlineDevices 函数获取所有的连线的 A/卖家类拍卖物品。
removeMessageInbound 函数实现 买家类拍卖物品或者 卖家类拍卖物品离线退出(服务端收到客户端关闭 WebSocket 连接事件,触发 WebSocketInbound 中的 onClose 方法),从连接池中删除连接拍卖物品客户端的连接实例。
processTextMessage 完成处理客户端消息,这里使用了消息处理的机制,包括解码客户端消息,根据消息构造 Event 事件,通过 EventHandle 多线程处理,处理完后向客户端返回,可以向该组 B 拍卖物品推送消息,也可以向发送消息的客户端推送消息。
sendMessageToAllDevices 函数实现发送数据给所有在线 A/卖家类拍卖物品客户端。sendMessageToSingleClient 函数实现向某一 A/卖家类拍卖物品客户端发送数据。
websocket.js客户端 客户端代码 websocket.js,客户端使用标准 HTML5 定义的 WebSocket API,从而保证支持 IE9+,Chrome,FireFox 等多种浏览器,并结合 jQueryJS 库 API 处理 JSON 数据的处理及发送。代码如下:
var websocket=window .WebSocket || window .MozWebSocket; var isConnected = false ;function doOpen ( ) { isConnected = true ; if (deviceType=='B' ){ mapArea='mapB' ; doLoginB(mapArea); } else { mapArea='mapA' ; doLoginA(mapArea); } } function doClose ( ) {showDiagMsg("infoField" ,"已经断开连接" , "infoDialog" ); isConnected = false ; } function doError ( ) {showDiagMsg("infoField" ,"连接异常!" , "infoDialog" ); isConnected = false ; } function doMessage (message ) {var event = $.parseJSON(message.data);doReciveEvent(event); } function doSend (message ) {if (websocket != null ) {websocket.send(JSON .stringify(message)); } else { showDiagMsg("infoField" ,"您已经掉线,无法与服务器通信!" , "infoDialog" ); } } function initWebSocket (wcUrl ) {if (window .WebSocket) {websocket = new WebSocket(encodeURI (wcUrl)); websocket.onopen = doOpen; websocket.onerror = doError; websocket.onclose = doClose; websocket.onmessage = doMessage; } else {showDiagMsg("infoField" ,"您的拍卖物品不支持 webSocket!" , "infoDialog" ); } }; function doReciveEvent (event ) {if (event.eventType==101 ){showDiagMsg("infoField" ,"拍卖物品不存在或拍卖物品号密码错!" , "infoDialog" ); websocket.close(); } else if (event.eventType==104 ||event.eventType==103 ){clearGMapOverlays(mapB); $.each(event.eventObjs,function (idx,item ) { var deviceNm = item.deviceNm; var deviceLocale = new BMap.Point(item.lng,item.lat); var newMarker; if (item.status=='target' ){ newMarker = addMarkToMap(mapB,deviceLocale,deviceNm,true ); } else { newMarker = addMarkToMap(mapB,deviceLocale,deviceNm); } markArray.push(newMarker); }); showDiagMsg("infoField" ,"有新报修拍卖物品或拍卖物品离线, 地图已更新!" , "infoDialog" ); } }
oOpen 回调函数处理打开 WebSocket,买家类拍卖物品或者卖家类拍卖物品连接上 WebSocket 服务端后,将初始化地图并显示默认位置,然后向服务端发送拍卖物品登入的消息。
doReciveEvent 函数处理关闭 WebSocket,买家类/卖家类拍卖物品离线(退出移动终端上的应用)时,服务端关闭 HTTP 长连接,客户端 WebSocket 对象执行 onclose 回调句柄。
initWebSocket 初始化 WebSocket,连接 WebSocket 服务端,并设置处理回调句柄,如果浏览器版本过低而不支持 HTML5,提示客户拍卖物品不支持 WebSocket。
doSend 函数处理客户端向服务端发送消息,注意 message 是 JSON OBJ 对象,通过 JSON 标准 API 格式化字符串。
doMessage 函数处理 WebSocket 服务端返回的消息,后台返回的 message 为 JSON 字符串,通过 jQuery 的 parseJSON API 格式化为 JSON Object 以便客户端处理 doReciveEvent 函数时客户端收到服务端返回消息的具体处理,由于涉及大量业务逻辑在此不再赘述。
项目类图
结果分析 Java EE 7 新特性中加强了对 HTML 5 动态可伸缩应用程序的支持、提高了开发人员的生产力和进一步满足了企业的苛刻需求。Java EE 7 使得开发人员可以使用依赖注入和默认资源的样本文件来减少代码的编写;更好地支持最新的 Web 应用和框架,拥有更好的扩展性和更丰富的功能;使得企业从便捷式批处理等新功能中获益。
(1)提高开发人员的生产力
从 Java EE 5 开始,重心就一直放在提高开发人员的生产力上。这对于 Java 开发者来说非常重要,因为这使得使用 Java EE 进行开发更加便捷,更重要的是能够满足快速管理和生产的需求。鉴于此,Java EE 7 大大提高了开发人员的生产力。首先,减少了编写大量核心业务逻辑所需要的样板代码。其次,该平台引入更多的注释 POJOS 来降低 XML 配置的复杂性。最后,Java EE 7 使用更紧密集成的技术,提供一个更加无缝的开发体验。
(2)减少冗余代码
Java EE 7 一直在致力于减少在核心业务逻辑代码运行前必须执行的样板代码。减少样板代码的三大核心区域是默认资源、JMS 2.0 和 JAX-RS 客户端 API。默认资源是一个新的功能,要求平台提供商预配置一个默认的数据源和一个默认的 JMS 连接工厂。这可以让开发人员直接使用默认的资源而无需进行额外的定义。JMS2.0 在可伸缩性和可移植性上经历了重大的改进,减少了冗余代码,已运用在无数的产品部署上,事实证明它是一个良好的规范,能够较好地满足企业的需求。
(3)更多带注释的POJO
通过注释 Java EE 使开发人员更专注于 Java 对象的编程而无需关注繁琐的配置。
CDI 现在默认情况下已不需要使用 beans.xml 文件就可以直接使用。开发人员可以不需要任何配置而是简单的使用 @Inject 来注入任何 Java 对象。包括新的资源注释 @JMSDestinationDefinition 和 @MailSessionDefinition ,使得开发人员在源代码中就可以指定元数据资源,简化了 DevOps 体验。
(4)更紧密集成的平台
Java EE 6 引入了 Managed Beans 1.0 作为第一步来朝着 EJBs、JSF Managed Beans 和 CDI beans 发展。Java EE 7 继承了这一点,例如,对 JSF Managed Beans 进行了改进来更好支持 CDI Beans。Java EE 7 为平台引入了易用的 EJB 容器管理事物,使用基于 CDI 拦截器的解决方案来保证事务可用在 CDI managed beans 和其它 Java EE 组件中,把注释 @Transactional 应用到任何 CDI bean 或者任何支持事务的方法中。
Bean Validation 在 Java EE 7 中使用广泛,现在可以用于方法级别的验证,包括内置和自定义的约束。约束可被应用于方法的参数以及返回值。约束也可以使用灵活渲染和违反约束的字符串格式的 Java EE 的表达语言。
Bean Validation 也延伸到 JAX-RS 2.0。注释约束可以应用到公共构造函数的参数、方法参数、字段和 bean 的属性。此外,他们还可以修饰资源类、实体参数和资源的方法。例如,约束可以通过 @ POST 和 @ PUT 应用于 JAX-RS 方法参数来验证表单提交的数据。
(5)通过精简现有技术来简化Java EE
Java EE 7 中新增加了许多新的特性,有些老的特性和功能已经被更简单的特性所替代或直接弃用。Java EE 6 为过时技术的弃用和功能的修剪引入了一个正式的流程,以下的 API 在 Java EE 7 中已成可选,但在 Java EE 8 中将会被移除:
Java EE Management (JSR-77),原本是用于为应用 服务器创建监控管理的 API,可各大供应商对此 API 热情并不高涨;
Java EE Application Deployment (JSR-88),JSR 88 是当初用于 J2EE 应用程序在应用 服务器上进行配置和部署的标准 API 。可是该 API 始终没有得到众供应商的支持;
JAX-RPC,是早期通过 RPC 调用和 SOAP web services 进行交互的编程模型。由于 Web services 成熟后从 RPC 模型中分离出来,被更加健壮和具备更多特性的 JAX-WS API 所替代;
EJB 2.x Entity Beans CMP,复杂、笨重、过度复杂的 EJB2.* 的 Entity Bean 模型已经被 Java EE 5 的基于 POJO 的流行轻量级 JPA 持久层模型所代替。
(6)对 HTML 5 动态可伸缩应用程序的支持
HTML5 是包括 HTML、JavaScript 和 CSS3 在内的一套技术组合,它加快了开发人员创建高度互动的应用程序的步伐。开发出的应用程序都是以高度互动的方式提供实时的数据,如聊天应用程序,比赛实况报 导等,并且这些应用程序只需要编写一次,就可以应用在桌面、移动客户端等不同设备上,具有非常好的跨平台性。这些高度动态的应用程序,使得用户可以在任何 地点任何时间进行访问,从而对服务器端向客户端传送数据的规模提出了更高的要求。Java EE 7 在更新现有技术如 JAX-RS 2.0、Java Server Faces 2.2、和 Servlet 3.1 NIO 基础上,又借助新的应用技术 WebSockets 和 JSON 处理为支持动态应用程序 HTML5 奠定了坚实的基础。
(7)低延迟数据交换:Java API for WebSocket 1.0
越来越多的 web 应用程序依赖于从中央服务器及时获取并更新数据。基于 HTTP 的 WebSockets 为解决低延迟和双向通信提供了一种解决方案。在 WebSocket API 的最基层是一个带注释的 Java 对象(POJO),如下边代码所示: 带注释的 Java 对象(POJO)
@ServerEndpoint ("/test" ) public class TestEndpoint{ @OnOpen public void onOpen (...){ } @OnClose public void onClose (...){ } @OnError public void onError (...){ } @OnMessage public void testMessage (String message,...){ } }
通过注释 @ServerEndpoint 来指定一个 URI。诸如客户端连接、接收消息和客户端断开这样的回调函数都可以用注释来指定。WebSocket API 的最基层支持发送和接收简单文本和二进制信息。API 的简单性也使得开发人员可以快速入门。
当然,功能丰富的应用拥有更复杂的需求,因此需要支持对最基础的网络协议进行控制和自定义,而 WebSocket API 正好能够满足以上需求。另外,WebSocket 利用现有 Web 容器的安全特性,开发人员只需付出较少的代价就可以建立良好的保密通信。
(8)简化应用数据分析和处理:Java API for JSON Processing 1.0 JSON 作为一个轻量级的数据交换格式被应用在许多流行的 Web 服务中用来调用和返回数据。许多流行的在线服务都是使用基于 JSON 的 RESTful 服务。在 Java EE 7 之前,Java 应用程序使用了不同的类库去生成和解析 RESTful 服务中的 JSON 对象。然而,现在这个功能已被标准化。
通过 Java API 中的 JSON Processing 1.0,JSON 处理过程标准化为一个单一的 API,应用程序不需要使用第三方的类库。这样使得应用程序更小更简便。同时 API 包括了支持任何转换器和生成器实现的插件,使得开发人员可以选择最好的实现方式去完成工作。
(9)可扩展的RESTful服务:JAX-RS 2.0 JAX-RS 2.0 增加了异步响应处理,这对于支持对数据有着高要求的 HTML5 客户端的扩展是至关重要的。异步处理是一种更好更有效利用线程处理的技术。在服务器端,处理请求的线程在等待外部任务去完成时应该避免阻塞,从而保证在这 一时间段内到达的其他请求能够得到响应。
同样的,在客户端,一个发出请求的线程在等待响应的时候也会发生阻塞,这影响了应用程序的性能。新 的 JAX-RS 2.0 异步客户端 API 使得客户端调用 RESTful 可以和其他客户端活动并行执行。异步的好处是使得一个客户端可以同时调用多个后台服务,对于一个使用者来说减少了总体的延迟时间。
同时为了增强 RESTful 服务,JAX-RS 2.0 开发人员可以使用过滤器和实体拦截器。这样开发人员就可以使用标准的 API 来实现过滤和拦截功能,使开发过程变得更加便捷和高效。
(10)增强开发的易用性:JSF 2.2
JavaServer Faces (JSF) 是一种用于构建 Web 应用程序的 Java 新标准框架。它提供了一种以组件为中心来开发 Java Web 用户界面的方法,从而简化了开发。在这个版本中,JSF 增加了对 HTML5 的支持。JSF 2.2 增加了一个叫“pass-through elements”的新功能。并为现有的元素增加了一系列的新属性,如输入元素“tel”、“range”和“date”等。
不幸的是,现有的 JSF 组件不能识别这些新的属性,因此 JSF 应用程序会忽略这些属性不能进行使用,直到创建专有的解决方案。对于“pass-through elements”,JSF 渲染器将会忽略这些元素,只是把它们传给支持 HTML5 的浏览器,这使得可以利用现有的 JSF 组件来利用 HTML5 的特性来正常渲染。
JSF 引入了一个新的 pass-through 命名空间 http://xmlns.jcp.org/jsf/passthrough
映射到“p:”,任何组件的 name/value 对都可以以“p:” 开始,然后传给浏览器。如清单 2 所示,HTML 5 “type=color”不需要 JSF 组件的任何解析就可以传递给浏览器。
<h:inputText Value =”#{bean.color}” P:type =”color” />
HTML5 的动态性使得服务器端处理信息更新的请求不断增多。在 Java EE 6,Servlet 异步 I/O 通过移除“一个请求需要一个线程”的限制,使一个线程可以处理多个并发请求。这可以使 HTML5 客户端快速得到响应。但是,如果服务器端读取数据的速度比客户端发送的速度要快,那么可能会由于缓慢的客户端连接而不能提供更多的数据导致线程阻塞,这样 就限制了扩展性。在 Java EE 7 中使用新的事件驱动 API Servlet 3.1 从客户端读取数据将不会造成阻塞。如果有数据可用时,Servlet 线程将会读取和处理这些数据,否则就去处理其他请求。
(11)满足苛刻的企业需求
Java EE 十几年来一直努力满足企业的需求,使用 Java 连接器连接到企业服务端、使用 Java 事务支持事务处理、使用 Java 消息服务让系统间可以进行相互通信。现在企业希望利用开发人员的 Java 技能编写基于标准的 API 并能够跨平台运行的批处理应用程序。企业也需构建高度可扩展的应用来满足更高的服务要求并提高现有资产的利用率。Concurrency Utilities 使得 Java EE 开发人员编写可扩展的应用程序成为可能。
(12)在Java平台中,提高批处理应用程序的效率使开发过程变得更加便捷和高效
绝 大部分的 Java EE 应用都是在线用户驱动的系统,但同时有一些需要进行批处理的服务器端应用程序,尤其是离线分析和 ETL 等。这些面向批处理的应用程序是非交互式的、需要长时间运行,这些任务通常需要大量计算,同时可以按顺序或者并行执行,并可以通过特定的事件启动或者定时 调度。批处理较适合选择闲置的时间进行处理,这样可以有效利用计算机资源。
以前,对于批处理程序没有标准的 Java 编程模型。现在,批处理应用程序为 Java 平台提供了如图4.1 非常容易理解的模型。批处理过程包括任务、步骤、存储库、读取 - 处理 - 写入模式和工作流等。
如图4.1 所示,一个任务 job 代表一系列执行离散业务流程但又密切相关的步骤。步骤可以按顺序或者并行执行。同时,在同一个工作流,当前步骤是可选的,基于前一步骤的运行结果决定当前 步骤将被执行或者跳过。另外,步骤可以根据实际的需要被重新执行。存储库 (repository) 存储了当前任务的信息,比如任务的最后执行时间。 通过操作员 (operator) 可以对任务进行排序、开始、停止、暂停和取消操作。