zoukankan      html  css  js  c++  java
  • Struts源码阅读心得之logic:notPresent篇

    Struts是Apache Group的一个优秀的MVC的前台框架,目前也使用的非常广泛。
    但仅仅停留在使用的层次上是没有挑战性的,研究其源码才是真正乐趣所在。
    本文以一个很小的例子入手,将这个例子中牵涉到的Struts源码做了一次剖析,
    豁然发现就在这个不起眼的例子中,也有许多值得学习的东西,故将其整理成文,目的旨在抛砖引玉,希望有兴趣者同乐。


    本文的例子就来自Struts自带的一个Example(struts-example.war)。在这个例子中的第一个JSP(index.jsp),一开始就有这样的一段代码:
    ==============================================
    Code: Select all
    <logic:notPresent name="database" scope="application">
      <font color="red">
        ERROR:  User database not loaded -- check servlet container logs
        for error messages.
      </font>
      <hr>
    </logic:notPresent>

    ==============================================

    从文档上的描述来看,这段代码主要有如下的作用:
    1、由于本sample的数据库是一个database.xml文件,所以在JSP的一开头就用这段代码来验证database.xml文件是否已被装载成功
    2、如果装载不成功,那么将打印中间那一段出错信息
    OK,由以上的描述,很自然的会想到,这个标签大概做的事情可能就是在/WEB-INF/目录下查找database.xml,成功就返回成功,错误就
    返回失败,如此如此,顺理成章
    可是Struts的源代码是否就是按照这样的逻辑写的呢?答案显然是否
    下面就开始层层剖析Struts的源码

    首先当然是从这个标签类入手,打开org.apache.struts.taglib.logic.NotPresentTag类,发现如下代码:
    ==============================================
    Code: Select all
    public class NotPresentTag extends PresentTag {

        // ------------------------------------------------------ Protected Methods

        /**
         * Evaluate the condition that is being tested by this particular tag,
         * and return <code>true</code> if the nested body content of this tag
         * should be evaluated, or <code>false</code> if it should be skipped.
         * This method must be implemented by concrete subclasses.
         *
         * @exception JspException if a JSP exception occurs
         */
        protected boolean condition() throws JspException {

            return (condition(false));

        }
    }

    ==============================================
    看来封装的不错,初步估计应该是这样:
    1、NotPresentTag继承自PresentTag类,但Tag标签类必须有doStartTag这样的方法啊,那自然就是在PresentTag类里面有了
    2、有一个condition方法,看来是PresentTag类的doStartTag方法调用了condition方法,正好这里利用面向对象的“多态”,调用到了这个方法,而不是
    PresentTag中的condition方法

    继续,打开PresentTag类,看到如下代码:
    ==============================================
    Code: Select all
    public class PresentTag extends ConditionalTagBase {

    //。。。中间有省略

    /**
         * Evaluate the condition that is being tested by this particular tag,
         * and return <code>true</code> if the nested body content of this tag
         * should be evaluated, or <code>false</code> if it should be skipped.
         * This method must be implemented by concrete subclasses.
         *
         * @exception JspException if a JSP exception occurs
         */
        protected boolean condition() throws JspException {

            return (condition(true));

        }


        /**
         * Evaluate the condition that is being tested by this particular tag,
         * and return <code>true</code> if the nested body content of this tag
         * should be evaluated, or <code>false</code> if it should be skipped.
         * This method must be implemented by concrete subclasses.
         *
         * @param desired Desired outcome for a true result
         *
         * @exception JspException if a JSP exception occurs
         */
        protected boolean condition(boolean desired) throws JspException {
            // Evaluate the presence of the specified value
            boolean present = false;
            HttpServletRequest request = (HttpServletRequest) pageContext.getRequest();
           
            if (cookie != null) {
                present = this.isCookiePresent(request);
               
            } else if (header != null) {
                String value = request.getHeader(header);
                present = (value != null);
               
            } else if (name != null) {
                present = this.isBeanPresent();
               
            } else if (parameter != null) {
                String value = request.getParameter(parameter);
                present = (value != null);
               
            } else if (role != null) {
                StringTokenizer st = new StringTokenizer(role, ROLE_DELIMITER, false);
                while (!present && st.hasMoreTokens()) {
                    present = request.isUserInRole(st.nextToken());
                }
               
            } else if (user != null) {
                Principal principal = request.getUserPrincipal();
                present = (principal != null) && user.equals(principal.getName());
               
            } else {
                JspException e = new JspException
                    (messages.getMessage("logic.selector"));
                TagUtils.getInstance().saveException(pageContext, e);
                throw e;
            }

            return (present == desired);

        }

        /**
         * Returns true if the bean given in the <code>name</code> attribute is found.
         * @since Struts 1.2
         */
        protected boolean isBeanPresent() {
            Object value = null;
            try {
                if (this.property != null) {
                    value = TagUtils.getInstance().lookup(pageContext, name, this.property, scope);
                } else {
                    value = TagUtils.getInstance().lookup(pageContext, name, scope);
                }
            } catch (JspException e) {
                value = null;
            }
           
            return (value != null);
        }

        /**
         * Returns true if the cookie is present in the request.
         * @since Struts 1.2
         */
        protected boolean isCookiePresent(HttpServletRequest request) {
            Cookie cookies[] = request.getCookies();
            if (cookies == null) {
                return false;
            }
           
            for (int i = 0; i < cookies.length; i++) {
                if (this.cookie.equals(cookies.getName())) {
                    return true;
                }
            }

            return false;
        }

    ==============================================
    通过这段代码,又看到了如下的东西:
    1、PresentTag又继承自ConditionalTagBase,抽象的比较精彩 :)
    2、代码中的一大堆变量如name, cookie, role等等,这些正好是logic:notPresent元素的属性
    3、这些变量没有在PresengTag中定义,看来是定义在了ConditionalTagBase里面了
    4、留心看name这个属性的处理,因为我们研究的那段代码(第一段代码),属性中最关键的也就是那个name="database"了
    看到她调用了isBeanPresent这个方法,而这个方法又调用了TagUtils.getInstance().lookup这个方法

    OK,到此,可以看到,研究方向转移到了TagUtils.getInstance().lookup这个方法,在开始看这个方法之前,还是把ConditionalTagBase
    这个类也看一看,说不定还有意外的收获,如下:
    ==============================================
    public abstract class ConditionalTagBase extends TagSupport {

    //。。。中间省略了那些属性(name, role, scope, user等等元素属性)的get/set方法
    //这些get/set方法将被自动调用,所以第一段代码中的name="database"中,database这个值将被自动赋给name变量


    Code: Select all
    /**
         * Perform the test required for this particular tag, and either evaluate
         * or skip the body of this tag.
         *
         * @exception JspException if a JSP exception occurs
         */
        public int doStartTag() throws JspException {

            if (condition())
                return (EVAL_BODY_INCLUDE);
            else
                return (SKIP_BODY);

        }


        /**
         * Evaluate the remainder of the current page normally.
         *
         * @exception JspException if a JSP exception occurs
         */
        public int doEndTag() throws JspException {

            return (EVAL_PAGE);

        }


        /**
         * Release all allocated resources.
         */
        public void release() {

            super.release();
            cookie = null;
            header = null;
            name = null;
            parameter = null;
            property = null;
            role = null;
            scope = null;
            user = null;

        }


        // ------------------------------------------------------ Protected Methods


        /**
         * Evaluate the condition that is being tested by this particular tag,
         * and return <code>true</code> if the nested body content of this tag
         * should be evaluated, or <code>false</code> if it should be skipped.
         * This method must be implemented by concrete subclasses.
         *
         * @exception JspException if a JSP exception occurs
         */
        protected abstract boolean condition() throws JspException;

    ==============================================
    由以上的代码,虽然没有意外收获,但是确认了不少之前的想法,如下:
    1、condition这个方法是一个抽象方法,具体实现在PresentTag和NotPresentTag类中
    2、doStartTag方法中调用了condition方法,导致了PresentTag和NotPresentTag类中condition方法的被调用

    猜测已被确认,接下来继续看TagUtils.getInstance().lookup这个方法,找到源代码如下:
    ==============================================
    Code: Select all
    /**
         * Locate and return the specified bean, from an optionally specified
         * scope, in the specified page context.  If no such bean is found,
         * return <code>null</code> instead.  If an exception is thrown, it will
         * have already been saved via a call to <code>saveException()</code>.
         *
         * @param pageContext Page context to be searched
         * @param name Name of the bean to be retrieved
         * @param scopeName Scope to be searched (page, request, session, application)
         *  or <code>null</code> to use <code>findAttribute()</code> instead
         * @return JavaBean in the specified page context
         * @exception JspException if an invalid scope name
         *  is requested
         */
        public Object lookup(PageContext pageContext, String name, String scopeName)
                throws JspException {

            if (scopeName == null) {
                return pageContext.findAttribute(name);
            }

            try {
                return pageContext.getAttribute(name, instance.getScope(scopeName));

            } catch (JspException e) {
                saveException(pageContext, e);
                throw e;
            }

        }

        /**
         * Locate and return the specified property of the specified bean, from
         * an optionally specified scope, in the specified page context.  If an
         * exception is thrown, it will have already been saved via a call to
         * <code>saveException()</code>.
         *
         * @param pageContext Page context to be searched
         * @param name Name of the bean to be retrieved
         * @param property Name of the property to be retrieved, or
         *  <code>null</code> to retrieve the bean itself
         * @param scope Scope to be searched (page, request, session, application)
         *  or <code>null</code> to use <code>findAttribute()</code> instead
         * @return property of specified JavaBean
         *
         * @exception JspException if an invalid scope name
         *  is requested
         * @exception JspException if the specified bean is not found
         * @exception JspException if accessing this property causes an
         *  IllegalAccessException, IllegalArgumentException,
         *  InvocationTargetException, or NoSuchMethodException
         */
        public Object lookup(
                PageContext pageContext,
                String name,
                String property,
                String scope)
                throws JspException {

            // Look up the requested bean, and return if requested
            Object bean = lookup(pageContext, name, scope);
            if (bean == null) {
                JspException e = null;
                if (scope == null) {
                    e = new JspException(messages.getMessage("lookup.bean.any", name));
                } else {
                    e =
                            new JspException(
                                    messages.getMessage("lookup.bean", name, scope));
                }
                saveException(pageContext, e);
                throw e;
            }

            if (property == null) {
                return bean;
            }

            // Locate and return the specified property
            try {
                return PropertyUtils.getProperty(bean, property);

            } catch (IllegalAccessException e) {
                saveException(pageContext, e);
                throw new JspException(
                        messages.getMessage("lookup.access", property, name));

            } catch (InvocationTargetException e) {
                Throwable t = e.getTargetException();
                if (t == null) {
                    t = e;
                }
                saveException(pageContext, t);
                throw new JspException(
                        messages.getMessage("lookup.target", property, name));

            } catch (NoSuchMethodException e) {
                saveException(pageContext, e);
                throw new JspException(
                        messages.getMessage("lookup.method", property, name));
            }

        }

    ==============================================


    由上,可以看到:
    1、TagUtils是一个Singleton的类
    2、代码Object bean = lookup(pageContext, name, scope);的用意显然是在寻找name变量绑定的对象
    3、2步骤查找的过程是首先查看scope变量是否为空,如果为空,将调用pageContext.findAttribute方法
    4、findAttribute方法将依次在page, request, session, application四个范围内查找name变量绑定的对象(来自Servlet API文档的解释)
    5、如果scope变量不为空(在我们的例子中,scope="Application"),那么直接调用pageContext.getAttribute方法,在scope里面查找
    name变量绑定的对象
    6、如果这个对象找到了,并且property属性没有被赋值(在我们的例子中确实没有赋值),那么直接将对象返回
    7、如果对象不为空的返回到了PresentTag的condition(boolean desire)方法,那么最终结果就是存在,notPresent这个标签里面的出错信息
    就不会被显示

    通过以上的分析,可以看到,现在的关键问题已经转移到看Application范围内是否有一个对象绑定一个key叫做"database"(看步骤5的描述)
    但是这个事情Struts的这个Example程序又是在什么时候做的呢?
    通过观察和分析,发现在struts-config配置中,将这项工作作为了一个PlugIn进行了配置,如下:
    ==============================================
    Code: Select all
    <plug-in className="org.apache.struts.webapp.example.memory.MemoryDatabasePlugIn">
        <set-property property="pathname" value="/WEB-INF/database.xml"/>
      </plug-in>

    ==============================================
    事到如今,终于离目标越来越近了,MemoryDatabasePlugIn这个类在Tomcat启动的时候启动,读取了database.xml文件,
    将其中的值读取出来并构建出了一个对象,然后将这个对象绑定到database这个key,放在了Application范围内的一个容器中
    这样的解释,终于可以和前面研究的结果相衔接起来了,不是么?:)
    迫不及待的打开MemoryDatabasePlugIn类,看到了如下代码:
    ==============================================
    Code: Select all
        /**
         * Initialize and load our initial database from persistent storage.
         *
         * @param servlet The ActionServlet for this web application
         * @param config The ApplicationConfig for our owning module
         *
         * @exception ServletException if we cannot configure ourselves correctly
         */
        public void init(ActionServlet servlet, ModuleConfig config)
            throws ServletException {

            log.info("Initializing memory database plug in from '" +
                     pathname + "'");

            // Remember our associated configuration and servlet
            this.config = config;
            this.servlet = servlet;

            // Construct a new database and make it available
            database = new MemoryUserDatabase();
            try {
                String path = calculatePath();
                if (log.isDebugEnabled()) {
                    log.debug(" Loading database from '" + path + "'");
                }
                database.setPathname(path);
                database.open();
            } catch (Exception e) {
                log.error("Opening memory database", e);
                throw new ServletException("Cannot load database from '" +
                                           pathname + "'", e);
            }

            // Make the initialized database available
            servlet.getServletContext().setAttribute(Constants.DATABASE_KEY,
                                                     database);

            // Setup and cache other required data
            setupCache(servlet, config);

        }

    ==============================================
    果然不出所料,这段代码做了如下的事情:
    1、生成了MemoryUserDatabase这个对象,并调用其setPathname和open方法,读取database.xml的值并填充到成员变量中
    2、将这个对象绑定到了ServletContext中,Constants.DATABASE_KEY这个常量就是"database"
    3、ServletContext是Servlet的一个上下文环境,也是ServletConfig的一个访问接口。ServletConfig中定义了Servlet和Servlet容器
    沟通时的一些信息(如Servlet的方法定义,参数等等)。这个上下文在一个JVM、一个Web应用中一般只会存在一份,所以显然是
    Application范围内的东西

    至此,终于搞清楚了Struts中这个logic:notPresent标签的整个运行过程,和我们的想像不完全相同的是,Struts是将database
    的配置在启动的时候就读取出来然后绑定到一个对象;然后这个标签中的代码去尝试查找这个对象,如果找到,表明database装载
    成功,否则就出错。

    另外,有一个收获是,标签的书写中,scope="Application"可以不写,因为如果不写scope属性值,Struts会依次在page, request, session,
    application四个范围内进行查找,一样可以找到这个对象,但有些地方还是有点不妥的,下面将提到。
    依次查找这个动作是通过pageContext类的一个抽象方法findAttribute方法来完成的,由于是抽象方法,所以这个方法的具体实现应该在Servlet
    容器的源代码中,也就是说,这个方法的实现是和容器相关的。另外,依次查找这个动作肯定没有指定scope查找来得快,所以性能上也会有影响
    我想这就是Struts的这个example中将scope手动指定的缘故吧。
    OK,让我们刨根问底,再来看findAttribute方法的实现,这里摘录的是Tomcat4.1的源代码,这个方法的实现在Tomcat的
    org.apache.jasper.runtime.PageContextImpl这个类中,代码如下:
    ==============================================
    Code: Select all
    public Object findAttribute(String name) {
            Object o = attributes.get(name);
            if (o != null)
                return o;

            o = request.getAttribute(name);
            if (o != null)
                return o;

            if (session != null) {
                o = session.getAttribute(name);
                if (o != null)
                    return o;
            }

    //这里的context就是ServletContext的一个实例
            return context.getAttribute(name);
        }

    ==============================================
    不用再解释了,再清楚不过了,的确是一个一个的找了过来

    至此,我们已经从logic:notPresent这个标签开始,做了一次全程历险,而且最后还到Tomcat家里去小坐了一下 :)
    总结来说,logic:notPresent这个标签是在指定scope中查找name变量绑定的一个对象,可以用来定位一些资源是否被装载
    但这个标签没有强大到自动去找指定的文件或其他资源的地步,需要我们在这个标签被调用之前,将资源装载并绑定到
    page, request, session或Application中

    Struts是一个优秀的开源项目,除了一整套Web运行机制外,她还带了一个大标签库。这个标签库中有很多比较实用的标签,
    但如果理解不透,很容易发生错用的现象。所以研读Struts的源码无其他目的,只是想好好搞清楚这些标签的作用而已。
     

  • 相关阅读:
    java线程池
    缓存命中
    dubbo面向服务使用
    ActiveMQ
    创建证书
    struts2的运行原理以及底层的工作机制
    jdbc,mybatis,hibernate各自优缺点及区别
    struts2与springmvc的区别
    linux vi 命令编辑操作
    Linux FTP基本操作命令 ( 序列二 ftp)
  • 原文地址:https://www.cnblogs.com/super119/p/1988616.html
Copyright © 2011-2022 走看看