RESTful方式的访问确实即方便又易用,确实是非常不错的一种架构模式。前面一直嘴馋了好长时间,但是由于时间及人员关系,一直没有启动,刚开年,时间也有了,人员也到位了,没啥说的,开工!
本人的习惯,在开工之前,先去学习、研究、了解别人是怎么做的,在看了大量的文档及Roy Thomas Fielding博士的论文还有大量的实践之后,对于它是个啥,解决了啥等等有了比较充分的认识,接现来就是实现了。
程序员同学的第一想法是采用Spring中的RESTful方案来进行解决,这个当然马上被我否掉,原因是这个会导致对SpringMVC的依赖,第二会导致所有的实现都和RESTful进行硬绑定,如果有一天,出来另外一种XXXful的支持,程序员们必须要通过修改代码才能实现,这个与本人提倡的一次开发到处使用有冲突,而且会让程序员知道RESTful相关的实现细节,这明显会增加程序员的学习与开发成本。如果要修改Restful的访问方式,还要去修改相关源码,这个也太恶了,难以忍受。
那程序员同学的问题就来了,你说怎么做?
佛家对看事物有三个层次,第一层是看山是山 看水是水,第二层是看山不是山,看水不是水,第三层次是看山还是那山,看水还是那水。
所以对于RESTful这个东东,也是同样的问题,如果套用上面的3个层次来分析的话,大概是这样的:
第一层次:看山是山 看水是水,看到RESTful就是RESTful,于是就直接针对这个做了。当然这个层次比较低,我前面说的程序员同学就是这个思路,缺点上面我都已经讲过了。
第二个层次:看山不是山,看水不是水,看到RESTful的时候,隐隐约约看着,它根本就不是啥新东西,它就是我们常用的WEB访问的一种新的约定和规范,再说得直接一点,它就是一种门面,对原来的访问方式进行了一种新的组织而已。
第三个层次:看山还是那山,看水还是那水,这个时候再看RESTful,就知道它的实质了,它根本就不是个啥,它就是定义了一套新的访问规范而已。
它通过HTTP方法来声明访问方式,它通过ACCEPT来声明返回数据格式,它通过多层的URL路径来传一些参数。
它和具体的代码与控制层实现逻辑有1毛钱关系么??从本来来说是没有的。因此,通过上面的抽象,我们可以认为,RESTful架构实际上可以认为就是一系列的URL地址的进行转换规则,它通过预定义好的规则,把RESTful格式的请求转换成我们原来的已经开发好的一些访问地址就可以了,其它的就该咋咋地好了。
通过上面的讲解,程序员同学似乎懂了我说的意思,于是去噼里啪啦敲代码了,半天以后,代码出来了,大致如下:
规则配置
转换规则定义:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
<
rules
>
<
rule
pattern
=
"/users/{id}"
>
<
mapping
method
=
"get"
accept
=
"text/html"
url
=
"queryUsersTiny.servicepage"
></
mapping
>
<
mapping
method
=
"post"
accept
=
"text/html"
url
=
"addUserTiny.servicepage"
></
mapping
>
<
mapping
method
=
"put"
accept
=
"text/html"
url
=
"updateUserTiny.servicepage"
></
mapping
>
<
mapping
method
=
"delete"
accept
=
"text/html"
url
=
"deleteUserTiny.servicepage"
></
mapping
>
<
mapping
method
=
"get"
accept
=
"text/json"
url
=
"queryUsersTiny.servicejson"
></
mapping
>
</
rule
>
<
rule
pattern
=
"/users/new/"
>
<
mapping
method
=
"get"
accept
=
"text/html"
url
=
"crud/restful/operate.page"
></
mapping
>
</
rule
>
<
rule
pattern
=
"/users/edit/{id}"
>
<
mapping
method
=
"get"
accept
=
"text/html"
url
=
"queryUserByIdTiny.servicepage"
></
mapping
>
</
rule
>
<
rule
pattern
=
"/users/{id}/classes/{name}"
>
<
mapping
method
=
"post"
accept
=
"text/html"
url
=
"queryclasses.servicepage"
></
mapping
>
</
rule
>
</
rules
>
<
div
>
</
div
>
|
rules下面放置一系列的url转换规则rule。
rule表示一条转换规则
pattern:属性是一条匹配表达式,表示这种类型的URL地址是归我处理的,它下面可以有占位符,来表示要提取一些值
在它下面呢,有多条的mapping 子规则,mapping 中有如下属性:
method:响应的请求方法
accept:响应的返回类型
url:真正执行的URL
管理器
管理器就是个规则仓库,里面放置了所有的规则。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
public
interface
UrlRestfulManager {
String URL_RESTFUL_XSTREAM =
"urlrestful"
;
/**
* 增加restful配置信息
* @param Rules
*/
public
void
addRules(Rules Rules);
/**
* 移除restful配置信息
* @param Rules
*/
public
void
removeRules(Rules Rules);
/**
* 根据请求路径、请求的方法以及请求头的accept 组装此次请求的上下文对象
* @param requestPath
* @param httpMethod
* @param accept
* @return
*/
public
Context getContext(String requestPath, String httpMethod, String accept);
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
|
public
class
UrlRestfulFileProcessor
extends
AbstractFileProcessor {
private
static
final
String RESTFUL_EXT_FILENAME =
".restful.xml"
;
private
UrlRestfulManager urlRestfulManager;
public
boolean
isMatch(FileObject fileObject) {
return
fileObject.getFileName().toLowerCase()
.endsWith(RESTFUL_EXT_FILENAME);
}
public
void
setUrlRestfulManager(UrlRestfulManager urlRestfulManager) {
this
.urlRestfulManager = urlRestfulManager;
}
public
void
process() {
XStream stream = XStreamFactory
.getXStream(UrlRestfulManager.URL_RESTFUL_XSTREAM);
for
(FileObject fileObject : deleteList) {
logger.logMessage(LogLevel.INFO,
"正在移除restful文件[{0}]"
,
fileObject.getAbsolutePath());
Rules Rules = (Rules) caches.get(fileObject
.getAbsolutePath());
if
(Rules !=
null
) {
urlRestfulManager.removeRules(Rules);
caches.remove(fileObject.getAbsolutePath());
}
logger.logMessage(LogLevel.INFO,
"移除restful文件[{0}]结束"
,
fileObject.getAbsolutePath());
}
for
(FileObject fileObject : changeList) {
logger.logMessage(LogLevel.INFO,
"正在加载restful文件[{0}]"
,
fileObject.getAbsolutePath());
Rules Rules = (Rules) stream.fromXML(fileObject
.getInputStream());
Rules oldRules = (Rules) caches.get(fileObject
.getAbsolutePath());
if
(oldRules !=
null
) {
urlRestfulManager.removeRules(oldRules);
}
urlRestfulManager.addRules(Rules);
caches.put(fileObject.getAbsolutePath(), Rules);
logger.logMessage(LogLevel.INFO,
"加载restful文件[{0}]结束"
,
fileObject.getAbsolutePath());
}
}
}
|
这个类用于匹配所有的RESTful规则文件并添加到管理器。
这样就可以进行良好的模块化,把各模块中的配置文件统一加载起来,统一使用。易于开发,自动集成。
URL重写处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
|
public
class
RestfulStyleSubstitutionHandler
implements
RewriteSubstitutionHandler {
private
static
final
String Accept =
"Accept"
;
private
static
final
String HTTP_METHOD_KEY =
"X-HTTP-METHOD-OVERRIDE"
;
private
UrlRestfulManager urlRestfulManager;
public
void
setUrlRestfulManager(UrlRestfulManager urlRestfulManager) {
this
.urlRestfulManager = urlRestfulManager;
}
/**
* 先获取原来的请求路径与UrlRestful的配置进行匹配,把匹配的值放到上下文中,最后重新设置请求的路径。
*/
public
void
postSubstitution(RewriteSubstitutionContext context) {
String originalPath = context.getPath();
String httpMethod = getHttpMethod(context);
String requestAccept = context.getParserWebContext().get(Accept);
Context restfulContext = urlRestfulManager
.getContext(originalPath, httpMethod, requestAccept);
if
(restfulContext !=
null
) {
ParameterParser parameterParser = context.getParameters();
setParameter(parameterParser, restfulContext.getVariableMap());
context.setPath(restfulContext.getMappingUrl());
}
}
private
String getHttpMethod(RewriteSubstitutionContext context) {
WebContext webContext = context.getParserWebContext();
String httpMethod = webContext.get(HTTP_METHOD_KEY);
if
(StringUtil.isBlank(httpMethod)) {
httpMethod = webContext.getRequest().getMethod();
}
return
httpMethod;
}
private
void
setParameter(ParameterParser parameterParser,
Map<String, String> variableMap) {
if
(!CollectionUtil.isEmpty(variableMap)) {
for
(String key : variableMap.keySet()) {
String value = variableMap.get(key);
if
(!StringUtil.isBlank(value)) {
String[] parameterValues = value.split(
","
);
if
(parameterValues.length >
1
) {
parameterParser.setObjects(key, parameterValues);
}
else
{
parameterParser.setObject(key, value);
}
}
}
}
}
}
|
这里有个处理逻辑是:X-HTTP-METHOD-OVERRIDE参数,这个参数可以通过放在HEADER、REQUEST等地方来声明请求类型,如果这个值有,则采用声明的方法,否则用HTTP请求的方法来进行处理,这就允许通过一个POST方法来处理所有的请求方式。推荐采用放在HEADER来进行声明的。
代码行统计
连接口带配置带实现,总结代码行数480行(ValueParser现在还未使用)。
总结
自此,已经完成了对Tiny框架的扩展,支持了RESTful访问方式完美支持,至于如何定义访问格式及路径层次结构规划,那是技术经理和开发人员的事情。对于已经使用Tiny框架的项目,只要增加新的依赖,然后增加配置就可以对外提供RESTful的访问支持了。对于非Tiny项目,也可以采用上面的开发思路,来做自己的RESTful风格访问支持。
功能特点:对原有实现无任何侵入性(重定向地址要转向RESTful格式的新地址)。
上面的分析及解决方案如有不对或不妥之处,欢迎批评指正。