• Struts2 源码分析——DefaultActionInvocation类的执行action


    本章简言

    上一章讲到关于拦截器的机制的知识点,让我们对拦截器有了一定的认识。我们也清楚的知道在执行用户action类实例之前,struts2会先去执行当前action类对应的拦截器。而关于在哪里执行action类实例,笔者根本就没有详细的讲到。更多只是几笔带过而以。虽然在《Struts2 源码分析——Action代理类的工作》章节里面也讲到过关于DefaultActionInvocation类的一些作用。提过DefaultActionInvocation类会去执行action类实例。但是还是没有具体的指出重点的代码。而本章就是来讲执行action类实例的代码落在哪里。即是DefaultActionInvocation类的invokeAction方法。

     DefaultActionInvocation类的执行action

    上一章里面有提到过DefaultActionInvocation类的invoke方法里面的invokeActionOnly方法。没有错!当所有拦截器前半部分执行结束之后,就会去执行invokeActionOnly方法。这个方法就是执行action类实例的入口。而invokeActionOnly方法实际是去调用本身类的invokeAction方法。看一下代码就知道了。

    DefaultActionInvocation类:

    1 public String invokeActionOnly() throws Exception {
    2         return invokeAction(getAction(), proxy.getConfig());
    3     }

    代码里面getAction方法就是获得action类实例。即是跟《Struts2 源码分析——Action代理类的工作》章节里面讲到的createAction方法有关。当程序执行到这里的时候,createAction方法已经新建好了action类实例。不清楚读者请到这章去看一下。让我们看一下invokeAction方法的源码吧。

     1 protected String invokeAction(Object action, ActionConfig actionConfig) throws Exception {
     2         String methodName = proxy.getMethod();//获得要执行的方法名。
     3 
     4         LOG.debug("Executing action method = {}", methodName);
     5 
     6         String timerKey = "invokeAction: " + proxy.getActionName();
     7         try {
     8             UtilTimerStack.push(timerKey);
     9 
    10             Object methodResult;
    11             try {
    12                 methodResult = ognlUtil.callMethod(methodName + "()", getStack().getContext(), action);//执行action类实例
    13             } catch (MethodFailedException e) {
    14                 // if reason is missing method,  try checking UnknownHandlers
    15                 if (e.getReason() instanceof NoSuchMethodException) {
    16                     if (unknownHandlerManager.hasUnknownHandlers()) {
    17                         try {
    18                             methodResult = unknownHandlerManager.handleUnknownMethod(action, methodName);
    19                         } catch (NoSuchMethodException ignore) {
    20                             // throw the original one
    21                             throw e;
    22                         }
    23                     } else {
    24                         // throw the original one
    25                         throw e;
    26                     }
    27                     // throw the original exception as UnknownHandlers weren't able to handle invocation as well
    28                     if (methodResult == null) {
    29                         throw e;
    30                     }
    31                 } else {
    32                     // exception isn't related to missing action method, throw it
    33                     throw e;
    34                 }
    35             }
    36             return saveResult(actionConfig, methodResult);
    37         } catch (NoSuchPropertyException e) {
    38             throw new IllegalArgumentException("The " + methodName + "() is not defined in action " + getAction().getClass() + "");
    39         } catch (MethodFailedException e) {
    40             // We try to return the source exception.
    41             Throwable t = e.getCause();
    42 
    43             if (actionEventListener != null) {
    44                 String result = actionEventListener.handleException(t, getStack());
    45                 if (result != null) {
    46                     return result;
    47                 }
    48             }
    49             if (t instanceof Exception) {
    50                 throw (Exception) t;
    51             } else {
    52                 throw e;
    53             }
    54         } finally {
    55             UtilTimerStack.pop(timerKey);
    56         }
    57     }

    这个方法的做的事情是很简单。就是获得当前action类实例要执行的方法名。在根据OgnlUtil工具类在执行对应action类实例的方法。显然想要知道更深一点的话就必须深入OgnlUtil工具类的源码。让我们看一下吧。

    OgnlUtil类:

    1 public Object callMethod(final String name, final Map<String, Object> context, final Object root) throws OgnlException {
    2         return compileAndExecuteMethod(name, context, new OgnlTask<Object>() {
    3             public Object execute(Object tree) throws OgnlException {
    4                 return Ognl.getValue(tree, context, root);
    5             }
    6         });
    7     }

    OgnlUtil类:

    private <T> Object compileAndExecuteMethod(String expression, Map<String, Object> context, OgnlTask<T> task) throws OgnlException {
            Object tree;
            if (enableExpressionCache) {
                tree = expressions.get(expression);
                if (tree == null) {
                    tree = Ognl.parseExpression(expression);
                    checkSimpleMethod(tree, context);
                }
            } else {
                tree = Ognl.parseExpression(expression);
                checkSimpleMethod(tree, context);
            }
    
            final T exec = task.execute(tree);
            // if cache is enabled and it's a valid expression, puts it in
            if(enableExpressionCache) {
                expressions.putIfAbsent(expression, tree);
            }
            return exec;
        }

    笔者看到这里的时候就有一点心烦。主要是一看就知道又要去学习一下关于ONGL相关的语法。这一点请读者自己去补充。Ognl.getValue(tree, context, root)这句代码就是ONGL语法的体现。其中tree就是ONGL的表达式。root就是根对象。即是用户action类实例。对于context笔者还真不知道要什么去讲解他。笔者把他理解为ONGL的上下文。通常跟ONGL表达式里面的“#”号相关。简单讲就是Ognl.getValue(tree, context, root)就是执行action方法。而接下深入就是ognl.jar的源码了。已经跳出了struts2源码的范围了。

    执行完上面的代码之后,就开始保存相应的结果。saveResult方法就是用于处理结果的。如果结果值是Result类型的话,就把他存在成员变量explicitResult上,并返回NULL。否则就转为String类型并返回。代码如下

     1 protected String saveResult(ActionConfig actionConfig, Object methodResult) {
     2         if (methodResult instanceof Result) {
     3             this.explicitResult = (Result) methodResult;
     4 
     5             // Wire the result automatically
     6             container.inject(explicitResult);
     7             return null;
     8         } else {
     9             return (String) methodResult;
    10         }
    11     }

    如果笔者没有查看源码的话,就不会知道原来action类实例在执行结束之后不只是一个String类型。还有一个叫Result类。如果有硬的讲的话,笔者觉得只是返回一个Result类。让我们看下面一段代码。就知道笔者为什么会有这样子的感觉。

    1            // now execute the result, if we're supposed to
    2                 if (proxy.getExecuteResult()) {
    3                     executeResult();
    4                 }           

    上面这段代码是在DefaultActionInvocation类的invoke方法的后半部分。如果返回的结果是字符串,最后还是会根据字符串来获得对应的Result类的实例。Result类的实例类却很多。如ServletDispatcherResult类等。找到了Result类的实例就会去执行他本身的execute方法。我们可以在executeResult方法的源码里面体现出来。看一下吧。

    DefaultActionInvocation类:

     1 private void executeResult() throws Exception {
     2         result = createResult();
     3 
     4         String timerKey = "executeResult: " + getResultCode();
     5         try {
     6             UtilTimerStack.push(timerKey);
     7             if (result != null) {
     8                 result.execute(this);
     9             } else if (resultCode != null && !Action.NONE.equals(resultCode)) {
    10                 throw new ConfigurationException("No result defined for action " + getAction().getClass().getName()
    11                         + " and result " + getResultCode(), proxy.getConfig());
    12             } else {
    13                 if (LOG.isDebugEnabled()) {
    14                     LOG.debug("No result returned for action {} at {}", getAction().getClass().getName(), proxy.getConfig().getLocation());
    15                 }
    16             }
    17         } finally {
    18             UtilTimerStack.pop(timerKey);
    19         }
    20     }

    DefaultActionInvocation类:

     1 public Result createResult() throws Exception {
     2         LOG.trace("Creating result related to resultCode [{}]", resultCode);
     3 
     4         if (explicitResult != null) {
     5             Result ret = explicitResult;
     6             explicitResult = null;
     7 
     8             return ret;
     9         }
    10         ActionConfig config = proxy.getConfig();
    11         Map<String, ResultConfig> results = config.getResults();
    12 
    13         ResultConfig resultConfig = null;
    14 
    15         try {
    16             resultConfig = results.get(resultCode);
    17         } catch (NullPointerException e) {
    18             LOG.debug("Got NPE trying to read result configuration for resultCode [{}]", resultCode);
    19         }
    20         
    21         if (resultConfig == null) {
    22             // If no result is found for the given resultCode, try to get a wildcard '*' match.
    23             resultConfig = results.get("*");
    24         }
    25 
    26         if (resultConfig != null) {
    27             try {
    28                 return objectFactory.buildResult(resultConfig, invocationContext.getContextMap());
    29             } catch (Exception e) {
    30                 LOG.error("There was an exception while instantiating the result of type {}", resultConfig.getClassName(), e);
    31                 throw new XWorkException(e, resultConfig);
    32             }
    33         } else if (resultCode != null && !Action.NONE.equals(resultCode) && unknownHandlerManager.hasUnknownHandlers()) {
    34             return unknownHandlerManager.handleUnknownResult(invocationContext, proxy.getActionName(), proxy.getConfig(), resultCode);
    35         }
    36         return null;
    37     }

    前一段代码:讲述executeResult方法。executeResult方法只是用于执行Result类的实例。即是执行Result类的execute方法。

    后一段代码:进述executeResult方法里面用到的createResult方法。也就是笔者前面讲到的会根据返回的结果字符串来获得对应的Result类的实例。我们可以看到如果他返回是一个Result类的话,就直接返回。如果不是,就是从配置信息里面获得对应在的result元素节点信息。通过result元素节点信息来生成对应的Result类的实例。显然我们可以看到ObjectFactory类又在一次发挥了作用。在前面action类实例也是靠他来完成的。

    到这里,相信有部分读者会跟笔者一样子有一个疑问?Result类到底是什么东东。其实他是用于对action类实例执行之后结果的处理。简单点讲可以理解为处理网页。结果也有了。接下来便是返回结果给用户显示出来。我们也看到了在这个过程中DefaultActionInvocation类实现做了很多的工作。包括拦截器的调用。返回Result类的处理。不得说DefaultActionInvocation类真的很重要。

    本章总结

    本章主要是进述DefaultActionInvocation类执行action的相关内容。让我们知道了是如何进行的执行action。其中用到ONGL相关的语法。也知道了action类实例执行之后。还会有对应的结果处理。当然这些用是交给于Result类的实例。

  • 相关阅读:
    最大子段和之可交换
    最大子段和之M子段和
    前端开发-日常开发沉淀之生产环境与开发环境
    开发技巧-解决打开谷歌浏览器跳转问题
    前端调试-跨域解决方式
    postman自动化,测试脚本
    自动化脚本测试,postman使用沉淀
    HMAC-SHA256 签名方法各个语音的实现方式之前端JavaScriptes6
    React中redux表单编辑
    前端JavaScript获取时间戳
  • 原文地址:https://www.cnblogs.com/hayasi/p/5888183.html
Copyright © 2020-2023  润新知