Struts2源码解析2

Posted shiyuan310

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Struts2源码解析2相关的知识,希望对你有一定的参考价值。

看了前面一节对Struts2各个模块运行有了大概了解,也对调用的函数有了一定的了解,本节希望打断点跑一个Struts2例子!

还是放在struts2结构图:

一:项目启动后解析web.xml文件,会解析到配置的StrutsPrepareAndExecuteFilter的过滤器。

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <web-app id="WebApp_9" version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
 3          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 4          xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
 5 
 6     <display-name>Struts2Demo</display-name>
 7 
 8     <filter>
 9         <filter-name>struts2</filter-name>
10         <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
11     </filter>
12 
13     <filter-mapping>
14         <filter-name>struts2</filter-name>
15         <url-pattern>/*</url-pattern>
16     </filter-mapping>
17 
18     <welcome-file-list>
19         <welcome-file>login.jsp</welcome-file>
20     </welcome-file-list>
21 </web-app>

在此研究的是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(dispatcher);
14             execute = new ExecuteOperations(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 }

只有短短的几行代码,getInitParameterNames是这个类的核心,将Filter初始化参数名称有枚举类型转为Iterator。此类的主要作为是对filterConfig 封装。

接下来,看下StrutsPrepareAndExecuteFilter中init方法中dispatcher = init.initDispatcher(config);这是初始化dispatcher的,是通过init对象的initDispatcher方法来初始化的,init是InitOperations类的对象,我们看看InitOperations中initDispatcher方法:

1     public Dispatcher initDispatcher( HostConfig filterConfig ) {
2         Dispatcher dispatcher = createDispatcher(filterConfig);
3         dispatcher.init();
4         return dispatcher;
5     }
创建Dispatcher,会读取 filterConfig 中的配置信息,将配置信息解析出来,封装成为一个Map,然后根绝servlet上下文和参数Map构造Dispatcher :
private Dispatcher createDispatcher( HostConfig filterConfig ) {
        //存放参数的Map
        Map<String, String> params = new HashMap<String, String>();
        //将参数存放到Map,这个地方就用到前面提到FilterHostConfig的转为list功能
        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方法如下:

 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(); // [1]
11             //加载struts-default.xml,struts-plugin.xml,struts.xml
12             init_TraditionalXmlConfigurations(); // [2]
13             init_LegacyStrutsProperties(); // [3]
14             //用户自己实现的ConfigurationProviders类 
15             init_CustomConfigurationProviders(); // [5]
16             //Filter的初始化参数 
17             init_FilterInitParameters() ; // [6]
18             init_AliasStandardObjects() ; // [7]
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,……关于文件是如何加载的,大家可以自己取看源文件,主要是由xwork核心类加载的,代码在xwork-core\\src\\main\\java\\com\\opensymphony\\xwork2\\config\\providers包里面。

现在,我们回到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             if (excludedPatterns != null && prepare.isUrlExcluded(request, excludedPatterns)) {
 8                 chain.doFilter(request, response);
 9             } else {
10                 //设置编码和国际化
11                 prepare.setEncodingAndLocale(request, response);
12                 //创建action上下文
13                 prepare.createActionContext(request, response);
14                 prepare.assignDispatcherToThread();
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                     execute.executeAction(request, response, mapping);
25                 }
26             }
27         } finally {
28             prepare.cleanupRequest(request);
29         }
30     }

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

(1)prepare.setEncodingAndLocale(request, response);

  第8行是调用prepare对象的setEncodingAndLocale方法,prepare是PrepareOperations类的对象,PrepareOperations类是用来做请求准备工作的。我们看下PrepareOperations类中的setEncodingAndLocale方法:

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

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

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

我们可以看到该方法只是简单的设置了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方法:

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

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

 1 /**
 2      * Create a context map containing all the wrapped request objects
 3      *
 4      * @param request The servlet request
 5      * @param response The servlet response
 6      * @param mapping The action mapping
 7      * @return A map of context objects
 8      *
 9      * @since 2.3.17
10      */
11     public Map<String,Object> createContextMap(HttpServletRequest request, HttpServletResponse response,
12             ActionMapping mapping) {
13 
14         // request map wrapping the http request objects
15         Map requestMap = new RequestMap(request);
16 
17         // parameters map wrapping the http parameters.  ActionMapping parameters are now handled and applied separately
18         Map params = new HashMap(request.getParameterMap());
19 
20         // session map wrapping the http session
21         Map session = new SessionMap(request);
22 
23         // application map wrapping the ServletContext
24         Map application = new ApplicationMap(servletContext);
25 
26         Map<String,Object> extraContext = createContextMap(requestMap, params, session, application, request, response);
27 
28         if (mapping != null) {
29             extraContext.put(ServletActionContext.ACTION_MAPPING, mapping);
30         }
31         return extraContext;
32     }

(3)request = prepare.wrapRequest(request)

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

 1    /**
 2      * Wraps the request with the Struts wrapper that handles multipart requests better
 3      * @return The new request, if there is one
 4      * @throws ServletException
 5      */
 6     public HttpServletRequest wrapRequest(HttpServletRequest oldRequest) throws ServletException {
 7         HttpServletRequest request = oldRequest;
 8         try {
 9             // Wrap request first, just in case it is multipart/form-data
10             // parameters might not be accessible through before encoding (ww-1278)
11             request = dispatcher.wrapRequest(request);
12             ServletActionContext.setRequest(request);
13         } catch (IOException e) {
14             throw new ServletException("Could not wrap servlet request with MultipartRequestWrapper!", e);
15         }
16         return request;
17     }

由第11行我们可以看到它里面调用的是dispatcher的wrapRequest方法,我们看下dispatcher的wrapRequest:

 1 public HttpServletRequest wrapRequest(HttpServletRequest request) 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();
//如果content_type是multipart/form-data类型,则将request包装成MultiPartRequestWrapper对象,否则包装成StrutsRequestWrapper对象
8 if (content_type != null && content_type.contains("multipart/form-data")) { 9 MultiPartRequest mpr = getMultiPartRequest(); 10 LocaleProvider provider = getContainer().getInstance(LocaleProvider.class); 11 request = new MultiPartRequestWrapper(mpr, request, getSaveDir(), provider, disableRequestAttributeValueStackLookup); 12 } else { 13 request = new StrutsRequestWrapper(request, disableRequestAttributeValueStackLookup); 14 } 15 16 return request; 17 }

此次包装根据请求内容的类型不同,返回不同的对象,如果为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                 if (dispatcher.isHandleException() || dispatcher.isDevMode()) {
14                     dispatcher.sendError(request, response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex);
15                 }
16             }
17         }
18 
19         return mapping;
20     }

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

 1     public ActionMapping getMapping(HttpServletRequest request, ConfigurationManager configManager) {
 2         ActionMapping mapping = new ActionMapping();
 3         //获得请求的uri,即请求路径URL中工程名以后的部分,如/userAction.action
 4         String uri = RequestUtils.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 = "";//将命名空间暂时设为""
使用eclipse查看源码的方法

struts2远程代码执行漏洞汇总整理

初识Spring源码 -- doResolveDependency | findAutowireCandidates | @Order@Priority调用排序 | @Autowired注入(代码片段

初识Spring源码 -- doResolveDependency | findAutowireCandidates | @Order@Priority调用排序 | @Autowired注入(代码片段

[原创]java WEB学习笔记61:Struts2学习之路--通用标签 property,uri,param,set,push,if-else,itertor,sort,date,a标签等(代码片段

Struts2 <s:doubleselect;级联下拉框 详解析