发现struts2要配的东西还不少,但对于一般的小项目 ,其实没有必要用到那么多功能。特别struts2和现在的好多云平台(BAE,GAE)都不是很好兼容。所以自己写的了一个MVC的东东代替STRTUS2,其实也是自己用习惯了。一般小项目主要用到还是STRUTS ACTION相关的功能。
先说MVC的一个流程吧:
1, 初始化Action相关的配置。(只要做一次就好了)
2, 截获相应的请求并根据配置初始化Action
3, 把对应的Request里的内容填充到Action。
4. 执行Action。
5, 根据Action的返回的结果填充Request并跳转。
1, 初始化Action相关的配置。(只要做一次就好了)
Struts2 conversion很好用,所以这个一定要仿,其实就是扫到相应包里的Class,再把他们做成Action.因为后面很多工作都要用到反射,所以把第一次反射后拿到的方法缓存起来。动手吧:
初始化配,因为还WEB应用,所以在web.xml里面用listener加载 相应配置:
<listener> <listener-class> com.demo.config.LifecycleInit </listener-class> </listener>
在这个类里扫描ACTION的包并加到配置类里去:
Set<Class<?>> classes = Utils.getClasses(Constants.ACTION_PACKAGE); for (Class c : classes) { MyConfig.getMyConfig().addConfigByClass(c); }
getClasses方法不多说了,可以看源代码,就是扫描到包里所有的类。
MyConfig是一个单例,因为配置类只要一份就OK了。addConfigByClass里会做更细的配置。
if (StringUtils.endsWith(clazz.getName(), Constants.ACTION_SUFFIX)) { ActionConfig actionConfig = ActionConfig.buildByClass(clazz); if (actionConfigs.get(actionConfig.getUrlName()) != null) throw new RuntimeException("duplicate action class found" + clazz.getName()); actionConfigs.put(actionConfig.getUrlName().toLowerCase(), actionConfig); }
ActionConfig.buildByClassu将创建具体的Action配置类。
Method[] methods = clazz.getDeclaredMethods(); //缓存方法 for (Method method : methods) { String methodName = method.getName(); if (StringUtils.startsWith(methodName, "get")) {//缓存数据方法 if (!StringUtils.endsWith(methodName,"Dao") ) { String proNameTmp = StringUtils.removeStart(methodName,"get" ); String proName = proNameTmp.substring(0,1).toLowerCase()+proNameTmp.substring(1); this.getMethod.put(proName, method); } } else { //缓存Action方法 this.methodMap.put(method.getName().toLowerCase(), method); } }
到这里配置就完成了。
2, 截获相应的请求并根据配置初始化Action
还是学Struts用filter来做url截获吧,自己写个ActionFilter
<filter> <filter-name>actionFilter</filter-name> <filter-class>com.demo.utils.ActionFilter</filter-class> </filter> <filter-mapping> <filter-name>actionFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
接下来就是这个filter里会根据URL和之前的配置类组装Action.对了Spring还是很好用的,所以里面还是用到了Spring
//根据名子得到Action MyConfig myConfig = MyConfig.getMyConfig(); ActionConfig actionConfig = myConfig.getActionConfigByName(actionName); //从SPRING里拿到Action bean ServletContext servletContext = request.getSession().getServletContext(); BasicAction actionClazz = (BasicAction) servletfindBean(servletContext, actionName.toLowerCase() + Constants.ACTION_SUFFIX); //初始化Action 的上下文 actionClazz.setActionConfig(actionConfig); actionClazz.setCurMethodName(methodName); actionClazz.setRequest(request); actionClazz.setResponse(response); //调用Action dispatch方法 actionClazz.dispatchUrl();
3, 把对应的Request里的内容填充到Action。
这里自己还是想偷下懒,用到ognl类里的表达式,这样可以很快的request里的数据自动装载到Action里面去
//云平台一般都要把SecurityManager设置成NULL OgnlRuntime.setSecurityManager(null); //因为属性是NULL的时候我们要自动构建对应的类 OgnlRuntime.setNullHandler(Object.class, new BasicAction.OnglNullHandler()); Map<String, Object> parMap = request.getParameterMap(); for (String name : parMap.keySet()) { Ognl.setValue(name, this, parMap.get(name)); }
4. 执行Action。
这个简单了:之前就找在filter里找到了Action方法,这里动态调用一下就OK了。
Method curMethod = actionConfig.getMethodByName(curMethodName); String result = (String) curMethod.invoke(this);
5, 根据Action的返回的结果填充Request并跳转。
最后就要把相应的属性放回到request里面去。还记得之前缓存过属性对应的方法,这里正好用得上:
for (String key : actionConfig.getGetMethod().keySet()) { Object value = actionConfig.getGetMethod().get(key).invoke(this); if (value != null) { request.setAttribute(key, actionConfig.getGetMethod().get(key).invoke(this)); } }
还差一步就是跳到对应的地方,里面可自己再定义更多的跳法,里面主要有仿TILES,redirect,和直接到JSP。
if (StringUtils.indexOf(result, Constants.ACTION_RESULT_SPLIT) > -1) { String[] results = StringUtils.split(result, Constants.ACTION_RESULT_SPLIT); if (results.length == 1) { //跳到默认模板 request.setAttribute("contentPageJsp", results[0]); request.getRequestDispatcher(StringUtils.removeEnd(Constants.ACTION_RESULT_DEFAULT, Constants.ACTION_RESULT_SPLIT)).forward(request, response); } else { if (StringUtils.removeEnd(Constants.ACTION_RESULT_REDIRECT, Constants.ACTION_RESULT_SPLIT).equals(results[0])) { //redirect response.sendRedirect(results[1]); } else { //跳到指定模板 request.setAttribute("contentPageJsp", results[1]); request.getRequestDispatcher(results[0]).forward(request, response); } } } else { //跳到JSP request.getRequestDispatcher(result).forward(request, response); }
再说一下我的JSP里如果是TILES的怎么写吧。其实很简单写INCLUDE就好了,如有必要全写成变量也OK,
<div id="together"> <div id="ggw_header" class="ggw_tile"><jsp:include page="/jsp/layout/header.jsp" /> </div> <div id="ggw_userheader" class="ggw_tile" style="z-index: 5;"> <jsp:include page="/jsp/layout/userheader.jsp" /> </div> <div id="ggw_content" class="ggw_tile" style="z-index: 0;"><jsp:include page="${contentPageJsp}"/></div> <div id="ggw_footer_transition" class="ggw_tile"></div> </div> <div id="ggw_footer"><jsp:include page="/jsp/layout/footer.jsp"/></div>
这里也可年到我用的JSTL EL去显示相量,用自己的MVC感觉也还OK,很多东西自己好控制了,不用为了struts2的BUG再烦了,有问题自己修。
用这个写了例子用baidu app engine:http://yijushequ.duapp.com/