• Struts2 源码分析——Action代理类的工作


    章节简言

    上一章笔者讲到关于如何加载配置文件里面的package元素节点信息。相信读者到这里心里面对struts2在启动的时候加载相关的信息有了一定的了解和认识。而本章将讲到关于struts2启动成功之后,接受到用户action请求之后如何处理并找到对应的action类。可以说这章是讲述《Struts2 源码分析——调结者(Dispatcher)之执行action》章节之后的事情。即是核心机制图片的蓝色(Struts core)分部的知识点。通过前面几章节的内容至少我们知道了struts2启动成之后,会把相关的信息存放在Container容器和DefaultConfiguration类的实例里面。而Dispatcher类的实例便是这俩个类的中间调节者。(不懂得的读者请先查看一下前面几章节来在)

     Action代理类的新建

    通过《Struts2 源码分析——调结者(Dispatcher)之执行action》章节我们知道执行action请求,最后会落到Dispatcher类的serviceAction方法上面。可惜笔者并没有在这一章里面对他自己详细的讲解。先让我们看一下代码吧?知道他在做什么吧。如下

    Dispatcher类:

     1 public void serviceAction(HttpServletRequest request, HttpServletResponse response, ActionMapping mapping)
     2             throws ServletException {
     3 
     4         Map<String, Object> extraContext = createContextMap(request, response, mapping);
     5 
     6         //如果之前就有了值栈,就是新建一个新的值栈,放入extraContext
     7         ValueStack stack = (ValueStack) request.getAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY);
     8         boolean nullStack = stack == null;
     9         if (nullStack) {
    10             ActionContext ctx = ActionContext.getContext();
    11             if (ctx != null) {
    12                 stack = ctx.getValueStack();
    13             }
    14         }
    15         if (stack != null) {
    16             extraContext.put(ActionContext.VALUE_STACK, valueStackFactory.createValueStack(stack));
    17         }
    18 
    19         String timerKey = "Handling request from Dispatcher";
    20         try {
    21             UtilTimerStack.push(timerKey);
    22             String namespace = mapping.getNamespace();//获得request请求里面的命名空间,即是struts.xml是的package节点元素
    23             String name = mapping.getName();//获得request请求里面的action名
    24             String method = mapping.getMethod();//要执行action的方法
    25 
    26             ActionProxy proxy = getContainer().getInstance(ActionProxyFactory.class).createActionProxy(namespace, name,
    27                     method, extraContext, true, false);//获得action的代理
    28 
    29             request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, proxy.getInvocation().getStack());
    30 
    31             // 如果action映射是直接就跳转到网页的话,
    32             if (mapping.getResult() != null) {
    33                 Result result = mapping.getResult();
    34                 result.execute(proxy.getInvocation());
    35             } else {
    36                 proxy.execute();//这里就是执行action
    37             }
    38 
    39             
    40             //
    41             if (!nullStack) {
    42                 request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, stack);
    43             }
    44         } catch (ConfigurationException e) {
    45             logConfigurationException(request, e);
    46             sendError(request, response, HttpServletResponse.SC_NOT_FOUND, e);
    47         } catch (Exception e) {
    48             if (handleException || devMode) {
    49                 sendError(request, response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e);
    50             } else {
    51                 throw new ServletException(e);
    52             }
    53         } finally {
    54             UtilTimerStack.pop(timerKey);
    55         }
    56     }

    1.根据传入的参数request, response, mapping来新建一个上下文Map。上下文Map就是一个存了关于RequestMap类,SessionMap类,ApplicationMap类等实例。即是request请求相关的信息,只是把他变成了对应的MAP类而以。

    2.从request请求中找到对应的值栈(ValueStack)。如果没有就新建值栈。然后存放到上下文Map里面,对应的KEY为ActionContext.VALUE_STACK常量的值。即是"com.opensymphony.xwork2.util.ValueStack.ValueStack"。

    3.从Mapping参数中提取对应的request请求的命名空间,action名字和方法名。

    4.从Container容器中找到ActionProxyFactory类,并根据request请求的命名空间,action名字和方法名,上下文Map来获得对应的action代理类(ActionProxy)。然后更新request请求中的对应的值栈(ValueStack)。

    5.根据Mapping参数来判断是否为直接输出结果。还是执行action代理类。

    6.最后在判断之前是否request请求没有找到对应的值栈(ValueStack)。如果有找到值栈(ValueStack),则更新request请求中的对应的值栈(ValueStack)。

    所以我们的目标很明确就是要去看一下action代理类(ActionProxy)。了解他到底做了什么。才能明白如何找到对应的action类,并执行对应的方法。从上面我们也知道action代理类的新建是通过ActionProxyFactory接口实例来进行的。即是DefaultActionProxyFactory类的实例。显然就是一个简章的工厂模式。让我们看一下新建action代理类的代码吧。

    DefaultActionProxyFactory类:

    1   public ActionProxy createActionProxy(String namespace, String actionName, String methodName, Map<String, Object> extraContext, boolean executeResult, boolean cleanupContext) {
    2         
    3         ActionInvocation inv = createActionInvocation(extraContext, true);
    4         container.inject(inv);
    5         return createActionProxy(inv, namespace, actionName, methodName, executeResult, cleanupContext);
    6     }

    看到了吧。在新建action代理类的时候还要用到ActionInvocation接口的实例。即是DefaultActionInvocation类的实例。前面几章笔者曾经讲过Dispatcher类才是正真执行action类实例的人。这里笔者不得不在提一下。Dispatcher类是重要的调结者,DefaultActionInvocation类是执行action类实例的行动者。而action代理类(ActionProxy类)则是他们之间的中间人。相当于Dispatcher类通过action代理类(ActionProxy类)命令DefaultActionInvocation类去执行action类实例。

    Action代理类的准备工作

    action代理类(ActionProxy类)在命令DefaultActionInvocation类去执行action类实例之前,还是有做了一些准备工作。好吧。笔者还是希望通过代码来说话。看一下代码吧。

    DefaultActionProxyFactory类:

    1 public ActionProxy createActionProxy(ActionInvocation inv, String namespace, String actionName, String methodName, boolean executeResult, boolean cleanupContext) {
    2 
    3         DefaultActionProxy proxy = new DefaultActionProxy(inv, namespace, actionName, methodName, executeResult, cleanupContext);
    4         container.inject(proxy);
    5         proxy.prepare();
    6         return proxy;
    7     }

    DefaultActionProxy类:

     1 protected void prepare() {
     2         String profileKey = "create DefaultActionProxy: ";
     3         try {
     4             UtilTimerStack.push(profileKey);
     5             config = configuration.getRuntimeConfiguration().getActionConfig(namespace, actionName);//根据空间命名和action名来找到对应的配置信息
     6 
     7             if (config == null && unknownHandlerManager.hasUnknownHandlers()) {
     8                 config = unknownHandlerManager.handleUnknownAction(namespace, actionName);
     9             }
    10             if (config == null) {
    11                 throw new ConfigurationException(getErrorMessage());
    12             }
    13 
    14             resolveMethod();//找到对应的方法名。
    15 
    16             if (config.isAllowedMethod(method)) {
    17                 invocation.init(this);
    18             } else {
    19                 throw new ConfigurationException(prepareNotAllowedErrorMessage());
    20             }
    21         } finally {
    22             UtilTimerStack.pop(profileKey);
    23         }
    24     }

    从上面的代码,我们可以看出在执行action类之前,大概做了俩件准备工作:

    1.获得ActionConfig类实例。并通过ActionConfig类实例找到对应的方法名。ActionConfig类就是存放配置文件里面的action元素节点的信息。

    2.实初始化DefaultActionInvocation类的实例。即是根据ActionProxy类实例找到对应的action类实例(用户自己定义的类)。

    代码中俩个方法是笔者希望读者明白的。一个是DefaultActionProxy类的resolveMethod方法。一个是DefaultActionInvocation类的init方法。为什么要讲这俩个方法。上面的俩件事情主要的功能都是在这俩个方法里面。让我们看一代码吧?

     DefaultActionProxy类:

     1  private void resolveMethod() {
     2         // 从配置中获得方法名。如果还是空的话,就用默认的值。即是"execute"方法。
     3         if (StringUtils.isEmpty(this.method)) {
     4             this.method = config.getMethodName();
     5             if (StringUtils.isEmpty(this.method)) {
     6                 this.method = ActionConfig.DEFAULT_METHOD;
     7             }
     8             methodSpecified = false;
     9     }
    10     }

    DefaultActionInvocation类:

     1 public void init(ActionProxy proxy) {
     2         this.proxy = proxy;
     3         Map<String, Object> contextMap = createContextMap();
     4 
     5         // Setting this so that other classes, like object factories, can use the ActionProxy and other
     6         // contextual information to operate
     7         ActionContext actionContext = ActionContext.getContext();
     8 
     9         if (actionContext != null) {
    10             actionContext.setActionInvocation(this);
    11         }
    12 
    13         createAction(contextMap);//找到对应的action类实例
    14 
    15         if (pushAction) {
    16             stack.push(action);
    17             contextMap.put("action", action);
    18         }
    19 
    20         invocationContext = new ActionContext(contextMap);
    21         invocationContext.setName(proxy.getActionName());
    22 
    23         createInterceptors(proxy);
    24     }

    看了代码就能清楚的知道一件事情。如果我们在struts.xml配置文件里面action元素节点里面没有指定方法的时候,就用会默认的方法。即是execute方法。而关于init方法就能明确明白为了找到action类并实例他。init方法里面调用了俩个非重要的方法。一个是用于新建action类实例的方法createAction。一个是用于获得相关拦截器的方法createInterceptors。看一下代码吧。

    DefaultActionInvocation类:

     1   protected void createAction(Map<String, Object> contextMap) {
     2         // load action
     3         String timerKey = "actionCreate: " + proxy.getActionName();
     4         try {
     5             UtilTimerStack.push(timerKey);
     6             action = objectFactory.buildAction(proxy.getActionName(), proxy.getNamespace(), proxy.getConfig(), contextMap);
     7         } catch (InstantiationException e) {
     8             throw new XWorkException("Unable to instantiate Action!", e, proxy.getConfig());
     9         } catch (IllegalAccessException e) {
    10             throw new XWorkException("Illegal access to constructor, is it public?", e, proxy.getConfig());
    11         } catch (Exception e) {
    12             String gripe;
    13 
    14             if (proxy == null) {
    15                 gripe = "Whoa!  No ActionProxy instance found in current ActionInvocation.  This is bad ... very bad";
    16             } else if (proxy.getConfig() == null) {
    17                 gripe = "Sheesh.  Where'd that ActionProxy get to?  I can't find it in the current ActionInvocation!?";
    18             } else if (proxy.getConfig().getClassName() == null) {
    19                 gripe = "No Action defined for '" + proxy.getActionName() + "' in namespace '" + proxy.getNamespace() + "'";
    20             } else {
    21                 gripe = "Unable to instantiate Action, " + proxy.getConfig().getClassName() + ",  defined for '" + proxy.getActionName() + "' in namespace '" + proxy.getNamespace() + "'";
    22             }
    23 
    24             gripe += (((" -- " + e.getMessage()) != null) ? e.getMessage() : " [no message in exception]");
    25             throw new XWorkException(gripe, e, proxy.getConfig());
    26         } finally {
    27             UtilTimerStack.pop(timerKey);
    28         }
    29 
    30         if (actionEventListener != null) {
    31             action = actionEventListener.prepare(action, stack);
    32         }
    33     }

    DefaultActionInvocation类:

    1  protected void createInterceptors(ActionProxy proxy) {
    2         // Get a new List so we don't get problems with the iterator if someone changes the original list
    3         List<InterceptorMapping> interceptorList = new ArrayList<>(proxy.getConfig().getInterceptors());
    4         interceptors = interceptorList.iterator();
    5     }

    相信读者一定能看的懂代码吧。俩个方法中在笔者看来最重要的体现便是ObjectFactory类。ObjectFactory类是用于新一个实例的。上面的方法里面就是用ObjectFactory类来创建一个action类的实例。

    好了。到了这里面action代理类(ActionProxy类)的准备工作算是做完了。让笔者理一下。准备工作完成之后。笔者至少知道action类实例有了。要执行的方法名也有了。要执行的拦截器也有了。有了这些信息难道strtus2会不知道去执行对应的工作吗?

    Action代理类的主要工作

    action代理类(ActionProxy类)的准备工作完成之后,就开始执行了。最顶部的代码中就很明确的看的出来(serviceAction方法)。先是根据参数mapping来判断是否为直接回返。如果不是才去执行action代理类(ActionProxy类)的execute方法。这便是action代理类(ActionProxy类)的主要工作。即是执行action请求。那么让我们看一下action代理类(ActionProxy类)的execute方法源码吧。

     1   public String execute() throws Exception {
     2         ActionContext nestedContext = ActionContext.getContext();
     3         ActionContext.setContext(invocation.getInvocationContext());
     4 
     5         String retCode = null;
     6 
     7         String profileKey = "execute: ";
     8         try {
     9             UtilTimerStack.push(profileKey);
    10 
    11             retCode = invocation.invoke();
    12         } finally {
    13             if (cleanupContext) {
    14                 ActionContext.setContext(nestedContext);
    15             }
    16             UtilTimerStack.pop(profileKey);
    17         }
    18 
    19         return retCode;
    20     }

    很好。从红色的代码部分我们就知道就是去执行DefaultActionInvocation类实例的invoke方法。DefaultActionInvocation类和action代理类(ActionProxy类)的关系看起相当的复杂。可以说是我中有你,你中有我。DefaultActionProxy类新建的时候需要DefaultActionInvocation类的实例。而DefaultActionInvocation类的实例初始化的时候,action代理类(ActionProxy类)的实例会传DefaultActionInvocation类里面并存放起来。即是在init方法的时候。值得注意的是这个时候的Action上下文(ActionContext类)有发生一件细微的变化。不是以前的了。而是从DefaultActionInvocation类的实例中得来的。cleanupContext参数表示要不要执行完成之后就清除掉当前的。把原来的放在去。最后回返结果。

    本章总结

    本章主要是讲到关于action代理类(ActionProxy类)的工作。知道了DefaultActionInvocation类是用去执行action类的行动者。而Dispatcher类是调结者。action代理类(ActionProxy类)是DefaultActionInvocation类和Dispatcher类的中间人。即是Dispatcher类通过action代理类(ActionProxy类)命令DefaultActionInvocation类去执行action类实例。

  • 相关阅读:
    Linux查看当前系统的发行版信息
    用 CentOS 7 打造合适的科研环境
    消息队列的使用场景
    RabbitMQ几种Exchange 模式
    JMS规范概览
    消息队列的学习
    springMVC参数传递实例
    java8时间处理实例
    windows电脑常用必备软件
    http后台json解析实例
  • 原文地址:https://www.cnblogs.com/hayasi/p/5872944.html
Copyright © 2020-2023  润新知