自定义struts(三)--ImitateStruts实现可插拔的拦截器

前面写了简单的,自己理解的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);
	}

}


 这个是ActionContext的实现,至于ServletActionContext更加简单,直接给request就行了。 
 

这个还需要维护他给出去的那个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();
}

在struts2里用的是ActionInvocation,我这里用InterceptorChain,用法就跟FilterChain一样。

再看下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;
	}
}

这个InterceptorChain里用到了一个InterceptorStack

执行的操作就是:

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

你可能感兴趣的:(struts,拦截器)