Struts2学习笔记——Struts框架的工作原理(源码调试)

来源:互联网 发布:人的极限寿命知乎 编辑:程序博客网 时间:2024/05/21 10:33

为了下面的Interceptor打基础,同时也是为了了解Struts的工作原理,花一段时间来调试一下Struts的源码是十分必要的:

在Struts版本的2.1.3之前的版本,都是用FilterDispather这个类来对URL进行过滤。在新版本中使用了StrutsPrepareAndExecuteFilter来代替。

web.xml:

<?xml version="1.0" encoding="UTF-8"?><web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">  <display-name></display-name>  <welcome-file-list>    <welcome-file>index.jsp</welcome-file>  </welcome-file-list>      <filter>        <filter-name>struts2</filter-name>        <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>    </filter>    <filter-mapping>        <filter-name>struts2</filter-name>        <url-pattern>/*</url-pattern>    </filter-mapping></web-app>

我们在web工程的配置文件中增加了Struts的配置之后。当我们启动应用服务器时(这里以Tomcat为例),就会开始加载Struts的配置信息。流程如下:

1、初始化过程

调用StrutsPrepareAndExecuteFilter的init方法:

public void init(FilterConfig filterConfig) throws ServletException {        InitOperations init = new InitOperations();        Dispatcher dispatcher = null;        try {            FilterHostConfig config = new FilterHostConfig(filterConfig);            init.initLogging(config);            dispatcher = init.initDispatcher(config);            init.initStaticContentLoader(config, dispatcher);            prepare = new PrepareOperations(filterConfig.getServletContext(), dispatcher);            execute = new ExecuteOperations(filterConfig.getServletContext(), dispatcher);            this.excludedPatterns = init.buildExcludedPatternsList(dispatcher);            postInit(dispatcher, filterConfig);        } finally {            if (dispatcher != null) {                dispatcher.cleanUpAfterInit();            }            init.cleanup();        }    }

这里有一个非常重要的类Dispather,可以说之后的加载动作几乎都是由这个类来完成:我们看一下dispatcher的形成过程,使用的是InitOperations的initDispather方法:

  /**     * Creates and initializes the dispatcher     */    public Dispatcher initDispatcher( HostConfig filterConfig ) {        Dispatcher dispatcher = createDispatcher(filterConfig);        dispatcher.init();        return dispatcher;    }

这里比较重要的句代码:

 dispatcher.init();
/**     * Load configurations, including both XML and zero-configuration strategies,     * and update optional settings, including whether to reload configurations and resource files.     */    public void init() {    if (configurationManager == null) {    configurationManager = createConfigurationManager(DefaultBeanSelectionProvider.DEFAULT_BEAN_NAME);    }        try {            init_FileManager();            init_DefaultProperties(); // [1]            init_TraditionalXmlConfigurations(); // [2]            init_LegacyStrutsProperties(); // [3]            init_CustomConfigurationProviders(); // [5]            init_FilterInitParameters() ; // [6]            init_AliasStandardObjects() ; // [7]            Container container = init_PreloadConfiguration();            container.inject(this);            init_CheckWebLogicWorkaround(container);            if (!dispatcherListeners.isEmpty()) {                for (DispatcherListener l : dispatcherListeners) {                    l.dispatcherInitialized(this);                }            }        } catch (Exception ex) {            if (LOG.isErrorEnabled())                LOG.error("Dispatcher initialization failed", ex);            throw new StrutsException(ex);        }    }
没错 这里就是读取我们配置文件的地方:

 private Container init_PreloadConfiguration() {        Configuration config = configurationManager.getConfiguration();        Container container = config.getContainer();        boolean reloadi18n = Boolean.valueOf(container.getInstance(String.class, StrutsConstants.STRUTS_I18N_RELOAD));        LocalizedTextUtil.setReloadBundles(reloadi18n);        ContainerHolder.store(container);        return container;    }
当然这里需要知道的是Configuration是一个接口 我们真正执行的是DefaultConfiguration这个默认的实现类。继续跟踪就可以看到配置文件的读取过程。具体读取可以参照XmlConfigurationProvider这个类。当我们把一切的配置文件都读取到之后。这时候我们就可以访问了。

2、访问过程

访问过程我们同样要用到StrutsPrepareAndExecuteFilter这个类,这次我们用到的是doFilter方法:

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {        HttpServletRequest request = (HttpServletRequest) req;        HttpServletResponse response = (HttpServletResponse) res;        try {            if (excludedPatterns != null && prepare.isUrlExcluded(request, excludedPatterns)) {                chain.doFilter(request, response);            } else {                prepare.setEncodingAndLocale(request, response);                prepare.createActionContext(request, response);                prepare.assignDispatcherToThread();                request = prepare.wrapRequest(request);                ActionMapping mapping = prepare.findActionMapping(request, response, true);                if (mapping == null) {                    boolean handled = execute.executeStaticResourceRequest(request, response);                    if (!handled) {                        chain.doFilter(request, response);                    }                } else {                    execute.executeAction(request, response, mapping);                }            }        } finally {            prepare.cleanupRequest(request);        }    }
在处理的时候:

prepare.setEncodingAndLocale(request, response);  //设置字符集和本地化prepare.createActionContext(request, response);   //创建Action的上下文prepare.assignDispatcherToThread();               //标记到线程中request = prepare.wrapRequest(request);           //重新包装
之后就要看当前的请求是不是一个符合配置的ActionMapping对象:

ActionMapping mapping = prepare.findActionMapping(request, response, true);
当然这个过程是是要对URI进行分析同时匹配配置文件:

具体代码实现是DefaultActionMapper的getMapping方法:

 /*     * (non-Javadoc)     *     * @see org.apache.struts2.dispatcher.mapper.ActionMapper#getMapping(javax.servlet.http.HttpServletRequest)     */    public ActionMapping getMapping(HttpServletRequest request, ConfigurationManager configManager) {        ActionMapping mapping = new ActionMapping();        String uri = RequestUtils.getUri(request);        int indexOfSemicolon = uri.indexOf(";");        uri = (indexOfSemicolon > -1) ? uri.substring(0, indexOfSemicolon) : uri;        uri = dropExtension(uri, mapping);        if (uri == null) {            return null;        }        parseNameAndNamespace(uri, mapping, configManager);        handleSpecialParameters(request, mapping);        return parseActionName(mapping);    }
 /**     * Parses the name and namespace from the uri     *     * @param uri     The uri     * @param mapping The action mapping to populate     */    protected void parseNameAndNamespace(String uri, ActionMapping mapping, ConfigurationManager configManager) {        String namespace, name;        int lastSlash = uri.lastIndexOf("/");        if (lastSlash == -1) {            namespace = "";            name = uri;        } else if (lastSlash == 0) {            // ww-1046, assume it is the root namespace, it will fallback to            // default            // namespace anyway if not found in root namespace.            namespace = "/";            name = uri.substring(lastSlash + 1);        } else if (alwaysSelectFullNamespace) {            // Simply select the namespace as everything before the last slash            namespace = uri.substring(0, lastSlash);            name = uri.substring(lastSlash + 1);        } else {            // Try to find the namespace in those defined, defaulting to ""            Configuration config = configManager.getConfiguration();            String prefix = uri.substring(0, lastSlash);            namespace = "";            boolean rootAvailable = false;            // Find the longest matching namespace, defaulting to the default            for (PackageConfig cfg : config.getPackageConfigs().values()) {                String ns = cfg.getNamespace();                if (ns != null && prefix.startsWith(ns) && (prefix.length() == ns.length() || prefix.charAt(ns.length()) == '/')) {                    if (ns.length() > namespace.length()) {                        namespace = ns;                    }                }                if ("/".equals(ns)) {                    rootAvailable = true;                }            }            name = uri.substring(namespace.length() + 1);            // Still none found, use root namespace if found            if (rootAvailable && "".equals(namespace)) {                namespace = "/";            }        }        if (!allowSlashesInActionNames) {            int pos = name.lastIndexOf('/');            if (pos > -1 && pos < name.length() - 1) {                name = name.substring(pos + 1);            }        }        mapping.setNamespace(namespace);        mapping.setName(cleanupActionName(name));    }
如果不是一个有效的ActionMapping 这里会调用一个比较有意思的方法:
boolean handled = execute.executeStaticResourceRequest(request, response);
当做一个静态的资源来访问。如果还是不能访问Struts就不能处理了。

当我们确认来的一个请求是一个有效的请求,这里就会调用Dispatcher的serviceAction方法,这又是一段核心的代码:

dispatcher.serviceAction(request, response, servletContext, mapping);

在Dispatcher.serviceAction()方法中,先加载Struts2的配置文件,如果没有人为配置,则默认加载struts-default.xml、struts-plugin.xml、struts.xml,并且将配置信息保存在形如com.opensymphony.xwork2.config.entities.XxxxConfig的类中。

ActionProxy proxy = config.getContainer().getInstance(ActionProxyFactory.class).createActionProxy(                    namespace, name, method, extraContext, true, false);            request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, proxy.getInvocation().getStack());            // if the ActionMapping says to go straight to a result, do it!            if (mapping.getResult() != null) {                Result result = mapping.getResult();                result.execute(proxy.getInvocation());            } else {                proxy.execute();            }
配置信息加载完成后,创建一个Action的代理对象——ActionProxy引用,实际上对Action的调用正是通过ActionProxy实现的,而ActionProxy又由ActionProxyFactory创建,ActionProxyFactory是创建ActionProxy的工厂。ActionProxy和ActionProxyFactory都是接口,他们的默认实现类分别是DefaultActionProxy和DefaultActionProxyFactory。
在这里,我们绝对有必要介绍一下com.opensymphony.xwork2.DefaultActionInvocation类,该类是对ActionInvocation接口的默认实现,负责Action
和截拦器的执行。
在DefaultActionInvocation类中,定义了invoke()方法,该方法实现了截拦器的递归调用和执行Action的execute()方法。

/**     * @throws ConfigurationException If no result can be found with the returned code     */    public String invoke() throws Exception {        String profileKey = "invoke: ";        try {            UtilTimerStack.push(profileKey);            if (executed) {                throw new IllegalStateException("Action has already executed");            }            if (interceptors.hasNext()) {                final InterceptorMapping interceptor = interceptors.next();                String interceptorMsg = "interceptor: " + interceptor.getName();                UtilTimerStack.push(interceptorMsg);                try {                                resultCode = interceptor.getInterceptor().intercept(DefaultActionInvocation.this);                            }                finally {                    UtilTimerStack.pop(interceptorMsg);                }            } else {                resultCode = invokeActionOnly();            }            // this is needed because the result will be executed, then control will return to the Interceptor, which will            // return above and flow through again            if (!executed) {                if (preResultListeners != null) {                    for (Object preResultListener : preResultListeners) {                        PreResultListener listener = (PreResultListener) preResultListener;                        String _profileKey = "preResultListener: ";                        try {                            UtilTimerStack.push(_profileKey);                            listener.beforeResult(this, resultCode);                        }                        finally {                            UtilTimerStack.pop(_profileKey);                        }                    }                }                // now execute the result, if we're supposed to                if (proxy.getExecuteResult()) {                    executeResult();                }                executed = true;            }            return resultCode;        }        finally {            UtilTimerStack.pop(profileKey);        }    }
执行完之后就执行Action返回Result了。

这真是一个悲伤地故事。

0 0
原创粉丝点击