1. Struts2架構圖
請求首先通過Filter chain,Filter主要包括ActionContextCleanUp,它主要清理當前線程的ActionContext和Dispatcher;FilterDispatcher主要通過AcionMapper來決定需要調用哪個Action。
ActionMapper取得了ActionMapping後,在Dispatcher的serviceAction方法裏創建ActionProxy,ActionProxy創建ActionInvocation,然後ActionInvocation調用Interceptors,執行Action本身,創建Result並返回,當然,如果要在返回之前做些什麼,可以實現PreResultListener。
2. Struts2部分類介紹
這部分從Struts2參考文檔中翻譯就可以了。
ActionMapper
ActionMapper其實是HttpServletRequest和Action調用請求的一個映射,它屏蔽了Action對於Request等java Servlet類的依賴。Struts2中它的默認實現類是DefaultActionMapper,ActionMapper很大的用處可以根據自己的需要來設計url格式,它自己也有Restful的實現,具體可以參考文檔的docs\actionmapper.html。
ActionProxy&ActionInvocation
Action的一個代理,由ActionProxyFactory創建,它本身不包括Action實例,默認實現DefaultActionProxy是由ActionInvocation持有Action實例。ActionProxy作用是如何取得Action,無論是本地還是遠程。而ActionInvocation的作用是如何執行Action,攔截器的功能就是在ActionInvocation中實現的。
ConfigurationProvider&Configuration
ConfigurationProvider就是Struts2中配置文件的解析器,Struts2中的配置文件主要是尤其實現類XmlConfigurationProvider及其子類StrutsXmlConfigurationProvider來解析。
3. Struts2請求流程
1、客戶端發送請求
2、請求先通過ActionContextCleanUp-->FilterDispatcher
3、FilterDispatcher通過ActionMapper來決定這個Request需要調用哪個Action
4、如果ActionMapper決定調用某個Action,FilterDispatcher把請求的處理交給ActionProxy,這兒已經轉到它的Delegate--Dispatcher來執行
5、ActionProxy根據ActionMapping和ConfigurationManager找到需要調用的Action類
6、ActionProxy創建一個ActionInvocation的實例
7、ActionInvocation調用真正的Action,當然這涉及到相關攔截器的調用
8、Action執行完畢,ActionInvocation創建Result並返回,當然,如果要在返回之前做些什麼,可以實現PreResultListener。添加PreResultListener可以在Interceptor中實現,不知道其它還有什麼方式?
4. Struts2(2.1.2)部分源碼閱讀
從org.apache.struts2.dispatcher.FilterDispatcher開始
- //創建Dispatcher,此類是一個Delegate,它是真正完成根據url解析,讀取對應Action的地方
- public void init(FilterConfig filterConfig) throws ServletException {
- try {
- this.filterConfig = filterConfig;
- initLogging();
- dispatcher = createDispatcher(filterConfig);
- dispatcher.init();
- dispatcher.getContainer().inject(this);
- //讀取初始參數pakages,調用parse(),解析成類似/org/apache/struts2/static,/template的數組
- String param = filterConfig.getInitParameter("packages");
- String packages = "org.apache.struts2.static template org.apache.struts2.interceptor.debugging";
- if (param != null) {
- packages = param + " " + packages;
- }
- this.pathPrefixes = parse(packages);
- } finally {
- ActionContext.setContext(null);
- }
- }
順着流程我們看Disptcher的init方法。init方法裏就是初始讀取一些配置文件等,先看init_DefaultProperties,主要是讀取properties配置文件。
- private void init_DefaultProperties() {
- configurationManager.addConfigurationProvider(new DefaultPropertiesProvider());
- }
打開DefaultPropertiesProvider
- public void register(ContainerBuilder builder, LocatableProperties props)
- throws ConfigurationException {
- Settings defaultSettings = null;
- 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);
- }
- loadSettings(props, defaultSettings);
- }
- //PropertiesSettings
- //讀取org/apache/struts2/default.properties的配置信息,如果項目中需要覆蓋,可以在classpath裏的struts.properties裏覆寫
- public PropertiesSettings(String name) {
- URL settingsUrl = ClassLoaderUtils.getResource(name + ".properties", getClass());
- if (settingsUrl == null) {
- LOG.debug(name + ".properties missing");
- settings = new LocatableProperties();
- return;
- }
- settings = new LocatableProperties(new LocationImpl(null, settingsUrl.toString()));
- // Load settings
- InputStream in = null;
- try {
- in = settingsUrl.openStream();
- settings.load(in);
- } catch (IOException e) {
- throw new StrutsException("Could not load " + name + ".properties:" + e, e);
- } finally {
- if(in != null) {
- try {
- in.close();
- } catch(IOException io) {
- LOG.warn("Unable to close input stream", io);
- }
- }
- }
- }
再來看init_TraditionalXmlConfigurations方法,這個是讀取struts-default.xml和Struts.xml的方法。
- private void init_TraditionalXmlConfigurations() {
- //首先讀取web.xml中的config初始參數值
- //如果沒有配置就使用默認的"struts-default.xml,struts-plugin.xml,struts.xml",
- //這兒就可以看出爲什麼默認的配置文件必須取名爲這三個名稱了
- //如果不想使用默認的名稱,直接在web.xml中配置config初始參數即可
- String configPaths = initParams.get("config");
- if (configPaths == null) {
- configPaths = DEFAULT_CONFIGURATION_PATHS;
- }
- String[] files = configPaths.split("\\s*[,]\\s*");
- //依次解析配置文件,xwork.xml單獨解析
- for (String file : files) {
- if (file.endsWith(".xml")) {
- if ("xwork.xml".equals(file)) {
- configurationManager.addConfigurationProvider(new XmlConfigurationProvider(file, false));
- } else {
- configurationManager.addConfigurationProvider(new StrutsXmlConfigurationProvider(file, false, servletContext));
- }
- } else {
- throw new IllegalArgumentException("Invalid configuration file name");
- }
- }
- }
對於其它配置文件只用StrutsXmlConfigurationProvider,此類繼承XmlConfigurationProvider,而XmlConfigurationProvider又實現ConfigurationProvider接口。類XmlConfigurationProvider負責配置文件的讀取和解析,addAction()方法負責讀取<action>標籤,並將數據保存在ActionConfig中;addResultTypes()方法負責將<result-type>標籤轉化爲ResultTypeConfig對象;loadInterceptors()方法負責將<interceptor>標籤轉化爲InterceptorConfi對象;loadInterceptorStack()方法負責將<interceptor-ref>標籤轉化爲InterceptorStackConfig對象;loadInterceptorStacks()方法負責將<interceptor-stack>標籤轉化成InterceptorStackConfig對象。而上面的方法最終會被addPackage()方法調用,將所讀取到的數據彙集到PackageConfig對象中。來看XmlConfigurationProvider的源代碼,詳細的我自己也就大體瀏覽了一下,各位可以自己研讀。
- protected PackageConfig addPackage(Element packageElement) throws ConfigurationException {
- PackageConfig.Builder newPackage = buildPackageContext(packageElement);
- if (newPackage.isNeedsRefresh()) {
- return newPackage.build();
- }
- .
- addResultTypes(newPackage, packageElement);
- loadInterceptors(newPackage, packageElement);
- loadDefaultInterceptorRef(newPackage, packageElement);
- loadDefaultCla***ef(newPackage, packageElement);
- loadGlobalResults(newPackage, packageElement);
- loadGobalExceptionMappings(newPackage, packageElement);
- NodeList actionList = packageElement.getElementsByTagName("action");
- for (int i = 0; i < actionList.getLength(); i++) {
- Element actionElement = (Element) actionList.item(i);
- addAction(actionElement, newPackage);
- }
- loadDefaultActionRef(newPackage, packageElement);
- PackageConfig cfg = newPackage.build();
- configuration.addPackageConfig(cfg.getName(), cfg);
- return cfg;
- }
這兒發現一個配置上的小技巧,我的xwork2.0.*是沒有的,但是看源碼是看到xwork2.1.*是可以的。繼續看XmlConfigurationProvider的源代碼:
- private List loadConfigurationFiles(String fileName, Element includeElement) {
- List<Document> docs = new ArrayList<Document>();
- if (!includedFileNames.contains(fileName)) {
- Element rootElement = doc.getDocumentElement();
- NodeList children = rootElement.getChildNodes();
- int childSize = children.getLength();
- for (int i = 0; i < childSize; i++) {
- Node childNode = children.item(i);
- if (childNode instanceof Element) {
- Element child = (Element) childNode;
- final String nodeName = child.getNodeName();
- //解析每個action配置是,對於include文件可以使用通配符*來進行配置
- //如Struts.xml中可配置成<include file="actions_*.xml"/>
- if (nodeName.equals("include")) {
- String includeFileName = child.getAttribute("file");
- if(includeFileName.indexOf('*') != -1 ) {
- ClassPathFinder wildcardFinder = new ClassPathFinder();
- wildcardFinder.setPattern(includeFileName);
- Vector<String> wildcardMatches = wildcardFinder.findMatches();
- for (String match : wildcardMatches) {
- docs.addAll(loadConfigurationFiles(match, child));
- }
- }
- else {
- docs.addAll(loadConfigurationFiles(includeFileName, child));
- }
- }
- }
- }
- docs.add(doc);
- loadedFileUrls.add(url.toString());
- }
- }
- return docs;
- }