StrutsPrepareAndExecuteFilter作用详解
1 目的
了解StrutsPrepareAndExecuteFilter的作用
2 作用
2.1 基本介绍
(1)FilterDispatcher是早期struts2的过滤器,后期的都用StrutsPrepareAndExecuteFilter了。
FilterDispatcher是struts2.0.x到2.1.2版本的核心过滤器。
StrutsPrepareAndExecuteFilter是自2.1.3开始就替代了FilterDispatcher的。
(2)StrutsPrepareAndExecuteFilter名字可知,prepare与execute,前者表示准备,可以说是指filter中的init方法,即配制的导入;后者表示进行过滤,指doFilter方法,即将request请求,转发给对应的 action去处理。
在使用struts的时候要在web.xml中配置一个过滤器,来拦截用户发起的请求,并进行一些预处理,根据配置文件把请求分配给对应的action并将请求中的参数与action中的字段进行对应赋值。
(3)在StrutsPrepareAndExecuteFilter过滤器中包含相应的功能。三个初始化参数:
1. config参数:指定要加载的配置文件。逗号分割。
2. actionPackages参数:指定Action类所在的包空间。逗号分割。
3. configProviders参数:自定义配置文件提供者,需要实现ConfigurationProvider接口类。逗号分割。
(4)要构建一个过滤器很简单就是实现javax.servlet.Filter接口即可。这个接口有三个方法:
1. init() :这个方法在容器实例化过滤器时被调用,它主要设计用于使过滤器为处理做准备。该方法接受一个 FilterConfig 类型的对象作为输入。
2. doFilter() :与 servlet 拥有一个 service() 方法(这个方法又调用 doPost() 或者 doGet() )来处理请求一样,过滤器拥有单个用于处理请求和响应的方法doFilter() 。这个方法接受三个输入参数:一个 ServletRequest 、 response 和一个 FilterChain 对象。
3. destroy() :正如您想像的那样,这个方法执行任何清理操作,这些操作可能需要在自动垃圾收集之前进行。
2.2 源码剖析
1. 概述
Struts2的核心是一个Filter,Action可以脱离web容器,那么是什么让http请求和action关联在一起的,下面我们深入StrutsPrepareAndExecuteFilter源码来分析下Struts2是如何工作的。
2. 源码属性方法简介
3. 源码剖析
1、init方法
init是Filter第一个运行的方法,我们看下struts2的核心Filter在调用init方法初始化时做哪些工作:
- public void init(FilterConfig filterConfig) throws ServletException {
- InitOperations init = new InitOperations();
- try {
- //封装filterConfig,其中有个主要方法getInitParameterNames将参数名字以String格式存储在List中
- FilterHostConfig config = new FilterHostConfig(filterConfig);
- // 初始化struts内部日志
- init.initLogging(config);
- //<strong>创建dispatcher ,并初始化,这部分下面我们重点分析,初始化时加载那些资源</strong>
- Dispatcher dispatcher = init.initDispatcher(config);
- init.initStaticContentLoader(config, dispatcher);
- //初始化类属性:prepare 、execute
- prepare = new PrepareOperations(filterConfig.getServletContext(), dispatcher);
- execute = new ExecuteOperations(filterConfig.getServletContext(), dispatcher);
- this.excludedPatterns = init.buildExcludedPatternsList(dispatcher);
- //回调空的postInit方法
- postInit(dispatcher, filterConfig);
- } finally {
- init.cleanup();
- }
- }
- public class FilterHostConfig implements HostConfig {
- private FilterConfig config;
- /**
- *构造函数
- */
- public FilterHostConfig(FilterConfig config) {
- this.config = config;
- }
- /**
- * 根据init-param配置的param-name获取param-value的值
- */
- public String getInitParameter(String key) {
- return config.getInitParameter(key);
- }
- /**
- * 返回初始化参数名的List
- */
- public Iterator<String> getInitParameterNames() {
- return MakeIterator.convert(config.getInitParameterNames());
- }
- public ServletContext getServletContext() {
- return config.getServletContext();
- }
- }
只有短短的几行代码,getInitParameterNames是这个类的核心,将Filter初始化参数名称有枚举类型转为Iterator。此类的主要作为是对filterConfig 封装。
2 重点来了,创建并初始化Dispatcher
- public Dispatcher initDispatcher( HostConfig filterConfig ) {
- Dispatcher dispatcher = createDispatcher(filterConfig);
- dispatcher.init();
- return dispatcher;
- }
创建Dispatcher,会读取 filterConfig 中的配置信息,将配置信息解析出来,封装成为一个Map,然后根绝servlet上下文和参数Map构造Dispatcher :
- private Dispatcher createDispatcher( HostConfig filterConfig ) {
- Map<String, String> params = new HashMap<String, String>();
- for ( Iterator e = filterConfig.getInitParameterNames(); e.hasNext(); ) {
- String name = (String) e.next();
- String value = filterConfig.getInitParameter(name);
- params.put(name, value);
- }
- return new Dispatcher(filterConfig.getServletContext(), params);
- }
- /**
- *初始化过程中依次加载如下配置文件
- */
- public void init() {
- if (configurationManager == null) {
- configurationManager = new ConfigurationManager(BeanSelectionProvider.DEFAULT_BEAN_NAME);
- }
- try {
- //加载org/apache/struts2/default.properties
- init_DefaultProperties(); // [1]
- //加载struts-default.xml,struts-plugin.xml,struts.xml
- init_TraditionalXmlConfigurations(); // [2]
- init_LegacyStrutsProperties(); // [3]
- //用户自己实现的ConfigurationProviders类
- init_CustomConfigurationProviders(); // [5]
- //Filter的初始化参数
- init_FilterInitParameters() ; // [6]
- init_AliasStandardObjects() ; // [7]
- Container container = init_PreloadConfiguration();
- container.inject(this);
- init_CheckConfigurationReloading(container);
- init_CheckWebLogicWorkaround(container);
- if (!dispatcherListeners.isEmpty()) {
- for (DispatcherListener l : dispatcherListeners) {
- l.dispatcherInitialized(this);
- }
- }
- } catch (Exception ex) {
- if (LOG.isErrorEnabled())
- LOG.error("Dispatcher initialization failed", ex);
- throw new StrutsException(ex);
- }
- }
初始化default.properties,具体的初始化操作在DefaultPropertiesProvider类中
- 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);
- }
3、doFilter方法
doFilter是过滤器的执行方法,它拦截提交的HttpServletRequest请求,HttpServletResponse响应,作为strtus2的核心拦截器,在doFilter里面到底做了哪些工作,我们将逐行解读其源码,源码如下:
- public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
- //父类向子类转:强转为http请求、响应
- HttpServletRequest request = (HttpServletRequest) req;
- HttpServletResponse response = (HttpServletResponse) res;
- try {
- //设置编码和国际化
- prepare.setEncodingAndLocale(request, response);
- //创建Action上下文(重点)
- prepare.createActionContext(request, response);
- prepare.assignDispatcherToThread();
- if ( excludedPatterns != null && prepare.isUrlExcluded(request, excludedPatterns)) {
- chain.doFilter(request, response);
- } else {
- request = prepare.wrapRequest(request);
- ActionMapping mapping = prepare.findActionMapping(request, response, true);
- if (mapping == null) {
- boolean handled = execute.executeStaticResourceRequest(request, response);
- if (!handled) {
- chain.doFilter(request, response);
- }
- } else {
- execute.executeAction(request, response, mapping);
- }
- }
- } finally {
- prepare.cleanupRequest(request);
- }
- }
setEncodingAndLocale调用了dispatcher方法的prepare方法:
- /**
- * Sets the request encoding and locale on the response
- */
- public void setEncodingAndLocale(HttpServletRequest request, HttpServletResponse response) {
- dispatcher.prepare(request, response);
- }
下面我们看下prepare方法,这个方法很简单只是设置了encoding 、locale ,做的只是一些辅助的工作:
- public void prepare(HttpServletRequest request, HttpServletResponse response) {
- String encoding = null;
- if (defaultEncoding != null) {
- encoding = defaultEncoding;
- }
- Locale locale = null;
- if (defaultLocale != null) {
- locale = LocalizedTextUtil.localeFromString(defaultLocale, request.getLocale());
- }
- if (encoding != null) {
- try {
- request.setCharacterEncoding(encoding);
- } catch (Exception e) {
- LOG.error("Error setting character encoding to '" + encoding + "' - ignoring.", e);
- }
- }
- if (locale != null) {
- response.setLocale(locale);
- }
- if (paramsWorkaroundEnabled) {
- request.getParameter("foo"); // simply read any parameter (existing or not) to "prime" the request
- }
- }
Action上下文创建(重点)
ActionContext是一个容器,这个容易主要存储request、session、application、parameters等相关信息.ActionContext是一个线程的本地变量,这意味着不同的action之间不会共享ActionContext,所以也不用考虑线程安全问题。其实质是一个Map,key是标示request、session、……的字符串,值是其对应的对象:
- static ThreadLocal actionContext = new ThreadLocal();
- Map<String, Object> context;
下面我们看下如何创建action上下文的,代码如下:
- /**
- *创建Action上下文,初始化thread local
- */
- public ActionContext createActionContext(HttpServletRequest request, HttpServletResponse response) {
- ActionContext ctx;
- Integer counter = 1;
- Integer oldCounter = (Integer) request.getAttribute(CLEANUP_RECURSION_COUNTER);
- if (oldCounter != null) {
- counter = oldCounter + 1;
- }
- //注意此处是从ThreadLocal中获取此ActionContext变量
- ActionContext oldContext = ActionContext.getContext();
- if (oldContext != null) {
- // detected existing context, so we are probably in a forward
- ctx = new ActionContext(new HashMap<String, Object>(oldContext.getContextMap()));
- } else {
- ValueStack stack = dispatcher.getContainer().getInstance(ValueStackFactory.class).createValueStack();
- stack.getContext().putAll(dispatcher.createContextMap(request, response, null, servletContext));
- //stack.getContext()返回的是一个Map<String,Object>,根据此Map构造一个ActionContext
- ctx = new ActionContext(stack.getContext());
- }
- request.setAttribute(CLEANUP_RECURSION_COUNTER, counter);
- //将ActionContext存如ThreadLocal
- ActionContext.setContext(ctx);
- return ctx;
- }