從一次編譯出發梳理概念: Jetty,Jersey,hk2,glassFish,Javax,Jakarta

羅西的思考發表於2020-09-19

從一次編譯出發梳理概念: Jetty,Jersey,hk2,glassFish,Javax,Jakarta

0x00 摘要

本文藉助一次開源專案的編譯過程,梳理了一些java相關概念,與大家分享此文。

0x01 緣由

最近在編譯螞蟻金服的sofa-registry,因為不可名狀的原因,無法完全下載依賴的maven包,所以只能手動一個一個下載。事實證明,這是一個痛苦的過程,因為各種java包環環相扣,於是一個個java相關概念躍入眼簾。索性把這些概念一一梳理下,與大家分享。

比如從下面 mvn dependency:tree 的輸出結果看,我們遇到的概念或者名詞就有:glassfish,javax.ws.rs,jersey,jetty,hk2,javax.inject,javax.annotation ......

[INFO] +- com.alipay.sofa:registry-remoting-http:jar:5.4.2:compile
[INFO] |  +- org.glassfish.jersey.core:jersey-server:jar:2.26:compile
[INFO] |  |  +- org.glassfish.jersey.core:jersey-common:jar:2.26:compile
[INFO] |  |  |  \- org.glassfish.hk2:osgi-resource-locator:jar:1.0.1:compile
[INFO] |  |  +- javax.ws.rs:javax.ws.rs-api:jar:2.1:compile
[INFO] |  |  +- org.glassfish.jersey.media:jersey-media-jaxb:jar:2.25.1:compile
[INFO] |  |  |  \- org.glassfish.hk2:hk2-api:jar:2.5.0-b32:compile
[INFO] |  |  +- javax.annotation:javax.annotation-api:jar:1.2:compile
[INFO] |  |  \- org.glassfish.hk2.external:javax.inject:jar:2.5.0-b42:compile
[INFO] |  +- org.glassfish.jersey.inject:jersey-hk2:jar:2.26:compile
[INFO] |  |  \- org.glassfish.hk2:hk2-locator:jar:2.5.0-b42:compile
[INFO] |  |     +- org.glassfish.hk2:hk2-utils:jar:2.5.0-b42:compile
[INFO] |  |     |  \- javax.inject:javax.inject:jar:1:compile
[INFO] |  |     \- org.javassist:javassist:jar:3.21.0-GA:compile
[INFO] |  +- org.eclipse.jetty:jetty-server:jar:9.4.19.v20190610:compile

0x02 概念

2.1 JSR

JSR 是 Java Specification Requests 的縮寫,意思是Java 規範提案。是指向 JCP (Java Community Process)提出新增一個標準化技術規範的正式請求。任何人都可以提交JSR,以向Java平臺增添新的API和服務。JSR已成為Java界的一個重要標準。

2.2 javax

java 和 javax 都是Java的API(Application Programming Interface)包,java是核心包,javax的x是extension的意思,也就是擴充套件包。

java類庫是java釋出之初就確定了的基礎庫,而javax類庫則是在上面增加的一層東西,就是為了保持版本相容要儲存原來的,但有些東西有了更好的解決方案,所以就加上些。典型的就是awt(Abstract Windowing ToolKit) 和swing。

2.3 JSR311

2.3.1 JSR311

JSR311是java中實現Restful Web Service的API規範(JSR311: JAX-RS: The Java API for RESTful Web Services)

JSR311有一個重要目標:使用註解(annotation)把POJO暴露成Web Service,這樣就比較輕量級。

  • jsr311-api - 這是JAX-RS 1.x系列的官方規範jar
  • javax.ws.rs-api - 這是JAX-RS 2.x系列的官方規範jar

2.3.2 javax.ws.rs

java.ws.rs 是JAX-RS規範中定義的包名。

jax-rs 全稱 Java API for RESTful Services,規範目前版本是 2.0。

jax-rs 中定義了:

  • 一組啟動方式 (以jee作為http容器,還是配合servlet作為http容器)
  • 一組註解 @GET, @POST, @DELETE, @PUT, @Consumes ... 通過 POJO Resource類, 提供Rest服務

就像 JSR 規範中定義了 Servlet 是 以繼承 HttpServlet 並重寫 doGet, doPost, do... 方法 一樣。只要遵循 這套標準的 我們我們都可以稱之為 Servlet 程式。你寫的 Servlet 程式,可以不經過任何修改,放到任何實現 Servlet 容器中執行。類似,你寫的 jax-rs 程式,可以不經任何修改,和任何 jax-rs 框架配合使用

而 Spring MVC 是以 Servlet 為http容器,並自己構建了一套Api,沒有遵循 jax-rs 規範。

2.3.3 框架

目前實現 jax-rs 標準的框架有很多:

  • Apache CXF,開源的Web服務框架。
  • Jersey, 由Sun提供的JAX-RS的參考實現。
  • RESTEasy,JBoss的實現。
  • Restlet,由Jerome Louvel和Dave Pawson開發,是最早的REST框架,先於JAX-RS出現。
  • Apache Wink,一個Apache軟體基金會孵化器中的專案,其服務模組實現JAX-RS規範

2.3.4 Jersey

Jersey 是 JAX-RS(JSR311)開源參考實現。

SpringMVC在開發REST應用時,是不支援 JSR311/JSR339 標準的。如果想要按照標準行事,最常用的實現了這兩個標準的框架就是Jersey和CxF了。但因為Jersey是最早的實現,也是JSR311參考的主要物件,可以說Jersey就是事實上的標準(類似Hibernate是JPA的事實上的標準),也是現在使用最為廣泛的REST開發框架之一。

Jersey用於構建 RESTful Web service。此外 Jersey 還提供一些額外的 API 和擴充套件機制,所以開發人員能夠按照自己的需要對 Jersey 進行擴充套件。

sun.Jersey 和 glassfish.Jersey 是Jersey的兩個版本,對應1.x和2.x,其中:

  • 1.x中Jersey的包是以com.sun開頭。
  • 2.x是以org.glassfish為字首。

2.4 JSR-330

2.4.1 JSR-330

JSR-330Java 的依賴注入標準。定義瞭如下的術語描述依賴注入:

  • A 型別依賴 B型別(或者說 B 被 A 依賴),則 A型別 稱為”依賴(物) dependency
  • 執行時查詢依賴的過程,稱為”解析 resolving“依賴
  • 如果找不到依賴的例項,稱該依賴是”不能滿足的 unsatisfied
  • 在”依賴注入 dependency injection”機制中,提供依賴的工具稱為 ”依賴注入器 dependency injector”

2.4.2 javax.inject

標準對依賴注入的使用進行了定義, 但是對實現和配置未定義。Java EEjavax.inject對應此標準。其中也僅定義了依賴注入的使用(即通過註解),同樣也未定義依賴注入的配置方式和實現方式。

javax.inject 提供如下5個註解(Inject、Qualifier、Named、Scope、Singleton)和1個介面(Provider)。

2.4.3 框架

支援JSR-330的框架有很多:

  • Android下面的Dagger2就是基於這個規範。在dagger2 中用的JSR-330標準註釋有:@Inject @Qualifier @Scope @Named等。
  • Guice是一個由Google實現的針對Java 6以上版本的流行的、輕量級的DI框架。
  • 而其他的注入框架如Spring也支援JSR-330。

當使用JSR-330標準的註解時,瞭解其和Spring註解的不同點也是十分必要的,參考如下表。

Spring javax.inject.* javax.inject 限制
@Autowired @Inject @Inject註解沒有required屬性,但是可以通過Java 8的Optional取代
@Component @Named JSR_330標準並沒有提供複合的模型,只有一種方式來識別元件
@Scope(“singleton”) @Singleton JSR-330預設的作用域類似Spring的prototype,然而,為何和Spring的預設保持一致,JSR-330標準中的Bean在Spring中預設也是單例的。如果要使用非單例的作用域,開發者應該使用Spring的@Scope註解。java.inject也提供一個@Scope註解,然而,這個註解僅僅可以用來建立自定義的作用域時才能使用。
@Qualifier @Qualifier/@Named javax.inject.Qualifier僅僅是一個元註解,用來構建自定義限定符的。而String的限定符(比如Spring中的@Qualifier)可以通過javax.inject.Named來實現
@Value - 不等價
@Required - 不等價
@Lazy - 不等價
ObjectFactory Provider javax.inject.Provider是SpringObjectFactory的另一個選擇,通過get()方法來代理,Provider可以和Spring的@Autowired組合使用

2.4.4 hk2

HK2是一個輕量級動態依賴注入框架,它是JSR-330的實現

HK2的全稱為“Hundred Kilobytes Kernel”,包括Modules Subsytem和Component Model兩部分。SUN在其開源的GlassFish J2EE應用伺服器專案中將HK2作為其系統核心實現。

在HK2元件模型中,一個元件的功能是通過服務介面-服務實現的模式宣告的。一個HK2服務介面 標識並描述了一個構建模組或者應用程式擴充套件點。HK2服務實現實現了HK2服務介面。

hk2包為 org.glassfish.hk2

2.5 JSR 250

2.5.1 JSR 250

JSR 250 規範包含用於將資源注入到端點實現類的註釋和用於管理應用程式生命週期的註釋。

2.5.2 javax.annotation

包含 JST 250 標準中的每一個註釋的 Java™ 類的名稱為 javax.annotation.xxx,其中 xxx 是“@”字元後面的註釋的名稱。 例如,@Resource 註釋的 Java 類名為 javax.annotation.resource。

javax.annotation 中主要包含以下幾個註解:

  • @Generated:生成資源的註解,通過該項標記產生的例項是一個資源。類似於Spring中的@Bean註解,用於生成一向資源。
  • @PostConstruct 創造資源之後的回撥處理。
  • @PreDestroy 銷燬資源之前的回撥處理。
  • @Resource 標記使用資源的位置。功能上有些類似於@Autowired、@Inject,但是兩者有不少的差別。
  • @Resources 標記使用多項資源的位置,類似於使用@Autowired向一個列表裝載資料。

2.5.3 框架

仔細看JSR-250定義的這些註解就會發現,他們都是關於“資源”的構建、銷燬、使用的。

Spring實現了@PostConstruct、@PreDestroy和@Resource

2.6 Jakarta

雖然Oracle 決定把 JavaEE 移交給開源組織 Eclipse 基金會,但是不希望 JavaEE 繼續使用 Java 這個名字。於是 Eclipse 做了一項民意調查,最終 JakartaEE 已明顯的優勢勝出。因此Eclipse 宣佈正式將 JavaEE 更名為 JakartaEE

Eclipse基金會也對 Java EE 標準的每個規範進行了重新命名,闡明瞭每個規範在Jakarta EE平臺未來的角色。

新的名稱Jakarta EE是Java EE的第二次重新命名。2006年5月,“J2EE”一詞被棄用,並選擇了Java EE這個名稱。在YouTube還只是一家獨立的公司的時候,數字2就就從名字中消失了,而且當時冥王星仍然被認為是一顆行星。同樣,作為Java SE 5(2004)的一部分,數字2也從J2SE中刪除了,那時谷歌還沒有上市。

因為不能再使用javax名稱空間,Jakarta EE提供了非常明顯的分界線。

  • Jakarta 9(2019及以後)使用jakarta名稱空間。
  • Java EE 5(2005)到Java EE 8(2017)使用javax名稱空間。
  • Java EE 4使用javax名稱空間。

提到java改名,我想起了javaeye網站更名為iteye之後,影響力急劇下降,令人扼腕

2.7 GlassFish

Eclipse Foundation不只是釋出規範。它還發布了Eclipse GlassFish 5.1,這是一個可立即執行的Jakarta EE 8實現。它還被認證為Jakarta EE 8平臺的開源相容實現。

GlassFish 是用於構建 Java EE 5應用伺服器的開源開發專案的名稱。它基於 Sun Microsystems 提供的 Sun Java System Application Server PE 9 的原始碼以及 Oracle 貢獻的 TopLink 永續性程式碼。該專案提供了開發高質量應用伺服器的結構化過程,以前所未有的速度提供新的功能。這是對希望能夠獲得原始碼併為開發 Sun 的下一代應用伺服器(基於 GlassFish)作出貢獻的 Java 開發者作出的回應。該專案旨在促進 Sun 和 Oracle 工程師與社群之間的交流,它將使得所有開發者都能夠參與到應用伺服器的開發過程中來。

過去,新EE功能誕生的過程稱為Java Community Process。

Java SE今天仍然使用JCP。但是,由於EE已經改變了它的所有權,從Oracle到Eclipse Foundation,我們有一個新的獨立流程。它是Eclipse Foundation Specification Process(EFSP),是Eclipse Development Process的擴充套件。

作為JCP的一部分,JSR需要一個具體的參考實現。這有點像實現介面的類。參考實現必須相容以往庫包或其他組織的開發人員建立自己的規範實現。

對於Java EE功能,JCP使用Glassfish作為其參考實現

2.8 Jetty

Jetty 是一個開源的servlet容器,它為基於Java的web容器,例如JSP和servlet提供執行環境。Jetty是使用Java語言編寫的,它的API以一組JAR包的形式釋出。開發人員可以將Jetty容器例項化成一個物件,可以迅速為一些獨立執行(stand-alone)的Java應用提供網路和web連線。由於其輕量、靈活的特性,Jetty也被應用於一些知名產品中,例如ActiveMQ、Maven、Spark、GoogleAppEngine、Eclipse、Hadoop等。

為什麼使用Jetty?

  • 非同步的 Servlet,支援更高的併發量
  • 模組化的設計,更靈活,更容易定製,也意味著更高的資源利用率
  • 在面對大量長連線的業務場景下,Jetty 預設採用的 NIO 模型是更好的選擇
  • 將jetty嵌入到應用中,使一個普通應用可以快速支援 http 服務

2.9 概念關係

以上涉及概念中,若干關係如下( 只是大致邏輯示意圖,不代表繼承等關係 ):

  +--------+                  +--------+                   +---------+            Jakarta
  | JSR311 |                  | JSR330 |                   | JSR 250 |
  +----+---+                  +----+---+                   +----+----+
       |                           |                            |
       v                           v                            v
+------+-----+              +------+-----+             +--------+-------+
|javax.ws.rs |              |javax.inject|             |javax.annotation|
+------+-----+              +------+-----+             +--------+-------+
       |                           |                            |
       |                           v                            |
       |                   +-------+---------+                  |
       |     +-----------+ |org.glassfish.hk2|                  |
       |     |             +-----------------+                  |
       |     |                                                  |
       v     v                                                  |
+------+-----+-------+                                          |
|org.glassfish.jersey| <----------------------------------------+
+-------------------++
                    |
                    v
                +---+---+
                | Jetty |
                +-------+

0x03 在SOFARegistry的使用

我們來看看前面提到的概念中,其中幾個在SOFARegistry中如何使用。

3.1 javax.ws.rs

javax.ws.rs是JSR311的包名。其重要目標是:使用註解(annotation)把POJO暴露成Web Service。

具體舉例如下,可以看到 DecisionModeResource 已經被暴露成 Web Service:

package com.alipay.sofa.registry.server.meta.resource;

import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

@Path("decisionMode")
public class DecisionModeResource {

    @Autowired
    private MetaServerConfig metaServerConfig;

    @POST
    @Produces(MediaType.APPLICATION_JSON)
    public Result changeDecisionMode(DecisionMode decisionMode) {
        ((MetaServerConfigBean) metaServerConfig).setDecisionMode(decisionMode);
        Result result = new Result();
        result.setSuccess(true);
        return result;
    }
}

3.2 jersey和jetty

因為jetty輕量級的特點,在SOFARegistry中,使用了 org.eclipse.jetty.server.Server,從而拉開了一場大戲。

com.alipay.sofa.registry.remoting.jersey.JerseyJettyServer

import javax.ws.rs.ProcessingException;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.glassfish.jersey.internal.guava.ThreadFactoryBuilder;
import org.glassfish.jersey.jetty.JettyHttpContainer;
import org.glassfish.jersey.jetty.internal.LocalizationMessages;
import org.glassfish.jersey.process.JerseyProcessingUncaughtExceptionHandler;
import org.glassfish.jersey.server.ContainerFactory;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.server.spi.Container;

public class JerseyJettyServer implements Server {
	private org.eclipse.jetty.server.Server server;
	
	public static org.eclipse.jetty.server.Server createServer(final URI uri,
                                                        final ResourceConfig resourceConfig,
                                                        final boolean start) {
    JettyHttpContainer handler = 
                 ContainerFactory.createContainer(JettyHttpContainer.class, resourceConfig);  
    final org.eclipse.jetty.server.Server server = new org.eclipse.jetty.server.Server(
            new JettyConnectorThreadPool());
    server.setHandler(handler);
    server.start();
  }
}

3.3 hk2

HK2是一個輕量級動態依賴注入框架,它是JSR-330的實現。其應用十分廣泛且底層,比如在 jersey 中就有各種直接或者間接的使用。

package org.glassfish.jersey.server;

...
import org.glassfish.jersey.internal.inject.Binder;
import org.glassfish.jersey.internal.inject.Bindings;
import org.glassfish.jersey.internal.inject.CompositeBinder;
import org.glassfish.jersey.internal.inject.InjectionManager;
import org.glassfish.jersey.internal.inject.Injections;
import org.glassfish.jersey.internal.inject.InstanceBinding;
import org.glassfish.jersey.internal.inject.Providers;
...
  
public final class ApplicationHandler implements ContainerLifecycleListener {
  ...
    public ApplicationHandler(final Class<? extends Application> jaxrsApplicationClass) {
        initialize(new ApplicationConfigurator(jaxrsApplicationClass), Injections.createInjectionManager(), null);
    }
  ...
}

org.glassfish.jersey.internal.inject.* 是一個對hk2的封裝,選取一個堆疊給大家看看,能夠看到最終呼叫到了 hk2。

value:73, NamedImpl (org.glassfish.hk2.utilities)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
invoke:309, AnnotationLiteral (org.glassfish.hk2.api)
hashCode:242, AnnotationLiteral (org.glassfish.hk2.api)
hash:339, HashMap (java.util)
put:612, HashMap (java.util)
add:220, HashSet (java.util)
getThreeThirtyDescriptor:1282, Utilities (org.jvnet.hk2.internal)
initialize:76, ServiceLocatorGeneratorImpl (org.jvnet.hk2.external.generator)
create:103, ServiceLocatorGeneratorImpl (org.jvnet.hk2.external.generator)
internalCreate:312, ServiceLocatorFactoryImpl (org.glassfish.hk2.internal)
create:268, ServiceLocatorFactoryImpl (org.glassfish.hk2.internal)
createLocator:114, AbstractHk2InjectionManager (org.glassfish.jersey.inject.hk2)
<init>:86, AbstractHk2InjectionManager (org.glassfish.jersey.inject.hk2)
<init>:62, ImmediateHk2InjectionManager (org.glassfish.jersey.inject.hk2)
createInjectionManager:79, Hk2InjectionManagerFactory$Hk2InjectionManagerStrategy$1 (org.glassfish.jersey.inject.hk2)
create:97, Hk2InjectionManagerFactory (org.glassfish.jersey.inject.hk2)
createInjectionManager:93, Injections (org.glassfish.jersey.internal.inject)
<init>:282, ApplicationHandler (org.glassfish.jersey.server)
<init>:469, JettyHttpContainer (org.glassfish.jersey.jetty)
createContainer:61, JettyHttpContainerProvider (org.glassfish.jersey.jetty)
createContainer:82, ContainerFactory (org.glassfish.jersey.server)
createServer:100, JerseyJettyServer (com.alipay.sofa.registry.remoting.jersey)
startServer:83, JerseyJettyServer (com.alipay.sofa.registry.remoting.jersey)
......

0xFF 參考

Glassfish的安裝與使用

Jersey是一個什麼框架,價值在哪裡?

jetty

javax

import java和javax有什麼區別?

http://jakarta.apache.org/

JEE、J2EE與Jakarta等概念解釋

從此再無 JavaEE,現在叫 JakartaEE

Jersey 開發RESTful(七)Jersey快速入門

javax.ws.rs restful開發

Java 依賴注入標準 JSR-330 簡介

厲害了,Java EE 再次更名為 Jakarta EE

輕量級IOC/DI 容量HK2初探(零配置)

JSR311讓Restful WebService變簡單

Spring - JSR-330 標準註解

相關文章