促织絮寒霜气白,隔墙谁弄紫鸾笙。——朱权《宮词》
本文主要讲解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
中的重要上下文对象,它已经帮助您绑定好了该对象,您可以使用该对象获取下边引用:
如此,您的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中关于过滤器部分的内容就讲解完了,简单总结一下:
Zero中的过滤器Filter类使用了JSR340中的javax.servlet.annotation.WebFilter
注解,并且该类必须实现Zero定义的过滤器接口io.vertx.up.uca.web.filter.Filter
。
推荐使用io.vertx.up.uca.web.filter.HttpFilter
抽象类,可引入上下文环境信息而在过滤器中获取更多vertx-web
中的核心对象,如Session、Cookie、Annal等。
若要对过滤器Filter排序,可使用Zero中的扩展注解io.vertx.up.annotations.Ordered
,如此编排后,您的过滤器就可以顺利形成一个过滤链。
在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支持分三种:
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中的基础编程规范如:
类名 | 关键字 | 含义 |
---|
| | |
| | 定义业务逻辑层实现类,曾经的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更容易。