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了。
这真是一个悲伤地故事。
- Struts2学习笔记——Struts框架的工作原理(源码调试)
- struts2学习笔记(1)——关于struts2的工作原理,以及基本组成
- Struts1学习笔记(Struts的工作原理)
- Struts框架的工作原理
- 从Struts2源码学习Struts2的工作原理
- Struts学习笔记:Struts Framework工作原理
- Struts2学习笔记(一)——工作原理
- J2EE学习(Struts2的工作原理)
- 【Struts2学习笔记(5)】Struts2的处理流程及工作原理
- Struts2学习笔记——Struts2原理
- 学习Struts框架系列(二):多角度剖析Struts工作原理
- struts2学习笔记(一)---工作原理
- Struts2的工作原理(源码分析)
- Struts2学习笔记(四)——Struts标签相关
- Struts2 框架的简单实现——轻松理解Struts原理
- Symfony2学习笔记二: 框架的工作原理
- struts2第十八讲学习笔记,struts2工作原理精华摘要
- Struts2笔记(2)——Struts的请求流程
- 学习boost::asio(1)
- ubuntu上编译Android源码(Build Android source
- 第十六周项目二 用指针玩转字符串-项目目录
- 把苹果iPhone手机传文件传给安卓系统手机
- Codeforces Round #282 (Div. 2) C
- Struts2学习笔记——Struts框架的工作原理(源码调试)
- NANDFLASH学习 <s3c2440>
- android网络通信之HttpUrlConnection
- 斐波那契数列
- Android 通过调用谷歌语音系统接口弹出语音识别对话框的方法 .
- JavaScript基础-BOM&DOM
- 二叉树的非递归实现
- 根据坐标系统构造转换矩阵(不包含缩放)
- 黑马程序员_Java基础_IO流(File类和properties类相关操作)