1. JAX-RS 規範
JAX-RS(Java API for RESTful Web Services)是 Java 平台的一項規範,旨在為開發 RESTful Web 服務提供支持。JAX-RS 定義了一組標準的 API 和註解,使得開發者可以通過簡單的註解方式來開發基於 REST 架構風格的 Web 服務。
實現 JAX-RS 規範的產品有很多,例如:Oracle 開發的 Jersey、JBoss(Red Hat)開發的 Resteasy、Apache CXF、Restlet 等等。
1.1. 特性
- 註解驅動: JAX-RS 提供了一組註解,如
@Path、@GET、@POST、@PUT、@DELETE、@Produces、@Consumes等,用於簡化 RESTful 服務的開發。 - HTTP 方法支持: 直接映射 HTTP 方法到 Java 方法,使得開發者能夠輕鬆實現 RESTful 服務的核心操作。
- 內容協商: 支持通過
@Produces和@Consumes註解定義支持的媒體類型(如 JSON、XML),從而實現請求和響應的內容協商。 - 參數綁定: 提供對 URI 路徑、查詢參數、表單參數、請求頭等的簡便綁定方式。
- 異常處理: 提供了一種機制來處理 Web 應用中的異常,使得錯誤處理更為集中和一致。
- 過濾器和攔截器: 允許開發者定義過濾器和攔截器來處理請求和響應的通用邏輯,如身份驗證、日誌記錄等。
1.2. Spring MVC 相似
JAX-RS 和 Spring MVC 都是用於構建 Web 服務的框架,它們提供的註解在功能和命名上確實有許多相似之處。這種相似性主要是因為它們都遵循 REST 架構風格,並且都需要處理 HTTP 請求和響應。
1. 相似之處
-
註解驅動:
- 兩者都使用註解來簡化 Web 服務的開發。
- 例如,JAX-RS 使用
@Path來定義資源路徑,Spring MVC 使用@RequestMapping或@GetMapping、@PostMapping等來定義請求路徑和方法。
-
HTTP 方法支持:
- JAX-RS 提供
@GET、@POST、@PUT、@DELETE等註解,直接映射到 HTTP 方法。 - Spring MVC 提供
@GetMapping、@PostMapping、@PutMapping、@DeleteMapping等註解,功能類似。
- JAX-RS 提供
-
參數綁定:
- JAX-RS 使用
@PathParam、@QueryParam、@FormParam等來綁定請求參數。 - Spring MVC 使用
@PathVariable、@RequestParam、@RequestBody等來實現類似功能。
- JAX-RS 使用
-
內容協商:
- JAX-RS 使用
@Produces和@Consumes註解來指定支持的媒體類型。 - Spring MVC 使用
@RequestMapping的produces和consumes屬性來實現相同的目的。
- JAX-RS 使用
2. 不同之處
-
規範 vs 框架:
- JAX-RS 是 Java EE 規範的一部分,定義了構建 RESTful 服務的標準接口。
- Spring MVC 是 Spring 框架的一部分,提供了完整的 Web 應用開發解決方案,並不僅限於 RESTful 服務。
-
生態系統和集成:
- JAX-RS 實現(如 Jersey、Resteasy)通常與 Java EE 應用服務器(如 WildFly、GlassFish)集成得很好。
- Spring MVC 是 Spring 框架的一部分,與 Spring 的其他模塊(如 Spring Boot、Spring Data、Spring Security)集成緊密,廣泛用於 Spring 應用程序。
-
靈活性和擴展性:
- Spring MVC 提供了更為靈活的配置和擴展能力,適合需要複雜業務邏輯和集成需求的應用。
- JAX-RS 專注於 RESTful 服務,通常用於構建輕量級的 Web 服務。
JAX-RS 和 Spring MVC 之間沒有直接的依賴關係或繼承關係,它們是獨立發展的技術棧。但由於它們都旨在解決類似的問題(即構建 Web 服務),因此在設計上出現了許多相似的理念和實現方式。Spring MVC 在 JAX-RS 發佈之前就已經存在,但隨着 RESTful 風格的普及,Spring MVC 也逐步演變以更好地支持 RESTful API。
2. Resteasy 開發組件
Resteasy 是一個開源框架,專門用於實現 JAX-RS(Java API for RESTful Web Services)規範。它由 JBoss(現為 Red Hat 的一部分)開發和維護,是 WildFly 應用服務器的默認 JAX-RS 實現。Resteasy 提供了一套完整的工具和功能,用於構建 RESTful Web 服務。
Resteasy 提供了一組靈活且強大的組件和模塊,使開發者能夠高效地構建和擴展 RESTful 服務應用。通過理解和正確使用這些組件,開發者可以定製 JAX-RS 應用的行為,以滿足各種業務需求。無論是處理複雜的請求和響應格式,還是集成異步處理和客户端功能,Resteasy 都能提供全面的支持。
1. ResteasyDeployment
-
功能作用:
ResteasyDeployment是 Resteasy 的核心配置類,負責管理和配置 JAX-RS 應用程序的各個方面。- 它用於定義和註冊資源類、提供者(providers)、異步執行器和其他 JAX-RS 組件。
- 在應用啓動時,
ResteasyDeployment會被初始化,並用於構建 RESTful 服務的運行時環境。
-
典型用法:
- 在配置 Resteasy 時,通常會創建一個
ResteasyDeployment實例,並通過它註冊資源和提供者。 - 可以通過程序化方式或通過 XML 配置文件進行配置。
- 在配置 Resteasy 時,通常會創建一個
2. Resource
-
功能作用:
Resource代表 JAX-RS 中的資源類。資源類是包含業務邏輯的 Java 類,用於處理 HTTP 請求。- 每個資源類通過
@Path註解定義一個 URI 路徑,並通過方法級別的註解(如@GET、@POST等)定義具體的操作。
-
典型用法:
- 開發者創建資源類來實現具體的 RESTful API 端點。
- 在資源類中使用註解來綁定 URI 路徑、HTTP 方法和請求參數。
3. Provider
-
功能作用:
Provider是用於擴展和定製 JAX-RS 運行時行為的組件。- 它可以用於消息體讀寫(如 JSON、XML 的序列化和反序列化)、異常映射、上下文解析等。
- 通過實現特定接口並使用
@Provider註解註冊,開發者可以創建自定義的提供者。
-
典型用法:
- 自定義
MessageBodyReader和MessageBodyWriter用於處理特定格式的請求和響應。 - 實現
ExceptionMapper接口用於統一處理應用程序中的異常。 - 使用
ContextResolver提供自定義的配置或對象給 JAX-RS 運行時。
- 自定義
4. Interceptors and Filters
-
功能作用:
- 攔截器和過濾器用於在請求處理的不同階段插入自定義邏輯。
- 攔截器可以圍繞具體的方法調用進行操作,而過濾器通常用於請求和響應的全局處理。
-
典型用法:
- 實現
ContainerRequestFilter和ContainerResponseFilter來對請求和響應進行預處理和後處理,如身份驗證、日誌記錄。 - 使用
@PreMatching註解在資源匹配之前執行過濾器邏輯。
- 實現
5. Async and Client Modules
-
功能作用:
- 異步模塊支持非阻塞的請求處理,提高應用的響應性能和可擴展性。
- 客户端模塊提供功能強大的 API,用於從 Java 應用程序中調用外部 RESTful 服務。
-
典型用法:
- 使用
@Suspended和AsyncResponse處理異步請求。 - 使用
ResteasyClient類創建和配置 RESTful 客户端。
- 使用
6. ResourceFactory
-
功能作用:
ResourceFactory是一個接口,負責創建和管理資源類的實例。- 它允許開發者控制資源類的生命週期,特別是在需要自定義實例創建邏輯或支持不同的作用域(如請求作用域、會話作用域)時。
-
典型用法:
- 自定義
ResourceFactory可以用於在實例化資源類之前進行依賴注入或其他初始化操作。 - 在配置 Resteasy 時,可以將自定義的
ResourceFactory註冊到ResteasyDeployment中。
- 自定義
7. ServletConfig
-
功能作用:
ServletConfig是 Servlet 規範中的一個接口,提供對 Servlet 的配置信息的訪問。- 在 Resteasy 中,
ServletConfig可以用於獲取初始化參數和 Servlet 上下文,從而在配置 RESTful 服務時使用。
-
典型用法:
- 在 Resteasy 的
HttpServletDispatcher中,ServletConfig用於獲取應用的配置參數。 - 開發者可以通過
web.xml文件配置初始化參數,並在應用啓動時讀取這些參數。
- 在 Resteasy 的
8. Dispatcher
-
功能作用:
Dispatcher是 Resteasy 的核心組件之一,負責將 HTTP 請求分派到相應的 JAX-RS 資源類和方法。- 它處理請求的路由、參數解析、異常處理等。
-
典型用法:
- Resteasy 在內部使用
Dispatcher來管理請求的整個生命週期。 - 開發者通常不需要直接與
Dispatcher交互,但可以通過擴展其行為來定製請求處理過程。
- Resteasy 在內部使用
9. Registry
-
功能作用:
Registry是一個接口,用於管理和註冊 JAX-RS 資源和提供者。- 它允許動態添加或移除資源類和提供者,支持應用的熱部署和配置更新。
-
典型用法:
- 在應用啓動時,通過
ResteasyDeployment註冊資源和提供者。 - 在應用運行時,可以通過
Registry接口動態更新配置。
- 在應用啓動時,通過
10. ResteasyProviderFactory
-
功能作用:
ResteasyProviderFactory是一個工廠類,負責創建和管理 JAX-RS 提供者(如MessageBodyReader、MessageBodyWriter)。- 它維護一個提供者的註冊表,並提供方法來查找和獲取合適的提供者實例。
-
典型用法:
- 開發者可以通過
ResteasyProviderFactory註冊自定義的提供者。 - 在請求處理過程中,Resteasy 使用
ResteasyProviderFactory來選擇合適的提供者進行請求和響應的序列化和反序列化。
- 開發者可以通過
11. HttpServletDispatcher
-
功能作用:
HttpServletDispatcher是 Resteasy 提供的一個 Servlet,負責將 HTTP 請求分派到 JAX-RS 資源。- 它集成了 Servlet API 和 JAX-RS API,使得 Resteasy 可以在任何 Servlet 容器中運行。
-
典型用法:
- 在
web.xml中配置HttpServletDispatcher,以便將請求轉發到 Resteasy 管理的資源。 - 開發者可以通過
web.xml配置初始化參數和 URL 映射。
- 在
下面通過一些示例看看如何使用 Resteasy
3. 示例
3.1. Tomcat 示例1
1. maven
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-servlet-initializer</artifactId>
<version>3.0.7.Final</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>8.5.95</version>
</dependency>
2. POJO
@NoArgsConstructor
@AllArgsConstructor
@Data
public class CustomVO {
private String name;
private Integer value;
}
3. Resource
定義一個 GET 方法,映射路徑是 /example,返回對象是 CustomVO。
@Path("/example")
public class MyResource {
@GET
@Produces(MediaType.APPLICATION_JSON)
public CustomVO getCustomObject() {
return new CustomVO("SampleName", 42);
}
}
4. Provider
針對 CustomVO 對象,定義序列化內容。
其實如果是常見的 JSON 轉換,可以不用針對每個 POJO 定義 Provider,可以通過引入 resteasy-jackson2-provider 依賴默認實現。
@Provider
@Produces(MediaType.APPLICATION_JSON)
public class MyProvider implements MessageBodyWriter<CustomVO> {
@Override
public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
return type == CustomVO.class;
}
@Override
public long getSize(CustomVO myCustomObject, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
return -1;
}
@Override
public void writeTo(CustomVO myCustomObject, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType,
MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream) throws IOException {
String json = "{\"name\":\"" + myCustomObject.getName() + "\", \"value\":" + myCustomObject.getValue() + "}";
entityStream.write(json.getBytes());
}
}
5. 啓動類
public class App {
public static void main(String[] args) throws Exception {
ResteasyDeployment deployment = new ResteasyDeployment();
Tomcat tomcat = new Tomcat();
tomcat.setPort(8080);
Context ctx = tomcat.addContext("", null);
Tomcat.addServlet(ctx, "ResteasyServlet", new HttpServletDispatcher());
ctx.addServletMappingDecoded("/*", "ResteasyServlet");
ctx.getServletContext().setAttribute(ResteasyDeployment.class.getName(), deployment);
tomcat.start();
deployment.setResourceClasses(Arrays.asList(MyResource.class.getName()));
deployment.setProviderClasses(Arrays.asList(MyProvider.class.getName()));
tomcat.getServer().await();
}
}
6. 運行結果
執行 main 方法,tomcat 啓動之後,然後在 tomcat 服務器上部署了 HttpServletDispatcher 的 Servlet,Resteasy 中配置了一個 Resource。
GET方法請求 http://localhost:8080/example 可以獲得 Provider 序列化後的結果。
ResteasyDeployment 類中的 setResourceClasses 和 setProviderClasses 方法用於配置不同類型的組件:
setResourceClasses:用於註冊 JAX-RS 資源類。資源類定義了 RESTful API 的端點和業務邏輯。setProviderClasses:用於註冊 JAX-RS 提供者類。提供者類用於擴展和定製 JAX-RS 的行為,例如消息體的讀寫、異常映射、上下文解析等。
3.2. Tomcat 示例2
這個示例要稍微複雜一點,但實際可以擴展的功能要更豐富些。為什麼要單獨拿出來這個示例,因為在看 Dubbo 框架 RestProtocol協議的實現方法時,看到就是這麼使用的。
具體細節可以看看com.alibaba.dubbo.rpc.protocol.rest.DubboHttpServer 中有關 Resteasy 的部署使用,示例中就保留對 Resteasy 組件的使用,通過簡單的 Demo 來執行。
1. maven
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-servlet-initializer</artifactId>
<version>3.0.7.Final</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>8.5.95</version>
</dependency>
2. POJO
@NoArgsConstructor
@AllArgsConstructor
@Data
public class UserVO {
private String name;
private Integer age;
}
3. Provider
@Provider
@Produces(MediaType.TEXT_PLAIN)
public class UserProvider implements MessageBodyWriter<UserVO> {
@Override
public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
return type == UserVO.class;
}
@Override
public long getSize(UserVO myCustomObject, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
return -1;
}
@Override
public void writeTo(UserVO userVO, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType,
MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream) throws IOException {
String json = userVO.getName()+"'s age is "+userVO.getAge() ;
entityStream.write(json.getBytes());
}
}
4. Resource 1
@Path("/say")
public class SayResource {
@GET
@Path("/hello")
@Produces(MediaType.TEXT_PLAIN)
public String hello() {
return "Hello!";
}
@GET
@Path("/world")
@Produces(MediaType.TEXT_PLAIN)
public String world() {
return "World!";
}
@GET
@Path("/user")
public UserVO user(){
return new UserVO("Tom", 12);
}
}
5. Resource 2
@Path("/action")
public class ActionResource {
@GET
@Path("/run")
@Produces(MediaType.TEXT_PLAIN)
public String run() {
return "Run!";
}
@GET
@Path("/fly")
@Produces(MediaType.TEXT_PLAIN)
public String fly() {
return "Fly!";
}
}
6. ServletConfig
public class CustomServletConfig implements ServletConfig {
private final Map<String, String> initParameters;
private final ServletContext servletContext;
public CustomServletConfig(ServletContext servletContext) {
this.servletContext = servletContext;
this.initParameters = new HashMap<>();
}
@Override
public String getServletName() {
return "ResteasyServlet";
}
@Override
public ServletContext getServletContext() {
return servletContext;
}
/**
* org.apache.catalina.servlets.DefaultServlet#init() 等實現類,會調用該方法讀取環境屬性配置
*
* @param name the name of the initialization parameter whose value to
* get
* @return Parameter
*/
@Override
public String getInitParameter(String name) {
return initParameters.get(name);
}
@Override
public Enumeration<String> getInitParameterNames() {
return new Vector<>(initParameters.keySet()).elements();
}
}
7. HttpServletDispatcher
public class DispatcherServlet extends HttpServlet {
private final HttpServletDispatcher dispatcher;
public DispatcherServlet(HttpServletDispatcher dispatcher) {
this.dispatcher = dispatcher;
}
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
dispatcher.service(request,response);
}
}
8. 啓動類
public class Main {
public static void main(String[] args) throws Exception {
ResteasyDeployment deployment = new ResteasyDeployment();
HttpServletDispatcher servletDispatcher = new HttpServletDispatcher();
Tomcat tomcat = new Tomcat();
tomcat.setPort(8080);
Context context = tomcat.addContext("", null);
Tomcat.addServlet(context, "servletDispatcher", new DispatcherServlet(servletDispatcher));
context.addServletMappingDecoded("/*", "servletDispatcher");
ServletContext servletContext = context.getServletContext();
tomcat.start();
servletContext.setAttribute(ResteasyDeployment.class.getName(), deployment);
ServletConfig servletConfig = new CustomServletConfig(servletContext);
servletDispatcher.init(servletConfig);
deployment.getRegistry().addResourceFactory(new SingletonResource(new SayResource()));
deployment.getRegistry().addResourceFactory(new SingletonResource(new ActionResource()));
deployment.getProviderFactory().registerProviderInstance(new UserProvider());
tomcat.getServer().await();
}
}
9. 執行結果
訪問 http://localhost:8080 下列路徑均可訪問:
- /say/hello
- /say/world
- /say/user
- /action/run
- /action/fly
4. Resteasy 特點
4.1. 單一Servlet
示例2中,配置了2個 Resource,每個 Resource 又有多個方法映射不同 @Path。但對於 Tomcat 服務器來説,其實就部署了一個 Servlet - HttpServletDispatcher。
在使用 Resteasy 和嵌入式 Tomcat 的情況下,無論你添加多少個 JAX-RS 資源,它們通常都部署在同一個 Servlet 上。這是因為 Resteasy 的 HttpServletDispatcher 作為一個單一的 Servlet 來處理所有的 JAX-RS 請求。
4.1.1. 工作機制
-
單一 Servlet:
- Resteasy 使用
HttpServletDispatcher作為一個統一的前端控制器(Front Controller)。 - 這個 Servlet 負責攔截所有傳入的 HTTP 請求,並將它們路由到適當的 JAX-RS 資源方法。如:示例2中
ctx.addServletMappingDecoded("/*", "ResteasyServlet"),攔截所有/*路徑下的請求。
- Resteasy 使用
-
路徑解析:
- 每個 JAX-RS 資源類和方法上的
@Path註解定義了該資源處理的 URI 路徑。 HttpServletDispatcher通過解析請求的 URI 路徑,將請求映射到相應的資源類和方法。
- 每個 JAX-RS 資源類和方法上的
-
集中管理:
- 通過這種集中式的請求處理機制,開發者只需要在應用中配置一個
HttpServletDispatcher,而不必為每個資源類單獨配置 Servlet。 - 這種方式簡化了配置、應用的部署和管理,使應用更易於擴展和維護。
- 通過這種集中式的請求處理機制,開發者只需要在應用中配置一個
4.1.2. 好處
通過使用單一的 HttpServletDispatcher,Resteasy 提供了一種高效、簡潔和靈活的方式來管理和處理 Web 服務請求。這種設計模式不僅降低了開發和維護的複雜性,還提高了應用的性能和可擴展性。對於開發者來説,這意味着可以更專注於業務邏輯的實現,而不必過多關注底層的請求管理細節。
1. 簡化配置
- 集中管理: 只需要配置一個
HttpServletDispatcher,無需為每個資源類單獨配置 Servlet。這減少了配置的複雜性,特別是在大型應用中,管理多個資源類時更加方便。
2. 易於維護和擴展
- 統一入口: 所有請求都通過同一個入口處理,使得日誌記錄、錯誤處理、身份驗證等跨切面邏輯可以在一個地方統一管理和應用。
- 易於擴展: 添加新的資源類和路徑只需在代碼中進行,無需額外的配置更改。
3. 提高性能
- 優化資源使用: 通過集中管理,可以更有效地使用服務器資源,因為只需一個 Servlet 實例來處理所有請求。
- 減少上下文切換: 由於所有請求都通過同一個 Servlet 處理,減少了不同 Servlet 之間的上下文切換開銷。
4. 增強靈活性
- 路徑映射靈活性: 通過
@Path註解,可以靈活地定義 URI 路徑,使得應用程序的路由更加直觀和可維護。 - 中間件集成: 由於有一個統一的入口,集成諸如安全、事務管理、監控等中間件變得更加簡單和一致。
5. 支持跨切面功能
- 過濾器和攔截器: 可以在
HttpServletDispatcher層面應用全局的過濾器和攔截器,處理請求和響應的通用邏輯,如身份驗證、日誌記錄、CORS 處理等。 - 異常處理: 提供集中化的異常處理機制,通過全局異常映射器可以一致地處理應用中的異常。
6. 與 JAX-RS 標準兼容
- 標準化: Resteasy 作為 JAX-RS 的實現,使用
HttpServletDispatcher保持了與 JAX-RS 標準的兼容性,使得應用可以在不同的 JAX-RS 實現之間更容易地移植。
4.2. Spring MVC 相似
之前在內嵌 Tomcat 的文章中講過,SpringBoot 的實現也是部署一個 Servlet。那 SpringBoot 和 Dubbo 一樣,也是用 Resteasy 實現的嗎?
Spring MVC 的 DispatcherServlet 和 Resteasy 的 HttpServletDispatcher 都是用於處理 HTTP 請求的核心組件,雖然它們屬於不同的框架並服務於不同的架構模式,但它們之間仍然有一些相似之處和不同之處。以下是它們的詳細比較:
1. 相似點
-
HTTP 請求處理:
- 兩者都是基於 Servlet 的實現,用於接收和處理 HTTP 請求。
-
請求路由:
- 都負責將請求路由到合適的處理器(Spring 中是控制器方法,Resteasy 中是 JAX-RS 資源方法)。
-
擴展支持:
- 都支持通過註解配置來簡化請求的映射和處理。
- 都可以通過註冊擴展(如提供者或攔截器)來增強功能。
-
Servlet 機制:
- 都是作為 Servlet 在應用服務器(如 Tomcat)中運行,利用 Servlet 規範的生命週期和配置機制。
2. 不同點
-
框架背景與目標:
-
DispatcherServlet:
- 屬於 Spring MVC 框架的一部分,主要用於構建 Web 應用,尤其是支持 MVC 模式的應用。
- 提供了視圖解析、數據綁定、驗證等豐富功能。
-
HttpServletDispatcher:
- 屬於 JAX-RS 的實現之一(Resteasy),專注於構建 RESTful API 服務。
- 主要處理 RESTful 請求,關注於 HTTP 方法和路徑的映射。
-
-
配置與自動化:
-
DispatcherServlet:
- Spring Boot 提供了自動配置,開發者通常不需要手動配置。
- 高度可配置,通過 Spring 配置文件或 Java 配置類進行調整。
-
HttpServletDispatcher:
- 通常需要手動配置,特別是在嵌入式服務器環境中。
- 配置相對簡單,以註解驅動的方式為主。
-
-
功能範圍:
-
DispatcherServlet:
- 提供全面的 Web 應用支持,包括會話管理、模板渲染、國際化等。
- 支持複雜的 Web 應用場景。
-
HttpServletDispatcher:
- 專注於 RESTful 風格的服務,處理 JSON/XML 等數據格式。
- 適合輕量級服務和 API 開發。
-
-
生態系統與集成:
-
DispatcherServlet:
- 支持 Spring 的整個生態系統,包括 Spring Security、Spring Data 等。
- 適合構建複雜的企業級應用。
-
HttpServletDispatcher:
- 集成 JAX-RS 標準,適用於與其他 JAX-RS 實現的互操作。
- 更專注於服務和 API 的開發。
-