框架对Struts2的扩展

近期,想将整条产品线的产品代码进行重构,希望将其组件化。这样对于Struts2希望能够在代价很小的前提下实现组件化。首先定义struts-parent.xml配置文件:

 

<struts>
     <package name="framework-default" extends="embeddedjsp-default">
         <result-types>
            <result-type name="embeddedJspExt" class="com.mycompany.framework.util.EmbeddedJSPResultExt"/>
        </result-types>
        <interceptors>
            <interceptor name="authority" class="com.mycompany.framework.security.LoginInterceptor"/>
            <interceptor-stack name="frameworkdefaultStack">
                <interceptor-ref name="defaultStack" />
                <interceptor-ref name="authority" />
            </interceptor-stack>
        </interceptors>
        <default-interceptor-ref name="frameworkdefaultStack" />
        <global-results>
            <result name="info">/info.jsp</result>
            <result name="error" type="redirect">/error.jsp</result>
            <result name="login" type="redirect">/index.action</result>
        </global-results>
    </package>
    
    <package name="framework-jar" extends="framework-default">
         <result-types>
            <result-type name="dispatcher" class="org.apache.struts2.dispatcher.ServletDispatcherResult" default="false"/>
            <result-type name="embeddedJspExt" class="com.mycompany.framework.util.EmbeddedJSPResultExt" default="true"/>
        </result-types>
    </package>
    
    <package name="framework-codebehind" extends="framework-default">
         <result-types>
            <result-type name="dispatcher" class="com.mycompany.framework.util.EmbeddedJSPResult4CodeBehind" default="true"/>
        </result-types>
    </package>
    <constant name="struts.codebehind.defaultPackage" value="framework-codebehind"/>
    <constant name="struts.configuration.classpath.defaultParentPackage" value="framework-codebehind"/>
    <bean type="com.opensymphony.xwork2.UnknownHandler" name="CodebehindUnknownHandler4Jar" class="com.mycompany.framework.util.CodebehindUnknownHandlerExt" />
</struts>


先定义了一个framework-default包,所有的二次开发定制action都将直接隶属于此包。它主要定了一个登录拦截器,和一些公共信息;然后定义了一个framework-jar包,所有的产品action都直接隶属于此包,它也是从framework-default中派生的,主要将默认的result换成了embeddedJspExt,这样默认就是支持JSP IN JAR的;然后又定义了一个framework-codebehind包,此包是不直接使用的,而是赋给了struts.codebehind.defaultPackage和struts.configuration.classpath.defaultParentPackage常量,它将dispatcher修改成了EmbeddedJSPResult4CodeBehind,使得codebehind能够支持JSP IN JAR;最后定义了一个com.opensymphony.xwork2.UnknownHandler类型的Bean CodebehindUnknownHandlerExt,目的是扩展异常处理。

相关代码如下:

 

 

public class LoginInterceptor extends AbstractInterceptor {
	@Override
	public void init() {
		super.init();
		mapSkipLoginUrlChache.put("/index.action", Boolean.TRUE);
    }
	
    @Override
    public String intercept(ActionInvocation invocation) throws Exception {
    	boolean bDontNeedLogin = UserUtil.isLogged();
    	String strContextPath = ServletActionContext.getRequest().getContextPath();
    	String strActionUri = ServletActionContext.getRequest().getRequestURI();
    	
    	if(strActionUri!=null && strContextPath!=null && strActionUri.length()>strContextPath.length()) {
    		strActionUri = strActionUri.substring(strContextPath.length());
    	}
    	
    	if(!bDontNeedLogin && !Utility.isEmpty(strActionUri)) {
			Boolean bCache = mapSkipLoginUrlChache.get(strActionUri);
			
			if(bCache != null) {
				bDontNeedLogin = bCache;
			} else if(ApplicationParameter.SKIPLOGINURIPREFIXS != null) {
    			for(String strSkipLoginUrlPrefix : ApplicationParameter.SKIPLOGINURIPREFIXS) {
    				if(strActionUri.startsWith(strSkipLoginUrlPrefix)) {
    					bDontNeedLogin = true;
    					break;
    				}
    			}
    			
    			mapSkipLoginUrlChache.put(strActionUri, Boolean.valueOf(bDontNeedLogin));
			}
    	}
    	
    	if(bDontNeedLogin) {
    		long iStart = System.currentTimeMillis();
    		String strRet = invocation.invoke();
    		log.debug("Total Used " + (System.currentTimeMillis()-iStart) + "ms to Solve URI " + strActionUri + ".");
    		return strRet;
    	}

    	log.error("You Should Login before Visit URI " + strActionUri + "!");
    	invocation.getInvocationContext().put("tip", "尚未登录");
        return Action.LOGIN;
    }
    
    protected static final Map<String, Boolean> mapSkipLoginUrlChache = new Hashtable<String, Boolean>();
	private static final Logger log = LoggerFactory.getLogger(LoginInterceptor.class);
}

public class EmbeddedJSPResult4CodeBehind extends EmbeddedJSPResultExt {
	@Override
	protected void doExecute(String finalLocation, ActionInvocation invocation) throws Exception {
		ServletContext servletContext = ServletActionContext.getServletContext();
		
		if(new File(servletContext.getRealPath(finalLocation)).exists()) {
			servletDispatcherResult.doExecute(finalLocation, invocation);
		} else {
			super.doExecute(finalLocation, invocation);
		}
    }
}

public class EmbeddedJSPResultExt extends EmbeddedJSPResult {	
	@Inject
    public void setUrlHelper(UrlHelper urlHelper) {
		servletDispatcherResult.setUrlHelper(urlHelper);
    }
	
	@Override
	protected void doExecute(String finalLocation, ActionInvocation invocation) throws Exception {
		long iStart = System.currentTimeMillis();
		ServletContext servletContext = ServletActionContext.getServletContext();
		String strCheckedFinalLocation = finalLocation;

		if(!finalLocation.startsWith("/")) {
			String strServletPath = ServletActionContext.getRequest().getServletPath();
			int iNamespaceLastIndex = strServletPath.lastIndexOf("/");
			
			if(iNamespaceLastIndex > 0) {
				strCheckedFinalLocation = strServletPath.substring(0, iNamespaceLastIndex+1) + finalLocation;
			}
		}

		String strClassPath = strBasePathLocation + strClassPathPrefix
				+ (strCheckedFinalLocation.charAt(0)=='/' ? strCheckedFinalLocation.substring(1) : strCheckedFinalLocation);
		String strRelativePath = strCheckedFinalLocation.toLowerCase().startsWith(strBasePathLocation.toLowerCase())
				? strCheckedFinalLocation.substring(strBasePathLocation.length()) : strCheckedFinalLocation;
		String strOverridePath = strBasePathLocation + strOverridePathPrefix
				+ (strRelativePath.charAt(0)=='/' ? strRelativePath.substring(1) : strRelativePath);
		Integer iDispatchKind = mapCache.get(strCheckedFinalLocation);
		
		if(iDispatchKind==null && new File(servletContext.getRealPath(strOverridePath)).exists() 
				|| iDispatchKind!=null && iDispatchKind==1) {
			if(iDispatchKind == null) {
				mapCache.put(strCheckedFinalLocation, 1);
			}
			
			dispatchResultByMerge(strOverridePath, invocation);
		} else if(iDispatchKind==null && new File(servletContext.getRealPath(strClassPath)).exists() 
				|| iDispatchKind!=null && iDispatchKind==2) {
			if(iDispatchKind == null) {
				mapCache.put(strCheckedFinalLocation, 2);
			}

			dispatchResultByMerge(strClassPath, invocation);
		} else if(iDispatchKind==null || iDispatchKind==3) {
			try {
				if(ApplicationParameter.IF_USEPLUGIN) {
					super.doExecute(strCheckedFinalLocation, invocation);
				} else {
					dispatchResultByMerge(strCheckedFinalLocation, invocation);
				}
				
				if(iDispatchKind == null) {
					mapCache.put(strCheckedFinalLocation, 3);
				}
			} catch (Exception e) {
				mapCache.put(strCheckedFinalLocation, 4);
				e.printStackTrace();
				prepareFileFromJar(strCheckedFinalLocation);
				dispatchResultByMerge(strCheckedFinalLocation, invocation);
			}
		} else {
			dispatchResultByMerge(strCheckedFinalLocation, invocation);
		}
		
		log.debug("Total Used " + (System.currentTimeMillis()-iStart) + "ms to Render Jsp " + strCheckedFinalLocation + ".");
    }
	
	protected void dispatchResultByMerge(String finalLocation, ActionInvocation invocation) {
		boolean bErrorOccur = false;
		Boolean bDispatchNeedSync = false;
		
		while (!bErrorOccur) {
			try {
				servletDispatcherResult.setLocation(getLocation());
				servletDispatcherResult.doExecute(finalLocation, invocation);
				break;
			} catch (Exception e) {
				int iStartIndex = e.getMessage().indexOf("File \"") + 6;
				int iEndIndex = e.getMessage().indexOf("\" not found");
				
				if(iStartIndex==-1 || iEndIndex==-1) {
					if(!bDispatchNeedSync) {
						e.printStackTrace();
						bErrorOccur = true;
					} else {
						try {
							Thread.sleep(5000);
							bDispatchNeedSync = false;
						} catch (InterruptedException e1) {
							e1.printStackTrace();
						}
					}
				} else {
					bDispatchNeedSync = true;
					String strRealFileName = e.getMessage().substring(iStartIndex, iEndIndex);
					String strDest = ServletActionContext.getServletContext().getRealPath(strRealFileName);
					File fileDest = new File(strDest);
					
					if(prepareSubPath(strDest)) {
						String strRelativePath = strRealFileName.toLowerCase().startsWith(strBasePathLocation.toLowerCase())
								? strRealFileName.substring(strBasePathLocation.length()) : strRealFileName;
						String strOverrideSrc = ServletActionContext.getServletContext().getRealPath(strBasePathLocation 
								+ strOverridePathPrefix + (strRelativePath.charAt(0)=='/' ? strRelativePath.substring(1) : strRelativePath));

						if(!copyFile(strOverrideSrc, strDest, fileDest)) {
							if(!getFileFromJar(strRealFileName, fileDest)) {
								String strClassPathSrc = ServletActionContext.getServletContext().getRealPath(strBasePathLocation 
										+ strClassPathPrefix + (strRealFileName.charAt(0)=='/' ? strRealFileName.substring(1) : strRealFileName));
								copyFile(strClassPathSrc, strDest, fileDest);
							}
						}
					} else {
						bErrorOccur = true;						
					}
				}
			}
		}
	}
	
	protected boolean copyFile(String strSrc, String strDest, File fileDest) {
		File fileSrc = new File(strSrc);
		
		if(fileSrc.exists()) {
			FileUtil.copyFile(fileSrc, fileDest);
			mapFileNeedSync.put(strSrc, strDest);
		
			synchronized (mapFileNeedSync) {
				if(!bSyncStarted) {
					bSyncStarted = true;
					thdFileSync = new Thread(new Runnable() {									
						@Override
						public void run() {
							while(bSyncStarted) {
								try {
									Thread.sleep(iSyncTick);
									
									for(String strSrc : mapFileNeedSync.keySet()) {
										File fileSrc = new File(strSrc);
										File fileDest = new File(mapFileNeedSync.get(strSrc));
										
										if(fileSrc.lastModified() > fileDest.lastModified()) {
											FileUtil.copyFile(fileSrc, fileDest);
										}
									}
								} catch (Exception e) {
									e.printStackTrace();
								}
							}										
						}
					});
					thdFileSync.start();
				}
			}
			
			return true;
		}
		
		return false;
	}
	
	protected void prepareFileFromJar(String finalLocation) {
		String strDest = ServletActionContext.getServletContext().getRealPath(finalLocation);
		File fileDest = new File(strDest);
		
		if(!fileDest.exists() && prepareSubPath(strDest)) {
			getFileFromJar(finalLocation, fileDest);
		}
	}
	
	protected boolean getFileFromJar(String finalLocation, File fileDest) {
		ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
		String pattern = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + finalLocation;		

		try {
			Resource[] resources = resourcePatternResolver.getResources(pattern);
	
			if(resources!=null && resources.length>0) {
				InputStream fis = resources[0].getInputStream();
				OutputStream fos = new FileOutputStream(fileDest);
				byte[] bArrBuffer = new byte[10240];
				int iCount = 0;
				
				while ((iCount = fis.read(bArrBuffer, 0, bArrBuffer.length)) != -1) {
					fos.write(bArrBuffer, 0, iCount);
				}
				
				fis.close();
				fos.close();
				return true;
			}
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}
		
		return false;
	}
	
	protected boolean prepareSubPath(String strDest) {			
		try {
			String strSubPath = strDest.substring(0, strDest.lastIndexOf(File.separator));
			FileUtil.creatDirs("", strSubPath);
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}
		
		return true;
	}
	
	protected ServletDispatcherResult servletDispatcherResult = new ServletDispatcherResult();
	
	protected static volatile boolean bSyncStarted = false;
	protected static Thread thdFileSync = null;
	protected static final int iSyncTick = 1000;
	
	private static final Map<String, Integer> mapCache = new Hashtable<String, Integer>();
	private static final Map<String, String> mapFileNeedSync = new Hashtable<String, String>();
	private static Logger log = LoggerFactory.getLogger(EmbeddedJSPResultExt.class);
	private static final String strOverridePathPrefix = "jsp-override/";
	private static final String strBasePathLocation = "/WEB-INF/";
	private static final String strClassPathPrefix = "classes/";
}

public class CodebehindUnknownHandlerExt extends CodebehindUnknownHandler {
	@Inject
    public CodebehindUnknownHandlerExt(@Inject("struts.codebehind.defaultPackage") String defaultPackage, 
                                       @Inject Configuration configuration) {
		super(defaultPackage, configuration);
	}
	
	@Override
	public ActionConfig handleUnknownAction(String namespace, String actionName) throws XWorkException {
        ActionConfig actionConfig = super.handleUnknownAction(namespace, actionName);
        
        if(actionConfig == null) {
        	String pathPrefix = determinePath(templatePathPrefix, namespace);
        
	        for (String ext : resultsByExtension.keySet()) {
	            if (LOG.isDebugEnabled()) {
	                LOG.debug("Trying to locate unknown action template with extension ."+ext+" in directory "+pathPrefix);
	            }
	            
	            String path = string(pathPrefix, actionName, "." , ext);

                if (isTemplateInJar(path)) {
                    actionConfig = buildActionConfig(path, namespace, actionName, resultsByExtension.get(ext));
                    break;
                }
	        }
        }
        
        return actionConfig;
    }
	
	@Override
	public Result handleUnknownResult(ActionContext actionContext, String actionName, 
            ActionConfig actionConfig, String resultCode) throws XWorkException {        
        Result result = super.handleUnknownResult(actionContext, actionName, actionConfig, resultCode);
        
        if(result == null) {
	        PackageConfig pkg = configuration.getPackageConfig(actionConfig.getPackageName());
	        String ns = pkg.getNamespace();
	        String pathPrefix = determinePath(templatePathPrefix, ns);
	
	        for (String ext : resultsByExtension.keySet()) {
	            if (LOG.isDebugEnabled()) {
	                LOG.debug("Trying to locate result with extension ."+ext+" in directory "+pathPrefix);
	            }
	            
	            String path = string(pathPrefix, actionName, "-", resultCode, "." , ext);

	            if (isTemplateInJar(path)) {
                    result = buildResult(path, resultCode, resultsByExtension.get(ext), actionContext);
                    break;
                }
	            
	            path = string(pathPrefix, actionName, "." , ext);

	            if (isTemplateInJar(path)) {
                    result = buildResult(path, resultCode, resultsByExtension.get(ext), actionContext);
                    break;
                }
	        }
        }
        
        return result;
    }	

	protected boolean isTemplateInJar(String path) {
		ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
		String pattern = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + path;
		
		try {
			Resource[] resources = resourcePatternResolver.getResources(pattern);
			return resources!=null && resources.length>0;
		} catch (IOException e) {
		}
		
		return false;
    }
}


对于FilterDispacher的扩展:

 

 

public class FilterDispatcherForFramework extends FilterDispatcherExt {
	// 增强并规范config属性
	@Override
	protected void solveStrutsConfig(Map<String, String> m) {
		if(ApplicationParameter.IF_COPYSTRUTS2XML) {
			m.put("config", "classpath:struts-default.xml,classpath:struts2-plugin/**/struts-plugin.xml,classpath:struts-parent.xml"
					+ ",classpath:struts2/**/*.xml,classpath:struts2-override/**/*.xml,classpath:struts2-local.xml");
		} else {
			m.put("config", "struts-default.xml,struts-plugin.xml,struts-parent.xml"
					+ ",classpath*:struts2/**/*.xml,classpath*:struts2-override/**/*.xml,classpath*:struts2-local.xml");
		}
		
		super.solveStrutsConfig(m);
	}
}

public class FilterDispatcherExt extends FilterDispatcher {
	@Override
	protected Dispatcher createDispatcher(FilterConfig filterConfig) {
		Map<String, String> params = new HashMap<String, String>();
		
		for (Enumeration e = filterConfig.getInitParameterNames(); e
				.hasMoreElements();) {
			String name = (String) e.nextElement();
			String value = filterConfig.getInitParameter(name);
			params.put(name, value);
		}
		
		solveStrutsConfig(params);
		return new Dispatcher(filterConfig.getServletContext(), params);
	}

	// 增强并规范config属性
	protected void solveStrutsConfig(Map<String, String> m) {
		String strConfigKey = "config";
		String[] strSystemXml = new String[]{"struts-default.xml", "struts-plugin.xml", "struts.xml"};
		String strOldPath = m.get(strConfigKey);
		String strNewPath = null;
		
		if(Utility.isEmpty(strOldPath)) {
			strNewPath = chgArray2String(strSystemXml);
		} else {
			String[] arrPath = strOldPath.split(",");
			List<String> lstConfigXml = new LinkedList<String>();
			
			for(String strPath : arrPath) {
				lstConfigXml.add(strPath);
			}
			
			if(!ApplicationParameter.IF_COPYSTRUTS2XML) {
				if(lstConfigXml.indexOf(strSystemXml[0]) == -1) {
					lstConfigXml.add(0, strSystemXml[0]);
				}
				
				if(lstConfigXml.indexOf(strSystemXml[1]) == -1) {
					lstConfigXml.add(1, strSystemXml[1]);
				}
			}
			
			for(int i=(ApplicationParameter.IF_COPYSTRUTS2XML?0:2); i<lstConfigXml.size();) {
				String strConfigXml = lstConfigXml.get(i);
				
				if(strConfigXml.indexOf("*")!=-1 || strConfigXml.indexOf("?")!=-1
						|| strConfigXml.indexOf("classpath")!=-1) {
					lstConfigXml.remove(i);
					scanAllMatchFile(strConfigXml, lstConfigXml);
				} else {
					++i;
				}		
			}
			
			arrPath = new String[lstConfigXml.size()];
			strNewPath = chgArray2String(lstConfigXml.toArray(arrPath));
		}

		m.put(strConfigKey, strNewPath);
	}
	
	private void scanAllMatchFile(String strConfigXml, List<String> lstConfigXml) {
		try {
			Resource[] resources = new PathMatchingResourcePatternResolver().getResources(strConfigXml);
			
			if(resources != null) {
				for(Resource resource : resources) {
					String strFullPath = resource.getURL().getPath();
					int iIndex = strFullPath.indexOf("!");
					
					if(iIndex != -1) {
						if(strFullPath.charAt(iIndex+1) == '/') {
							++iIndex;
						}
						
						strFullPath = strFullPath.substring(iIndex+1);
					}
					
					lstConfigXml.add(strFullPath.toString());
				}
			}
		} catch (IOException e) {
		}
	}
	
	private String chgArray2String(String[] keys) {
		if(keys==null || keys.length==0) {
			return null;
		}
		
		String strRet = keys[0];
		
		for(int i=1; i<keys.length; ++i) {
			strRet += "," + keys[i];
		}
		
		return strRet;
	}
}

 

这里需要注意ApplicationParameter.IF_USEPLUGIN和ApplicationParameter.IF_COPYSTRUTS2XML两个常量,前者表示是否使用JSP IN JAR插件,如果不使用则需要在Tomcat的最后一个Lisenter中将JSP从JAR包中拷贝到本地来;后者表示是否拷贝Struts2的配置文件,如果拷贝则需要在Tomcat的最后一个Lisenter中将Struts的配置文件从JAR包中拷贝到本地来。对于stuts2-plugin.xml这一配置文件可能有多个,需要特殊处理。

如果项目使用的是高版本struts,则:

 

public class FilterConfigAdapter extends FilterDispatcherForFramework implements FilterConfig {
	public FilterConfigAdapter (FilterConfig filterConfig) {
		this.filterConfig = filterConfig;
		this.params = new HashMap<String, String>();
		
		for (Enumeration e = filterConfig.getInitParameterNames(); e
				.hasMoreElements();) {
			String name = (String) e.nextElement();
			String value = filterConfig.getInitParameter(name);
			params.put(name, value);
		}
		
		solveStrutsConfig(params);
	}

	@Override
	public String getInitParameter(String name) {
		String mappedValue = params.get(name);
		
		if(mappedValue != null) {
			return mappedValue;
		}
		
		return filterConfig.getInitParameter(name);
	}

	@Override
	public Enumeration getInitParameterNames() {
		return Collections.enumeration(params.keySet());
	}

	@Override
	public String getFilterName() {
		return filterConfig.getFilterName();
	}
	
	@Override
	public ServletContext getServletContext() {
		return filterConfig.getServletContext();
	}
	
	private FilterConfig filterConfig;
	private Map<String, String> params;
}

public class StrutsPrepareAndExecuteFilterExt extends StrutsPrepareAndExecuteFilter {
	@Override
	public void init(FilterConfig filterConfig) throws ServletException {
		FilterConfig filterConfigAdapter = new FilterConfigAdapter(filterConfig);
		super.init(filterConfigAdapter);
	}
}

public class StrutsPrepareFilterExt extends StrutsPrepareFilter {
	@Override
	public void init(FilterConfig filterConfig) throws ServletException {
		FilterConfig filterConfigAdapter = new FilterConfigAdapter(filterConfig);
		super.init(filterConfigAdapter);
	}
}

public class StrutsExecuteFilterExt extends StrutsExecuteFilter {
	@Override
	public void init(FilterConfig filterConfig) throws ServletException {
		FilterConfig filterConfigAdapter = new FilterConfigAdapter(filterConfig);
		super.init(filterConfigAdapter);
	}
}

 

后记:如果项目要定制action配置,如增加result,这个需求struts2本来就支持的,只要重新写个action即可覆盖原先的action定义。注意,重新定义的action所在的package名字必须和原来的不同,struts2会忽略同名的package,但会覆盖同名的action,甚是奇怪~

如果想实现自己的一套action配置的覆盖机制,如假定struts2-override中的bean不是覆盖已有的同名bean,而是和它合并,他中没有定义的result添加进去,有的不管或者覆盖,可以从org.apache.struts2.config.DefaultConfiguration中派生一个类,覆盖掉buildRuntimeConfiguration方法,再覆盖常量struts.configuration的值为定义的派生类即可。buildRuntimeConfiguration方法主要是根据packageContexts保存的所有配置文件信息来生成namespaceActionConfigs,namespaceActionConfigs就是全局的struts2配置信息。只要扩展这一机制即可。

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值