Struts2(5)——Struts2的程序骨架

0x00 Struts2初始化主线

程序入口

查看StrutsPrepareAndExecuteFilter.java的init程序

public class StrutsPrepareAndExecuteFilter implements StrutsStatics, Filter {
    // 进行HTTP请求预处理的操作类
    protected PrepareOperations prepare;
    // 进行HTTP请求的逻辑执行处理类
    protected ExecuteOperations execute;
    // 配置哪些形式的URL模式被排除在Struts2的处理之外
    protected List<Pattern> excludedPatterns = null;

    public StrutsPrepareAndExecuteFilter() {
    }

    public void init(FilterConfig filterConfig) throws ServletException {
        // 初始化操作了类
        InitOperations init = new InitOperations();
        Dispatcher dispatcher = null;

        try {
            FilterHostConfig config = new FilterHostConfig(filterConfig);
            init.initLogging(config);
            // 初始化核心分发器Dispatcher
            dispatcher = init.initDispatcher(config);
            // 初始化静态资源加载器
            init.initStaticContentLoader(config, dispatcher);
            // 初始化HTTP预处理的操作类
            this.prepare = new PrepareOperations(dispatcher);
            // 初始化进行HTTP请求处理的逻辑执行操作类
            this.execute = new ExecuteOperations(dispatcher);
            this.excludedPatterns = init.buildExcludedPatternsList(dispatcher);
            // 自定义初始化过程,留作用户扩展
            this.postInit(dispatcher, filterConfig);
        } finally {
            if (dispatcher != null) {
                dispatcher.cleanUpAfterInit();
            }
            // 初始化中的清理工作
            init.cleanup();
        }

    }
    // 初始化中的核心扩展工作
    protected void postInit(Dispatcher dispatcher, FilterConfig filterConfig) {
    }
}

初始化过程中最重要的元素是:Dispatcher(核心分发器),Dispatcher是Struts2与XWork的分界点,也是将MVC与Web容器隔离的分界点

Dispatcher

核心驱动力:Struts2初始化过程的目的,是对各种配置形式所进行的一次统一的对象化管理

Dispatcher初始化的InitOperations.java

public class InitOperations {
    public Dispatcher initDispatcher(HostConfig filterConfig) {
        Dispatcher dispatcher = this.createDispatcher(filterConfig);
        dispatcher.init();
        return dispatcher;
    }

    private Dispatcher createDispatcher(HostConfig filterConfig) {
        Map<String, String> params = new HashMap();
        Iterator e = filterConfig.getInitParameterNames();

        while(e.hasNext()) {
            String name = (String)e.next();
            String value = filterConfig.getInitParameter(name);
            params.put(name, value);
        }

        return new Dispatcher(filterConfig.getServletContext(), params);
    }
}

createDispatcher方法将filterConfig中初始化参数通过构造函数传入Dispatcher来创建一个新的Dispatcher实例并返回,filterConfig中所包含的参数来自web.xml中

看一看Dispatcher的初始化过程

public class Dispatcher {
    private static ThreadLocal<Dispatcher> instance = new ThreadLocal();
    public static Dispatcher getInstance() {
        return (Dispatcher)instance.get();
    }
    public static void setInstance(Dispatcher instance) {
        Dispatcher.instance.set(instance);
    }

    public void init() {
        // 初始化configurationManager
        if (this.configurationManager == null) {
            this.configurationManager = this.createConfigurationManager("struts");
        }

        try {
            // 初始化各种形式配置加载方式
            this.init_FileManager();
            this.init_DefaultProperties();
            this.init_TraditionalXmlConfigurations();
            this.init_LegacyStrutsProperties();
            this.init_CustomConfigurationProviders();
            this.init_FilterInitParameters();
            this.init_AliasStandardObjects();
            // 初始化容器
            Container container = this.init_PreloadConfiguration();
            container.inject(this);
            // 初始化weblogic服务器的特殊设置和指定DispatcherListener的逻辑
            this.init_CheckWebLogicWorkaround(container);
            if (!dispatcherListeners.isEmpty()) {
                Iterator i$ = dispatcherListeners.iterator();

                while(i$.hasNext()) {
                    DispatcherListener l = (DispatcherListener)i$.next();
                    l.dispatcherInitialized(this);
                }
            }

            this.errorHandler.init(this.servletContext);
        } catch (Exception var4) {
            LOG.error("Dispatcher initialization failed", var4);
            throw new StrutsException(var4);
        }
    }

首先利用ThreadLocal承载实际类型是Dispatcher本身的instance,保证线程安全

初始化过程的步骤如下

  • 创建ConfigurationManager
  • 初始化各种配置加载器
    • 初始化Struts2默认Properties配置文件加载器
    • 初始化XML配置加载器
    • 初始化Properties配置加载器
    • 初始化用户自定义配置加载器
    • 初始化由web.xml中传入的运行参数
    • 初始化默认容器内置对象加载起
  • 初始化容器(容器知识上一篇有讲)
    • 创建容器
    • 对容器进行依赖注入
  • 执行额外的初始化工作
    • 初始化Weblogic配置选项
    • 执行DispatcherListener的逻辑

Dispatcher还有更多的功能作用,我们在下面说明

0x01 Struts2的请求处理主线

我们在前面的文章中说到过,Struts2的请求处理主线分成两个阶段

  • 第一阶段:HTTP请求预处理,为真正的业务逻辑执行做必要的数据环境和运行环境的准备
  • 第二阶段:XWork执行业务逻辑,Struts2完成HTTP请求预处理后将HTTP请求中的数据封装成普通的Java对象,程序控制权移交给了XWork,由XWord负责执行具体的业务逻辑,这一阶段完全不依赖于Web容器,是为了消除核心程序对运行环境(Web容器)的依赖

HTTP请求预处理阶段

public class StrutsPrepareAndExecuteFilter implements StrutsStatics, Filter {
    // 执行HTTP请求预处理的操作类
    protected PrepareOperations prepare;
    // 执行HTTP请求处理的逻辑执行的操作类
    protected ExecuteOperations execute;
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest)req;
        HttpServletResponse response = (HttpServletResponse)res;

        try {
            String uri = RequestUtils.getUri(request);
            if (this.excludedPatterns != null && this.prepare.isUrlExcluded(request, this.excludedPatterns)) {
                LOG.trace("Request {} is excluded from handling by Struts, passing request to other filters", new Object[]{uri});
                chain.doFilter(request, response);
            } else {
                LOG.trace("Checking if {} is a static resource", new Object[]{uri});
                boolean handled = this.execute.executeStaticResourceRequest(request, response);
                if (!handled) {
                    LOG.trace("Assuming uri {} as a normal action", new Object[]{uri});
                    // 设置Encoding和Locale
                    this.prepare.setEncodingAndLocale(request, response);
                    // 创建Action执行所对应的ActionContext
                    this.prepare.createActionContext(request, response);
                    // 把核心分发器Dispatcher分配至当前线程
                    this.prepare.assignDispatcherToThread();
                    // 对request进行一定封装
                    request = this.prepare.wrapRequest(request);
                    // 根据request获取ActionMapping
                    ActionMapping mapping = this.prepare.findActionMapping(request, response, true);
                    if (mapping == null) {
                        LOG.trace("Cannot find mapping for {}, passing to other filters", new Object[]{uri});
                        chain.doFilter(request, response);
                    } else {
                        LOG.trace("Found mapping {} for {}", new Object[]{mapping, uri});
                        // 执行URL请求的处理
                        this.execute.executeAction(request, response, mapping);
                    }
                }
            }
        } finally {
            this.prepare.cleanupRequest(request);
        }

    }
}
  • Dispatcher:核心分发类,执行Struts2处理逻辑的实际场所
  • PrepareOperations:HTTP预处理类,对所有HTTP请求执行预处理
  • ExecuteOperations:HTTP处理执行类,执行HTTP请求的场所

在进行URL处理时,doFilter方法会根据指责不同,将URL处理过程分配给PrepareOperations和ExecuteOperations来分别完成,不过在这里,PrepareOperations和ExecuteOperations实际上也只是一层薄薄的代理层,其中起决定性作用的还是Dispatcher

PrepareOperations(HTTP请求预处理类)

我们看PrepareOperations所执行的一些

public class PrepareOperations {
    public void setEncodingAndLocale(HttpServletRequest request, HttpServletResponse response) {
        this.dispatcher.prepare(request, response);
    }

    public ActionContext createActionContext(HttpServletRequest request, HttpServletResponse response) {
        Integer counter = 1;
        Integer oldCounter = (Integer)request.getAttribute("__cleanup_recursion_counter");
        if (oldCounter != null) {
            counter = oldCounter + 1;
        }

        ActionContext oldContext = ActionContext.getContext();
        ActionContext ctx;
        if (oldContext != null) {
            ctx = new ActionContext(new HashMap(oldContext.getContextMap()));
        } else {
            ValueStack stack = ((ValueStackFactory)this.dispatcher.getContainer().getInstance(ValueStackFactory.class)).createValueStack();
            stack.getContext().putAll(this.dispatcher.createContextMap(request, response, (ActionMapping)null));
            ctx = new ActionContext(stack.getContext());
        }

        request.setAttribute("__cleanup_recursion_counter", counter);
        ActionContext.setContext(ctx);
        return ctx;
    }

    public void assignDispatcherToThread() {
        Dispatcher.setInstance(this.dispatcher);
    }

    public HttpServletRequest wrapRequest(HttpServletRequest oldRequest) throws ServletException {
        HttpServletRequest request = oldRequest;

        try {
            request = this.dispatcher.wrapRequest(request);
            ServletActionContext.setRequest(request);
            return request;
        } catch (IOException var4) {
            throw new ServletException("Could not wrap servlet request with MultipartRequestWrapper!", var4);
        }
    }

    public ActionMapping findActionMapping(HttpServletRequest request, HttpServletResponse response, boolean forceLookup) {
        ActionMapping mapping = (ActionMapping)request.getAttribute("struts.actionMapping");
        if (mapping == null || forceLookup) {
            try {
                mapping = ((ActionMapper)this.dispatcher.getContainer().getInstance(ActionMapper.class)).getMapping(request, this.dispatcher.getConfigurationManager());
                if (mapping != null) {
                    request.setAttribute("struts.actionMapping", mapping);
                }
            } catch (Exception var6) {
                this.dispatcher.sendError(request, response, 500, var6);
            }
        }

        return mapping;
    }

我们按照doFilter的顺序列出了上面代码

  • setEncodingAndLocale:设置Encoding和Locale,封装了Dispatcher对HttpServletRequest的设置,只是做一个转发
  • createActionContext:生成ActionContext和ValueStack,将HttpServletRequest、HttpServletResponse中的数据封装成普通的Java对象,这是Struts2将MVC实现与Web容器解耦的第一步
  • assignDispatcher:将核心分发器Dispatcher绑定至当前线程,因为Dispatcher是ThreadLocal模式的,这一步是保证当前Dispatcher是线程安全实例,可以放心使用
  • wrapRequest:对HttpServletRequest进行一定封装,HttpServletRequest经过这一步处理,已经不是Web容器默认的实现了,而是被装饰成一个包装类
  • findActionMapping:根据HTTP请求查找ActionMapping,ActionMapping是URL Mapping和Java对象对应的一个组建类,实际是ActionMapper实现类在运行期间查找对应事件映射关系并生成ActionMapping对象

ExecuteOperations(HTTP请求的执行类)

public class ExecuteOperations {
    private Dispatcher dispatcher;

    public ExecuteOperations(Dispatcher dispatcher) {
        this.dispatcher = dispatcher;
    }

    public boolean executeStaticResourceRequest(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
        // 查找静态资源根路径
        String resourcePath = RequestUtils.getServletPath(request);
        if ("".equals(resourcePath) && null != request.getPathInfo()) {
            resourcePath = request.getPathInfo();
        }
        // 获取静态资源的加载处理类
        StaticContentLoader staticResourceLoader = (StaticContentLoader)this.dispatcher.getContainer().getInstance(StaticContentLoader.class);
        // 判断静态资源加载处理类是否能处理URL对应的静态资源
        if (staticResourceLoader.canHandle(resourcePath)) {
            staticResourceLoader.findStaticResource(resourcePath, request, response);
            return true;
        } else {
            return false;
        }
    }

    public void executeAction(HttpServletRequest request, HttpServletResponse response, ActionMapping mapping) throws ServletException {
        // 将所有参数传入dispatcher中执行逻辑
        this.dispatcher.serviceAction(request, response, mapping);
    }
}

ExecuteOperations的代码很简单,可以全部看一下

和PrepareOperations一样,ExecuteOperation基本只是一个执行句柄,所有逻辑还是围绕Dispatcher展开

我们看Dispatcher的serviceAction方法

public void serviceAction(HttpServletRequest request, HttpServletResponse response, ActionMapping mapping) throws ServletException {
    // 创建MVC运行的数据环境
    Map<String, Object> extraContext = this.createContextMap(request, response, mapping);
    // 如果之前创建过ValueStack,则直接做一个ValueStack的复制
    ValueStack stack = (ValueStack)request.getAttribute("struts.valueStack");
    boolean nullStack = stack == null;
    // 没有找到已存在的ValueStack则从ActionContext中获取当前线程的ValueStack
    if (nullStack) {
        ActionContext ctx = ActionContext.getContext();
        if (ctx != null) {
            // ActionContext是线程安全的,所以这里获取的是当前线程的ValueStack
            stack = ctx.getValueStack();
        }
    }
    // 将ValueStack设置到数据环境中
    if (stack != null) {
        extraContext.put("com.opensymphony.xwork2.util.ValueStack.ValueStack", this.valueStackFactory.createValueStack(stack));
    }
    String timerKey = "Handling request from Dispatcher";
    try {
        UtilTimerStack.push(timerKey);
        // 从ActionMapping获得Action的三大要素
        String namespace = mapping.getNamespace();
        String name = mapping.getName();
        String method = mapping.getMethod();
        // 创建一个ActionProxy,这里就进入了XWork的世界了
        ActionProxy proxy = ((ActionProxyFactory)this.getContainer().getInstance(ActionProxyFactory.class)).createActionProxy(namespace, name, method, extraContext, true, false);
        request.setAttribute("struts.valueStack", proxy.getInvocation().getStack());
        // 如果ActionMapping中包含Result对象,表明直接跳过Action执行Result
        if (mapping.getResult() != null) {
            // 执行ActionProxy,真正运行XWork中的MVC实现
            Result result = mapping.getResult();
            result.execute(proxy.getInvocation());
        } else {
            proxy.execute();
        }
        // 如果之前存在ValueStack,重新设回Reqeust对象中
        if (!nullStack) {
            request.setAttribute("struts.valueStack", stack);
        }
    } catch (ConfigurationException var17) {
        this.logConfigurationException(request, var17);
        this.sendError(request, response, 404, var17);
    } catch (Exception var18) {
        if (!this.handleException && !this.devMode) {
            throw new ServletException(var18);
        }
        this.sendError(request, response, 500, var18);
    } finally {
        UtilTimerStack.pop(timerKey);
    }
}

所以XWork框架的相关逻辑实际上由Dispatcher创建并负责驱动执行

Dispatcher负责HTTP请求不同处理阶段的数据转发工作,从而保证代码在不同执行阶段的无缝切换

在XWork框架调用接口ActionProxy中,我们将不再看到任何与Web容器相关的对象

因此,Dispatcher是真正将HTTP请求与MVC实现(XWork框架)分离的核心分发器,而Dispatcher的逻辑,被分散到PrepareOperations和ExecuteOperations两个代理类中执行调度,保证了程序的扩展性

到此为止,Struts2的工作完成,所有剩下的HTTP请求处理工作交给XWork

XWork处理阶段

Dispatcher的serviceAction方法是Dispatcher处理HTTP的场所,也是这个方法中,Struts2将处理执行权转交给XWork

Struts2的执行移交过程实际上围绕这ActionProxy来进行,并使用了Struts2的一贯方法,先初始化在执行逻辑

Dispatcher中ActionProxy proxy = ((ActionProxyFactory)this.getContainer().getInstance(ActionProxyFactory.class)).createActionProxy(namespace, name, method, extraContext, true, false);一行带过了ActionProxy的初始化过程,其实这一过程,也是XWork执行环境的初始化

ActionProxy的输入是配置关系映射(namespace、actionName、methodName)和运行上下文环境(extraContext),也就是ActionProxy

从DefaultActionProxy中找到createActionProxy方法

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

这里创建了ActionInvocation,ActionInvocation的init方法中有createContextMap

protected Map<String, Object> createContextMap() {
    Map contextMap;
    if (this.extraContext != null && this.extraContext.containsKey("com.opensymphony.xwork2.util.ValueStack.ValueStack")) {
        this.stack = (ValueStack)this.extraContext.get("com.opensymphony.xwork2.util.ValueStack.ValueStack");
        if (this.stack == null) {
            throw new IllegalStateException("There was a null Stack set into the extra params.");
        }

        contextMap = this.stack.getContext();
    } else {
        this.stack = this.valueStackFactory.createValueStack();
        contextMap = this.stack.getContext();
    }

    if (this.extraContext != null) {
        contextMap.putAll(this.extraContext);
    }

    contextMap.put("com.opensymphony.xwork2.ActionContext.actionInvocation", this);
    contextMap.put("com.opensymphony.xwork2.ActionContext.container", this.container);
    return contextMap;
}

可以看到,ActionInvocation初始化时,对ActionProxy进行了一次检查并重置,这里考虑到Struts2作为外部调用,XWork独立的情况

再来看ActionInvocation的完整初始化过程

public void init(ActionProxy proxy) {
    this.proxy = proxy;
    // 创建上下文环境,contextMap和ActionContext一致 
    Map<String, Object> contextMap = this.createContextMap();
    // 将ActionInvocation对象设置到ActionContext中,可以利用ActionContext的数据共享特性,将ActionInvocation在整个执行周期共享
    ActionContext actionContext = ActionContext.getContext();
    if (actionContext != null) {
        actionContext.setActionInvocation(this);
    }

    // 创建Action对象
    this.createAction(contextMap);
    // 将Action对象置于ValueStack中,这是将XWork的数据流元素与控制流元素进行融合的关键步骤
    if (this.pushAction) {
        this.stack.push(this.action);
        contextMap.put("action", this.action);
    }
    // 构建ActionInvocation的上下文环境
    this.invocationContext = new ActionContext(contextMap);
    this.invocationContext.setName(proxy.getActionName());
    this.createInterceptors(proxy);
}

ActionInvocation初始化之后调用invoke执行,这里我们上一篇文章分析过了,最后调用invokeAction的时候

protected String invokeAction(Object action, ActionConfig actionConfig) throws Exception {
    String methodName = proxy.getMethod();

    LOG.debug("Executing action method = {}", methodName);

    String timerKey = "invokeAction: " + proxy.getActionName();
    try {
        UtilTimerStack.push(timerKey);

        Object methodResult;
        try {
            methodResult = ognlUtil.callMethod(methodName + "()", getStack().getContext(), action);
        } catch (MethodFailedException e) {
            // if reason is missing method,  try checking UnknownHandlers
            if (e.getReason() instanceof NoSuchMethodException) {
                if (unknownHandlerManager.hasUnknownHandlers()) {
                    try {
                        methodResult = unknownHandlerManager.handleUnknownMethod(action, methodName);
                    } catch (NoSuchMethodException ignore) {
                        // throw the original one
                        throw e;
                    }
                } else {
                    // throw the original one
                    throw e;
                }
                // throw the original exception as UnknownHandlers weren't able to handle invocation as well
                if (methodResult == null) {
                    throw e;
                }
            } else {
                // exception isn't related to missing action method, throw it
                throw e;
            }
        }
        return saveResult(actionConfig, methodResult);
    } catch (NoSuchPropertyException e) {
        throw new IllegalArgumentException("The " + methodName + "() is not defined in action " + getAction().getClass() + "");
    } catch (MethodFailedException e) {
        // We try to return the source exception.
        Throwable t = e.getCause();

        if (actionEventListener != null) {
            String result = actionEventListener.handleException(t, getStack());
            if (result != null) {
                return result;
            }
        }
        if (t instanceof Exception) {
            throw (Exception) t;
        } else {
            throw e;
        }
    } finally {
        UtilTimerStack.pop(timerKey);
    }
}

执行Action的方法后执行Result的execute,生成Result

坚持原创技术分享,您的支持将鼓励我继续创作!