> For the complete documentation index, see [llms.txt](https://lang-yu.gitbook.io/zero/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://lang-yu.gitbook.io/zero/000.index/011.jsr330.md).

# 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更容易。
