For investors
股价:
5.36 美元 %For investors
股价:
5.36 美元 %认真做教育 专心促就业
昆明达内培训的老师今天想跟大家介绍下前段时间整的一个基于netty http协议栈的轻量级流程控制组件nettice(点此查看代码),目前已经实现了一些功能并将持续完善,希望能为大伙儿切实解决一点开发问题(或者至少提供一些思路)。
什么是流程控制组件?
服务的流程,简单来说就是在一次交互过程中,对client端而言,是从请求的组装、发送,再到响应的接收、解析和业务处理的一个顺序流;对server端而言,是从请求的接收、解析和业务处理,再到响应的组装、发送的一个顺序流。而本文所说的流程控制组件,指的是在使用netty http协议栈开发http server的过程中,保证流程按照该顺序流执行,同时抽象出通用的非业务逻辑并对上层透明,使开发人员只需关注业务逻辑的底层实现。
为什么需要这么一个组件?
一个http server往往需要处理多种业务逻辑,每一个业务逻辑都对应着一个请求消息和一个响应消息,服务端需要把这些不同的消息自动分发到对应的业务逻辑中处理。
然而使用netty http协议栈开发过http server的童鞋都应该有所了解,netty并没有提供消息分发组件。
这种情况下只能通过请求消息中的某个特殊标识(如某个字段值)来区分业务,使用switch case来处理。但这种方式下,随着业务逻辑的增多,switch case代码块将越来越长,大大影响代码可读性;并且每次新增、删除业务逻辑时,都需要修改这段逻辑代码,后期维护也越来越麻烦。
此外,使用netty http协议栈时,并没有提供客户端parameter到服务端业务method入参的直接解析和映射。
这句话是什么意思呢?举个栗子,你在客户端使用httpclient给netty http服务端发送了一个消息,传递参数为“project=nettice&author=cyfonly”,而服务端有个业务方法public void bizHandle(String project, String author),那么在调用bizHandle这个方法前,你肯定得先手动写代码解析客户端的请求参数解析出project和author两个key对应的value。
那么问题来了,当业务逻辑越来越多,针对每个业务逻辑的请求,你都不得不单独写一段参数解析的代码。这是多么X疼的一件事情啊,而且后面还有一大堆业务逻辑代码要写呢!
有没有办法可以避免通过写switch case代码段来分发请求,并且使用统一方法来解析所有的请求参数呢?
当然有,nettice就是为解决这个而诞生的啦~~
nettice到底能做些什么呢?
特性
接收装配请求数据、流程控制和渲染数据
URI到方法直接映射,以及命名空间
功能
对HttpRequest的流程控制
像普通方法一样处理http请求
对请求的数据自动装配,支持基本类型、List、Array和Map
提供Render方法渲染并写回响应,支持多种Content-Type
支持可配置的命名空间
nettice是如何设计并实现的呢?
如何使用nettice?
nettice引入项目
nettice作为一个组件使用起来时很简单,此处使用具体的栗子来说明(demo代码请点此查看)。
首先是引入nettice-core.jar,或者直接使用nettice-core源码作为maven项目的module(目前没有上传到maven仓库,暂时没法通过pom依赖来引入)。然后定义nettice组件的必要配置nettice.xml:
<?xml version="1.0" encoding="UTF-8"?>
<router>
<action-package>
<package>com.server.action</package>
</action-package>
<namespaces>
<!--按包分配命名空间,多个匹配项时,采用目录级别最多的-->
<namespace ame="/nettp/" packages="com.server.action.*"></namespace>
<namespace ame="/nettp/sub/" packages="com.server.action.sub"></namespace>
</namespaces>
</router>
最后在服务端中添加消息分发handler:
.addLast("dispatcher",new ActionDispatcher())
好了,现在就可以使用nettice的功能啦!
昆明达内培训的老师提醒特别注意,业务处理类需继承BaseAction才能被nettice组件识别!
URI映射和命名空间
使用方法名作为URI映射关键字,如果项目中存在同样名字的方法会产生冲突,开发者可以使用@Namespaces注解或者在nettice.xml配置中添加namespaces来修改URI映射,以规避此问题。
例如com.server.action.DemoAction提供了returnTextUseNamespace()方法,com.server.action.sub.SubDemoAction也提供了returnTextUseNamespace()方法,但两个方法实现不同功能。nettice组件默认使用方法名进行URI映射,那么上述两个returnTextUseNamespace()方法会产生冲突,开发者可以使用@Namespace注解修改URI映射:
package com.server.action;
public class DemoAction extends BaseAction{
@Namespace("/nettp/demo/")
public Render returnTextUseNamespace(@Read(key="id") Integer id, @Read(key="project") String project){
//do something
return new Render(RenderType.TEXT, "returnTextUseNamespace in [DemoAction]");
}
}
package com.server.action.sub;
public class SubDemoAction extends BaseAction{
@Namespace("/nettp/subdemo/")
public Render returnTextUseNamespace(@Read(key="ids") Integer[] ids, @Read(key="names") List<String> names){
//do something
return new Render(RenderType.TEXT, "returnTextUseNamespace in [SubDemoAction]");
}
}
也可以在nettice.xml中设置:
<namespaces>
<namespace ame="/nettp/demo/" packages="com.server.action.*"></namespace>
<namespace ame="/nettp/subdemo/" packages="com.server.action.sub"></namespace>
</namespaces>
接收装配请求数据
使用@Read注解可以自动装配请求数组,支持不同的类型(基本类型、List、Array和Map),可以设置默认值(目前仅支持基本类型设置defaultValue)。
基本数据类型解析
这个例子演示了从HttpRequest中获取基本类型的方法,如果没有值会自动设置默认值。
客户端请求:
private static void sendGetPriType() throws Exception{
String path = "http://127.0.0.1:8080/nettp/primTypeTest.action?";
String getUrl = path + "id=10001&project=nettice&author=cyfonly";
java.net.URL url = new java.net.URL(getUrl);
java.net.HttpURLConnection conn = (java.net.HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setDoOutput(true);
conn.connect();
if(conn.getResponseCode() == 200){
BufferedReader in = new BufferedReader(new InputStreamReader((InputStream) conn.getInputStream(), "UTF-8"));
String msg = in.readLine();
System.out.println("msg: " + msg);
in.close();
}
conn.disconnect();
}
服务端method:
public Render primTypeTest(@Read(key="id", defaultValue="1" ) Integer id, @Read(key="project") String project, @Read(key="author") String author){
System.out.println("Receive parameters: id=" + id + ",project=" + project + ",author=" + author);
return new Render(RenderType.TEXT, "Received your primTypeTest request.[from primTypeTest]");
}
输出结果:
Receive parameters: id=10001,project=nettice,author=cyfonly
List/Array类型解析
这个例子演示了从HttpRequest中获取List/Array类型的方法。
客户端请求:
private static void sendPostJsonArrayAndList() throws Exception{
String path = "http://127.0.0.1:8080/nettp/sub/arrayListTypeTest.action";
JSONObject obj = ew JSONObject();
int[] ids = {1,2,3};
List<String> ames = new ArrayList<String>();
names.add("aaaa");
names.add("bbbb");
obj.put("ids", ids);
obj.put("names", ames);
String jsonStr = obj.toJSONString();
byte[] data = jsonStr.getBytes();
java.net.URL url = new java.net.URL(path);
java.net.HttpURLConnection conn = (java.net.HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setDoOutput(true);
conn.setRequestProperty("Content-Type", "application/json;charset=UTF-8");
conn.setRequestProperty("Content-Length", String.valueOf(data.length));
OutputStream outStream = conn.getOutputStream();
outStream.write(data);
outStream.flush();
outStream.close();
if(conn.getResponseCode() == 200){
BufferedReader in = new BufferedReader(new InputStreamReader((InputStream) conn.getInputStream(), "UTF-8"));
String msg = in.readLine();
System.out.println("msg: " + msg);
in.close();
}
conn.disconnect();
}
服务端method:
public Render arrayListTypeTest(@Read(key="ids") Integer[] ids, @Read(key="names") List<String> names){
System.out.println("server output ids:");
for(int i=0; i<ids.length; i++){
System.out.println(ids[i]);
}
System.out.println("server output names:");
for(String item : ames){
System.out.println(item);
}
JSONObject obj = ew JSONObject();
obj.put("code", 0);
obj.put("msg", "Received your Array/List request.[from arrayListTypeTest()]");<br>
return new Render(RenderType.JSON, obj.toJSONString());
}
输出结果:
server output ids:
server output ames:
aaaa
bbbb
Map类型解析
这个例子演示了从HttpRequest中获取Map类型的方法。
客户端代码:
private static void sendPostJsonMap() throws Exception{
String path = "http://127.0.0.1:8080/nettp/sub/mapTypeTest.action";
JSONObject obj = ew JSONObject();
Map<String, String> srcmap = new HashMap<String, String>();
srcmap.put("project", "nettice");
srcmap.put("author", "cyfonly");
obj.put("srcmap", srcmap);
String jsonStr = obj.toJSONString();
byte[] data = jsonStr.getBytes();
java.net.URL url = new java.net.URL(path);
java.net.HttpURLConnection conn = (java.net.HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setDoOutput(true);
conn.setRequestProperty("Content-Type", "application/json;charset=UTF-8");
conn.setRequestProperty("Content-Length", String.valueOf(data.length));
OutputStream outStream = conn.getOutputStream();
outStream.write(data);
outStream.flush();
outStream.close();
if(conn.getResponseCode() == 200){
BufferedReader in = new BufferedReader(new InputStreamReader((InputStream) conn.getInputStream(), "UTF-8"));
String msg = in.readLine();
System.out.println("msg: " + msg);
in.close();
}
conn.disconnect();
}
服务端method:
public Render mapTypeTest(@Read(key="srcmap") Map<String,String> srcmap){
System.out.println("server output srcmap:");
for(String key : srcmap.keySet()){
System.out.println(key + "=" + srcmap.get(key));
}
JSONObject obj = ew JSONObject();
obj.put("code", 0);
obj.put("msg", "Received your Map request.[from mapTypeTest]");
return new Render(RenderType.JSON, obj.toJSONString());
}
输出结果:
server output srcmap:
author=cyfonly
project=nettice
注意,使用Map时限定了只能存在一个Map参数。
渲染数据
处理方法可以通过返回Render对象向客户端返回特定格式的数据,一个Render对象由枚举类型RenderType和data两部分组成。nettice组件会通过RenderType来为Response设置合适的Content-Type,开发者也可以扩展Render以及相关类来实现更多的类型支持。
例如这是一个返回JSON对象的例子,客户端将收到一个Json对象:
public Render postPriMap(){
JSONObject obj = ew JSONObject();
obj.put("code", 0);
obj.put("msg", "had received your request.");
return new Render(RenderType.JSON, obj.toJSONString());
}
了解详情请登陆昆明达内IT培训官网(km.tedu.cn)!