请求过程

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,配置内容如下:

  1. <filter>
  2. <filter-name>struts2</filter-name>
  3. <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
  4. </filter>
  5. <filter-mapping>
  6. <filter-name>struts2</filter-name>
  7. <url-pattern>/*</url-pattern>
  8. </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,并执行初始化方法,初始化方法如下:

  1. public void init(FilterConfig filterConfig) throws ServletException {
  2. InitOperations init = new InitOperations();
  3. Dispatcher dispatcher = null;
  4. try {
  5. //封装filterConfig,其中有个主要方法getInitParameterNames将配置文件中的初始化参数名字以String格式存储在List中
  6. FilterHostConfig config = new FilterHostConfig(filterConfig);
  7. //初始化struts内部日志
  8. init.initLogging(config);
  9. //创建dispatcher ,并初始化
  10. dispatcher = init.initDispatcher(config);
  11. init.initStaticContentLoader(config, dispatcher);
  12. //初始化类属性:prepare 、execute
  13. prepare = new PrepareOperations(filterConfig.getServletContext(), dispatcher);
  14. execute = new ExecuteOperations(filterConfig.getServletContext(), dispatcher);
  15. this.excludedPatterns = init.buildExcludedPatternsList(dispatcher);
  16. //回调空的postInit方法
  17. postInit(dispatcher, filterConfig);
  18. } finally {
  19. if (dispatcher != null) {
  20. dispatcher.cleanUpAfterInit();
  21. }
  22. init.cleanup();
  23. }
  24. }

关于封装filterConfig,首先看下FilterHostConfig ,源码如下:

  1. public class FilterHostConfig implements HostConfig {
  2.  
  3. private FilterConfig config;
  4. //构造方法
  5. public FilterHostConfig(FilterConfig config) {
  6. this.config = config;
  7. }
  8. //根据init-param配置的param-name获取param-value的值
  9. public String getInitParameter(String key) {
  10. return config.getInitParameter(key);
  11. }
  12. //返回初始化参数名的迭代器
  13. public Iterator<String> getInitParameterNames() {
  14. return MakeIterator.convert(config.getInitParameterNames());
  15. }
  16. //返回Servlet上下文
  17. public ServletContext getServletContext() {
  18. return config.getServletContext();
  19. }
  20. }

接下来,看下StrutsPrepareAndExecuteFilter中init方法中dispatcher = init.initDispatcher(config);这是初始化dispatcher的。

  1. public Dispatcher initDispatcher( HostConfig filterConfig ) {
  2. Dispatcher dispatcher = createDispatcher(filterConfig);
  3. dispatcher.init();
  4. return dispatcher;
  5. }

创建Dispatcher,会读取 filterConfig 中的配置信息,将配置信息解析出来,封装成为一个Map,然后根绝servlet上下文和参数Map构造Dispatcher :

  1. private Dispatcher createDispatcher( HostConfig filterConfig ) {
  2. //存放参数的Map
  3. Map<String, String> params = new HashMap<String, String>();
  4. //将参数存放到Map
  5. for ( Iterator e = filterConfig.getInitParameterNames(); e.hasNext(); ) {
  6. String name = (String) e.next();
  7. String value = filterConfig.getInitParameter(name);
  8. params.put(name, value);
  9. }
  10. //根据servlet上下文和参数Map构造Dispatcher
  11. return new Dispatcher(filterConfig.getServletContext(), params);
  12. }

这样dispatcher对象创建完成,接着就是dispatcher对象的初始化,打开Dispatcher类,看到它的init方法如下:

  1. public void init() {
  2.  
  3. if (configurationManager == null) {
  4. configurationManager = createConfigurationManager(BeanSelectionProvider.DEFAULT_BEAN_NAME);
  5. }
  6.  
  7. try {
  8. init_FileManager();
  9. //加载org/apache/struts2/default.properties
  10. init_DefaultProperties();
  11. //加载struts-default.xml,struts-plugin.xml,struts.xml
  12. init_TraditionalXmlConfigurations();
  13. init_LegacyStrutsProperties();
  14. //用户自己实现的ConfigurationProviders类
  15. init_CustomConfigurationProviders();
  16. //Filter的初始化参数
  17. init_FilterInitParameters() ;
  18. init_AliasStandardObjects() ;
  19.  
  20. Container container = init_PreloadConfiguration();
  21. container.inject(this);
  22. init_CheckWebLogicWorkaround(container);
  23.  
  24. if (!dispatcherListeners.isEmpty()) {
  25. for (DispatcherListener l : dispatcherListeners) {
  26. l.dispatcherInitialized(this);
  27. }
  28. }
  29. } catch (Exception ex) {
  30. if (LOG.isErrorEnabled())
  31. LOG.error("Dispatcher initialization failed", ex);
  32. throw new StrutsException(ex);
  33. }
  34. }

这里主要是加载一些配置文件的,将按照顺序逐一加载:default.properties,struts-default.xml,struts-plugin.xml,struts.xml,……

现在,我们回到StrutsPrepareAndExecuteFilter类中,刚才我们分析了StrutsPrepareAndExecuteFilter类的init方法,该方法在web容器一启动就会调用的,当用户访问某个action的时候,首先调用核心过滤器StrutsPrepareAndExecuteFilter的doFilter方法,该方法内容如下:

  1. public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
  2.  
  3. HttpServletRequest request = (HttpServletRequest) req;
  4. HttpServletResponse response = (HttpServletResponse) res;
  5.  
  6. try {
  7. //设置编码和国际化
  8. prepare.setEncodingAndLocale(request, response);
  9. //创建action上下文
  10. prepare.createActionContext(request, response);
  11. prepare.assignDispatcherToThread();
  12. if (excludedPatterns != null && prepare.isUrlExcluded(request, excludedPatterns)) {
  13. chain.doFilter(request, response);
  14. } else {
  15. request = prepare.wrapRequest(request);
  16. ActionMapping mapping = prepare.findActionMapping(request, response, true);
  17. //如果mapping为空,则认为不是调用action,会调用下一个过滤器链,直到获取到mapping才调用action
  18. if (mapping == null) {
  19. boolean handled = execute.executeStaticResourceRequest(request, response);
  20. if (!handled) {
  21. chain.doFilter(request, response);
  22. }
  23. } else {
  24. //执行action
  25. execute.executeAction(request, response, mapping);
  26. }
  27. }
  28. } finally {
  29. prepare.cleanupRequest(request);
  30. }
  31. }

下面对doFilter方法中的重点部分一一讲解:

(1)prepare.setEncodingAndLocale(request, response)

  1. public void setEncodingAndLocale(HttpServletRequest request, HttpServletResponse response) {
  2. dispatcher.prepare(request, response);
  3. }

这方法里面我们可以看到它只是调用了dispatcher的prepare方法而已,下面我们看看dispatcher的prepare方法:

  1. String encoding = null;
  2. if (defaultEncoding != null) {
  3. encoding = defaultEncoding;
  4. }
  5. // check for Ajax request to use UTF-8 encoding strictly http://www.w3.org/TR/XMLHttpRequest/#the-send-method
  6. if ("XMLHttpRequest".equals(request.getHeader("X-Requested-With"))) {
  7. encoding = "UTF-8";
  8. }
  9.  
  10. Locale locale = null;
  11. if (defaultLocale != null) {
  12. locale = LocalizedTextUtil.localeFromString(defaultLocale, request.getLocale());
  13. }
  14.  
  15. if (encoding != null) {
  16. applyEncoding(request, encoding);
  17. }
  18.  
  19. if (locale != null) {
  20. response.setLocale(locale);
  21. }
  22.  
  23. if (paramsWorkaroundEnabled) {
  24. request.getParameter("foo"); // simply read any parameter (existing or not) to "prime" the request
  25. }
  26. }

我们可以看到该方法只是简单的设置了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类中时如下定义的:

  1. static ThreadLocal<ActionContext> actionContext = new ThreadLocal<ActionContext>();

我们看下PrepareOperations类的createActionContext方法:

  1. public void prepare(HttpServletRequest request, HttpServletResponse response) {
  2. /**
  3. * Creates the action context and initializes the thread local
  4. */
  5. public ActionContext createActionContext(HttpServletRequest request, HttpServletResponse response) {
  6. ActionContext ctx;
  7. Integer counter = 1;
  8. Integer oldCounter = (Integer) request.getAttribute(CLEANUP_RECURSION_COUNTER);
  9. if (oldCounter != null) {
  10. counter = oldCounter + 1;
  11. }
  12. //此处是从ThreadLocal中获取此ActionContext变量
  13. ActionContext oldContext = ActionContext.getContext();
  14. if (oldContext != null) {
  15. // detected existing context, so we are probably in a forward
  16. ctx = new ActionContext(new HashMap<String, Object>(oldContext.getContextMap()));
  17. } else {
  18. ValueStack stack = dispatcher.getContainer().getInstance(ValueStackFactory.class).createValueStack();
  19. stack.getContext().putAll(dispatcher.createContextMap(request, response, null, servletContext));
  20. //stack.getContext()返回的是一个Map<String,Object>,根据此Map构造一个ActionContext
  21. ctx = new ActionContext(stack.getContext());
  22. }
  23. request.setAttribute(CLEANUP_RECURSION_COUNTER, counter);
  24. //将ActionContext存到ThreadLocal
  25. ActionContext.setContext(ctx);
  26. return ctx;
  27. }

上面第18行代码中dispatcher.createContextMap,如何封装相关参数:

  1. public Map<String,Object> createContextMap(HttpServletRequest request, HttpServletResponse response,
  2. ActionMapping mapping, ServletContext context) {
  3.  
  4. // request map wrapping the http request objects
  5. Map requestMap = new RequestMap(request);
  6.  
  7. // parameters map wrapping the http parameters. ActionMapping parameters are now handled and applied separately
  8. Map params = new HashMap(request.getParameterMap());
  9.  
  10. // session map wrapping the http session
  11. Map session = new SessionMap(request);
  12.  
  13. // application map wrapping the ServletContext
  14. Map application = new ApplicationMap(context);
  15. //requestMap、params、session等Map封装成为一个上下文Map
  16. Map<String,Object> extraContext = createContextMap(requestMap, params, session, application, request, response, context);
  17.  
  18. if (mapping != null) {
  19. extraContext.put(ServletActionContext.ACTION_MAPPING, mapping);
  20. }
  21. return extraContext;
  22. }

(3)request = prepare.wrapRequest(request)

我们再次回到StrutsPrepareAndExecuteFilter的doFilter方法中,看到第15行:request = prepare.wrapRequest(request);这一句是对request进行包装的,我们看下prepare的wrapRequest方法:

  1. public HttpServletRequest wrapRequest(HttpServletRequest oldRequest) throws ServletException {
  2. HttpServletRequest request = oldRequest;
  3. try {
  4. // Wrap request first, just in case it is multipart/form-data
  5. // parameters might not be accessible through before encoding (ww-1278)
  6. request = dispatcher.wrapRequest(request, servletContext);
  7. } catch (IOException e) {
  8. throw new ServletException("Could not wrap servlet request with MultipartRequestWrapper!", e);
  9. }
  10. return request;
  11. }

我们看下dispatcher的wrapRequest:

  1. public HttpServletRequest wrapRequest(HttpServletRequest request, ServletContext servletContext) throws IOException {
  2. // don't wrap more than once
  3. if (request instanceof StrutsRequestWrapper) {
  4. return request;
  5. }
  6.  
  7. String content_type = request.getContentType();
  8. //如果content_type是multipart/form-data类型,则将request包装成MultiPartRequestWrapper对象,否则包装成StrutsRequestWrapper对象
  9. if (content_type != null && content_type.contains("multipart/form-data")) {
  10. MultiPartRequest mpr = getMultiPartRequest();
  11. LocaleProvider provider = getContainer().getInstance(LocaleProvider.class);
  12. request = new MultiPartRequestWrapper(mpr, request, getSaveDir(servletContext), provider);
  13. } else {
  14. request = new StrutsRequestWrapper(request, disableRequestAttributeValueStackLookup);
  15. }
  16.  
  17. return request;
  18. }

此次包装根据请求内容的类型不同,返回不同的对象,如果为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方法:

  1. public ActionMapping findActionMapping(HttpServletRequest request, HttpServletResponse response, boolean forceLookup) {
  2. //首先从request对象中取mapping对象,看是否存在
  3. ActionMapping mapping = (ActionMapping) request.getAttribute(STRUTS_ACTION_MAPPING_KEY);
  4. //不存在就创建一个
  5. if (mapping == null || forceLookup) {
  6. try {
  7. //首先创建ActionMapper对象,通过ActionMapper对象创建mapping对象
  8. mapping = dispatcher.getContainer().getInstance(ActionMapper.class).getMapping(request, dispatcher.getConfigurationManager());
  9. if (mapping != null) {
  10. request.setAttribute(STRUTS_ACTION_MAPPING_KEY, mapping);
  11. }
  12. } catch (Exception ex) {
  13. dispatcher.sendError(request, response, servletContext, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex);
  14. }
  15. }
  16.  
  17. return mapping;
  18. }

下面是ActionMapper接口的实现类DefaultActionMapper的getMapping()方法的源代码:

  1. public ActionMapping getMapping(HttpServletRequest request, ConfigurationManager configManager) {
  2. ActionMapping mapping = new ActionMapping();
  3. //获得请求的uri,即请求路径URL中工程名以后的部分,如/userAction.action
  4. String uri = getUri(request);
  5. //修正url的带;jsessionid 时找不到的bug
  6. int indexOfSemicolon = uri.indexOf(";");
  7. uri = (indexOfSemicolon > -1) ? uri.substring(0, indexOfSemicolon) : uri;
  8. //删除扩展名,如.action或者.do
  9. uri = dropExtension(uri, mapping);
  10. if (uri == null) {
  11. return null;
  12. }
  13. //从uri中分离得到请求的action名、命名空间。
  14. parseNameAndNamespace(uri, mapping, configManager);
  15. //处理特殊的请求参数
  16. handleSpecialParameters(request, mapping);
  17. //如果允许动态方法调用,即形如/userAction!getAll.action的请求,分离action名和方法名
  18. return parseActionName(mapping);
  19. }

下面对getMapping方法中的重要部分一一讲解:

①:parseNameAndNamespace(uri, mapping, configManager)

我们主要看下第14行的parseNameAndNamespace(uri, mapping, configManager);这个方法的主要作用是分离出action名和命名空间:

  1. protected void parseNameAndNamespace(String uri, ActionMapping mapping, ConfigurationManager configManager) {
  2. String namespace, name;
  3. int lastSlash = uri.lastIndexOf("/"); //最后的斜杆的位置
  4. if (lastSlash == -1) {
  5. namespace = "";
  6. name = uri;
  7. } else if (lastSlash == 0) {
  8. // ww-1046, assume it is the root namespace, it will fallback to
  9. // default
  10. // namespace anyway if not found in root namespace.
  11. namespace = "/";
  12. name = uri.substring(lastSlash + 1);
  13. //允许采用完整的命名空间,即设置命名空间是否必须进行精确匹配
  14. } else if (alwaysSelectFullNamespace) {
  15. // Simply select the namespace as everything before the last slash
  16. namespace = uri.substring(0, lastSlash);
  17. name = uri.substring(lastSlash + 1);
  18. } else {
  19. // Try to find the namespace in those defined, defaulting to ""
  20. Configuration config = configManager.getConfiguration();
  21. String prefix = uri.substring(0, lastSlash); //临时的命名空间,将会用来进行匹配
  22. namespace = "";//将命名空间暂时设为""
  23. boolean rootAvailable = false;//rootAvailable作用是判断配置文件中是否配置了命名空间"/"
  24. // Find the longest matching namespace, defaulting to the default
  25. for (Object cfg : config.getPackageConfigs().values()) { //循环遍历配置文件中的package标签
  26. String ns = ((PackageConfig) cfg).getNamespace(); //获取每个package标签的namespace属性
  27. //进行匹配
  28. if (ns != null && prefix.startsWith(ns) && (prefix.length() == ns.length() || prefix.charAt(ns.length()) == '/')) {
  29. if (ns.length() > namespace.length()) {
  30. namespace = ns;
  31. }
  32. }
  33. if ("/".equals(ns)) {
  34. rootAvailable = true;
  35. }
  36. }
  37.  
  38. name = uri.substring(namespace.length() + 1);
  39.  
  40. // Still none found, use root namespace if found
  41. if (rootAvailable && "".equals(namespace)) {
  42. namespace = "/";
  43. }
  44. }
  45.  
  46. if (!allowSlashesInActionNames) {
  47. int pos = name.lastIndexOf('/');
  48. if (pos > -1 && pos < name.length() - 1) {
  49. name = name.substring(pos + 1);
  50. }
  51. }
  52. //将分离后的acion名和命名空间保存到mapping对象
  53. mapping.setNamespace(namespace);
  54. mapping.setName(cleanupActionName(name));
  55. }

看到上面代码的第14行,参数alwaysSelectFullNamespace我们可以通过名字就能大概猜出来"允许采用完整的命名空间",即设置命名空间是否必须进行精确匹配,true必须,false可以模糊匹配,默认是false。进行精确匹配时要求请求url中的命名空间必须与配置文件中配置的某个命名空间必须相同,如果没有找到相同的则匹配失败。这个参数可通过struts2的"struts.mapper.alwaysSelectFullNamespace"常量配置,如:

  1. <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

②:parseActionName(mapping)

我们看到18行:return parseActionName(mapping);主要是用来处理形如/userAction!getAll.action的请求,分离action名和方法名:

  1. protected ActionMapping parseActionName(ActionMapping mapping) {
  2. if (mapping.getName() == null) {
  3. return null;
  4. }
  5. //如果允许动态方法调用
  6. if (allowDynamicMethodCalls) {
  7. // handle "name!method" convention.
  8. String name = mapping.getName();
  9. int exclamation = name.lastIndexOf("!");
  10. //如果包含"!"就进行分离
  11. if (exclamation != -1) {
  12. //分离出action名
  13. mapping.setName(name.substring(0, exclamation));
  14. //分离出方法名
  15. mapping.setMethod(name.substring(exclamation + 1));
  16. }
  17. }
  18. return mapping;
  19. }

到此为止getMapping方法已经分析结束了!

(5)execute.executeAction(request, response, mapping)

上面我们分析完了mapping的获取,继续看doFilter方法:

  1. //如果mapping为空,则认为不是调用action,会调用下一个过滤器链
  2. if (mapping == null) {
  3. //执行请求css,js文件。并返回是否成功。
  4. boolean handled = execute.executeStaticResourceRequest(request, response);
  5. if (!handled) {
  6. chain.doFilter(request, response);
  7. }
  8. } else {
  9. //执行action
  10. execute.executeAction(request, response, mapping);
  11. }

如果mapping对象不为空,则会执行action

  1. public void executeAction(HttpServletRequest request, HttpServletResponse response, ActionMapping mapping) throws ServletException {
  2. dispatcher.serviceAction(request, response, servletContext, mapping);
  3. }

我们可以看到它里面只是简单的调用了dispatcher的serviceAction方法:我们找到dispatcher的serviceAction方法:

  1. public void serviceAction(HttpServletRequest request, HttpServletResponse response, ActionMapping mapping)
  2. throws ServletException {
  3. //封转上下文环境,主要将requestMap、params、session等Map封装成为一个上下文Map
  4. Map<String, Object> extraContext = createContextMap(request, response, mapping);
  5.  
  6. //如果之前没有值栈,就从ActionContext中先取出值栈,放入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. if (!nullStack) {
  40. request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, stack);
  41. }
  42. } catch (ConfigurationException e) {
  43. logConfigurationException(request, e);
  44. sendError(request, response, HttpServletResponse.SC_NOT_FOUND, e);
  45. } catch (Exception e) {
  46. if (handleException || devMode) {
  47. sendError(request, response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e);
  48. } else {
  49. throw new ServletException(e);
  50. }
  51. } finally {
  52. UtilTimerStack.pop(timerKey);
  53. }
  54. }

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. }

Dispatcher类是重要的调结者,DefaultActionInvocation类是执行action类实例的行动者。而action代理类(ActionProxy类)则是他们之间的中间人。相当于Dispatcher类通过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. }

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

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

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. }

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类:

  1. public String invoke() throws Exception {
  2. String profileKey = "invoke: ";
  3. try {
  4. UtilTimerStack.push(profileKey);
  5.  
  6. if (executed) {
  7. throw new IllegalStateException("Action has already executed");
  8. }
  9.  
  10. if (interceptors.hasNext()) {//获得一个拦截器
  11. final InterceptorMapping interceptor = interceptors.next();
  12. String interceptorMsg = "interceptor: " + interceptor.getName();
  13. UtilTimerStack.push(interceptorMsg);
  14. try {
  15. resultCode = interceptor.getInterceptor().intercept(DefaultActionInvocation.this);//执行拦截器
  16. } finally {
  17. UtilTimerStack.pop(interceptorMsg);
  18. }
  19. } else {
  20. resultCode = invokeActionOnly();
  21. }
  22.  
  23. // this is needed because the result will be executed, then control will return to the Interceptor, which will
  24. // return above and flow through again
  25. if (!executed) {
  26. if (preResultListeners != null) {
  27. LOG.trace("Executing PreResultListeners for result [{}]", result);
  28.  
  29. for (Object preResultListener : preResultListeners) {
  30. PreResultListener listener = (PreResultListener) preResultListener;
  31.  
  32. String _profileKey = "preResultListener: ";
  33. try {
  34. UtilTimerStack.push(_profileKey);
  35. listener.beforeResult(this, resultCode);
  36. }
  37. finally {
  38. UtilTimerStack.pop(_profileKey);
  39. }
  40. }
  41. }
  42.  
  43. // now execute the result, if we're supposed to
  44. if (proxy.getExecuteResult()) {
  45. executeResult();
  46. }
  47.  
  48. executed = true;
  49. }
  50.  
  51. return resultCode;
  52. }
  53. finally {
  54. UtilTimerStack.pop(profileKey);
  55. }
  56. }

上面的红色的代码是这个方法的核心点之一。让我们看一下红色代码做什么?判断interceptors是否有拦截器。如果没有就直接执行invokeActionOnly方法。即是执行action类实例对应的方法。如果有就获得拦截器并执行拦截器(执行intercept方法)。好了。关键点就在这个执行拦截器身上。即是执行intercept方法。intercept方法有一个参数就是DefaultActionInvocation类的接口。这个参数让struts2的AOP思想能够进行。为什么这样子讲呢?不清楚读者有没有想过。为什么这边判断拦截器是用if而不是用for 或是 while呢?必竟拦截器不只一个。我们都清楚AOP的目标就是让业务模块选择对应的切面。那么就有可能存在多个拦截器。这也是为什么亮点的原因了。看一下拦截器的代码就知道了。如下

LoggingInterceptor类:

  1. public String intercept(ActionInvocation invocation) throws Exception {
  2. logMessage(invocation, START_MESSAGE);
  3. String result = invocation.invoke();
  4. logMessage(invocation, FINISH_MESSAGE);
  5. return result;
  6. }

拦截器开始的时候,执行相关的拦截器逻辑,然后又重新调用DefaultActionInvocation类的invoke方法。从而获得下一个拦截器。就是这样子下一个拦截器又开始执行自己的intercept方法。做了相关的拦截器逻辑之后。又一次重新调用DefaultActionInvocation类的invoke方法。又做了相似的工作。只到没有了拦截器,执行用户action类实例的方法并返回结果。有了结果之后,就开始续继执行当前上一个拦截器的后半部分代码。直到返回到最开始的拦截器执行后半部分的代码。

Struts2 源码分析-----工作原理分析的更多相关文章

  1. Spring Cloud Netflix Eureka源码导读与原理分析

    Spring Cloud Netflix技术栈中,Eureka作为服务注册中心对整个微服务架构起着最核心的整合作用,因此对Eureka还是有很大的必要进行深入研究. 本文主要分为四部分,一是对项目构建 ...

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

    章节简言 上一章笔者讲到关于如何加载配置文件里面的package元素节点信息.相信读者到这里心里面对struts2在启动的时候加载相关的信息有了一定的了解和认识.而本章将讲到关于struts2启动成功 ...

  3. Spring Boot 揭秘与实战 源码分析 - 工作原理剖析

    文章目录 1. EnableAutoConfiguration 帮助我们做了什么 2. 配置参数类 – FreeMarkerProperties 3. 自动配置类 – FreeMarkerAutoCo ...

  4. Struts2 源码分析——过滤器(Filter)

    章节简言 上一章笔者试着建一个Hello world的例子.是一个空白的struts2例子.明白了运行struts2至少需要用到哪一些Jar包.而这一章笔者将根据前面章节(Struts2 源码分析—— ...

  5. Tomcat源码分析——请求原理分析(上)

    前言 谈起Tomcat的诞生,最早可以追溯到1995年.近20年来,Tomcat始终是使用最广泛的Web服务器,由于其使用Java语言开发,所以广为Java程序员所熟悉.很多人早期的J2EE项目,由程 ...

  6. Struts2 源码分析——DefaultActionInvocation类的执行action

    本章简言 上一章讲到关于拦截器的机制的知识点,让我们对拦截器有了一定的认识.我们也清楚的知道在执行用户action类实例之前,struts2会先去执行当前action类对应的拦截器.而关于在哪里执行a ...

  7. Struts2 源码分析——拦截器的机制

    本章简言 上一章讲到关于action代理类的工作.即是如何去找对应的action配置信息,并执行action类的实例.而这一章笔者将讲到在执行action需要用到的拦截器.为什么要讲拦截器呢?可以这样 ...

  8. Struts2 源码分析——配置管理之PackageProvider接口

    本章简言 上一章讲到关于ContainerProvider的知识.让我们知道struts2是如何注册相关的数据.也知道如何加载相关的配置信息.本章笔者将讲到如何加载配置文件里面的package元素节点 ...

  9. Struts2 源码分析——配置管理之ContainerProvider接口

    本章简言 上一章笔者讲到关于Dispatcher类的执行action功能,知道了关于执行action需要用到的信息.而本章将会讲到的内容也跟Dispatcher类有关系.那就是配置管理中的Contai ...

随机推荐

  1. php简单实现通讯录采集,我的第一个php,适合新手

    起源于要整理通讯录,原先用的是文件调查,现在学了php,就自己试一下.程序短小精悍,适于学习.有两个文件,bj.html用于显示和采集表单信息.bj.php用于处理数据和反馈结果.突出之处在于可以上传 ...

  2. plsql点击不再提示后需重新提示

    工具--首选项---用户界面---选项---DSA对话框---内容如下 ;显示含有“不再显示这个信息”的对话框;你可以删除用于你要启用的信息的行 CharSetMismatch@RIM40=1Char ...

  3. python 正则表达式(一)

    正则表达式(简称RE)本质上可以看作一个小的.高度专业化的编程语言,在Python中可以通过re模块使用它.使用正则表达式,你需要为想要匹配的字符串集合指定一套规则,字符串集合可以包含英文句子.e-m ...

  4. Java中常见的集合框架

    1. 一.collection (有序)接口的实现的接口 set  list 其中set接口的实现类是HashSet,List接口的实现类是ArrayList.LinkList.Vector 二.Ma ...

  5. Silk codec的一些资料

    Skype表示它最近将开始向第三方开发人员和硬件制造商提供免版税认证(RF)的Silk宽带音频编码器. Silk下载地址如下 http://developer.skype.com/silk/SILK_ ...

  6. [转]CSS遮罩——如何在CSS中使用遮罩

    特别声明:此篇文章由D姐根据Christian Schaefer的英文文章原名<CSS Masks – How To Use Masking In CSS Now>进行翻译,整个译文带有我 ...

  7. Docker容器里的进程为什么要前台运行

    <第一本Docker书>里面,讲到Docker容器启动web服务时,都指定了前台运行的参数,例如apache: ENTRYPOINT [ "/usr/sbin/apache2&q ...

  8. c++11中用_sntprintf代替_stprintf

    sprintf.swprintf 分别是对单字节/双字节字符格式化的,wsprintf根据预定义指示符的不同可以对单字节/双字节字符格式化. wsprintf和swprintf比较,其实这两个函数对用 ...

  9. n文件的上传和下载,struts2和springmvc

    首先,struts2的上传下载的配置 因为struts2是配置的上传的拦截器,很简单的步揍就可以上传, 首先是配置struts的action映射 <!-- 4. 修改上传文件的最大大小为30M ...

  10. 【转】 Pro Android学习笔记(六七):HTTP服务(1):HTTP GET

    目录(?)[-] HTTP GET小例子 简单小例子 出现异常NetworkOnMainThreadException 通过StrictMode进行处理 URL带键值对 Andriod应用可利用ser ...