最近抽些时间研究了一下struts2源码,总结了一下前辈的研究成果,形成自己的一点心得吧。
框架整合WEB的入口位于web.xml文件,只有配置在web.xml文件中Servlet才会被应用加载。Struts2推荐的入口方法是StrutsPrepareAndExecuteFilter其在工程中作为一个Filter配置在web.xml中,配置如下:
<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>
/** * * Struts 2框架的处理过程: * 1. 客户端初始化一个指向Servlet容器(如Tomcat)的请求。 * 2. 这个请求经过一系列过滤器(如ActionContextCleanUp、SiteMesh等)。 * 3. StrutsPrepareAndExecuteFilter/FilterDispatcher被调用,FilterDispatcher询问ActionMapper来决定这个请求是否需要调用某个Action。 * 4. 如果ActionMapper决定需要调用某个Action,FilterDispatcher把请求交给ActionProxy。 * 这儿已经转到它的Delegate--Dispatcher来执行 * 5. ActionProxy通过Configuration Manager询问Struts配置文件,找到需要调用的Action类。 * 6. ActionProxy创建一个ActionInvocation实例。 * 7. ActionInvocation实例使用命名的模式来调用,回调Action的execute方法。 当然这涉及到相关拦截器的调用 * 8. 一旦Action执行完毕,ActionInvocation负责根据Struts.xml的配置返回结果 * 当然如果要在返回之前做些什么,可以实现PreResultListener。添加PreResultListener可以在Interceptor中实现 * * @author Josn * */
StrutsPrepareAndExecuteFilter中主要组成有:
属性:
protected PrepareOperations prepare; protected ExecuteOperations execute; protected List<Pattern> excludedPatterns=null;
方法:
init(FilterConfig filterConfig)
继承自Filter,初始化参数
doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
继承自Filter,执行方法
postInit(Dispatcher dispatcher, FilterConfig filterConfig)
一个空的方法,用于方法回调初始化
destroy()
继承自Filter,用于资源释放
具体实现及其代码描述如下:
/* 继承自Filter,初始化参数
* @see javax.servlet.Filter#init(javax.servlet.FilterConfig) */ public void init(FilterConfig filterConfig) throws ServletException { InitOperations init=new InitOperations();//初始化辅助对象,封装了初始化的一些操作 try { System.out.println(this.getClass().getName()+":"+filterConfig.getFilterName()); Enumeration e=filterConfig.getInitParameterNames(); while(e.hasMoreElements()){ System.out.println(e.nextElement().toString()); } //对filterConfig进行封装 其中有个主要方法getInitParameterNames将参数名字以String格式存储在List中 FilterHostConfig config=new FilterHostConfig(filterConfig); //通过config, 初始化struts内部日志 Iterator<String> cs=config.getInitParameterNames(); init.initLogging(config); Dispatcher dispatcher=init.initDispatcher(config); //通过config,创建并初始化dispatcher init.initStaticContentLoader(config, dispatcher);//通过config和dispatcher,初始化与过滤器相关的静态内容加载器 prepare=new PrepareOperations(filterConfig.getServletContext(),dispatcher); //通过config和dispatcher,创建request被处理前的系列操作对象 execute=new ExecuteOperations(filterConfig.getServletContext(),dispatcher);//通过config和dispatcher,创建处理request的系列操作对象 this.excludedPatterns=init.buildExcludedPatternsList(dispatcher);//构建Struts2不处理的URL列表 //回调空的postInit方法 postInit(dispatcher,filterConfig); } finally { init.clearup();//清空ActionContext } } /* * 继承自Filter,用于资源释放 */ public void destroy() { prepare.cleanupDispatcher(); } /* 继承自Filter,执行方法 * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain) */ public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { //父类向子类转:强转为http请求、响应 HttpServletRequest request=(HttpServletRequest)req; HttpServletResponse response=(HttpServletResponse) resp; try{ //setEncodingAndLocale调用了dispatcher方法的prepare方法: prepare.setEncodingAndLocale(request, response); //设置request的编码和LOCAL //创建Action上下文 prepare.createActionContext(request, response);//创建ACTIONCONTEXT,并初始化Theadlocal prepare.assignDispatcherToThread(); //指派dispatcher给Theadlocal把dispatcher绑定到当前线程上,这样就可以通过 Dispatcher.getInstance()来获得当前线程的dispatcher,这样dispatcher就可以被所有请求共享 // 以上操作完成后就根据excludedPatterns来判断这个请求是否应该交给当前这个filter进行处理,如果不需要就调 //用chain.doFilter(request, response);交给其他filter进行处理,如果需要,那么PrepareOperations就执行 if ( excludedPatterns != null && prepare.isUrlExcluded(request, excludedPatterns)) { chain.doFilter(request, response); } else { request = prepare.wrapRequest(request);//对request进行包装,包装成StrutsRequestWrapper或者 MultiPartRequestWrapper(请求类型是multipart/form-data的情况) ActionMapping mapping = prepare.findActionMapping(request, response, true);//查找并选择创建ActionMapping根据request的请求找 //到对应的ActionMapping,如果返回的是null,那就当做静态资源进行处理boolean handled = //execute.executeStaticResourceRequest(request, response);处理失败就交给其他的filter,如果返回的不是 //null,那么就调用execute.executeAction(request, response, mapping);处理完成之后 //prepare.cleanupRequest(request);会把当前线程的ActionContext和Dispatcher设置为null if (mapping == null) {//如果映射不存在 boolean handled=execute.executeStaticResourceRequest(request, response); if(!handled){ chain.doFilter(request, response); } }else{ //如果存在映射 execute.executeAction(request, response, mapping);//执行action } } }finally{ prepare.cleanupRequest(request);//清除request的Threadlocal } } /** * Callback for post initialization(一个空的方法,用于方法回调初始化) * @param dispatcher * @param filterConfig */ protected void postInit(Dispatcher dispatcher,FilterConfig filterConfig){ }
ActionContext保存了当前请求的上下文信息,当接到一个request以后,经过一些准备工作,都是再去调用Dispatcher.serviceAction(HttpServletRequest, HttpServletResponse, ServletContext, ActionMapping)方法处理请求,也就是这一步,让后续的Action能够和Servlet体系解耦而单独存在。StrutsPrepareAndExecuteFilter里面,先准备好上下文需要的东西,接着就会 构造出一个ActionMapping对象,这对象很简单(跟xwork.xml/struts.xml里的一个节点相对应的)。构造完,就扔给Dispatcher的serviceAction方法了。Dispatcher根据拿到的actionMapping对 象,创建ActionProxy对象(内部包含了目标Action),然后开始执行。ActionProxy的默认实现DefaultActionProxy里有一个叫ActionInvocation的对象,采用Command模式实现调用,命令: Action 调用者:ActionInvocation。
init是Filter第一个运行的方法,我们看下struts2的核心Filter在调用init方法初始化时做哪些工作:
在init中带哦用了方法init.initDispatcher(config);这个方法里面有两个动作:
createDispatcher(filterConfig);
dispatcher.init();
来看这两个方法体的实现:
/**
* 创建Dispatcher,会读取 filterConfig 中的配置信息,将配置信息解析出来,封装成为一个Map, * 然后根绝servlet上下文和参数Map构造Dispatcher * @param filterConfig * @return */ private Dispatcher createDispatcher(HostConfig filterConfig){ Map<String,String> params=new HashMap<String, String>(); Iterator e=filterConfig.getInitParameterNames(); while(e.hasNext()){ String key=(String)e.next(); String value=filterConfig.getInitParameter(key); params.put(key, value); } return new Dispatcher(filterConfig.getServletContext(),params); } public void init(){ if(configurationManager == null){ configurationManager=new ConfigurationManager(); } try { init_DefaultProperties();// [1]读取properties信息,默认的default.properties struts.properties/xwork.properties init_TraditionalXmlConfigurations();//[2]读取xml配置文件,默认的struts-default.xml,struts-plugin.xml,struts.xml ,xwork.xml文件(我们配置的具体的action的文件 init_LegacyStrutsProperties(); // [3]读取用户自定义的struts.properties国际化相关的配置 init_CustomConfigurationProviders(); // [5]自定义的configProviders init_FilterInitParameters() ; // [6]载入FilterDispatcher传进来的initParams 整了一些Servlet相关的对象 init_AliasStandardObjects() ; // [7]将配置文件中的bean与具体的类映射 结合第1步读到的东西,注册StrutsConstants里提到的种种配置 Container container=init_PreloadConfiguration();//构建一个用于依赖注射的Container对象 //在这里面会循环调用上面七个ConfigurationProvider的register方法 //其中的重点就是DefaultConfiguration的#reload()方法 container.inject(this); init_CheckConfigurationReloading(container); 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); } } /** * 这个方法中是将一个DefaultPropertiesProvider对象追加到ConfigurationManager对象内部的ConfigurationProvider队列中。 * DefaultPropertiesProvider的#register()方法可以载入org/apache/struts2/default.properties中定义的属性。 */ private void init_DefaultProperties(){ configurationManager.addConfigurationProvider(new DefaultPropertiesProvider()); } /** * 这里负责载入的是FilterDispatcher的配置中所定义的config属性。 * 如果用户没有定义config属性,struts默认会载入DEFAULT_CONFIGURATION_PATHS这个值所代表的xml文件。 * 它的值为"struts-default.xml,struts-plugin.xml,struts.xml"。也就是说框架默认会载入这三个项目xml文件。 * 下一步框架会逐个判断每个config属性中定义的文件。 * 如果文件名为"xwork.xml",框架会用XmlConfigurationProvider类去处理, * 反之则用StrutsXmlConfigurationProvider类去处理。 */ private void init_TraditionalXmlConfigurations(){ String configPaths=initParams.get("config"); if(configPaths==null){ configPaths=DEFAULT_CONFIGURATION_PATHS; } String[] files=configPaths.split("\\s*[,]\\s*"); for(String file:files){ if(file.endsWith(".xml")){ if("xwork.xml".equals(file)){ configurationManager.addConfigurationProvider(createXmlConfigurationProvider(file,false)); }else{ configurationManager.addConfigurationProvider(createStrutsXmlConfigurationProvider(file, false, servletContext)); } }else { throw new IllegalArgumentException("Invalid configuration file name"); } } } protected XmlConfigurationProvider createXmlConfigurationProvider(String filename, boolean errorIfMissing) { return new XmlConfigurationProvider(filename, errorIfMissing); } protected XmlConfigurationProvider createStrutsXmlConfigurationProvider(String filename, boolean errorIfMissing, ServletContext ctx) { return new StrutsXmlConfigurationProvider(filename, errorIfMissing, ctx); } /** * 向ConfigurationManager加入了一个LegacyPropertiesConfigurationProvider。 */ private void init_LegacyStrutsProperties(){ configurationManager.addConfigurationProvider(new LegacyPropertiesConfigurationProvider()); } /** * 此方法处理的是FilterDispatcher的配置中所定义的configProviders属性。 * 负责载入用户自定义的ConfigurationProvider。 */ private void init_CustomConfigurationProviders(){ String configProvs = initParams.get("configProviders"); if (configProvs != null) { String[] classes = configProvs.split("\\s*[,]\\s*"); for (String cname : classes) { try { Class cls = ClassLoaderUtils.loadClass(cname, this.getClass()); ConfigurationProvider prov = (ConfigurationProvider)cls.newInstance(); configurationManager.addConfigurationProvider(prov); } catch (InstantiationException e) { throw new ConfigurationException("Unable to instantiate provider: "+cname, e); } catch (IllegalAccessException e) { throw new ConfigurationException("Unable to access provider: "+cname, e); } catch (ClassNotFoundException e) { throw new ConfigurationException("Unable to locate provider class: "+cname, e); } } } } /** * 此方法用来处理FilterDispatcher的配置中所定义的所有属性。 */ @SuppressWarnings("deprecation") private void init_FilterInitParameters(){ configurationManager.addConfigurationProvider(new ConfigurationProvider() { public void destroy() {} public void init(Configuration configuration) throws ConfigurationException {} public void loadPackages() throws ConfigurationException {} public boolean needsReload() { return false; } public void register(ContainerBuilder builder, LocatableProperties props) throws ConfigurationException { props.putAll(initParams); } }); } private void init_AliasStandardObjects(){ configurationManager.addConfigurationProvider(new BeanSelectionProvider()); } /** * 执行完七个init_*方法后,Dispatcher的#init()会接着调用#init_PreloadConfiguration(), * 构建一个用于依赖注射的Container对象。 * 此方法首先获取到ConfigurationManager中的Configuration对象, * 在#getConfiguration()内部,调用上边6步添加到ConfigurationManager的ConfigurationProviders的#register()方法。 */ 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); return container; }