前面写了简单的,自己理解的struts。
最近学习了struts的拦截器,也稍微看了一下struts的源代码,感觉自己的结构和apache的是完全不一样。
不仅如此,连最终的action都不是一样的:
这个是我写的struts的action执行时的对象-
这个是正常struts的action-
class com.aii.test.TestAction
感觉我的action好脏,为什么?因为我用了cglib,用的是自动生成的action的子类。
还使得action.class.getDeclaredFields不好使,还增加了字节码,加重了FullGc的压力。
————————————————————————————————————————————————————————————————
简单看了下struts的原理:
从PrepareAndExecutorFilter看来,就像这个过滤器的名字一样,做了两件事:1.prepare2.execute
然后再看了一下过滤器是怎么工作的:发现他是通过ActionInvocation,InterceptorStack来一环扣一环执行下去的,最后执行action的方法。
————————————————————————————————————————————————————————————————
贴的代码可能有小错误,以下载地址最新的源代码为准
再来说下我新写的struts:
因为之前写的太乱,所以重新写了个,这个叫ImitateStruts.
目前实现的功能主要有2个:
1.实现ActionContext以及ServletActionContext
2.实现可插拔的Interceptor
还需要做的:
result还未处理,action属性注入也未实现。
由于设计多线程的ThreadLocal处理,现在还未进行测试优化,很可能会发生内存泄漏。
目前就实现大概的一个功能,更多细节需要补充
—————————————————————————————————————————————————————————————————
看下代码:
1.filter(前端控制器)
这里我也把过滤器的功能分为两块:1.负责javaEE方面servlet等信息的记录2.负责执行action以及拦截链
package com.aii.imitate.struts; import java.io.IOException; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.aii.imitate.struts.manager.ActionExecutor; import com.aii.imitate.struts.manager.InterceptorManager; import com.aii.imitate.struts.manager.InterceptorStack; import com.aii.imitate.struts.manager.JavaEEManager; import com.aii.imitate.struts.utils.ConfigChecker; import com.aii.imitate.struts.utils.ErrorPrinter; /** * 全局过滤器,相当于前端控制器 * */ public class JavaEEAndActionFilter implements Filter { /** * 此对象用来管理,记录javaEE中相关的引用<br/> * 如:HttpServletRequest,HttpServletResponse,ServletContext * */ private JavaEEManager javaEEManager = null; /** * 此对象用来管理action以及拦截器的有序执行 * */ private ActionExecutor actionExecutor = null; public void init(FilterConfig filterconfig) throws ServletException { javaEEManager = new JavaEEManager(filterconfig.getServletContext()); actionExecutor = new ActionExecutor(filterconfig.getServletContext()); } public void doFilter(ServletRequest servletrequest, ServletResponse servletresponse, FilterChain filterchain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) servletrequest; HttpServletResponse response = (HttpServletResponse) servletresponse; if (!ConfigChecker.isActionContained(request.getRequestURI(), javaEEManager)) { System.out.println("未检测到配置,直接放行"); filterchain.doFilter(servletrequest, servletresponse); return; } System.out.println("进入拦截器"); try { // 1.将request与response传递过去,交给javaEEManager来管理 javaEEManager.init(request, response); // 2.将request传递过去,获取目标uri,以便执行相应的action与拦截器 actionExecutor.init(request); // 3.初始化拦截器栈 InterceptorStack interceptorStack = InterceptorManager.wrap( request.getRequestURI(), javaEEManager); // 4.执行拦截链 actionExecutor.doChain(interceptorStack); } catch (Throwable e) { ErrorPrinter.print(response.getWriter(), e); } finally { // 清理工作,释放threalLocal中的垃圾 InterceptorManager.clean(); javaEEManager.cleanContext(); actionExecutor.destory(); } } public void destroy() { } }2.javaEE方面引用的记录实现
其实就是将一些以后可能要用到的信息记录到一个变量中:
这里要用到的信息包括两块:
①.全局的:也就是ServletContext,一个工程就一个,在filter的init方法中可以拿到
②.每个线程都有的:也就是HttpServletRequest,HttpServletResponse,这个当然在doFilter中拿到。
怎么存:
由于ServletContext是全局一个的,所以就直接用一个静态的来表示,放在ActionContext中,
request与response是线程相关的,所以用ThreadLocal来存放。
<pre name="code" class="java">package com.aii.imitate.struts.context; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; import java.util.Set; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; import com.aii.imitate.struts.manager.JavaEEManager; import com.aii.imitate.struts.proxy.CallBackMap; /** * 由struts封装的action上下文对象,可以用于设置request,session的属性 * */ public class ActionContext { private static ThreadLocal<JavaEEManager> eeManagers = new ThreadLocal<JavaEEManager>(); private ActionContext() { } private static ActionContext actionContext = new ActionContext(); public static ActionContext getActionContext() { return actionContext; } /** * 获取一个map,该map负责维护request中的内容 * */ public Map<String, Object> getRequest() { final Map<String, Object> map = new HashMap<String, Object>(); final HttpServletRequest request = eeManagers.get().getRequest(); putParameterToMap(map, request); CallBackMap.CallBack callback = new CallBackMap.CallBack() { public void call(Object[] args) { request.setAttribute((String) args[0], args[1]); } }; return CallBackMap.getProxyMap(map, callback); } @SuppressWarnings("unchecked") public void putParameterToMap(Map<String, Object> map, HttpServletRequest request) { Enumeration<String> e = request.getAttributeNames(); while (e.hasMoreElements()) { String name = e.nextElement(); Object value = request.getAttribute(name); map.put(name, value); } } private void putParameterToMap(Map<String, Object> map, HttpSession session) { Enumeration<String> e = session.getAttributeNames(); while (e.hasMoreElements()) { String name = e.nextElement(); Object value = session.getAttribute(name); map.put(name, value); } } /** * 获取一个map,该map负责维护session中的内容 * */ public Map<String, Object> getSession() { final Map<String, Object> map = new HashMap<String, Object>(); final HttpSession session = eeManagers.get().getRequest().getSession(); putParameterToMap(map, session); CallBackMap.CallBack callback = new CallBackMap.CallBack() { public void call(Object[] args) { session.setAttribute((String) args[0], args[1]); } }; return CallBackMap.getProxyMap(map, callback); } }
这个还需要维护他给出去的那个map,怎么把给出去的map set值以后的map放回到request.setAttribute()中。
这里我采用了一个奇怪的方法:
将map的set方法给代理了,设置了一个回调,每次set方法执行后,将属性放入到request的attribute中。
这个方法我感觉还是不错的。(不过我还没测试过,,等我写完了result的处理再测试。。。。)
3.interceptor拦截链的实现:
为了能够实现可插拔的拦截器,所以这里先定义了一个接口,只要实现这个接口,配置一下,就能够将这个拦截器加入。
package com.aii.imitate.struts.interceptor; import com.aii.imitate.struts.manager.InterceptorChain; public interface Interceptor { void init(); /** * use chain.doIntercept() to continue the chain * */ String intercept(InterceptorChain chain); void destroy(); }
再看下InterceptorChain是怎么实现链式调用Interceptor的:
package com.aii.imitate.struts.manager; import java.lang.reflect.Method; import com.aii.imitate.struts.interceptor.Interceptor; public class InterceptorChain { private InterceptorStack stack; InterceptorChain(InterceptorStack stack) { this.stack = stack; } public String doIntercept() { Interceptor interceptor = stack.next(); String result = null; if (interceptor != null) { // do interceptors try { interceptor.init(); result = interceptor.intercept(this); } finally { interceptor.destroy(); } } else { // do action Method method = ActionExecutor.getActionMethod(); if (method != null) { try { result = (String) method.invoke(ActionExecutor.getAction(), null); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException( "uncaught exception occured running action method"); } } } return result; } }
执行的操作就是:
1.从InterceptorStack中取拦截器,取到了就调用拦截器的intercept方法
-拦截器的intercept方法执行了一些操作,又回到自己方法,接着调用下一个 这就形成一个链
2.如果没有了那就调用action的方法
最后看下这个拦截器链是从哪里开始运行的。
package com.aii.imitate.struts.manager; import java.lang.reflect.Method; import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; import com.aii.imitate.struts.utils.StrutsConfigReader; public class ActionExecutor { private static ThreadLocal<HttpServletRequest> requests = new ThreadLocal<HttpServletRequest>(); private static ThreadLocal<Object> actions = new ThreadLocal<Object>(); private static ServletContext context = null; public ActionExecutor(ServletContext context) { ActionExecutor.context = context; } public void init(HttpServletRequest request) { requests.set(request); String target = request.getRequestURI() .replace(context.getContextPath(), ""); String className = StrutsConfigReader.getActionClass(target); try { actions.set(Class.forName(className).newInstance()); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException("instance action error"); } } public void destory() { } public static Object getAction(){ return actions.get(); } public static Method getActionMethod() { String target = requests.get().getRequestURI() .replace(context.getContextPath(), ""); String className = StrutsConfigReader.getActionClass(target); String methodName = StrutsConfigReader.getActionMethodName(target); if (methodName == null) { methodName = "execute"; } try { Class clazz = Class.forName(className); return clazz.getDeclaredMethod(methodName, null); } catch (Exception e) { e.printStackTrace(); System.out.println("no match method found in action "); } return null; } public String doChain(InterceptorStack interceptorStack) { return new InterceptorChain(interceptorStack).doIntercept(); } }这个类维护了action,method等属性,其中doChain方法是整个拦截器链的入口
这个方法中new 出一个链对象,将要拦截器栈放进去,然后让InterceptorChain来负责处理里面的Interceptor
那InterceptorStack是什么?
其实就是一个用来存放要调用的Interceptor的队列,我一开始也被骗了,看名字以为是栈结构。
叫他stack是因为在InterceptorChain调用的时候,后执行的interceptor先结束执行。
至于InterceptorStack中的数据结构,其实是个队列:根据配置文件interceptor的顺序决定那个拦截器最先执行,放在最外层的,最后执行完的。
在struts源码中,也是靠数组来实现的,并不是栈
</pre><pre name="code" class="java"> protected InterceptorStackConfig() { this.interceptors = new ArrayList<InterceptorMapping>(); }
我的InterceptorStack
package com.aii.imitate.struts.manager; import java.util.LinkedList; import java.util.Queue; import com.aii.imitate.struts.interceptor.Interceptor; public class InterceptorStack { private Queue<Interceptor> interceptorQueue = new LinkedList<Interceptor>(); public void push(Interceptor interceptor) { interceptorQueue.offer(interceptor); } public Interceptor next() { return interceptorQueue.poll(); } }
介绍完毕,只要在struts.xml中配置拦截器,并且实现接口,即可添加拦截器。
源码下载地址:
ImitateStruts