三、页面跳转封装

【接上】在《【斗医】【4】Web应用开发50天》中页面跳转封装只做了如下工作:名称为action的Servlet启动时,解析/WEB-INF/config下各业务模块的XXX-action.xml,并把其业务封装为FrameBusiness对象缓存到全局变量Map中:

083741396.png

接下来要完成如下功能:

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文件,在里面填充如下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<? 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():

1
2
3
4
5
@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类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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对象处理

1
2
3
4
5
6
7
8
9
10
11
12
13
@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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
  * 由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来决定跳转页面

220339683.png


6、下面初步测试一下:

I、修改D:\medical\war\index.html文件,该文件加载完就跳转到index.act

1
2
3
4
5
6
7
8
9
<!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文件

1
2
3
4
5
6
< 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>标题:

1
2
3
4
5
6
7
8
9
<!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>显示出来了,见下图:

215023971.png


【备注】:细心的读者可能发现了页面进入了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

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
/**
  * 斗医系统业务缺省跳转处理器
  *
  * @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名称把相关的跳转动作类反射出来

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
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()方法,在其中增加跳转动作逻辑部分的处理(即下图红框部分):

215156404.png


8、测试跳转功能:

I、打开D:\medical\war\WEB-INF\config\sm\system-action.xml文件,对"index.act"动作指定逻辑响应处理类Demo

1
2
3
4
5
6
< 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()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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"对应的业务配置

1
2
3
4
5
6
< 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文件,填充如下内容:

1
2
3
4
5
6
7
8
9
<!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上,如图:

220318215.png


【备注】:上面的测试也说明当业务配置了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进行具体的页面跳转