隨着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("===============");
// Log the resulting string
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.WebListener
public 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=---------------------------263081694432439
Content-Length: 441
-----------------------------263081694432439
Content-Disposition: form-data; name="file"; filename="sample.txt"
Content-Type: text/plain
Data from sample file
-----------------------------263081694432439
Content-Disposition: form-data; name="destination"
/tmp
-----------------------------263081694432439
Content-Disposition: form-data; name="upload"
Upload
-----------------------------263081694432439--
該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");
// Create path components to save the file
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"?>
<xs:schema version="1.0" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="address" type="address" />
<xs:element name="customer" type="customer" />
<xs:complexType name="address">
<xs:sequence>
<xs:element name="id" type="xs:long" minOccurs="0" />
<xs:element name="number" type="xs:int" />
<xs:element name="street" type="xs:string" />
<xs:element name="city" type="xs:string" />
<xs:element name="province" type="xs:string" />
<xs:element name="zip" type="xs:string" />
<xs:element name="country" type="xs:string" />
</xs:sequence>
</xs:complexType>
<xs:complexType name="customer">
<xs:sequence>
<xs:element name="firstname" type="xs:string" />
<xs:element name="lastname" type="xs:string" />
<xs:element ref="address" />
<xs:element name="email" type="xs:string" />
<xs:element name="phone" type="xs:string" />
</xs:sequence>
<xs:attribute name="id" type="xs:int" use="required" />
</xs:complexType>
</xs: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 GET
204 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請求:
Accept: application/json
方法還可以接受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() {
// Implementation here
}
@GET
@Path("/books/{bookId}")
@Produces(MediaType.APPLICATION_JSON)
public JsonObject getBook(@PathParam("bookId") Long id) {
// Implementation here
}
@POST
@Path("/books")
@Consumes(MediaType.APPLICATION_JSON)
public Response addBook(@Context final UriInfo uriInfo, JsonObject obj) {
// Implementation here
}
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"); // means open it
}
@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) {
// Ignore
}
}
}
}
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() {
// Remove elements with "noscript" class - <noscript> is not allowed in XHTML
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 = 1L;
@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");
}
}
//初始話 WebSocket
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;
//google api
// var deviceLocale = new google.maps.LatLng(item.lag,item.lat);
//baidu api
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) 可以對任務進行排序、開始、停止、暫停和取消操作。