# 1.11.笙歌散：JSR330/JSR340

> 促织絮寒霜气白，隔墙谁弄紫鸾笙。——朱权《宮词》

* 项目地址：<https://github.com/silentbalanceyh/vertx-zero-example/>（子项目：**up-rhea**）

    本文主要讲解Zero中对两个核心JSR的支持情况：

* JSR340：Java Servelt 3.1 Specification（只支持Filter功能）
* JSR330：Dependency Injection for Java（依赖注入功能）

## 「壹」JSR340

    由于Vert.x的整体架构和传统服务器有所区别，从实战经验上看，支持Servlet/JSP并没有太大必要，现阶段大部分系统都使用了前后端分离，后端更多使用的是RESTful，所以Zero对JSR340的支持中，主要是支持@WebFilter的\*\* 前端过滤器\*\*。

    **责任链**全称是Chain of Responsibility（COR），它是设计模式中的一种，它将一些请求处理的对象串在一个“绳子”上，前一个处理完后，下一个继续处理，直到所有对象都处理完。责任链上的每一个对象处理过程中只向下一个对象传递数据，相互之间各自独立（近似前一章节的插件模式）。

### 1.1. 代码示例

    先看一段基础代码：

**Agent代码**

```java
package cn.vertxup.filter;

import io.vertx.core.json.JsonObject;
import io.vertx.up.annotations.EndPoint;

import jakarta.ws.rs.BodyParam;
import jakarta.ws.rs.ContextParam;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;

@EndPoint
@Path("/api")
public class FilterAgent {
    @POST
    @Path("/jsr340/agent")
    public JsonObject filter(@BodyParam final JsonObject data,
                             @ContextParam("contextKey") final String filtered) {
        return new JsonObject()
                .put("filter", filtered)
                .mergeIn(data);
    }
}
```

**Filter代码**

```java
package cn.vertxup.filter;

import io.vertx.core.VertxException;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.core.http.HttpServerResponse;
import io.vertx.up.uca.web.filter.HttpFilter;

import javax.servlet.annotation.WebFilter;
import java.io.IOException;

@WebFilter("/hi/jsr340/*")
public class FirstFilter extends HttpFilter {
    @Override
    public void doFilter(final HttpServerRequest request,
                         final HttpServerResponse response)
            throws IOException, VertxException {
        this.put("contextKey", "Lang Filter");
    }
}
```

    运行上述代码并发送请求：

```json
{
    "username": "Lang"
}
```

    可得到如下响应：

```json
{
    "data": {
        "filter": "Lang Filter",
        "username": "Lang"
    }
}
```

    FirstFilter的执行在FilterAgent之前，它修改了请求数据，并且将数据设置到上下文对象中，而且使用了JSR340中的核心注解`javax.servlet.annotation.WebFilter` ，对Servlet中Filter熟悉的读者应该对这种代码不陌生。

> 在读取数据信息过程中，代码使用了`@ContextParam`注解从上下文环境中读取Filter填充的数据。

### 1.2. HttpFilter类

    注意上述示例中代码FirstFilter是直接从HttpFilter继承而来，该类的定义如下：

```java
package io.vertx.up.uca.web.filter;
// ...
public abstract class HttpFilter implements Filter { 
    // ...
}
```

    而此处`Filter`接口的定义如下：

```java
package io.vertx.up.uca.web.filter;

import io.vertx.core.VertxException;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.core.http.HttpServerResponse;
import io.vertx.ext.web.RoutingContext;

import java.io.IOException;

public interface Filter {

    void doFilter(final HttpServerRequest request,
                  final HttpServerResponse response)
            throws IOException, VertxException;

    default void init(final RoutingContext context) {
    }
}
```

    Vert.x的`vertx-web` 项目中，请求和响应对象和常用JSR340中的ServletRequest以及ServletResponse有所区别，所以Zero不得不创建自己的Filter结构去适配@WebFilter的基础逻辑，并且将常用的ServletException异常改成VertxException异常。虽然二者的请求和响应对象有所不同，但逻辑层面上从代码运行结果可以知道，它们是类似的，Zero容器也将@WebFilter部分的逻辑补充完整了。

    HttpFilter中保存了`io.vertx.ext.web.RoutingContext`引用，该对象是`vertx-web` 中的重要上下文对象，它已经帮助您绑定好了该对象，您可以使用该对象获取下边引用：

* Session会话对象
* Cookie对象
* Annal日志对象

    如此，您的Filter代码就简化很多了；为了让曾经开发过Servlet的Filter开发人员对此部分开发顺利上手，方法名称直接使用了`doFilter`。

### 1.3. 链式结构

    最后讲解一个JSR340的**责任链**代码示例：

**Agent代码**

```java
    @POST
    @Path("/filter2")
    public JsonObject filter2(@BodyParam final JsonObject data,
                              @ContextParam("contextKey") final String key1,
                              @ContextParam("nextKey") final String key2) {
        return new JsonObject()
                .put("contextKey", key1)
                .put("nextKey", key2)
                .mergeIn(data);
    }
```

**FirstFilter**

> 直接修改第一个例子中FirstFilter主逻辑，增加打印语句查看执行顺序。

```java
    @Override
    public void doFilter(final HttpServerRequest request,
                         final HttpServerResponse response)
            throws IOException, VertxException {
        System.out.println("1. First Filter");
        this.put("contextKey", "Lang Filter");
    }
```

**SecondFilter**

```java
package cn.vertxup.filter;

import io.vertx.core.VertxException;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.core.http.HttpServerResponse;
import io.vertx.up.annotations.Ordered;
import io.vertx.up.uca.web.filter.HttpFilter;

import javax.servlet.annotation.WebFilter;
import java.io.IOException;

@WebFilter("/hi/jsr340/*")
@Ordered(2)
public class SecondFilter extends HttpFilter {

    @Override
    public void doFilter(final HttpServerRequest request,
                         final HttpServerResponse response)
            throws IOException, VertxException {
        System.out.println("2. Second Filter");
        this.put("nextKey", "Second Filter");
    }
}
```

    运行上述代码并发送请求：

```json
{
    "username": "Lang"
}
```

    可得到如下响应：

```json
{
    "data": {
        "contextKey": "Lang Filter",
        "nextKey": "Second Filter",
        "username": "Lang"
    }
}
```

    后台输出可看到Filter的执行顺序：

```shell
1. First Filter
2. Second Filter
```

    目前为止，Zero中关于过滤器部分的内容就讲解完了，简单总结一下：

1. Zero中的过滤器Filter类使用了JSR340中的`javax.servlet.annotation.WebFilter` 注解，并且该类必须实现Zero定义的过滤器接口`io.vertx.up.uca.web.filter.Filter`。
2. **推荐**使用`io.vertx.up.uca.web.filter.HttpFilter`抽象类，可引入上下文环境信息而在过滤器中获取更多`vertx-web`中的核心对象，如Session、Cookie、Annal等。
3. 若要对过滤器Filter排序，可使用Zero中的扩展注解`io.vertx.up.annotations.Ordered`，如此编排后，您的过滤器就可以顺利形成一个过滤链。
4. 在Agent中若要读取上下文中参数信息可使用`jakarta.ws.rs.ContextParam`注解。

## 「贰」JSR330

    这里就不解释依赖注入的详细内容了，读者可以上网查阅，铺天盖地的依赖注入，如今的DI几乎已经成为了一种系统设计标准。

    依赖注入全称Dependency Injection（DI），简单说就是组件之间的依赖关系由容器全程控制并且在运行时决定，容器的控制使得这些组件之间不存在过多的依赖关系，从传统调用到依赖注入的转变参考下图：

![](/files/E1wbFJbdcy9U7iq47Z2N)

    上图是**反转控制**的一个基本转变。左边的图中，B对象由A使用`new B()` 创建，也就是说如果A对象被销毁，B对象会随之销毁，等价于B类依赖A类而存在，控制权在A；右边的图中，B对象由容器使用`new B()` 创建，而A只是获取了B的引用，这种时候，如果A销毁，B对象本身并不会受到影响，控制权从A转换到容器中，而调用B类的模式并没有发生太大变化。

    Zero主要使用三个注解：

* JSR330：`javax.inject.Inject`
* JSR330：`javax.inject.Named`
* Zero：`io.vertx.up.annotations.Qualifier`

    Zero中的依赖注入主要是服务于实战，并非规范，所以它有一定的限制：在Zero中，默认**注入**实例是单例的，不需使用`javax.inject.Singleton` 注解，所以只能用它注入非数据对象，如果是数据对象类似Pojo和JavaBean，在Zero中建议不使用，——即使使用了注解，也是单例的。

    Zero中的JSR330支持分三种：

* 简单Java对象
* Java接口和单类实现
* Java接口和多类实现

### 2.1. 简单Java对象

    在简单Java对象注入过程中，注入的Java对象使用class定义。

**Java对象代码**

```java
package cn.vertxup.inject;

import io.vertx.core.json.JsonObject;

public class SimpleObject {

    public JsonObject getData(final JsonObject data) {
        data.put("className", this.getClass().getName());
        return data;
    }
}
```

**Agent代码段**

```java
@EndPoint
@Path("/hi")
public class SimpleAgent {

    @Path("inject/simple")
    @GET
    @Address("ZERO://INJECT/SIMPLE")
    public JsonObject sayInject(
            @QueryParam("username") final String username
    ) {
        return new JsonObject()
                .put("age", 33)
                .put("username", username);
    }
}
```

**Worker代码**

```java
package cn.vertxup.inject;

import io.vertx.core.Future;
import io.vertx.core.json.JsonObject;
import io.vertx.up.annotations.Address;
import io.vertx.up.annotations.Queue;

import javax.inject.Inject;

@Queue
public class SimpleActor {

    @Inject
    private transient SimpleObject simple;

    @Address("ZERO://INJECT/SIMPLE")
    public Future<String> process(final JsonObject user) {
        final JsonObject processed = this.simple.getData(user);
        return Future.succeededFuture(processed)
                .compose(item -> Future.succeededFuture(item.encode()));
    }
}
```

    Zero中定义注入点的代码如下：

```java
    // 此处注入的SimpleObject使用class定义
    @Inject        
    private transient SimpleObject simple;
```

    `javax.inject.Inject` 是JSR330中的核心注解，有了该注解，就可以直接对某些非数据对象执行注入，而SimpleObject使用的就是简单Java对象（class定义）。发送请求后您将得到如下响应信息：

```json
{
    "data": {
        "age": 33,
        "username": "Lang",
        "className": "cn.vertxup.inject.SimpleObject"
    }
}
```

### 2.2. Java接口和单类实现

    这种类型中，使用了两个Java文件定义被注入对象（interface定义接口，class定义实现）。

**Java接口代码**

```java
package cn.vertxup.inject;

import io.vertx.core.json.JsonObject;

public interface OneStub {

    JsonObject getData(JsonObject input);
}
```

**Java类代码**

```java
package cn.vertxup.inject;

import io.vertx.core.json.JsonObject;

public class OneService implements OneStub {
    @Override
    public JsonObject getData(final JsonObject input) {
        input.put("className", this.getClass().getName());
        return input;
    }
}
```

**Agent代码段**

```java
@EndPoint
@Path("/hi")
public class OneAgent {

    @Path("inject/one")
    @PUT
    @Address("ZERO://INJECT/ONE")
    public JsonObject sayInject(final JsonObject data) {
        return new JsonObject()
                .put("age", 33)
                .mergeIn(data);
    }
}
```

**Worker代码**

```java
package cn.vertxup.inject;

import io.vertx.core.Future;
import io.vertx.core.json.JsonObject;
import io.vertx.up.annotations.Address;
import io.vertx.up.annotations.Queue;

import javax.inject.Inject;

@Queue
public class OneActor {
    // 此处注入的OneStub是接口而不是类，使用interface定义
    @Inject
    private transient OneStub stub;

    @Address("ZERO://INJECT/ONE")
    public Future<JsonObject> process(final JsonObject user) {
        final JsonObject processed = this.stub.getData(user);
        return Future.succeededFuture(processed);
    }
}
```

    发送请求到对应接口：

```json
{
    "username":"lang.yu",
    "email":"silentbalanceyh@126.com"
}
```

    您可以得到如下响应：

```json
{
    "data": {
        "age": 33,
        "username": "lang.yu",
        "email": "silentbalanceyh@126.com",
        "className": "cn.vertxup.inject.OneService"
    }
}
```

    从实战中可以知道，这种模式是在Zero项目开发中使用**最高频**的一种方式，在复杂的企业系统中，很少定义一个单独的class来做逻辑实现而是沿用**面向接口** 的设计，之所以使用单个实现类是因为Worker组件中使用的组件往往是曾经可直接生成的业务逻辑层Service的逻辑代码（如果使用生成框架，几乎都是一对一），于是本小节的情况就频繁使用了。

    Zero中的基础编程规范如：

| 类名         | 关键字       | 含义                           |
| ---------- | --------- | ---------------------------- |
| XxxStub    | interface | 定义业务逻辑层接口，曾经的XxxService      |
| XxxService | class     | 定义业务逻辑层实现类，曾经的XxxServiceImpl |

    **思考** ：注入的使用在系统里最终会让系统内部组件和组件之间耦合性很低，但是反过来思考，如果您正在开发一个内聚性很高的组合组件，那么是不是所有调用和联结点都一定要使用注入呢？在我看来不尽然，在合适的地方使用注入，在某些地方将两个组件结合到一起也是一种选择，归根到底，软件设计是自由的，不能太遵循约定盲目而为之。

### 2.3. Java接口和多类实现

    最后一种情况是一个接口和多个实现类，在多个实现类中，由于系统无法根据接口注入找到唯一实现类，所以这种情况下还依赖另外两个注解。

* JSR330：`javax.inject.Named`
* Zero：`io.vertx.up.annotations.Qualifier`

    看看如下代码示例：

**Java接口代码**

```java
package cn.vertxup.inject;

import io.vertx.core.json.JsonObject;

public interface MultiStub {

    JsonObject getData(JsonObject input);
}
```

**实现类A**

```java
package cn.vertxup.inject;

import io.vertx.core.json.JsonObject;

import javax.inject.Named;

@Named("ServiceA")
public class MultiAService implements MultiStub {
    @Override
    public JsonObject getData(final JsonObject input) {
        input.put("className", this.getClass().getName());
        return input;
    }
}
```

**实现类B**

```java
package cn.vertxup.inject;

import io.vertx.core.json.JsonObject;

import javax.inject.Named;

@Named("ServiceB")
public class MultiBService implements MultiStub {
    @Override
    public JsonObject getData(final JsonObject input) {
        input.put("className", this.getClass().getName());
        return input;
    }
}
```

**Agent代码段**

```java
@EndPoint
@Path("/hi")
public class MultiAgent {

    @Path("inject/multi")
    @PUT
    @Address("ZERO://INJECT/MULTI")
    public JsonObject sayInject(final JsonObject data
    ) {
        return new JsonObject()
                .put("age", 33)
                .mergeIn(data);
    }
}
```

**Worker代码**

```java
package cn.vertxup.inject;

import io.vertx.core.Future;
import io.vertx.core.json.JsonObject;
import io.vertx.up.annotations.Address;
import io.vertx.up.annotations.Qualifier;
import io.vertx.up.annotations.Queue;

import javax.inject.Inject;

@Queue
public class MultiActor {

    @Inject
    @Qualifier("ServiceB")
    private transient MultiStub stub;

    @Address("ZERO://INJECT/MULTI")
    public Future<JsonObject> process(final JsonObject user) {
        final JsonObject processed = this.stub.getData(user);
        return Future.succeededFuture(processed);
    }
}
```

    上述代码最终测试响应如：

```json
{
    "data": {
        "age": 33,
        "username": "lang.yu",
        "email": "lang.yu@hpe.com",
        "className": "cn.vertxup.inject.MultiServiceB"
    }
}
```

## 「叁」小结

    本章节主要讲解了Zero对JSR340和JSR330的支持，Zero中对二者的支持**不是完整的** ，但现有的功能已经足够使用，后续版本中会根据实际项目需求逐渐开放其他功能，了解了本文中的内容后，您就可以直接在Zero中使用这两种规范了。

    Zero在注解的添加和设计过程中，尽可能少地引入自定义注解，其目的是让开发人员不用学习太多新注解，如果您对`JSR311，JSR303，JSR340，JSR330` 足够熟悉的话，那么相关功能在Zero中是可以直接使用的，这样就使得读者上手Zero更容易。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://lang-yu.gitbook.io/zero/000.index/011.jsr330.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
