趣谈人与软件设计(struts1源码)

struts1源码解析:
得益于设计大师的精心设计,我觉得整个struts1框架完全像个人一样,只不过这个人只会简单的识别外界向他请求的命令;
他的整个运行机制完全和人的运行机制大同小异,下面我一一介绍这种大同小异:
人与外界的交流的大致过程就是这样的:
人只要是有生命的,那么他都会对外界对其的影响作出反映;(外界对其的影响可以概括为一切能够影响其状态的东西包括:别人与之交流,看到的一切以及感觉到的一切);
他是怎么作出反映的呢?
很简单,就是根据自己大脑里保存的各种信息,比如:生活常识,生活阅历以及经验还有各种科学知识等等;
比如:我们在走路的时候会注意观察一下自己脚下的路是否平坦,然后调整自己走路的姿势;为什么我们会对脚下的路是否平坦作出反映呢?
因为我们大脑里保存着这样一条信息:如果脚下的路不平坦,那么我们不调整走路姿势的话就会很容易摔倒,就会很惨!所以我们才作出相应的正确的反映;
可以这样说:我们所做的一切反映都是根据自己大脑里已经保存的信息来进行的,大脑的神经系统就是起到这样一个很重要的作用:对与各种刺激能够通过神经系统来找到正确的信息参考并顺利的
完成此次反映;
所以我们会很容易理解疯子的行为?其主要原因就是他们的神经系统出现了不同程度的损伤导致其不能对刺激作出正确的反映,因为他们不能够正确的获取到能够处理此次刺激的大脑中已存在的信息;
说多了!我的目的是说现在的软件在人类的精心设计下已经能够达到人类的这种行为反映模式;就比如最前面提到的struts1框架:
struts1框架在启动的时候会进行下面主要的操作:
 initInternal();
 initOther();
 initServlet();
 initChain();
 getServletContext().setAttribute(Globals.ACTION_SERVLET_KEY, this);
            initModuleConfigFactory();

            // Initialize modules as needed
            ModuleConfig moduleConfig = initModuleConfig("", config);

            initModuleMessageResources(moduleConfig);
            initModulePlugIns(moduleConfig);
            initModuleFormBeans(moduleConfig);
            initModuleForwards(moduleConfig);
            initModuleExceptionConfigs(moduleConfig);
            initModuleActions(moduleConfig);
            moduleConfig.freeze();

            Enumeration names = getServletConfig().getInitParameterNames();

            while (names.hasMoreElements()) {
                String name = (String) names.nextElement();

                if (!name.startsWith(configPrefix)) {
                    continue;
                }

                String prefix = name.substring(configPrefixLength);

                moduleConfig =
                    initModuleConfig(prefix,
                        getServletConfig().getInitParameter(name));
                initModuleMessageResources(moduleConfig);
                initModulePlugIns(moduleConfig);
                initModuleFormBeans(moduleConfig);
                initModuleForwards(moduleConfig);
                initModuleExceptionConfigs(moduleConfig);
                initModuleActions(moduleConfig);
                moduleConfig.freeze();
            }

            this.initModulePrefixes(this.getServletContext());

            this.destroyConfigDigester();
        } catch (UnavailableException ex) {
            throw ex;
        } catch (Throwable t) {
            // The follow error message is not retrieved from internal message
            // resources as they may not have been able to have been
            // initialized
            log.error("Unable to initialize Struts ActionServlet due to an "
                + "unexpected exception or error thrown, so marking the "
                + "servlet as unavailable.  Most likely, this is due to an "
                + "incorrect or missing library dependency.", t);
            throw new UnavailableException(t.getMessage());
        }
  以上这些代码就相当于我们制造了一个人,并且我们已经为其组装了胳膊啊,腿啊等等对外界刺激作出反映的前提物质;此时他还只是个白痴,不能对外界的刺激作出相应的反映,这个时候即使你骂他他也不会用手打你;
  还有就是我们还为其的大脑
  输入了各种信息,这些信息能够指导他怎么正确的对外界刺激作出反映!此时就不能无辜骂他了,不然他会还击哦!因为我们为他输入了这样一个信息:如果有人骂我,我就打他!呵呵开玩笑!
  以上代码主要是做了如下的工作:
  另外需要特别说明一下:以上步骤创建的不仅仅人类的大脑还有很多的信息类似于人脑中存储的各种信息;一旦创建好之后里面的各种信息就不会变动了,这点也是软件与人类之间最大的差距:
  因为人类可以通过不停的学习来增加大脑中存储的信息,而软件(机器人)就不具备学习能力!具备学习能力的机器人在好莱坞有些科幻电影中出现过,不过对于人类来说是很可怕的呵呵!
  *************************************** initInternal();****************************************
  
  initInternal();主要是加载一个资源文件,这个文件里面包含了整个框架需要的信息文件,此信息文件只是用于框架内部处理各种异常,加载过程的提示信息;这点要比人类的大脑还要先进,
  因为如果你生病的话医生不是很容易准确的立马就知道你的病因,因为你没有记录你生病的原因,但是这个框架如果运行的过程出现了问题就会记录问题根源;而上面那个资源文件里面就包含了
  所有可能出现的问题描述信息。所以其因为描述就是内部信息,也就是这些问题是框架本身的有关各种信息描述,与你利用它编写的应用程序无关。
  这个方法的执行回涉及到以下几个主要的类:
  他们之间的关系如下图所以:
  现在我们要仔细的说一下MessageResources这个类的主要方法,也就是或许特定信息描述的方法:
  在此之前先简单的说一下国际资源文件:
  举个例子:好比一个人,他如果向周游世界的话他本应该学会很多语言,包括他所有游览的所有国家的所有语言:这样他的大脑里就有很多信息描述,并且是针对各个国家的;
  他与人打招呼的信息描述可能有:在中国:你好;在美国:Hello!等;
  国际网站也是如此,如果用户是在中国方位的话,用户请求的连接参数后面就会附加上这样一个参数:Local=CHINA,这样该框架就会知道这个用户是中国人,他会期待中文显示,于是返回给他的信息描述全部是中文的;
  因此MessageResources的子类PropertyMessageResources便包含了一个变量(protected HashMap locales = new HashMap();)用户保存此次加载的信息资源文件有几种类型的并把信息放进去;
  并具体实现了父类中的
  public String getMessage(Locale locale, String key) 这个方法,这个方法也是我们最为关心的方法;系统获取信息描述就是利用这个方法的!
  差点忘记了还有一个比较常用的方法: public String getMessage(Locale locale, String key, Object[] args):他的作用就是可以自己附上参数然后个性化信息描述;
  比如:这个信息描述是:XX你好!然后你的参数是{"赵宇飞"},那么实际显示的信息是“赵宇飞你好!”,信息描述里面还可以包含多个XX,后面的参数的顺序要和实际的XX位置保持一致!
  *************************************** initOther();****************************************
    此方法里面主要是执行了下面一句:
     value = getServletConfig().getInitParameter("config");
     设置框架默认模块(moduel)的配置文件路径;
     ***************************************  initServlet();****************************************
     此方法主要是读取ActionServlet在Web.xml中的配置并设置UrlPattern以及ServletName代码如下:
     this.servletName = getServletConfig().getServletName();

        // Prepare a Digester to scan the web application deployment descriptor
        Digester digester = new Digester();

        digester.push(this);
        digester.setNamespaceAware(true);
        digester.setValidating(false);

        // Register our local copy of the DTDs that we can find
        for (int i = 0; i < registrations.length; i += 2) {
            URL url = this.getClass().getResource(registrations[i + 1]);

            if (url != null) {
                digester.register(registrations[i], url.toString());
            }
        }

        // Configure the processing rules that we need
        digester.addCallMethod("web-app/servlet-mapping", "addServletMapping", 2);
        digester.addCallParam("web-app/servlet-mapping/servlet-name", 0);
        digester.addCallParam("web-app/servlet-mapping/url-pattern", 1);

        // Process the web application deployment descriptor
        if (log.isDebugEnabled()) {
            log.debug("Scanning web.xml for controller servlet mapping");
        }

        InputStream input =
            getServletContext().getResourceAsStream("/WEB-INF/web.xml");

        if (input == null) {
            log.error(internal.getMessage("configWebXml"));
            throw new ServletException(internal.getMessage("configWebXml"));
        }

        try {
            digester.parse(input);
        } catch (IOException e) {
            log.error(internal.getMessage("configWebXml"), e);
            throw new ServletException(e);
        } catch (SAXException e) {
            log.error(internal.getMessage("configWebXml"), e);
            throw new ServletException(e);
        } finally {
            try {
                input.close();
            } catch (IOException e) {
                log.error(internal.getMessage("configWebXml"), e);
                throw new ServletException(e);
            }
        }

        // Record a servlet context attribute (if appropriate)
        if (log.isDebugEnabled()) {
            log.debug("Mapping for servlet '" + servletName + "' = '"
                + servletMapping + "'");
        }

        if (servletMapping != null) {
            getServletContext().setAttribute(Globals.SERVLET_KEY, servletMapping);
        }
    }
 里面主要用到XML解析框架Degister很好的XML解析参数它能够根据自定义的rule来讲XML中的元素赋值到类中的变量,关于它的具体用法我已经写过有关的文章;
 从以上的代码中
 ***************************************  initChain();****************************************
 这个方法里面做的东西真的是奇妙,下面就详细的介绍它:
 首先它里面主要用到一个开源的工具类commons-chains-1.2.jar;这是个很有名的实现了典型的职责链设计模式的java开源工具JAR包;
 关于职责链设计模式很有意思;大家可能对于工厂里德生产线很熟悉;工人把一个东西放到传送带的起始端,然后传送带开始move,每到一个特定的地方就会自动有工人或者
 机器来对其进行加工,就这样直到那个东西随着传送带move到末端,产品也即加工完毕!
 软件中的职责链模式也和上述过程大体类似,比如:struts1中,ActionContext这个容易里包含了用户请求信息以及系统相关信息,然后作为参数传递给Chain(相当于放到了传送带上)
 ,经过chain中的各个Command依次处理(就相当于流水线上的不同工人或机器部件的处理)最终整个用户请求处理也就结束了。它具有极大地可扩展性,因为你可以很容易的向chain中增加新的命令
 用于处理自己感兴趣的内容;我心中能够想象到的一个情景就是:一个包含了各种信息的小球,在一个链条上运动,一到一个事前具体设置的位置,位于链条上该位置的东西并会根据需要更改小球里面的
 信息,就这样直到小球运行到链条的末端,整个小球需要更改的信息便会更改完毕!很有意思!
 下面是代码:
  try {
            String value;

            value = getServletConfig().getInitParameter("chainConfig");

            if (value != null) {
                chainConfig = value;
            }

            ConfigParser parser = new ConfigParser();
            List urls = splitAndResolvePaths(chainConfig);
            URL resource;

            for (Iterator i = urls.iterator(); i.hasNext();) {
                resource = (URL) i.next();
                log.info("Loading chain catalog from " + resource);
                parser.parse(resource);
            }
        } catch (Exception e) {
            log.error("Exception loading resources", e);
            throw new ServletException(e);
        }
  其中chainConfig默认的值是 "org/apache/struts/chain/chain-config.xml";
  里面配置了框架需要的所有Command,用来完成框架处理用户请求的需要的所有步骤;
  后面将会介绍ActionServlet是如何利用已经初始化好的职责链来处理用户请求的。
  ***************************************  Each Module Instantiation ****************************************
  接下来的代码就是根据Web.xml中的配置来进行相关Moduel的配置;
  其实Struts1中的moduel就是负责处理用户请求的一块块功能单元;在这些功能单元里面可以个性化用户处理流程,就是前面说的那些职责链,每个Moduel都可以自己定义职责链内容也就是那个文件;这样就要求我们在
  架构系统的时候要对整个系统的需求清楚然后将系统可能接收到的请求划分成一个个单独的模块,当然属于各个模块的用户请求处理过程是很类似的,这样才应该放到一个Module里面。
  接着我们看struts1框架是如何用Degister来读取模块对应的配置文件并将其中的各个节点信息准确无误的赋值到Module实体中的对应属性上面;
  看下关键代码:
     Enumeration names = getServletConfig().getInitParameterNames();
           获取所有的你配置的模块前缀名字枚举
            while (names.hasMoreElements()) {
                String name = (String) names.nextElement();

                if (!name.startsWith(configPrefix)) {
                    continue;
                }

                String prefix = name.substring(configPrefixLength);
    获取模块对应的名称,这个名称很重要,属于该模块的Action请求字符串里面就有这个名称,框架就是通过判断请求字符串里面的整个名称来决定采用哪个模块来对其进行相应;
               
                moduleConfig =initModuleConfig(prefix,getServletConfig().getInitParameter(name));
    接着就是解析指定的Module配置文件,这个方法是解析Moduel配置文件中主要的方法;下面进行详细介绍;
                initModuleMessageResources(moduleConfig);
                initModulePlugIns(moduleConfig);
                initModuleFormBeans(moduleConfig);
                initModuleForwards(moduleConfig);
                initModuleExceptionConfigs(moduleConfig);
                initModuleActions(moduleConfig);
                moduleConfig.freeze();
            }

            this.initModulePrefixes(this.getServletContext());

            this.destroyConfigDigester();
        } catch (UnavailableException ex) {
            throw ex;
        } catch (Throwable t) {
            // The follow error message is not retrieved from internal message
            // resources as they may not have been able to have been
            // initialized
            log.error("Unable to initialize Struts ActionServlet due to an "
                + "unexpected exception or error thrown, so marking the "
                + "servlet as unavailable.  Most likely, this is due to an "
                + "incorrect or missing library dependency.", t);
            throw new UnavailableException(t.getMessage());
        }
  *******************************************initModuleConfig(prefix,getServletConfig().getInitParameter(name));*****************************************************
  此方法接受两个参数:模块对应名称,模块对应的配置文件;
  下面这个方法会执行下面具体复杂的代码:
  其实我们也能想到:首先初始化那个XML解析工具类Digester,并为其设置特殊的解析规则:
   Digester digester = initConfigDigester();
   此方法又会执行:
      // Create a new Digester instance with standard capabilities
        configDigester = new Digester();
        configDigester.setNamespaceAware(true);
        configDigester.setValidating(this.isValidating());
        configDigester.setUseContextClassLoader(true);
        configDigester.addRuleSet(new ConfigRuleSet());
  这个类里面包含了所有的Moduel配置文件中节点的处理方式,感兴趣的自己可以去看看!
  至此,整个框架已经初始化完毕,下面就可以开始一多线程的方式来相应和处理用户请求了!
  用于处理用户请求的代码入口为:
  protected void process(HttpServletRequest request,
        HttpServletResponse response)
        throws IOException, ServletException {
        ModuleUtils.getInstance().selectModule(request, getServletContext());
        根据用户请求路劲来获得用户请求所属于的模块并将模块设置到request中,Key为:Globals.MODULE_KEY
        ModuleConfig config = getModuleConfig(request);
         获取模块;
        RequestProcessor processor = getProcessorForModule(config);
  ****************************getProcessorForModule具体执行代码start*************************************************
         /*
  private RequestProcessor getProcessorForModule(ModuleConfig config)
        String key = Globals.REQUEST_PROCESSOR_KEY + config.getPrefix();
        return (RequestProcessor) getServletContext().getAttribute(key);           
   
   */
   ****************************getProcessorForModule具体执行代码end*************************************************
  
        if (processor == null) {
            processor = getRequestProcessor(config);
    ****************************getRequestProcessor具体执行代码start*************************************************
   /*
    RequestProcessor processor = this.getProcessorForModule(config);

        if (processor == null) {
            try {
                processor =
                    (RequestProcessor) RequestUtils.applicationInstance(config.getControllerConfig()
                                                                              .getProcessorClass());
            } catch (Exception e) {
                throw new UnavailableException(
                    "Cannot initialize RequestProcessor of class "
                    + config.getControllerConfig().getProcessorClass() + ": "
                    + e);
            }

            processor.init(this, config);

            String key = Globals.REQUEST_PROCESSOR_KEY + config.getPrefix();

            getServletContext().setAttribute(key, processor);
        }

        return (processor);
  */
  ****************************getRequestProcessor具体执行代码end*************************************************
        }

        processor.process(request, response);
    }
 下面就是最最主要的部分了,用于用户请求的处理:
 这个处理过程是由RequestProcessor的子类完成的,它就是这个类:ComposableRequestProcessor;用户是可以自己个性化定义的,下面我们就拿框架中已存在的默认的来详细的介绍一下其整个处理流程:
 
  
   待续。。。。。。

发布了17 篇原创文章 · 获赞 4 · 访问量 5657

猜你喜欢

转载自blog.csdn.net/jasstion/article/details/83875913