三、页面跳转封装

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

【斗医】【5】Web应用开发20天_第1张图片

接下来要完成如下功能:

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




    
        action
        com.medical.frame.FrameLauncher
        1
    
    
        action
        *.act
    
    
         index.html
    



【备注】:该内容在前面已配置过,这里再强调一次一方面是理顺思路,另一方面是强调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来决定跳转页面

【斗医】【5】Web应用开发20天_第2张图片


6、下面初步测试一下:

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



    
        medical
        
            top.location="index.act";
        
      


II、修改D:\medical\war\WEB-INF\config\sm\system-action.xml文件


    
         
        
    


当系统加载时名称为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只是输出一个

This is main page!

标题:



    
        medical      
    
    
        

This is main page!

    

IV、启动Tomcat服务,在浏览器中输入http://localhost:8080/medical,会发现页面直接把

This is main page!

显示出来了,见下图:

【斗医】【5】Web应用开发20天_第3张图片


【备注】:细心的读者可能发现了页面进入了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()方法,在其中增加跳转动作逻辑部分的处理(即下图红框部分):

【斗医】【5】Web应用开发20天_第4张图片


8、测试跳转功能:

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


    
        
        
    


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"对应的业务配置


    
        
        
    


IV、在D:\medical\war\module\login下创建login.html文件,填充如下内容:



    
        medical
    
    
        

This is Login page!

    


V、启动Tomcat后在浏览器中输入http://localhost:8080/medical回车,查看页面,页面上输出“This is Login page!”,这说明页面是跳转到了login.html上,如图:

【斗医】【5】Web应用开发20天_第5张图片


【备注】:上面的测试也说明当业务配置了business-class,则相应动作执行类跳转动作的优先级要高于



OK,到这里跳转封装的基本功能已完成,可能会有人问:“只是一个跳转,使用HTML的标签,岂不是更简单,何苦折腾这么一通呢?”

有这个疑问是一件好事情,说明大家都进行了思考。正如上面所说,一些逻辑性处理需要在服务端进行(如有些场景需要根据逻辑判断决定跳转到哪个页面,若纯使用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进行具体的页面跳转