三、页面跳转封装
【接上】在《【斗医】【4】Web应用开发50天》中页面跳转封装只做了如下工作:名称为action的Servlet启动时,解析/WEB-INF/config下各业务模块的XXX-action.xml,并把其业务封装为FrameBusiness对象缓存到全局变量Map中:
接下来要完成如下功能:
Web容器(Tomcat)接受到HTTP请求后,Web容器把HTTP转换为HttpServletRequest对象,然后根据请求名称匹配规则找到相应的Servlet,调用servlet-class对应类的doGet()或doPost()方法,在该方法中根据请求名称从全局缓存(FrameCache.businessMap)中读取业务对象(FrameBusiness),由FrameBusiness对象跳转到相应的页面。
1、配置名称为action的Servlet。
打开D:\medical\war\WEB-INF\web.xml文件,在里面填充如下内容:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd"> <web-app> <servlet> <servlet-name>action</servlet-name> <servlet-class>com.medical.frame.FrameLauncher</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>action</servlet-name> <url-pattern>*.act</url-pattern> </servlet-mapping> <welcome-file-list> <welcome-file>index.html</welcome-file> </welcome-file-list> </web-app>
【备注】:该内容在前面已配置过,这里再强调一次一方面是理顺思路,另一方面是强调Web容器处理HTTP请求的流程。
2、把HttpServletRequest请求均交给get处理
打开D:\medical\src\com\medical\frame\FrameLauncher.java,把doPost()处理交给doGet():
@Override public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException { doGet(request, response); }
3、为了保持FrameLauncher功能的简洁性,这里定义一个FrameDispatcher类,FrameLauncher.doGet()的业务逻辑交给FrameDispatcher处理:
I、定义com.medical.frame.FrameDispatcher.java类
public class FrameDispatcher { private HttpServlet servlet = null; /** * 构造器 */ public FrameDispatcher(HttpServlet servlet) { this.servlet = servlet; } public void process(HttpServletRequest request, HttpServletResponse response) throws FrameException { // TODO } }
II、FrameLauncher.doGet()交给FrameDispatcher对象处理
@Override public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException { FrameDispatcher dispatcher = new FrameDispatcher(this); try { dispatcher.process(request, response); } catch (FrameException e) { logger.error(e.getErrorDesc(), e); } }
4、定义FrameDispatcher.getRequestName(HttpServletRequest)方法从HttpServletRequest对象中解析出请求名称(比如从/medical/index.act中获取index)
/** * 由HttpServletRequest对象获取请求名称 */ private String getRequestName(HttpServletRequest request) { String name = request.getRequestURI(); if (name == null) { return null; } if (name.endsWith(FrameConstant.BUS_NAME_POSTFIX) == false) { return null; } int headIndex = name.lastIndexOf("/") + 1; int rearIndex = name.lastIndexOf(FrameConstant.BUS_NAME_POSTFIX); return name.substring(headIndex, rearIndex); }
5、修改FrameDispatcher.process()方法,其功能是由业务名称获取对应的业务对象,然后由业务对象的forward来决定跳转页面
6、下面初步测试一下:
I、修改D:\medical\war\index.html文件,该文件加载完就跳转到index.act
<!DOCTYPE HTML> <html> <head> <title>medical</title> <script type="text/javascript"> top.location="index.act"; </script> </head> </html>
II、修改D:\medical\war\WEB-INF\config\sm\system-action.xml文件
<business name="index" mustlogin="false"> <forward> <path name="success" path="/module/main/main.html"/> <path name="failure" path="/module/main/main.html"/> </forward> </business>
当系统加载时名称为action的Servlet会把该文件读入全局缓存。访问index.html时执行top.location="index.act",action接受到"*.act"就执行FrameDispatcher.process()方法,从而跳转到/module/main/main.html页面
III、在D:\medical\war\module\main\下创建main.html文件,这里只是为了测试,所以main.html只是输出一个<h1>This is main page!</h1>标题:
<!DOCTYPE HTML> <html> <head> <title>medical</title> </head> <body> <h1>This is main page!</h1> </body> </html>
IV、启动Tomcat服务,在浏览器中输入http://localhost:8080/medical,会发现页面直接把<h1>This is main page!</h1>显示出来了,见下图:
【备注】:细心的读者可能发现了页面进入了main.html,但地址却依旧停留在index.act处,见上图。其中原因请大家在谷歌上搜索一下“JSP forward”,有文章会给出详细说明。
7、再仔细考虑一个场景:在跳转到页面A之前,(1)有可能需要把当前页面的数据保存在数据库中,(2)或者需要进行一些逻辑判断,符合条件才能进入A页面,不符合则不能进入A页面。这就是当时XXX-action.xml中业务定义"business-class"的原因,但我们的FrameDispatcher.process()并没有这方面的处理,现在进行修改:
I、定义缺省跳转动作执行类com.medical.frame.FrameDefaultAction.java
/** * 斗医系统业务缺省跳转处理器 * * @author qingkechina */ public class FrameDefaultAction { /** * 请求对象 */ protected HttpServletRequest request = null; /** * 响应对象 */ protected HttpServletResponse response = null; /** * 会话对象 */ protected HttpSession session = null; public void setRequest(HttpServletRequest request) { this.request = request; } public void setResponse(HttpServletResponse response) { this.response = response; } public void setSession(HttpSession session) { this.session = session; } /** * 读取Request对象中的参数 */ public String getParameter(String param) { return request.getParameter(param); } /** * 缺省响应动作 */ public String execute() throws FrameException { return null; } }
【备注】:这里没有使用接口或抽象类,建议读者在自已实现该功能时最好能抽象出接口,这样做的好处可参见设计模式相关内容
II、定义动作工厂,可以根据XXX-action.xml中的业务business-class名称把相关的跳转动作类反射出来
package com.medical.frame; import com.medical.frame.constant.FrameConstant; import com.medical.frame.constant.FrameErrorCode; /** * 斗医系统业务跳转动作工厂类 * * @author qingkechina */ public class FrameActionFactory { private static FrameActionFactory instance = new FrameActionFactory(); private FrameActionFactory() { } public static FrameActionFactory getInstance() { return instance; } /** * 反射跳转动作实现类 */ public FrameDefaultAction implement(String className) throws FrameException { if (className == null || className.trim().length() == 0) { className = FrameConstant.FORWARD_DEFAULT_ACTION; } try { return (FrameDefaultAction) Class.forName(className).newInstance(); } catch (Exception e) { throw new FrameException(FrameErrorCode.JAVA_REFLECT_ERROR, e); } } }
III、修改FrameDispatcher.process()方法,在其中增加跳转动作逻辑部分的处理(即下图红框部分):
8、测试跳转功能:
I、打开D:\medical\war\WEB-INF\config\sm\system-action.xml文件,对"index.act"动作指定逻辑响应处理类Demo
<business name="index" mustlogin="false" business-class="com.medical.frame.forward.Demo"> <forward> <path name="success" path="/module/main/main.html"/> <path name="failure" path="/module/main/main.html"/> </forward> </business>
II、定义com.medical.frame.forward.Demo类,该类继承FrameDefaultAction类,并重写execute()方法
package com.medical.frame.forward; import com.medical.frame.FrameDefaultAction; import com.medical.frame.FrameException; public class Demo extends FrameDefaultAction { @Override public String execute() throws FrameException { // 这里可以处理一些逻辑 System.out.println("===================这里可以进行逻辑处理"); // 逻辑处理完后跳转到相应页面 return "login.act"; } }
从代码上可以看出,这里逻辑处理完之后,就直接跳转到login.act对应的页面
III、打开D:\medical\war\WEB-INF\config\sm\system-action.xml文件,修改"login.act"对应的业务配置
<business name="login" mustlogin=""> <forward> <path name="success" path="/module/login/login.html"/> <path name="failure" path="/module/login/login.html"/> </forward> </business>
IV、在D:\medical\war\module\login下创建login.html文件,填充如下内容:
<!DOCTYPE HTML> <html> <head> <title>medical</title> </head> <body> <h1>This is Login page!</h1> </body> </html>
V、启动Tomcat后在浏览器中输入http://localhost:8080/medical回车,查看页面,页面上输出“This is Login page!”,这说明页面是跳转到了login.html上,如图:
【备注】:上面的测试也说明当业务配置了business-class,则相应动作执行类跳转动作的优先级要高于<forward>
OK,到这里跳转封装的基本功能已完成,可能会有人问:“只是一个跳转,使用HTML的<a>标签,岂不是更简单,何苦折腾这么一通呢?”
有这个疑问是一件好事情,说明大家都进行了思考。正如上面所说,一些逻辑性处理需要在服务端进行(如有些场景需要根据逻辑判断决定跳转到哪个页面,若纯使用WEB前端处理,显的客户端比较胖或者叫富客户端),另外把眼光放远一些,试想Struts做了哪些事情呢?除了过滤器之外无非也是逻辑判断,数据加工后的页面显示。
下面我们再理顺一下思路:
(1)启动Tomcat时,会加载web.xml,根据web.xml把名称为action的Servlet拉起
(2)名称为action的Servlet启动时,根据其配置规则反射FrameLauncher类,并调用它的init()方法
(3)在init()方法中会读取运行环境中的XXX-action.xml文件,解析该文件后把动作名及动作名对应的业务对象FrameBusiness缓存入全局变量
(4)当在浏览器中输入http://localhost:8080/medical时,Tomcat根据web.xml配置首先加载index.html
(5)在index.html加载完毕后直接使用top.location="index.act"跳转到index.act
(6)tomcat服务器一看到是.act结尾的请求,就使用FrameLauncher的doGet()方法来处理
(7)FrameLauncher.doGet()委托给FrameDispatcher.process(),在process()中它根据index.act在全局缓存中查找FrameBusiness
(8)首先看一下FrameBusiness是否有class-name,若有就获取逻辑处理类指定的路径;若没有就使用forward指定的路径
(9)然后把该路径交给HttpServlet进行具体的页面跳转