1.11.笙歌散:JSR330/JSR340

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

    本文主要讲解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代码

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代码

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");
    }
}

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

{
    "username": "Lang"
}

    可得到如下响应:

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

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

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

1.2. HttpFilter类

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

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

    而此处Filter接口的定义如下:

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代码

    @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主逻辑,增加打印语句查看执行顺序。

    @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

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");
    }
}

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

{
    "username": "Lang"
}

    可得到如下响应:

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

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

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),简单说就是组件之间的依赖关系由容器全程控制并且在运行时决定,容器的控制使得这些组件之间不存在过多的依赖关系,从传统调用到依赖注入的转变参考下图:

    上图是反转控制的一个基本转变。左边的图中,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对象代码

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代码段

@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代码

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中定义注入点的代码如下:

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

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

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

2.2. Java接口和单类实现

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

Java接口代码

package cn.vertxup.inject;

import io.vertx.core.json.JsonObject;

public interface OneStub {

    JsonObject getData(JsonObject input);
}

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代码段

@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代码

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);
    }
}

    发送请求到对应接口:

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

    您可以得到如下响应:

{
    "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接口代码

package cn.vertxup.inject;

import io.vertx.core.json.JsonObject;

public interface MultiStub {

    JsonObject getData(JsonObject input);
}

实现类A

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

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代码段

@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代码

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);
    }
}

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

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

Last updated