扩展Struts(译文)

来源:互联网 发布:谷可女装淘宝旗舰店 编辑:程序博客网 时间:2024/04/29 01:04

                                                                   扩展Struts
                                                                                        作者:Sunil Patil

简介
    我见过很多项目,开发人员都自己去实现一个MVC的开发框架;他们这样做并不是因为他们要实现一个与Struts完全不同的开发框架,而是因为他们不知道如何去扩展Struts的功能。对于你自己开发的MVC开发框架,你可以很好的去控制它们,但同时你不得不需要大量的资源去开发它们;有时候,在正常的开发时间内,你很难完成开发它们的任务。
    Struts不仅仅是一个强大的开发工具,而且它的扩展性也非常好。你可以使用下面三种方法来扩展Struts:
    1.插件:如果你希望在系统启动的时候和关闭的时候处理一些商务逻辑,实现你自己的插件类吧。
    2.RequestProcessor:在请求处理阶段,如果你想在某个特定的功能点处理一些商务逻辑,实现你自己的RequestProcessor吧。例如,你可以扩展RequestProcessor来检测用户是否登陆,或者在处理每一个请求之前检测用户是否有处理一些特殊行为的角色。
    3.ActionServlet:如果你想在请求处理阶段或者系统启动、关闭的时候处理一些特殊的商务逻辑,你可以扩展ActionServlet类。但是你只能在当扩展插件和扩展RequestProcessor都不能满足你的要求时候使用。
    在这篇文章下,我们将使用Struts应用的例子来展示如何使用上述三种方法来扩展Struts。在文章的结尾,你可以在Resources 章节下载这篇文章的代码。两个最为成功的例子是Struts Validation框架和Tiles框架。
    这篇文章假定读者都很熟悉Struts框架并且能使用该框架写出一些简单的例子。如果你希望学习Struts框架,请查看文章结尾的Resources章节。

插件
    Struts的文件说,“插件是一个对模块级的资源或服务的包装,这些资源或服务必须被通知到应用的启动和关闭事件。”这意味着你可以编写一个实现了Plugin接口的类来在系统的启动或关闭阶段做一些事情。
    例如,我要实现一个以Hibernate为持久层的WEB应用,我们需要在系统启动的时候同时初始化Hibernate,这样在我们的系统收到第一个请求的时候,Hibernate已经配置好了,并且能够被使用;同时,我们也希望在系统关闭的时候,Hibernate也被关闭。我们可以通过下面的两个步骤来实现一个满足上述要求的Hibernate插件。
    1.编写一个实现了Plugin接口的类,如下:
public class HibernatePlugIn implements PlugIn{
 private String configFile;
//这个方法会在系统关闭阶段被调用
 public void destroy() {
  System.out.println("Entering HibernatePlugIn.destroy()");
//在这里编写hibernate关闭的代码
  System.out.println("Exiting HibernatePlugIn.destroy()");
 }
//这个方法将在系统启动阶段被调用
 public void init(ActionServlet actionServlet, ModuleConfig config)
  throws ServletException {
  System.out.println("Entering HibernatePlugIn.init()");
  System.out.println("Value of init parameter " +
                      getConfigFile());
  System.out.println("Exiting HibernatePlugIn.init()");
 }
 public String getConfigFile() {
  return name;
 }
 public void setConfigFile(String string) {
  configFile = string;
 }
}
    这个实现了Plugin接口的类必须实现两个方法:destroy()和init()。init()在系统启动的时候调用,而destroy()在系统关闭的时候调用。Struts允许你传递init()参数到你的Plugin类。通过参数,你可以为你的每一个参数创建一个javabean类型的setter方法在你的插件类里。在HibernatePlugIn 类里,我希望传递configFile 而不是在系统里写硬代码。
    2.添加如下的配置到struts-config.xml来通知Struts有新的插件:

 ...
 
           "sample1.resources.ApplicationResources"/>

 
 
       value="/hibernate.cfg.xml"/>
 


    属性className用来填写实现了Plugin接口的类的路径的类名。添加属性来设置你在插件类需要初始化的参数。在我们的例子中,我们需要传递一个配置文件的文件名,所以我们添加了一个属性来设置一个带相对路径的配置文件的文件名。
    通过读取配置文件,Tiles 和Validator 框架都是以插件的形式启动的。在我们的插件类里,我们可以做至少如下的两件事:
    。如果你的应用依赖配置文件,你可以通过插件类来检测是否有配置文件,如果没有的话,抛出ServletException违例。这种结果我们在ActionServlet 里得不到。
    。如果你想在模块的配置里做一些修改,那么插件接口的init()将是你最后的机会,这些模块配置收集了描述基于Struts的模块的静态配置信息,一旦所有的插件运行完毕,Struts不再读取模块配置。

一个请求是如何被处理的
    ActionServlet是Struts框架的唯一一个Servlet,它的责任是处理所有的请求。只要它收到了一个请求,它就试图找到一个子系统来处理这个请求。一旦找到了这个子系统,它就为那个字系统产生一个RequestProcessor 对象,并且通过传递HttpServletRequest 对象和HttpServletResponse 对象来调用RequestProcessor 对象的process()方法。
    RequestProcessor.process() 方法是大多数请求被处理的地方。process()通过应用模板方法模式来实现,在这个方法里,处理请求的每一个阶段都被作为一个独立的方法,所有这些方法都在process()方法里被顺序调用。例如,有一个独立的方法是用来寻找与当前请求相关的ActionForm 类,并且检测当前用户是否拥有处理当前请求影射的角色。这就增加了系统的复杂性。处理Struts分发的RequestProcessor类只为每一个请求处理过程提供了一个默认的实现。这意味着你能够重载那些你感兴趣的方法,而其他的方法你使用默认的实现。例如,默认情况下,Struts调用request.isUserInRole()来获取用户是否有处理当前ActionMapping所需要的角色之一,但是如果你想获取用户是否有权限来处理数据库,就不得不重载processRoles()方法,通过检测用户是否有权限处理数据库来返回true或false。
    首先,我们来看在默认情况下,process()方法的实现:
public void process(HttpServletRequest request,
                        HttpServletResponse response)
    throws IOException, ServletException {
        // Wrap multipart requests with a special wrapper
        request = processMultipart(request);
        // Identify the path component we will
        // use to select a mapping
        String path = processPath(request, response);
        if (path == null) {
            return;
        }
        if (log.isDebugEnabled()) {
            log.debug("Processing a '" + request.getMethod() +
                      "' for path '" + path + "'");
        }
        // Select a Locale for the current user if requested
        processLocale(request, response);
        // Set the content type and no-caching headers
        // if requested
        processContent(request, response);
        processNoCache(request, response);
        // General purpose preprocessing hook
        if (!processPreprocess(request, response)) {
            return;
       }
        // Identify the mapping for this request
        ActionMapping mapping =
            processMapping(request, response, path);
        if (mapping == null) {
            return;
        }
        // Check for any role required to perform this action
        if (!processRoles(request, response, mapping)) {
            return;
        }
        // Process any ActionForm bean related to this request
        ActionForm form =
            processActionForm(request, response, mapping);
        processPopulate(request, response, form, mapping);
        if (!processValidate(request, response, form, mapping)) {
            return;
        }
        // Process a forward or include specified by this mapping
        if (!processForward(request, response, mapping)) {
            return;
        }
        if (!processInclude(request, response, mapping)) {
            return;
        }
        // Create or acquire the Action instance to
        // process this request
        Action action =
            processActionCreate(request, response, mapping);
        if (action == null) {
            return;
        }
        // Call the Action instance itself
        ActionForward forward =
            processActionPerform(request, response,
                                action, form, mapping);
        // Process the returned ActionForward instance
        processForwardConfig(request, response, forward);
    }
    1.processMultipart():在这里方法里,Struts将读取请求,以便找出contentType 是否为multipart/form-data。如果是这样的话,Struts将解析它并且把它包装到一个实现了HttpServletRequest接口的包装器。当你产生一个HTML表单来传递数据,请求的contentType 默认是application/x-www-form-urlencoded。但是如果你的表单使用FILE类型来允许用户下载文件的话,你就必须修改你的表单的contentType 为multipart/form-data。如果那样做的话,你将不能通过HttpServletRequest类的getParameter()方法来获取用户传递的数据。你将不得不将一个请求以InputStream 的形式读取并且解析以便获取数据。
    2.processPath():在这个方法里,Struts将读取请求的URI,用来决定用来获取ActionMapping 元素的路径元素。
    3.processLocale():在这个方法里,Struts将为当前请求获取Locale 。如果Locale配置了,那么它将被作为org.apache.struts.action.LOCALE 属性的值保存到HttpSession 中。作为这个方法的副产品,HttpSession 也将产生一个实例。如果你不想这些发生,通过添加如下配置到你的struts-config.xml里,你设置了ControllerConfig 的Locale 属性为false:

 

    4.processContent():通过调用response.setContentType()来为响应设置contentType 。这个方法首先是试图从struts-config.xml里获取contentType ,默认情况下,它会使用text/html,如果想重载它,使用如下配置:

 

    5.processNoCache():如果被配置成no-cache的话,Struts将在每一个返回里如下的三行头代码:
response.setHeader("Pragma", "No-cache");
response.setHeader("Cache-Control", "no-cache");
response.setDateHeader("Expires", 1);
    如果你想设置no-cache的文件头,添加如下配置到struts-config.xml:

 

    6.processPreprocess():一般情况下,预处理hook能够被子类重载。它在RequestProcessor 类里什么也不做,经常返回true。返回false的话讲终止请求的执行。
    7.processMapping():这个方法将使用路径信息来获取一个ActionMapping 对象。ActionMapping 对象代表了struts-config.xml 文件的 元素:
        name="newContactForm" scope="request">
 
 

    ActionMapping 元素包含了在处理请求过程的所需要的Action 的名称和ActionForm 等信息。它也包含了当前ActionMapping的ActionForwards 配置的信息。
    8.processRoles():Struts Web应用的安全体系只提供一个授权模式。这意味着一旦用户登陆容器,Struts的processRoles()方法将通过调用request.isUserInRole()来检测他是否拥有一个运行给定ActionMapping 的所需要的权限。

    例如,你有AddUserAction 类,并且只希望administrator 能增加用户。你要做的是,添加一个值为administrator 的role属性到AddUserAction 的action元素。这样的话,在运行AddUserAction 类之前,它会检测该用户是否拥有administrator 的角色。
    9.processActionForm():每一个ActionMapping 都有与它相关的ActionForm 。当Struts运行ActionMapping的时候,它将从元素的name属性找到对应ActionForm 的名字。
           type="org.apache.struts.action.DynaActionForm">
                            type="java.lang.String"/>
                            type="java.lang.String"/>

    在我们的例子中,它首先是寻找是否在请求范围内有org.apache.struts.action.DynaActionForm类的实例。如果存在,它就使用它。如果不存在,它就创建一个并且设置到请求范围内。
    10.processPopulate():在这个方法里,Struts将使用匹配请求参数的值来组织ActionForm 类的实例的属性。
    11.processValidate():Struts将调用ActionForm 类的validate()方法。如果validate()方法返回ActionErrors ,它将冲定向到 的input 属性所指示的页面。
    12.processForward()和processInclude():在这些方法里,Struts将检测元素的forward 或include 属性的值。如果发现,用配置页的值赋值forward 或include 。

 
    你可以从这些名字上猜到,processForward()结束了,就调用RequestDispatcher.forward(),而processInclude()调用RequestDispatcher.include()。如果你把forward 属性和include 属性都配置了,Struts将首先调用forward,因为它将首先执行。
    13.processActionCreate():这个方法从元素的type 属性取得Action 类的名字,并且返回该类的一个实例。在我们的例子里,它将产生一个com.sample.NewContactAction 类的实例。
    14.processActionPerform():这个方法调用你的Action类的execute()方法,在那里你编写你的业务逻辑的代码。
    15.processForwardConfig():execute()方法将返回一个ActionForward对象,它用来指示哪一个页面将显示给用户。所以Struts将为页面产生一个RequestDispatcher 对象,并且调用RequestDispatcher.forward()方法。
    上面的列表给出了RequestProcessor 类在请求的每一个阶段的默认实现,以及这些实现的执行顺序。就像你看到的一样,RequestProcessor 类相当复杂;但是它允许你通过设置元素的属性来配置它。例如,如果你希望应用产生XML而不是HTML,你可以通过设置元素的属性来通知Struts。

创建你自己的RequestProcessor
    前面,我们看到了一个默认的RequestProcessor 实现是如何工作的。现在,我们将给出一个例子,来看看如何通过创建我们自己的定制RequestProcessor类来定制它。为了展示我们的定制RequestProcessor,我们将更改我们的例子以适应下面的两个商业需求:
    。我们将创建一个ContactImageAction 类,这个类用来产生图像,而不是HTML文件。
    。在处理每一个请求之前,我们要通过检测session里的userName属性来检测用户是否登陆,如果没有,将页面重定向到login页面。
    为了实现上面的需求,我们将分两个步骤来修改我们的例子。
    1.创建继承了RequestProcessor 类的你自己的CustomRequestProcessor 类,如下:
public class CustomRequestProcessor
    extends RequestProcessor {
        protected boolean processPreprocess (
            HttpServletRequest request,
            HttpServletResponse response) {
            HttpSession session = request.getSession(false);
        //If user is trying to access login page
        // then don't check
        if( request.getServletPath().equals("/loginInput.do")
            || request.getServletPath().equals("/login.do") )
            return true;
        //Check if userName attribute is there is session.
        //If so, it means user has allready logged in
        if( session != null &&
        session.getAttribute("userName") != null)
            return true;
        else{
            try{
                //If no redirect user to login Page
                request.getRequestDispatcher
                    ("/Login.jsp").forward(request,response);
            }catch(Exception ex){
            }
        }
        return false;
    }

    protected void processContent(HttpServletRequest request,
                HttpServletResponse response) {
            //Check if user is requesting ContactImageAction
            // if yes then set image/gif as content type
            if( request.getServletPath().equals("/contactimage.do")){
                response.setContentType("image/gif");
                return;
            }
        super.processContent(request, response);
    }
}

    在CustomRequestProcessor 类的processPreprocess 方法里,我们检测session的userName属性,如果没有找到的话,重定向到login页面。
    为了在ContactImageAction 类运行后产生图像作为输出,我们重写了processContent 方法,首先检测请求的路径是否为/contactimage,如果是的话,我们设置contentType 属性为image/gif,否则,设置为text/html。
    2.添加下面的配置到你的struts-config.xml 文件,用来通知Struts使用CustomRequestProcessor 而不是默认的RequestProcessor 类:

  value="com.sample.util.CustomRequestProcessor"/>

    请注意:如果你有少量的Action类,在那里,你需要产生一个输出内容的contentType 不是text/html,重写processContent()方法是有用的。如果不是那样,你应该创建一个子系统来处理那些要求输出是图像的Action,并且把它的contentType 设置为image/gif。
    Tiles框架使用了它自己的RequestProcessor 类来装配由Struts产生的输出。

ActionServlet
    如果你查看你的Struts Web应用的web.xml文件,它看起来像下面的样子:

       
            action=
            org.apache.struts.action.ActionServlet
           
       

       
            action
            *.do
       


    这意味着在Struts中,ActionServlet 担负着处理所有的请求的责任。你可以创建一个ActionServlet 的子类来处理你在应用的开始、结束或每一个请求所要处理的事情。但是你最好创建一个插件或一个RequestProcessor ,而不是ActionServlet的子类。Servlet 1.1以前,Tiles框架是通过扩展ActionServlet 来装配输出;但是从1.1以后,它使用TilesRequestProcessor 类。

结论
    决定开发你自己的MVC框架是一个需要慎重的决定:你需要考虑开发和维护代码所需要的时间和资源。Struts是一个强大的和稳定的开发框架,你可以扩展它来实现你的绝大多数的应用需求。
    另一方面,不要轻易做出扩展Struts的决定。如果你写了一些低效的代码到RequestProcessor 类里,它会在每一个请求上执行,从而降低你整个应用系统的性能。如果是那样的话,你开发一个你自己的MVC框架都可能比扩展Struts好。

资料
这篇文章的代码:http://www.onjava.com/onjava/2004/11/10/examples/sample1.zip
Struts主页:http://struts.apache.org/
介绍Struts:http://www.onjava.com/pub/a/onjava/2001/09/11/jsp_servlets.html
学习Struts1.1:http://www.onjava.com/pub/a/onjava/pub/a/2002/11/06/struts1.html

关于作者
    Sunil Patil 在J2EE开发方面有着5年以上的经验。他感兴趣的领域包括:ORM、UI开发框架和Portal。




原创粉丝点击