本文來自:曹勝歡博客專欄。轉載請註明出處:http://blog.csdn.net/csh624366188
前面博客我們介紹了開發struts2應用程序的基本流程(細談struts2之開發第一個struts2的實例),通過前面我們知道了struts2實現請求轉發和配置文件加載都是攔截器進行的操作,這也就是爲什麼我們要在web.xml配置struts2的攔截器的原因了。我們知道,在開發struts2應用開發的時候我們要在web.xml進行配置攔截器org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter(在一些老版的一般配置org.apache.struts2.dispatcher.FilterDispatcher),不知道大家剛開始學的時候有沒有這個疑問,爲什麼通過這個攔截器我們就可以攔截到我們提交的請求,並且一些配置文件就可以得到加載呢?不管你有沒有,反正我是有。我想這個問題的答案,我們是非常有必要去看一下這個攔截器的源碼去找。
打開StrutsPrepareAndExecuteFilter攔截器源碼我們可以看出以下類的信息
屬性摘要:
Protected List<Pattern> excludedPatterns
protected ExecuteOperations execute
protected PrepareOperations prepare
我們可以看出StrutsPrepareAndExecuteFilter與普通的Filter並無區別,方法除繼承自Filter外,僅有一個回調方法,第三部分我們將按照Filter方法調用順序,init—>doFilter—>destroy順序地分析源碼。
提供的方法:
destroy() |
繼承自Filter,用於資源釋放 |
doFilter(ServletRequest req, ServletResponse res, FilterChain chain) |
繼承自Filter,執行方法 |
init(FilterConfig filterConfig) |
繼承自Filter,初始化參數 |
postInit(Dispatcher dispatcher, FilterConfig filterConfig) |
Callback for post initialization(一個空的方法,用於方法回調初始化) |
下面我們一一對這些方法看一下:
1.init方法:我們先整體看一下這個方法:
- public void init(FilterConfig filterConfig) throws ServletException {
- InitOperations init = new InitOperations();
- try {
- //封裝filterConfig,其中有個主要方法getInitParameterNames將參數名字以String格式存儲在List中
- FilterHostConfig config = new FilterHostConfig(filterConfig);
- // 初始化struts內部日誌
- init.initLogging(config);
- //創建dispatcher ,並初始化,這部分下面我們重點分析,初始化時加載那些資源
- 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 {
- init.cleanup();
- }
- }
首先開一下FilterHostConfig 這個封裝configfilter的類: 這個類總共不超過二三十行代碼getInitParameterNames是這個類的核心,將Filter初始化參數名稱有枚舉類型轉爲Iterator。此類的主要作爲是對filterConfig 封裝。具體代碼如下:
- public Iterator<String> getInitParameterNames() {
- return MakeIterator.convert(config.getInitParameterNames());
- }
下面咱接着一塊看Dispatcher dispatcher = init.initDispatcher(config);這是重點,創建並初始化Dispatcher ,看一下具體代碼:
- public Dispatcher initDispatcher( HostConfig filterConfig ) {
- Dispatcher dispatcher = createDispatcher(filterConfig);
- dispatcher.init();
- return dispatcher;
- }
- span style="FONT-SIZE: 18px"><span style="color:#000000;BACKGROUND: rgb(255,255,255)"> </span><span style="color:#000000;BACKGROUND: rgb(255,255,255)"><span style="color:#cc0000;"><strong>創建<span style="font-family:Verdana;">Dispatcher</span><span style="font-family:宋體;">,會讀取 </span><span style="font-family:Verdana;">filterConfig </span><span style="font-family:宋體;">中的配置信息,將配置信息解析出來,封裝成爲一個</span><span style="font-family:Verdana;">Map</span></strong></span><span style="font-family:宋體;">,然後</span></span><span style="color:#000000;BACKGROUND: rgb(255,255,255)">根據</span><span style="color:#000000;BACKGROUND: rgb(255,255,255)">servlet<span style="font-family:宋體;">上下文和參數</span><span style="font-family:Verdana;">Map</span><span style="font-family:宋體;">構造</span><span style="font-family:Verdana;">Dispatcher </span><span style="font-family:宋體;">:</span></span></span>
- private Dispatcher createDispatcher( HostConfig filterConfig ) {
- Map<String, String> params = new HashMap<String, String>();
- for ( Iterator e = filterConfig.getInitParameterNames(); e.hasNext(); ) {
- String name = (String) e.next();
- String value = filterConfig.getInitParameter(name);
- params.put(name, value);
- }
- return new Dispatcher(filterConfig.getServletContext(), params);
- }
Dispatcher構造玩以後,開始對他進行初始化,加載struts2的相關配置文件,將按照順序逐一加載:default.properties,struts-default.xml,struts-plugin.xml,struts.xml,……我們一起看看他是怎麼一步步的加載這些文件的 dispatcher的init()方法:
- public void init() {
- if (configurationManager == null) {
- configurationManager = createConfigurationManager(BeanSelectionProvider.DEFAULT_BEAN_NAME);
- }
- try {
- init_DefaultProperties(); // [1]
- init_TraditionalXmlConfigurations(); // [2]
- init_LegacyStrutsProperties(); // [3]
- init_CustomConfigurationProviders(); // [5]
- init_FilterInitParameters() ; // [6]
- init_AliasStandardObjects() ; // [7]
- Container container = init_PreloadConfiguration();
- container.inject(this);
- init_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);
- }
- }
下面我們一起來看一下【1】,【2】,【3】,【5】,【6】的源碼,看一下什麼都一目瞭然了:
1.這個方法中是將一個DefaultPropertiesProvider對象追加到ConfigurationManager對象內部的ConfigurationProvider隊列中。 DefaultPropertiesProvider的register()方法可以載入org/apache/struts2/default.properties中定義的屬性。
- try {
- defaultSettings = new PropertiesSettings("org/apache/struts2/default");
- } catch (Exception e) {
- throw new ConfigurationException("Could not find or error in org/apache/struts2/default.properties", e);
- }
2. 調用init_TraditionalXmlConfigurations()方法,實現載入FilterDispatcher的配置中所定義的config屬性。 如果用戶沒有定義config屬性,struts默認會載入DEFAULT_CONFIGURATION_PATHS這個值所代表的xml文件。它的值爲"struts-default.xml,struts-plugin.xml,struts.xml"。也就是說框架默認會載入這三個項目xml文件。如果文件類型是XML格式,則按照xwork-x.x.dtd模板進行讀取。如果,是Struts的配置文件,則按struts-2.X.dtd模板進行讀取。
private static final String DEFAULT_CONFIGURATION_PATHS = "struts-default.xml,struts-plugin.xml,struts.xml";
3.創建一個LegacyPropertiesConfigurationProvider類,並將它追加到ConfigurationManager對象內部的ConfigurationProvider隊列中。LegacyPropertiesConfigurationProvider類載入struts.properties中的配置,這個文件中的配置可以覆蓋default.properties中的。其子類是DefaultPropertiesProvider類
5.init_CustomConfigurationProviders()此方法處理的是FilterDispatcher的配置中所定義的configProviders屬性。負責載入用戶自定義的ConfigurationProvider。
- 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);
- }
- }
- }
6.init_FilterInitParameters()此方法用來處理FilterDispatcher的配置中所定義的所有屬性
7. init_AliasStandardObjects(),將一個BeanSelectionProvider類追加到ConfigurationManager對象內部的ConfigurationProvider隊列中。BeanSelectionProvider類主要實現加載org/apache/struts2/struts-messages。
- private void init_AliasStandardObjects() {
- configurationManager.addConfigurationProvider(
- new BeanSelectionProvider());
- }
相信看到這大家應該明白了,struts2的一些配置的加載順序和加載時所做的工作,其實有些地方我也不是理解的很清楚。其他具體的就不在說了,init方法佔時先介紹到這
2、doFilter方法
doFilter是過濾器的執行方法,它攔截提交的HttpServletRequest請求,HttpServletResponse響應,作爲strtus2的核心攔截器,在doFilter裏面到底做了哪些工作,我們將逐行解讀其源碼,大體源碼如下:
- public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
- //父類向子類轉:強轉爲http請求、響應
- 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);
- if (mapping == null) {
- boolean handled = execute.executeStaticResourceRequest(request, response);
- if (!handled) {
- chain.doFilter(request, response);
- }
- } else {
- execute.executeAction(request, response, mapping);
- }
- }
- } finally {
- prepare.cleanupRequest(request);
- }
- }
下面我們就逐句的來的看一下:設置字符編碼和國際化很簡單prepare調用了setEncodingAndLocale,然後調用了dispatcher方法的prepare方法:
- /**
- * Sets the request encoding and locale on the response
- */
- public void setEncodingAndLocale(HttpServletRequest request, HttpServletResponse response) {
- dispatcher.prepare(request, response);
- }
看下prepare方法,這個方法很簡單只是設置了encoding 、locale ,做的只是一些輔助的工作:
- public void prepare(HttpServletRequest request, HttpServletResponse response) {
- String encoding = null;
- if (defaultEncoding != null) {
- encoding = defaultEncoding;
- }
- Locale locale = null;
- if (defaultLocale != null) {
- locale = LocalizedTextUtil.localeFromString(defaultLocale, request.getLocale());
- }
- if (encoding != null) {
- try {
- request.setCharacterEncoding(encoding);
- } catch (Exception e) {
- LOG.error("Error setting character encoding to '" + encoding + "' - ignoring.", e);
- }
- }
- if (locale != null) {
- response.setLocale(locale);
- }
- if (paramsWorkaroundEnabled) {
- request.getParameter("foo"); // simply read any parameter (existing or not) to "prime" the request
- }
- }
下面咱重點看一下創建Action上下文重點
Action上下文創建(重點)
ActionContext是一個容器,這個容易主要存儲request、session、application、parameters等相關信息.ActionContext是一個線程的本地變量,這意味着不同的action之間不會共享ActionContext,所以也不用考慮線程安全問題。其實質是一個Map,key是標示request、session、……的字符串,值是其對應的對象:
static ThreadLocal actionContext = new ThreadLocal();
Map<String, Object> context;
下面我們看起來下創建action上下文的源碼:
- /**
- *創建Action上下文,初始化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;
- }
一句句來看:
ValueStackstack= dispatcher.getContainer().getInstance(ValueStackFactory.class).createValueStack();
dispatcher.getContainer().getInstance(ValueStackFactory.class)根據字面估計一下就是創建ValueStackFactory的實例。這個地方我也只是根據字面來理解的。ValueStackFactory是接口,其默認實現是OgnlValueStackFactory,調用OgnlValueStackFactory的createValueStack():
下面看一下OgnlValueStack的構造方法
- protected OgnlValueStack(XWorkConverter xworkConverter, CompoundRootAccessor accessor, TextProvider prov, boolean allowStaticAccess) {
- //new一個CompoundRoot出來
- setRoot(xworkConverter, accessor, new CompoundRoot(), allowStaticAccess);
- push(prov);
- }
接下來看一下setRoot方法:
- protected void setRoot(XWorkConverter xworkConverter, CompoundRootAccessor accessor, CompoundRoot compoundRoot,
- boolean allowStaticMethodAccess) {
- //OgnlValueStack.root = compoundRoot;
- this.root = compoundRoot;
- 1 //方法/屬性訪問策略。
- this.securityMemberAccess = new SecurityMemberAccess(allowStaticMethodAccess);
- //創建context了,創建context使用的是ongl的默認方式。
- //Ognl.createDefaultContext返回一個OgnlContext類型的實例
- //這個OgnlContext裏面,root是OgnlValueStack中的compoundRoot,map是OgnlContext自己創建的private Map _values = new HashMap(23);
- this.context = Ognl.createDefaultContext(this.root, accessor, new OgnlTypeConverterWrapper(xworkConverter), securityMemberAccess);
- //不是太理解,猜測如下:
- //context是剛剛創建的OgnlContext,其中的HashMap類型_values加入如下k-v:
- //key:com.opensymphony.xwork2.util.ValueStack.ValueStack
- //value:this,這個應該是當前的OgnlValueStack實例。
- //剛剛用斷點跟了一下,_values裏面是:
- //com.opensymphony.xwork2.ActionContext.container=com.opensymphony.xwork2.inject.ContainerImpl@96231e
- //com.opensymphony.xwork2.util.ValueStack.ValueStack=com.opensymphony.xwork2.ognl.OgnlValueStack@4d912
- context.put(VALUE_STACK, this);
- //此時:OgnlValueStack中的compoundRoot是空的;
- //context是一個OgnlContext,其中的_root指向OgnlValueStack中的root,_values裏面的東西,如剛纔所述。
- //OgnlContext中的額外設置。
- Ognl.setClassResolver(context, accessor);
- ((OgnlContext) context).setTraceEvaluations(false);
- ((OgnlContext) context).setKeepLastEvaluation(false);
- }
上面代碼中dispatcher.createContextMap,如何封裝相關參數:,我們以RequestMap爲例,其他的原理都一樣:主要方法實現:
- //map的get實現
- public Object get(Object key) {
- return request.getAttribute(key.toString());
- }
- //map的put實現
- public Object put(Object key, Object value) {
- Object oldValue = get(key);
- entries = null;
- request.setAttribute(key.toString(), value);
- return oldValue;
- }
到此,幾乎StrutsPrepareAndExecuteFilter大部分的源碼都涉及到了。自己感覺都好亂,所以還請大家見諒,能力有限,希望大家可以共同學習