posts - 33, comments - 0, trackbacks - 0, articles - 0
  BlogJava :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理

2011年11月22日

      在第一篇时就说过框架要在URL上作文章,是的,本文就框架怎样充分利用url上作尽可能详细的说明。

      做web开发的不可能对url陌生,早在web1.0时代,url作为统一资源定位符,在对web中资源的如何获得上起到巨大作用。不论用户请求的时静态页面或者是各种图片、脚本文件,通过url总能从web网站获取要访问的资源。Web2.0更是常常使用url作为get请求时参数的传递,如http://xxx.xxx.xxx/xxx.jsp?user=admin。以及近几年很火restful web service 摒弃soap而使用url传递请求参数 都说明合理利用url的可行和流行。

      当然不止是使用了url就算好的实践,而是能够做到优雅的使用,保证层次分明和整体的简洁,这才算是好的方式,这也正是本框架对使用url 所追求的目标。

      首先来看几个例子:

http://www.cnblogs.com/p2
http://www.xxx.com/index.do?page=2

http://www.xxx.com/product/
http://www.xxx.com/channel.do?channel=product

http://www.xxx.com/product/mobile
http://www.xxx.com/channel.do?channel=product&&subChannel=mobile

      相信各位看官不用我说也能明白,这几组的实现肯定第一种实现的方式更佳。抛开它能屏蔽服务器端使用的技术这一特性不说,它还能够更好地说明动态网站的层次结构,让用户在访问时能明确知道在网站的什么位置,而不会觉得是陷入了一个迷宫。

      当然上面列举的例子是网站前端所使用的url表现方式,因为表现方式可以多种多样,个人喜好不同,本框架在设计时没有给指定前端url的表现方式,而是定义接口,把这个权利留给使用的用户。框架将考虑更多 通用性的东西而不是 个性 自由的东西。

 

      下面对框架里默认使用的url Router AMPPathRouter做详细的介绍,包括设计的思想和实现的方式。首先AMPPathRouter的用途定位为后台使用。为了理解快速的理解它的工作原理,先来和struts做一下对比。

      Struts关于请求的配置:

 

<action name="login" class="com.lscmjx.action.LoginAction" method="login">
<result name="success">
main.jsp
</result>
<result name="failure">
login.jsp
</result>
</action>

    它提交的url会是http://xxx.xxx.xxx/login,访问web服务器时会把此url传递到struts框架交给它处理,之后struts会在struts.xml中寻找login的相关的配置,像上面例子,struts会找到LoginAction的类,并且调用其login的方法。

      写到这里,我请问这是最好的方式吗?当然不是,至少我在使用struts时就认为这是相当撇脚的设计。上面例子只是列举一个login方法,假如一个系统中要对后台调用的方法是100个,那岂不是就需要在struts.xml中写100个与之类似的配置。想想都头大,这样繁琐的工作,应该是由框架自己去处理,而不是人工给配置。

 

      再来看实现相同功能的Unicorn web框架的配置。

 

<action class="com.mh.action.UserAction"></action>

      当然提交的url肯定需要包含多一些的信息,来保证能通过url正确调用框架Action里的方法。这里提交的url方式:http://xxx.xxx.xxx/UserAction/login/

      通过在url里附加调用的Action类的信息,可以省略为不同的方法都在xml里配置的麻烦。假如UserAction里有100个方法,框架也只需这一行的配置。

      有了大体的认识之后,来看框架的核心部分AMPPathRouter的具体实现。

 

  /**
* 检查url是否是此Router类要处理的,/Action/Method/Param 格式的将会被检查合格,返回true
*
@param relativeUri
*
@param actionMap
*
@return
*/
public boolean checkUrl(String relativeUri, Map<String, ActionSupport> actionMap) {
Pattern pattern = Pattern.compile("^/\\w+/\\w+/\\S*");
Matcher matcher = pattern.matcher(relativeUri);
if(matcher.matches()) {
String actionName = relativeUri.split("/")[1];
ActionSupport actionSupport = actionMap.get(actionName);
if(null != actionSupport) {
String actionMethodName = relativeUri.split("/")[2];
Class<?> actionClass = actionSupport.getClass();
Method[] methods = actionClass.getMethods();
for(int i = 0; i < methods.length; i++) {
Method method = methods[i];
String methodName = method.getName();
if(methodName.equals(actionMethodName)) {
return true;
}
}
} else {
return false;
}
}
return false;
}
/**
* 匹配规则为:
* 1、符合/Action/method/param格式,
* 2、并且Action在actionMap中的确存在
* 3、method在此Action中存在
*/
@Override
public boolean route(String relativeUri, UrlFilter urlFilter) {
Map<String, ActionSupport> actionMap = urlFilter.getActionMap();
if(!this.checkUrl(relativeUri, actionMap)) {
return false;
}
// 拦截Action/Method/Param方式的请求,并构建ActionSupport类的属性
String[] params = relativeUri.split("/");
try {
ActionSupport actionSupport = actionMap.get(params[1]);
Class<?> action = actionSupport.getClass();
Method method = action.getMethod(params[2], new Class[] {});
if(params.length > 3) {
this.boxingRequest(urlFilter.getRequest(), params[3]);
}
// 只要找到ActionSupport的子类,则初始化其所具有的属性
Object newInstance = action.newInstance();
this.initActionSupport(newInstance, urlFilter);
String result = (String) method.invoke(newInstance, new Object[] {});
if (null == result || ActionSupport.AJAX.equals(result) || ActionSupport.FORWARD.equals(result) || ActionSupport.WEB_SERVICE.equals(result)) {
return true;
}
if(ActionSupport.REDIRECT.equals(result)) {
urlFilter.getResponse().sendRedirect(result);
return true;
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
/**
* 把url中得param加入到request的attribute里
*
@param request
*
@param parameter
*/
private void boxingRequest(HttpServletRequest request, String parameter) {
String[] parameters = parameter.split("&");
for (int i = 0; i < parameters.length; i++) {
String param = parameters[i];
String[] key_value = param.split("=");
if(key_value.length == 2) {
request.setAttribute(key_value[0], key_value[1]);
}
}
}
/**
* 初始化ActionSupport类中所需的request、response、session、application等对象
*
@param obj
*
@param urlFilter
*/
private void initActionSupport(Object obj, UrlFilter urlFilter) {
ActionSupport action = (ActionSupport) obj;
action.setRequest(urlFilter.getRequest());
action.setResponse(urlFilter.getResponse());
action.setSession(urlFilter.getSession());
action.setApplication(urlFilter.getApplication());
}

 

      这便是AMPPathRouter的全部内容,其中在把请求分发到ActionSupport的子类 并调用相关方法时 是通过反射实现,其他地方地方都是相当容易理解的。

      空说无凭,把框架应用到实战中才是硬道理:

 

      好了,下一篇介绍Action 和 json。

posted @ 2011-11-25 18:18 马航 阅读(248) | 评论 (0)编辑 收藏

      上篇说过,所有提交到web程序的url都被此UrlFilter拦截。拦截到请求后,UrlFilter则召集它的好多个得力干将Router 们, 询问他们:“谁能处理此URL啊 ?”

这时一位叫做AMPRouter 首当其冲 说:“这个url交给我了”。这时filter就会把此url全权交给AMPRouter来办,至于如何去处理,filter也不再过问,它觉得:“我把任务都交给你了,怎么解决是你的事”。

      根据单一职责的原则,UrlFilter就负责上面情景中的分发urlRouter中的差事,url如何分发交给Router处理。并且Router实际是一个接口,使用框架的用户完全可以自己实现Router,这样用户可以自主定义的url分发的策略。另外呢,框架初始化的一些操作它也是 推脱不掉的,像根据unicorn-config.xml初始化系统中的RouterAction'。下面是具体的代码:

 

@Override
public void init(FilterConfig config) throws ServletException {
	application = config.getServletContext();
	String loadPath = Thread.currentThread().getContextClassLoader().getResource("").getPath();
	String classPath = loadPath.substring(1, loadPath.length());
	ArrayList<String> actions = XMLReader.getNodeValues(classPath + "unicorn-config.xml", "actions");
	this.initActions(actions);
	ArrayList<String> routers = XMLReader.getNodeValues(classPath + "unicorn-config.xml", "routers");
	this.initRouters(routers);
}

 

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
			throws IOException, ServletException {
	HttpServletRequest request = (HttpServletRequest) servletRequest;
	String path = request.getContextPath();
	String uri = request.getRequestURI();
	String relativeUri = uri.substring(path.length(), uri.length());
	this.request = request;
	this.session = request.getSession();
	this.response = (HttpServletResponse) servletResponse;
	// 用户自定义的Router优先级最高,url先通过用户定义的
	Iterator<IPathRouter> iterator = routerList.iterator();
	while(iterator.hasNext()) {
		IPathRouter router = iterator.next();
		if(router.route(relativeUri, this)) {
			return ;
		}
	}
	// 拦截不到的继续访问
	filterChain.doFilter(servletRequest, servletResponse);
}

        其中Router类的初始化,Action类的初始化于这个类似:

private void initRouters(ArrayList<String> routers) {
	routerList = new ArrayList<IPathRouter>();
	for (int i = 0; i < routers.size(); i++) {
		String routerName = routers.get(i);
		try {
			Class<?> clz = Class.forName(routers.get(i));
			// 单例模式通过方法获取对象实例
			IPathRouter router = (IPathRouter) clz.newInstance();
			routerList.add(router);
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (SecurityException e) {
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (InstantiationException e) {
			e.printStackTrace();
		}
	}
	// 最后把框架默认的Router加入进来
	routerList.add(new AMPPathRouter());
}

        其中unicorn-config.xml文件的编写,拿其中我一个项目里的这个文件来举例:

<?xml version="1.0" encoding="UTF-8" ?>
<config>
<routers>
<router class="com.mh.router.MySessionCheckRouter"></router>
</routers>
<actions>
<action class="com.mh.action.UserAction"></action>
<action class="com.mh.action.InformationAction"></action>
<action class="com.mh.action.UploadInformationIconAction"></action>
<action class="com.mh.action.TempPicAction"></action>
<action class="com.mh.action.MobileAction"></action>
</actions>
</config>

      这里即定义了Action,也定义了自己的Router,并且从名称上可以看出,这个SessionCheckRouter是要判断所有提交到服务器的指定url的请求 是否已经登录过,没有登录,可能会把此请求遣送会登录页。以及初始化所有的Action,在Router处理完请求,分发给action时,可以从filter里面去取。

 


posted @ 2011-11-25 12:35 马航 阅读(252) | 评论 (0)编辑 收藏

      承接上篇的简单介绍,下面详细介绍整个框架的大致结构。

      先来看一下整个框架包的结构:

 

      可以看出框架包含的包很少,包的结构也超简单。这里 涉及FilterActionSupportRouter等三个概念,他们之间的关系,通过下图来表示:

 

      图也不规范,说不上来是哪个UML图,不过通过它也能看出一个请求到达时,框架基本的处理流程。首先由Filter拦截到所有请求,然后把请求交给所有注册的Router类,如果请求的Url正好是一个Router要拦截的,则把此请求交给这个Router,框架不再把请求向下传递。Router得到请求后,分析Url,通过Url里的信息把请求交给对应的ActionSupport的子类来处理。

      这里拦截采用Filter来处理,这跟多数的web框架一样,使用FilterServlet有更多的能力进行请求的分发。首先在一个web工程的web.xml文件中配置框架的UrlFilter类来拦截所有的请求。需要注意的一点是dispatcher 要设置为request,如果设置了forward的话,由框架内部进行的forward又会被框架拦截,从而造成无限的循环。Url-pattern设置为/*,表示所有的请求都会拦截,从而把对url分发的权利交由框架本身,而不是采用jsp规范里的url分发策略。框架在处理所有请求的url 时,依次交给各个Router类来处理,如果Router类判断是符合自己的url格式,则分发给 action 处理。如果不能处理再交给下一级的Router,最后url经由所有Router处理完,剩下的资源文件的url,如http://xxx.xxx.xxx.jpg,则框架调用filterdoChain()方法,通过filter的过滤去访问web里的资源。

<filter>

      <filter-name>unicornWeb</filter-name>

      <filter-class>com.mh.mvc.filter.UrlFilter</filter-class>

</filter>

<filter-mapping>

    <filter-name>unicornWeb</filter-name>

    <url-pattern>/*</url-pattern>

    <dispatcher>REQUEST</dispatcher>

</filter-mapping>

      大致的原理就是这样,在下篇介绍框架的详细实现。

posted @ 2011-11-25 11:43 马航 阅读(328) | 评论 (0)编辑 收藏

      我承认有点标题党了,不过题目中所说的几项技术确实有其相似之处,欲知事情原委,且听我详细道来。

      项目一开始只是不满 struts 庞大的体积,于是想自己根据其原理实现一个tiny 版。后来的开发中觉得,完全可以把上述的ajax、Restuful web service的一些思想加入进来。经过几周的努力,便开发出了一个基本成型的web 框架,暂且起名为unicorn(独角兽,吼吼)。下文开始便对这个自编写的框架做一些列的介绍,并且初步打算是将其开源,希望能一起交流和完善它。

      首先,为了能快速了解它是什么,先来看一下配置文件:

<?xml version="1.0" encoding="UTF-8" ?>
<config>
<routers>
<router class="com.mh.router.MySessionCheckRouter"></router>
</routers>
<actions>
<action class="com.mh.action.UserAction"></action>
<action class="com.mh.action.InformationAction"></action>
<action class="com.mh.action.MobileAction"></action>
</actions>
</config>

      上面就是整个工程的配置文件,可以看出需要配置的东西非常少,只需要制定action类 和 router类有哪些就Ok。框架奉行约定大于配置的思想,至于请求如何分发,这个不需要人工配置,框架自动解决。这里要介绍两个概念Action 和 Router,熟悉Struts的肯定都知道Action,Action替代Servlet、JSP时代的Servlet,所有提交的请求由struts分发给不同的Action来处理。这里道理也是一样的,Action就是经过框架处理后的请求接受者。再来说一下Router,字面意思路由器,学过计算机网络的都知道,ip数据包在网络上之所以能够顺利到达,就是因为路由器根据路由表来来确定出来传输的途径。这里Router也是这个作用,根据访问服务器的URL来制定分发策略。Router是完全可以自定义的,用户可以定义自己的Router来制定URL分发的策略,并且用户自定义的Router比系统默认的Router有更高的优先权。

二、URL上做文章

/UserAction/login/username=admin&&password=admin

      先来介绍系统MethodRouter的处理方式。上面的url根据"/"分为三个部分,第一部分是请求的Action类,第二部分是类中的方法Method,第三部分是提交的参数Param。这一点受上篇文章优酷的架构里URL设计的启发。

      经过这样的设计,就明白在上述配置文件中为何可以如此简单了。

      当然也可以不以这样的方式,框架提供自定义Router的支持。比如你想这样处理URL:/前台页面/子栏目/子栏目

      想实现上面的方式,就可以自己定义Router,在Router里面获取上述的URL,然后做处理、forward到相应的jsp页面。

三、使用Json传输数据

      Ajax请求很容易处理json数据,ajax可以与系统轻松交互。

      当初Web Service使用SOAP的xml格式传输数据,如今也有人指责这是大费周折。Restful方式提倡遵循HTTP语义,完全使用URL结合GET、POST、PUT、DELETE来传输请求,结果在roil阵营里广泛使用,认为是web service更优雅的方式。所以本框架也吸取他们的优点,也完全可以通过url传输请求的数据,如上述URL中的Param部分。不过没有遵循Restful强调的Http语义,全部使用Get和POST的请求方式,当然也可以制定为其他,这完全看你的心情,因为这对功能实现无关紧要。而且我觉得统一使用一种,更避免了需要指定请求方式的麻烦。

      数据的返回使用json格式,比SOAP更为轻量简洁和优雅,而且有更多的平台直接支持。如在android平台,本身就支持json格式的处理, 如果使用web service 的SOAP,你可能还要导入KSOAP的第三方库。

      在非浏览器的客户端,可以借助编写的工具类,来完成web service方式的操作,

public interface IWebService {
	public List<LiteInformationDTO> getInformationsOfOwnerApp(String ownerApp, int start, int limit) throws SocketTimeoutException;
}

      经过这样的封装,已经与使用web service毫无差别,而且还会更加高效,因为处理json总比处理SOAP的xml要容易。

 

      先简单写这么多,之后的续篇详细介绍。

posted @ 2011-11-22 16:22 马航 阅读(270) | 评论 (0)编辑 收藏