请求过程
struts2 架构图如下图所示:
依照上图, 我们可以看出一个请求在 struts 的处理大概有如下步骤:
1, 客户端初始化一个指向 Servlet 容器 (例如 Tomcat) 的请求;
2, 这个请求经过一系列的过滤器(Filter)(这些过滤器中有一个叫做 ActionContextCleanUp 的可选过滤器, 这个过滤器对于 Struts2 和其他框架的集成很有帮助, 例如: SiteMesh Plugin);
3, 接着 StrutsPrepareAndExecuteFilter 被调用, StrutsPrepareAndExecuteFilter 询问 ActionMapper 来决定这个请求是否需要调用某个 Action;
4, 如果 ActionMapper 决定需要调用某个 Action,FilterDispatcher 把请求的处理交给 ActionProxy;
5,ActionProxy 通过 Configuration Manager 询问框架的配置文件, 找到需要调用的 Action 类;
6,ActionProxy 创建一个 ActionInvocation 的实例.
7,ActionInvocation 实例使用命名模式来调用, 在调用 Action 的过程前后, 涉及到相关拦截器 (Intercepter) 的调用.
8, 一旦 Action 执行完毕, ActionInvocation 负责根据 struts.xml 中的配置找到对应的返回结果. 返回结果通常是 (但不总是, 也可能是另外的一个 Action 链) 一个需要被表示的 JSP 或者 FreeMarker 的模版. 在表示的过程中可以使用 Struts2 框架中继承的标签. 在这个过程中需要涉及到 ActionMapper.
9, 接着按照相反次序执行拦截器链 ( 执行 Action 调用之后的部分 ). 最后, 响应通过滤器链返回(过滤器技术执行流程与拦截器一样, 都是先执行前面部分, 后执行后面部). 如果过滤器链中存在 ActionContextCleanUp,FilterDispatcher 不会清理线程局部的 ActionContext. 如果不存在 ActionContextCleanUp 过滤器, FilterDispatcher 会清除所有线程局部变量.
strut2 源码分析
首先我们使用 struts2 框架都会在 Web.xml 中注册和映射 struts2, 配置内容如下:
- <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>
注: 在早期的 struts2 中, 都是使用 FilterDispathcer, 从 Struts 2.1.3 开始, 它已不推荐使用. 如果你使用的 Struts 的版本>= 2.1.3, 推荐升级到新的 Filter,StrutsPrepareAndExecuteFilter. 在此研究的是 StrutsPrepareAndExecuteFilter.
StrutsPrepareAndExecuteFilter 中的方法:
void init(FilterConfig filterConfig) | 继承自 Filter,过滤器的初始化 |
doFilter(ServletRequest req, ServletResponse res, FilterChain chain) | 继承自 Filter,执行过滤器 |
void destroy() | 继承自 Filter,用于资源释放 |
void postInit(Dispatcher dispatcher, FilterConfig filterConfig) | Callback for post initialization(一个空的方法,用于方法回调初始化) |
Web 容器一启动, 就会初始化核心过滤器 StrutsPrepareAndExecuteFilter, 并执行初始化方法, 初始化方法如下:
- public void init(FilterConfig filterConfig) throws ServletException {
- InitOperations init = new InitOperations();
- Dispatcher dispatcher = null;
- try {
- // 封装 filterConfig, 其中有个主要方法 getInitParameterNames 将配置文件中的初始化参数名字以 String 格式存储在 List 中
- FilterHostConfig config = new FilterHostConfig(filterConfig);
- // 初始化 struts 内部日志
- init.initLogging(config);
- // 创建 dispatcher , 并初始化
- dispatcher = init.initDispatcher(config);
- init.initStaticContentLoader(config, dispatcher);
- // 初始化类属性: prepare ,execute
- prepare = new PrepareOperations(filterConfig.getServletContext(), dispatcher);
- execute = new ExecuteOperations(filterConfig.getServletContext(), dispatcher);
- this.excludedPatterns = init.buildExcludedPatternsList(dispatcher);
- // 回调空的 postInit 方法
- postInit(dispatcher, filterConfig);
- } finally {
- if (dispatcher != null) {
- dispatcher.cleanUpAfterInit();
- }
- init.cleanup();
- }
- }
关于封装 filterConfig, 首先看下 FilterHostConfig , 源码如下:
- public class FilterHostConfig implements HostConfig {
- private FilterConfig config;
- // 构造方法
- public FilterHostConfig(FilterConfig config) {
- this.config = config;
- }
- // 根据 init-param 配置的 param-name 获取 param-value 的值
- public String getInitParameter(String key) {
- return config.getInitParameter(key);
- }
- // 返回初始化参数名的迭代器
- public Iterator<String> getInitParameterNames() {
- return MakeIterator.convert(config.getInitParameterNames());
- }
- // 返回 Servlet 上下文
- public ServletContext getServletContext() {
- return config.getServletContext();
- }
- }
接下来, 看下 StrutsPrepareAndExecuteFilter 中 init 方法中 dispatcher = init.initDispatcher(config); 这是初始化 dispatcher 的.
- public Dispatcher initDispatcher( HostConfig filterConfig ) {
- Dispatcher dispatcher = createDispatcher(filterConfig);
- dispatcher.init();
- return dispatcher;
- }
创建 Dispatcher, 会读取 filterConfig 中的配置信息, 将配置信息解析出来, 封装成为一个 Map, 然后根绝 servlet 上下文和参数 Map 构造 Dispatcher :
- private Dispatcher createDispatcher( HostConfig filterConfig ) {
- // 存放参数的 Map
- Map<String, String> params = new HashMap<String, String>();
- // 将参数存放到 Map
- for ( Iterator e = filterConfig.getInitParameterNames(); e.hasNext(); ) {
- String name = (String) e.next();
- String value = filterConfig.getInitParameter(name);
- params.put(name, value);
- }
- // 根据 servlet 上下文和参数 Map 构造 Dispatcher
- return new Dispatcher(filterConfig.getServletContext(), params);
- }
这样 dispatcher 对象创建完成, 接着就是 dispatcher 对象的初始化, 打开 Dispatcher 类, 看到它的 init 方法如下:
- public void init() {
- if (configurationManager == null) {
- configurationManager = createConfigurationManager(BeanSelectionProvider.DEFAULT_BEAN_NAME);
- }
- try {
- init_FileManager();
- // 加载 org/apache/struts2/default.properties
- init_DefaultProperties();
- // 加载 struts-default.xml,struts-plugin.xml,struts.xml
- init_TraditionalXmlConfigurations();
- init_LegacyStrutsProperties();
- // 用户自己实现的 ConfigurationProviders 类
- init_CustomConfigurationProviders();
- //Filter 的初始化参数
- init_FilterInitParameters() ;
- init_AliasStandardObjects() ;
- 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);
- }
- }
这里主要是加载一些配置文件的, 将按照顺序逐一加载: default.properties,struts-default.xml,struts-plugin.xml,struts.xml,......
现在, 我们回到 StrutsPrepareAndExecuteFilter 类中, 刚才我们分析了 StrutsPrepareAndExecuteFilter 类的 init 方法, 该方法在 Web 容器一启动就会调用的, 当用户访问某个 action 的时候, 首先调用核心过滤器 StrutsPrepareAndExecuteFilter 的 doFilter 方法, 该方法内容如下:
- public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
- HttpServletRequest request = (HttpServletRequest) req;
- HttpServletResponse response = (HttpServletResponse) res;
- try {
- // 设置编码和国际化
- prepare.setEncodingAndLocale(request, response);
- // 创建 action 上下文
- prepare.createActionContext(request, response);
- prepare.assignDispatcherToThread();
- if (excludedPatterns != null && prepare.isUrlExcluded(request, excludedPatterns)) {
- chain.doFilter(request, response);
- } else {
- request = prepare.wrapRequest(request);
- ActionMapping mapping = prepare.findActionMapping(request, response, true);
- // 如果 mapping 为空, 则认为不是调用 action, 会调用下一个过滤器链, 直到获取到 mapping 才调用 action
- if (mapping == null) {
- boolean handled = execute.executeStaticResourceRequest(request, response);
- if (!handled) {
- chain.doFilter(request, response);
- }
- } else {
- // 执行 action
- execute.executeAction(request, response, mapping);
- }
- }
- } finally {
- prepare.cleanupRequest(request);
- }
- }
下面对 doFilter 方法中的重点部分一一讲解:
- (1)prepare.setEncodingAndLocale(request, response)
- public void setEncodingAndLocale(HttpServletRequest request, HttpServletResponse response) {
- dispatcher.prepare(request, response);
- }
这方法里面我们可以看到它只是调用了 dispatcher 的 prepare 方法而已, 下面我们看看 dispatcher 的 prepare 方法:
- String encoding = null;
- if (defaultEncoding != null) {
- encoding = defaultEncoding;
- }
- // check for Ajax request to use UTF-8 encoding strictly http://www.w3.org/TR/XMLHttpRequest/#the-send-method
- if ("XMLHttpRequest".equals(request.getHeader("X-Requested-With"))) {
- encoding = "UTF-8";
- }
- Locale locale = null;
- if (defaultLocale != null) {
- locale = LocalizedTextUtil.localeFromString(defaultLocale, request.getLocale());
- }
- if (encoding != null) {
- applyEncoding(request, encoding);
- }
- if (locale != null) {
- response.setLocale(locale);
- }
- if (paramsWorkaroundEnabled) {
- request.getParameter("foo"); // simply read any parameter (existing or not) to "prime" the request
- }
- }
我们可以看到该方法只是简单的设置了 encoding 和 locale , 做的只是一些辅助的工作.
(2)prepare.createActionContext(request, response)
我们回到 StrutsPrepareAndExecuteFilter 的 doFilter 方法, 看到第 10 行代码: prepare.createActionContext(request, response); 这是 action 上下文的创建, ActionContext 是一个容器, 这个容易主要存储 request,session,application,parameters 等相关信 息. ActionContext 是一个线程的本地变量, 这意味着不同的 action 之间不会共享 ActionContext, 所以也不用考虑线程安全问 题. 其实质是一个 Map,key 是标示 request,session,...... 的字符串, 值是其对应的对象, 我们可以看到 com.opensymphony.xwork2.ActionContext 类中时如下定义的:
static ThreadLocal<ActionContext> actionContext = new ThreadLocal<ActionContext>();
我们看下 PrepareOperations 类的 createActionContext 方法:
- public void prepare(HttpServletRequest request, HttpServletResponse response) {
- /**
- * Creates the action context and initializes the thread local
- */
- public ActionContext createActionContext(HttpServletRequest request, HttpServletResponse response) {
- ActionContext ctx;
- Integer counter = 1;
- Integer oldCounter = (Integer) request.getAttribute(CLEANUP_RECURSION_COUNTER);
- if (oldCounter != null) {
- counter = oldCounter + 1;
- }
- // 此处是从 ThreadLocal 中获取此 ActionContext 变量
- ActionContext oldContext = ActionContext.getContext();
- if (oldContext != null) {
- // detected existing context, so we are probably in a forward
- ctx = new ActionContext(new HashMap<String, Object>(oldContext.getContextMap()));
- } else {
- ValueStack stack = dispatcher.getContainer().getInstance(ValueStackFactory.class).createValueStack();
- stack.getContext().putAll(dispatcher.createContextMap(request, response, null, servletContext));
- //stack.getContext()返回的是一个 Map<String,Object>, 根据此 Map 构造一个 ActionContext
- ctx = new ActionContext(stack.getContext());
- }
- request.setAttribute(CLEANUP_RECURSION_COUNTER, counter);
- // 将 ActionContext 存到 ThreadLocal
- ActionContext.setContext(ctx);
- return ctx;
- }
上面第 18 行代码中 dispatcher.createContextMap, 如何封装相关参数:
- public Map<String,Object> createContextMap(HttpServletRequest request, HttpServletResponse response,
- ActionMapping mapping, ServletContext context) {
- // request map wrapping the http request objects
- Map requestMap = new RequestMap(request);
- // parameters map wrapping the http parameters. ActionMapping parameters are now handled and applied separately
- Map params = new HashMap(request.getParameterMap());
- // session map wrapping the http session
- Map session = new SessionMap(request);
- // application map wrapping the ServletContext
- Map application = new ApplicationMap(context);
- //requestMap,params,session 等 Map 封装成为一个上下文 Map
- Map<String,Object> extraContext = createContextMap(requestMap, params, session, application, request, response, context);
- if (mapping != null) {
- extraContext.put(ServletActionContext.ACTION_MAPPING, mapping);
- }
- return extraContext;
- }
- (3)request = prepare.wrapRequest(request)
我们再次回到 StrutsPrepareAndExecuteFilter 的 doFilter 方法中, 看到第 15 行: request = prepare.wrapRequest(request); 这一句是对 request 进行包装的, 我们看下 prepare 的 wrapRequest 方法:
- public HttpServletRequest wrapRequest(HttpServletRequest oldRequest) throws ServletException {
- HttpServletRequest request = oldRequest;
- try {
- // Wrap request first, just in case it is multipart/form-data
- // parameters might not be accessible through before encoding (ww-1278)
- request = dispatcher.wrapRequest(request, servletContext);
- } catch (IOException e) {
- throw new ServletException("Could not wrap servlet request with MultipartRequestWrapper!", e);
- }
- return request;
- }
我们看下 dispatcher 的 wrapRequest:
- public HttpServletRequest wrapRequest(HttpServletRequest request, ServletContext servletContext) throws IOException {
- // don't wrap more than once
- if (request instanceof StrutsRequestWrapper) {
- return request;
- }
- String content_type = request.getContentType();
- // 如果 content_type 是 multipart/form-data 类型, 则将 request 包装成 MultiPartRequestWrapper 对象, 否则包装成 StrutsRequestWrapper 对象
- if (content_type != null && content_type.contains("multipart/form-data")) {
- MultiPartRequest mpr = getMultiPartRequest();
- LocaleProvider provider = getContainer().getInstance(LocaleProvider.class);
- request = new MultiPartRequestWrapper(mpr, request, getSaveDir(servletContext), provider);
- } else {
- request = new StrutsRequestWrapper(request, disableRequestAttributeValueStackLookup);
- }
- return request;
- }
此次包装根据请求内容的类型不同, 返回不同的对象, 如果为 multipart/form-data 类型, 则返回 MultiPartRequestWrapper 类型的对象, 该对象服务于文件上传, 否则返回 StrutsRequestWrapper 类型的对象, MultiPartRequestWrapper 是 StrutsRequestWrapper 的子类, 而这两个类都是 HttpServletRequest 接口的实现.
(4)ActionMapping mapping = prepare.findActionMapping(request, response, true)
包装 request 后, 通过 ActionMapper 的 getMapping()方法得到请求的 Action,Action 的配置信息存储在 ActionMapping 对象中, 如 StrutsPrepareAndExecuteFilter 的 doFilter 方法中第 16 行: ActionMapping mapping = prepare.findActionMapping(request, response, true); 我们找到 prepare 对象的 findActionMapping 方法:
- public ActionMapping findActionMapping(HttpServletRequest request, HttpServletResponse response, boolean forceLookup) {
- // 首先从 request 对象中取 mapping 对象, 看是否存在
- ActionMapping mapping = (ActionMapping) request.getAttribute(STRUTS_ACTION_MAPPING_KEY);
- // 不存在就创建一个
- if (mapping == null || forceLookup) {
- try {
- // 首先创建 ActionMapper 对象, 通过 ActionMapper 对象创建 mapping 对象
- mapping = dispatcher.getContainer().getInstance(ActionMapper.class).getMapping(request, dispatcher.getConfigurationManager());
- if (mapping != null) {
- request.setAttribute(STRUTS_ACTION_MAPPING_KEY, mapping);
- }
- } catch (Exception ex) {
- dispatcher.sendError(request, response, servletContext, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex);
- }
- }
- return mapping;
- }
下面是 ActionMapper 接口的实现类 DefaultActionMapper 的 getMapping()方法的源代码:
- public ActionMapping getMapping(HttpServletRequest request, ConfigurationManager configManager) {
- ActionMapping mapping = new ActionMapping();
- // 获得请求的 uri, 即请求路径 URL 中工程名以后的部分, 如 / userAction.action
- String uri = getUri(request);
- // 修正 url 的带; jsessionid 时找不到的 bug
- int indexOfSemicolon = uri.indexOf(";");
- uri = (indexOfSemicolon> -1) ? uri.substring(0, indexOfSemicolon) : uri;
- // 删除扩展名, 如. action 或者. do
- uri = dropExtension(uri, mapping);
- if (uri == null) {
- return null;
- }
- // 从 uri 中分离得到请求的 action 名, 命名空间.
- parseNameAndNamespace(uri, mapping, configManager);
- // 处理特殊的请求参数
- handleSpecialParameters(request, mapping);
- // 如果允许动态方法调用, 即形如 / userAction!getAll.action 的请求, 分离 action 名和方法名
- return parseActionName(mapping);
- }
下面对 getMapping 方法中的重要部分一一讲解:
1:parseNameAndNamespace(uri, mapping, configManager)
我们主要看下第 14 行的 parseNameAndNamespace(uri, mapping, configManager); 这个方法的主要作用是分离出 action 名和命名空间:
- 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;//rootAvailable 作用是判断配置文件中是否配置了命名空间 "/"
- // Find the longest matching namespace, defaulting to the default
- for (Object cfg : config.getPackageConfigs().values()) { // 循环遍历配置文件中的 package 标签
- String ns = ((PackageConfig) cfg).getNamespace(); // 获取每个 package 标签的 namespace 属性
- // 进行匹配
- 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);
- }
- }
- // 将分离后的 acion 名和命名空间保存到 mapping 对象
- mapping.setNamespace(namespace);
- mapping.setName(cleanupActionName(name));
- }
看到上面代码的第 14 行, 参数 alwaysSelectFullNamespace 我们可以通过名字就能大概猜出来 "允许采用完整的命名空间", 即设置命名空间是否必须进行精确匹配, true 必须, false 可以模糊匹配, 默认是 false. 进行精确匹配时要求请求 url 中的命名空间必须与配置文件中配置的某个命名空间必须相同, 如果没有找到相同的则匹配失败. 这个参数可通过 struts2 的 "struts.mapper.alwaysSelectFullNamespace" 常量配置, 如:
<constant name="struts.mapper.alwaysSelectFullNamespace" value="true" />
当 alwaysSelectFullNamespace 为 true 时, 将 uri 以 lastSlash 为分割, 左边的为 namespace, 右边的为 name. 如: http://localhost:8080/myproject/home/actionName!method.action, 此时 uri 为 / home/actionName!method.action(不过前面把后缀名去掉了, 变成 / home/actionName!method),lastSlash 的, 当前值是 5, 这样 namespace 为 "/home",name 为 actionName!method.
2:parseActionName(mapping)
我们看到 18 行: return parseActionName(mapping); 主要是用来处理形如 / userAction!getAll.action 的请求, 分离 action 名和方法名:
- protected ActionMapping parseActionName(ActionMapping mapping) {
- if (mapping.getName() == null) {
- return null;
- }
- // 如果允许动态方法调用
- if (allowDynamicMethodCalls) {
- // handle "name!method" convention.
- String name = mapping.getName();
- int exclamation = name.lastIndexOf("!");
- // 如果包含 "!" 就进行分离
- if (exclamation != -1) {
- // 分离出 action 名
- mapping.setName(name.substring(0, exclamation));
- // 分离出方法名
- mapping.setMethod(name.substring(exclamation + 1));
- }
- }
- return mapping;
- }
到此为止 getMapping 方法已经分析结束了!
(5)execute.executeAction(request, response, mapping)
上面我们分析完了 mapping 的获取, 继续看 doFilter 方法:
- // 如果 mapping 为空, 则认为不是调用 action, 会调用下一个过滤器链
- if (mapping == null) {
- // 执行请求 CSS,JS 文件. 并返回是否成功.
- boolean handled = execute.executeStaticResourceRequest(request, response);
- if (!handled) {
- chain.doFilter(request, response);
- }
- } else {
- // 执行 action
- execute.executeAction(request, response, mapping);
- }
如果 mapping 对象不为空, 则会执行 action
- public void executeAction(HttpServletRequest request, HttpServletResponse response, ActionMapping mapping) throws ServletException {
- dispatcher.serviceAction(request, response, servletContext, mapping);
- }
我们可以看到它里面只是简单的调用了 dispatcher 的 serviceAction 方法: 我们找到 dispatcher 的 serviceAction 方法:
- public void serviceAction(HttpServletRequest request, HttpServletResponse response, ActionMapping mapping)
- throws ServletException {
- // 封转上下文环境, 主要将 requestMap,params,session 等 Map 封装成为一个上下文 Map
- Map<String, Object> extraContext = createContextMap(request, response, mapping);
- // 如果之前没有值栈, 就从 ActionContext 中先取出值栈, 放入 extraContext
- ValueStack stack = (ValueStack) request.getAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY);
- boolean nullStack = stack == null;
- if (nullStack) {
- ActionContext ctx = ActionContext.getContext();
- if (ctx != null) {
- stack = ctx.getValueStack();
- }
- }
- if (stack != null) {
- extraContext.put(ActionContext.VALUE_STACK, valueStackFactory.createValueStack(stack));
- }
- String timerKey = "Handling request from Dispatcher";
- try {
- UtilTimerStack.push(timerKey);
- String namespace = mapping.getNamespace();// 获得 request 请求里面的命名空间, 即是 struts.xml 是的 package 节点元素
- String name = mapping.getName();// 获得 request 请求里面的 action 名
- String method = mapping.getMethod();// 要执行 action 的方法
- ActionProxy proxy = getContainer().getInstance(ActionProxyFactory.class).createActionProxy(namespace, name,
- method, extraContext, true, false);// 获得 action 的代理
- request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, proxy.getInvocation().getStack());
- // 如果 action 映射是直接就跳转到网页的话,
- if (mapping.getResult() != null) {
- Result result = mapping.getResult();
- result.execute(proxy.getInvocation());
- } else {
- proxy.execute();// 这里就是执行 action
- }
- if (!nullStack) {
- request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, stack);
- }
- } catch (ConfigurationException e) {
- logConfigurationException(request, e);
- sendError(request, response, HttpServletResponse.SC_NOT_FOUND, e);
- } catch (Exception e) {
- if (handleException || devMode) {
- sendError(request, response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e);
- } else {
- throw new ServletException(e);
- }
- } finally {
- UtilTimerStack.pop(timerKey);
- }
- }
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 类:
- 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);
- }
Dispatcher 类是重要的调结者, DefaultActionInvocation 类是执行 action 类实例的行动者. 而 action 代理类 (ActionProxy 类) 则是他们之间的中间人. 相当于 Dispatcher 类通过 action 代理类 (ActionProxy 类) 命令 DefaultActionInvocation 类去执行 action 类实例.
DefaultActionProxyFactory 类:
- public ActionProxy createActionProxy(ActionInvocation inv, String namespace, String actionName, String methodName, boolean executeResult, boolean cleanupContext) {
- DefaultActionProxy proxy = new DefaultActionProxy(inv, namespace, actionName, methodName, executeResult, cleanupContext);
- container.inject(proxy);
- proxy.prepare();
- return proxy;
- }
DefaultActionProxy 类:
- protected void prepare() {
- String profileKey = "create DefaultActionProxy:";
- try {
- UtilTimerStack.push(profileKey);
- config = configuration.getRuntimeConfiguration().getActionConfig(namespace, actionName);// 根据空间命名和 action 名来找到对应的配置信息
- if (config == null && unknownHandlerManager.hasUnknownHandlers()) {
- config = unknownHandlerManager.handleUnknownAction(namespace, actionName);
- }
- if (config == null) {
- throw new ConfigurationException(getErrorMessage());
- }
- resolveMethod();// 找到对应的方法名.
- if (config.isAllowedMethod(method)) {
- invocation.init(this);
- } else {
- throw new ConfigurationException(prepareNotAllowedErrorMessage());
- }
- } finally {
- UtilTimerStack.pop(profileKey);
- }
- }
1. 获得 ActionConfig 类实例. 并通过 ActionConfig 类实例找到对应的方法名. ActionConfig 类就是存放配置文件里面的 action 元素节点的信息.
2. 实初始化 DefaultActionInvocation 类的实例. 即是根据 ActionProxy 类实例找到对应的 action 类实例(用户自己定义的类).
DefaultActionProxy 类:
- private void resolveMethod() {
- // 从配置中获得方法名. 如果还是空的话, 就用默认的值. 即是 "execute" 方法.
- if (StringUtils.isEmpty(this.method)) {
- this.method = config.getMethodName();
- if (StringUtils.isEmpty(this.method)) {
- this.method = ActionConfig.DEFAULT_METHOD;
- }
- methodSpecified = false;
- }
- }
DefaultActionInvocation 类:
- public void init(ActionProxy proxy) {
- this.proxy = proxy;
- Map<String, Object> contextMap = createContextMap();
- // Setting this so that other classes, like object factories, can use the ActionProxy and other
- // contextual information to operate
- ActionContext actionContext = ActionContext.getContext();
- if (actionContext != null) {
- actionContext.setActionInvocation(this);
- }
- createAction(contextMap);// 找到对应的 action 类实例
- if (pushAction) {
- stack.push(action);
- contextMap.put("action", action);
- }
- invocationContext = new ActionContext(contextMap);
- invocationContext.setName(proxy.getActionName());
- createInterceptors(proxy);
- }
看了代码就能清楚的知道一件事情. 如果我们在 struts.xml 配置文件里面 action 元素节点里面没有指定方法的时候, 就用会默认的方法. 即是 execute 方法. 而关于 init 方法就能明确明白为了找到 action 类并实例他. init 方法里面调用了俩个非重要的方法. 一个是用于新建 action 类实例的方法 createAction. 一个是用于获得相关拦截器的方法 createInterceptors. 看一下代码吧.
DefaultActionInvocation 类:
- protected void createAction(Map<String, Object> contextMap) {
- // load action
- String timerKey = "actionCreate:" + proxy.getActionName();
- try {
- UtilTimerStack.push(timerKey);
- action = objectFactory.buildAction(proxy.getActionName(), proxy.getNamespace(), proxy.getConfig(), contextMap);
- } catch (InstantiationException e) {
- throw new XWorkException("Unable to instantiate Action!", e, proxy.getConfig());
- } catch (IllegalAccessException e) {
- throw new XWorkException("Illegal access to constructor, is it public?", e, proxy.getConfig());
- } catch (Exception e) {
- String gripe;
- if (proxy == null) {
- gripe = "Whoa! No ActionProxy instance found in current ActionInvocation. This is bad ... very bad";
- } else if (proxy.getConfig() == null) {
- gripe = "Sheesh. Where'd that ActionProxy get to? I can't find it in the current ActionInvocation!?";
- } else if (proxy.getConfig().getClassName() == null) {
- gripe = "No Action defined for'" + proxy.getActionName() + "'in namespace'" + proxy.getNamespace() + "'";
- } else {
- gripe = "Unable to instantiate Action," + proxy.getConfig().getClassName() + ", defined for'" + proxy.getActionName() + "'in namespace'" + proxy.getNamespace() + "'";
- }
- gripe += ((("--" + e.getMessage()) != null) ? e.getMessage() : "[no message in exception]");
- throw new XWorkException(gripe, e, proxy.getConfig());
- } finally {
- UtilTimerStack.pop(timerKey);
- }
- if (actionEventListener != null) {
- action = actionEventListener.prepare(action, stack);
- }
- }
DefaultActionInvocation 类:
- protected void createInterceptors(ActionProxy proxy) {
- // Get a new List so we don't get problems with the iterator if someone changes the original list
- List<InterceptorMapping> interceptorList = new ArrayList<>(proxy.getConfig().getInterceptors());
- interceptors = interceptorList.iterator();
- }
action 代理类 (ActionProxy 类) 的准备工作完成之后, 就开始执行了. 最顶部的代码中就很明确的看的出来 (serviceAction 方法). 先是根据参数 mapping 来判断是否为直接回返. 如果不是才去执行 action 代理类(ActionProxy 类) 的 execute 方法. 这便是 action 代理类 (ActionProxy 类) 的主要工作. 即是执行 action 请求. 那么让我们看一下 action 代理类 (ActionProxy 类) 的 execute 方法源码吧.
- public String execute() throws Exception {
- ActionContext nestedContext = ActionContext.getContext();
- ActionContext.setContext(invocation.getInvocationContext());
- String retCode = null;
- String profileKey = "execute:";
- try {
- UtilTimerStack.push(profileKey);
- retCode = invocation.invoke();
- } finally {
- if (cleanupContext) {
- ActionContext.setContext(nestedContext);
- }
- UtilTimerStack.pop(profileKey);
- }
- return retCode;
- }
从红色的代码部分我们就知道就是去执行 DefaultActionInvocation 类实例的 invoke 方法
DefaultActionInvocation 类:
- 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) {
- LOG.trace("Executing PreResultListeners for result [{}]", result);
- 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);
- }
- }
上面的红色的代码是这个方法的核心点之一. 让我们看一下红色代码做什么? 判断 interceptors 是否有拦截器. 如果没有就直接执行 invokeActionOnly 方法. 即是执行 action 类实例对应的方法. 如果有就获得拦截器并执行拦截器(执行 intercept 方法). 好了. 关键点就在这个执行拦截器身上. 即是执行 intercept 方法. intercept 方法有一个参数就是 DefaultActionInvocation 类的接口. 这个参数让 struts2 的 AOP 思想能够进行. 为什么这样子讲呢? 不清楚读者有没有想过. 为什么这边判断拦截器是用 if 而不是用 for 或是 while 呢? 必竟拦截器不只一个. 我们都清楚 AOP 的目标就是让业务模块选择对应的切面. 那么就有可能存在多个拦截器. 这也是为什么亮点的原因了. 看一下拦截器的代码就知道了. 如下
推荐博客
程序员写代码之外, 如何再赚一份工资?
LoggingInterceptor 类:
- public String intercept(ActionInvocation invocation) throws Exception {
- logMessage(invocation, START_MESSAGE);
- String result = invocation.invoke();
- logMessage(invocation, FINISH_MESSAGE);
- return result;
- }
拦截器开始的时候, 执行相关的拦截器逻辑, 然后又重新调用 DefaultActionInvocation 类的 invoke 方法. 从而获得下一个拦截器. 就是这样子下一个拦截器又开始执行自己的 intercept 方法. 做了相关的拦截器逻辑之后. 又一次重新调用 DefaultActionInvocation 类的 invoke 方法. 又做了相似的工作. 只到没有了拦截器, 执行用户 action 类实例的方法并返回结果. 有了结果之后, 就开始续继执行当前上一个拦截器的后半部分代码. 直到返回到最开始的拦截器执行后半部分的代码.
来源: https://www.cnblogs.com/java-chen-hao/p/10869984.html