Struts2是一种基于MVC模式的javaWeb框架,本质上相当于Servlet。
所谓MVC,就是模型-视图-控制器。
Model(模型)表示应用程序核心(比如数据库记录列表)。
View(视图)显示数据(数据库记录)。
Controller(控制器)处理输入(写入数据库记录)
而Struts的作用实际上是作为控制器,建立模型层和视图层的数据交互(就是通常所说控制model和jsp之间的数据交互)
上面都是废话,你只要知道struts2能控制model和jsp之间的数据交互即可。
web程序想要使用struts2框架,除了要引用相关jar包外,还要在web.xml中对struts2进行配置,只有配置后,struts2才能对浏览器的请求进行一系列处理。
怎么在web应用中配置struts2?
首先在web.xml中添加struts2的使用,代码如下:
<!-- 配置Struts2 核心 Filter --> <filter> <filter-name>action2</filter-name> <filter-class>org.apache.struts2.dispatcher.filter.StrutsPrepareAndExecuteFilter</filter-class> </filter> <filter-mapping> <filter-name>action2</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
上述代码使用了Filter,那我们就回顾一下Filter的作用。
回顾:Filter,中文名为过滤器,通过Filter可以对web服务器的资源进行管理,例如Jsp,Servlet, 静态图片文件等进行拦截,从而实现一些特殊的功能。
实现URL级别的权限访问控制、过滤敏感词汇、压缩响应信息等一些高级功能
从代码层面来讲,只要创建一个实现了Filter接口的实现类,然后将这个类在web.xml进行配置,那么这个类就可以让用户在访问某个目标资源之前,对访问的请求和响应进行拦截。
更多介绍可以看我的另外一篇文章:http://blog.csdn.net/u012605477/article/details/75258358
注意:Struts2在web.xml中看来就是一个Filter,只不过其实现类StrutsPrepareAndExecuteFilter不是我们写的,是Struts2的。
为了验证StrutsPrepareAndExecuteFilter也实现了Filter接口,我们将其和我们自写的过滤器进行对比:
下面是自写的过滤器FilterA配置:
<filter> <filter-name>FilterA</filter-name> <filter-class>com.strategy.jpa.FilterA</filter-class> </filter> <filter-mapping> <filter-name>FilterA</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>自写FilterA的代码:
package com.strategy.jpa; 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; public class FilterA implements Filter { public void init(FilterConfig filterConfig) throws ServletException { } public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { chain.doFilter(request, response); response.reset(); } public void destroy() { } }web中配置Struts2的代码:
* $Id: DefaultActionSupport.java 651946 2008-04-27 13:41:38Z apetrelli $ package org.apache.struts2.dispatcher.filter; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.struts2.RequestUtils; import org.apache.struts2.StrutsStatics; import org.apache.struts2.dispatcher.Dispatcher; import org.apache.struts2.dispatcher.mapper.ActionMapping; import org.apache.struts2.dispatcher.ExecuteOperations; import org.apache.struts2.dispatcher.InitOperations; import org.apache.struts2.dispatcher.PrepareOperations; 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 java.io.IOException; import java.util.List; import java.util.regex.Pattern; /** * Handles both the preparation and execution phases of the Struts dispatching process. This filter is better to use * when you don't have another filter that needs access to action context information, such as Sitemesh. */ public class StrutsPrepareAndExecuteFilter implements StrutsStatics, Filter { private static final Logger LOG = LogManager.getLogger(StrutsPrepareAndExecuteFilter.class); protected PrepareOperations prepare; protected ExecuteOperations execute; protected List<Pattern> excludedPatterns = null; public void init(FilterConfig filterConfig) throws ServletException { InitOperations init = new InitOperations(); Dispatcher dispatcher = null; try { FilterHostConfig config = new FilterHostConfig(filterConfig); init.initLogging(config); dispatcher = init.initDispatcher(config); init.initStaticContentLoader(config, dispatcher); prepare = new PrepareOperations(dispatcher); execute = new ExecuteOperations(dispatcher); this.excludedPatterns = init.buildExcludedPatternsList(dispatcher); postInit(dispatcher, filterConfig); } finally { if (dispatcher != null) { dispatcher.cleanUpAfterInit(); } init.cleanup(); } } /** * Callback for post initialization * * @param dispatcher the dispatcher * @param filterConfig the filter config */ protected void postInit(Dispatcher dispatcher, FilterConfig filterConfig) { } public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; try { String uri = RequestUtils.getUri(request); if (excludedPatterns != null && prepare.isUrlExcluded(request, excludedPatterns)) { LOG.trace("Request {} is excluded from handling by Struts, passing request to other filters", uri); chain.doFilter(request, response); } else { LOG.trace("Checking if {} is a static resource", uri); boolean handled = execute.executeStaticResourceRequest(request, response); if (!handled) { LOG.trace("Assuming uri {} as a normal action", uri); prepare.setEncodingAndLocale(request, response); prepare.createActionContext(request, response); prepare.assignDispatcherToThread(); request = prepare.wrapRequest(request); ActionMapping mapping = prepare.findActionMapping(request, response, true); if (mapping == null) { LOG.trace("Cannot find mapping for {}, passing to other filters", uri); chain.doFilter(request, response); } else { LOG.trace("Found mapping {} for {}", mapping, uri); execute.executeAction(request, response, mapping); } } } } finally { prepare.cleanupRequest(request); } } public void destroy() { prepare.cleanupDispatcher(); } }对比可知,struts2也实现了Filter接口,只是他比我们自己写的FilterA多实现了一个接口StrutsStatics,其它的没有变化。
我们对上面的代码进行了解:
在Init方法里面,我们看到有一个config对象,该类对象的作用是对FilterConfig进行封装。
package org.apache.struts2.dispatcher.filter; import org.apache.struts2.util.MakeIterator; import javax.servlet.FilterConfig; import javax.servlet.ServletContext; import java.util.Iterator; import org.apache.struts2.dispatcher.HostConfig; /** * Host configuration that wraps FilterConfig */ public class FilterHostConfig implements HostConfig { private FilterConfig config; public FilterHostConfig(FilterConfig config) { this.config = config; } public String getInitParameter(String key) { return config.getInitParameter(key); } public Iterator<String> getInitParameterNames() { return MakeIterator.convert(config.getInitParameterNames()); } public ServletContext getServletContext() { return config.getServletContext(); } }
然后又在Init方法里面通过创建的config对象,并调用initDispatcher方法创建了转发器对象dispatcher对象。以及通过initLogging方法初始化日志记录器。
private Dispatcher createDispatcher( HostConfig filterConfig ) { Map<String, String> params = new HashMap<>(); 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 cleanup() { ActionContext.setContext(null); }
public void initLogging( HostConfig filterConfig ) { String factoryName = filterConfig.getInitParameter("loggerFactory"); if (factoryName != null) { try { Class cls = ClassLoaderUtil.loadClass(factoryName, this.getClass()); LoggerFactory fac = (LoggerFactory) cls.newInstance(); LoggerFactory.setLoggerFactory(fac); } catch ( InstantiationException e ) { System.err.println("Unable to instantiate logger factory: " + factoryName + ", using default"); e.printStackTrace(); } catch ( IllegalAccessException e ) { System.err.println("Unable to access logger factory: " + factoryName + ", using default"); e.printStackTrace(); } catch ( ClassNotFoundException e ) { System.err.println("Unable to locate logger factory class: " + factoryName + ", using default"); e.printStackTrace(); } } }
接下来调用了init对象initStaticContentLoader(config, dispatcher);方法加载一些静态资源。
public StaticContentLoader initStaticContentLoader( HostConfig filterConfig, Dispatcher dispatcher ) { StaticContentLoader loader = dispatcher.getContainer().getInstance(StaticContentLoader.class); loader.setHostConfig(filterConfig); return loader; }
最重要的就是dispatcher,它主要将filter拦截到的请求转入struts2的请求处理模块,我们必须知道这一点。
其次是FilterConfig,它将我们在web.xml中的Filter的配置信息也保存到了dispatcher中。下面是该对象的构造方法。
public Dispatcher(ServletContext servletContext, Map<String, String> initParams) { this.servletContext = servletContext; this.initParams = initParams; }
接下来就是prepare和execute对象,和InitOperations类似,也是进行了封装一些操作,都是截取一部分重要的代码。
public PrepareOperations(Dispatcher dispatcher) { this.dispatcher = dispatcher; } 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; } ActionContext oldContext = ActionContext.getContext(); if (oldContext != null) { // detected existing context, so we are probably in a forward ctx = new ActionContext(new HashMap<>(oldContext.getContextMap())); } else { ValueStack stack = dispatcher.getContainer().getInstance(ValueStackFactory.class).createValueStack(); stack.getContext().putAll(dispatcher.createContextMap(request, response, null)); ctx = new ActionContext(stack.getContext()); } request.setAttribute(CLEANUP_RECURSION_COUNTER, counter); ActionContext.setContext(ctx); return ctx; }execute对象
public ExecuteOperations(Dispatcher dispatcher) { this.dispatcher = dispatcher; } public boolean executeStaticResourceRequest(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { // there is no action in this request, should we look for a static resource? String resourcePath = RequestUtils.getServletPath(request); if ("".equals(resourcePath) && null != request.getPathInfo()) { resourcePath = request.getPathInfo(); } StaticContentLoader staticResourceLoader = dispatcher.getContainer().getInstance(StaticContentLoader.class); if (staticResourceLoader.canHandle(resourcePath)) { staticResourceLoader.findStaticResource(resourcePath, request, response); // The framework did its job here return true; } else { // this is a normal request, let it pass through return false; } }
从上面的代码可以看出,这两个对象封装了请求预处理和请求处理的操作,当处理请求时方法被调用
在上述的代码中有个很重要的方法未罗列出来,那就是Disptcher中得init方法,该方法初始读取一些配置文件,包含我们想要知道的读取struts2.xml的方法。
public void init() { if (configurationManager == null) { configurationManager = createConfigurationManager(DefaultBeanSelectionProvider.DEFAULT_BEAN_NAME); } try { init_FileManager(); init_DefaultProperties(); // [1] init_TraditionalXmlConfigurations(); // [2] init_LegacyStrutsProperties(); // [3] init_CustomConfigurationProviders(); // [5] init_FilterInitParameters() ; // [6] init_AliasStandardObjects() ; // [7] Container container = init_PreloadConfiguration(); container.inject(this); init_CheckWebLogicWorkaround(container); if (!dispatcherListeners.isEmpty()) { for (DispatcherListener l : dispatcherListeners) { l.dispatcherInitialized(this); } } errorHandler.init(servletContext); } catch (Exception ex) { LOG.error("Dispatcher initialization failed", ex); throw new StrutsException(ex); } }上面是init方法,里面调用了诸多读取配置文件的方法,包含我们想要知道的读取struts.xml的方法:
private void init_TraditionalXmlConfigurations() { String configPaths = initParams.get("config"); if (configPaths == null) { configPaths = DEFAULT_CONFIGURATION_PATHS; } String[] files = configPaths.split("\\s*[,]\\s*"); for (String file : files) { if (file.endsWith(".xml")) { if ("xwork.xml".equals(file)) { configurationManager.addContainerProvider(createXmlConfigurationProvider(file, false)); } else { configurationManager.addContainerProvider(createStrutsXmlConfigurationProvider(file, false, servletContext)); } } else { throw new IllegalArgumentException("Invalid configuration file name"); } } }在上面的代码中,设定了读取struts.xml的默认路径,其内容如下:
private static final String DEFAULT_CONFIGURATION_PATHS = "struts-default.xml,struts-plugin.xml,struts.xml";至此,web应用配置struts2框架,以及struts2如何将Filter封装成struts2的,甚至如何读取struts.xml以及其他配置,并进行相关struts2参数初始化都有了大概了解,接下来说一下Action。
回到StrutsPrepareAndExecuteFilter类的doFilter中,
//每次发送一个Request,StrutsPrepareAndExecuteFilter都会调用doFilter方法 public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; try { //设置编码和国际化 prepare.setEncodingAndLocale(request, response); //ActionContext创建 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); //如果找不到对应的action配置 if (mapping == null) { /* * 就是如果path是以“/struts”开头,则到初始参数packages配置的包路径去查找对应的静态资源并输出到页面流中, * 当然.class文件除外。如果再没有则跳转到404 */ boolean handled = execute.executeStaticResourceRequest(request, response); if (!handled) { chain.doFilter(request, response); } } else { /* * 找到对应action配置文件后,调用ExecuteOperations类中executeAction, * 开始谳用Action的方法。 */ execute.executeAction(request, response, mapping); } } } finally { prepare.cleanupRequest(request); } }相关准备(prepare)方法,设置编码的方法不再叙述(setEncodingAndLocale),我们主要说一下ActionContext。
prepare.createActionContext(request, response);
ActionContext是一个struts2容器,主要存储request、session、application、parameters等相关信息。
ActionContext是一个线程的本地变量,这意味着不同的action之间不会共享ActionContext,所以也不用考虑线程安全问题
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; } ActionContext oldContext = ActionContext.getContext(); if (oldContext != null) { // detected existing context, so we are probably in a forward ctx = new ActionContext(new HashMap<>(oldContext.getContextMap())); } else { ValueStack stack = dispatcher.getContainer().getInstance(ValueStackFactory.class).createValueStack(); stack.getContext().putAll(dispatcher.createContextMap(request, response, null)); ctx = new ActionContext(stack.getContext()); } request.setAttribute(CLEANUP_RECURSION_COUNTER, counter); ActionContext.setContext(ctx); return ctx; }
我们再看一下ActionContext是什么,取部分代码,由此可知ActionContext是一个键值对集合,跟Spring中的bean类似:
public class ActionContext implements Serializable { static ThreadLocal<ActionContext> actionContext = new ThreadLocal<>(); private Map<String, Object> context; public ActionContext(Map<String, Object> context) { this.context = context; }有了创建Action的方法,肯定有使用Action的方法,我们现在去找使用的方法:
public void serviceAction(HttpServletRequest request, HttpServletResponse response, ActionMapping mapping) throws ServletException { Map<String, Object> extraContext = createContextMap(request, response, mapping); // If there was a previous value stack, then create a new copy and pass it in to be used by the new Action ValueStack stack = (ValueStack) request.getAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY); boolean nullStack = stack == null; if (nullStack) { ActionContext ctx = ActionContext.getContext(); if (ctx != null) { stack = ctx.getValueStack(); } } if (stack != null) { extraContext.put(ActionContext.VALUE_STACK, valueStackFactory.createValueStack(stack)); } String timerKey = "Handling request from Dispatcher"; try { UtilTimerStack.push(timerKey); String namespace = mapping.getNamespace(); String name = mapping.getName(); String method = mapping.getMethod(); ActionProxy proxy = getContainer().getInstance(ActionProxyFactory.class).createActionProxy( namespace, name, method, extraContext, true, false); request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, proxy.getInvocation().getStack()); // if the ActionMapping says to go straight to a result, do it! if (mapping.getResult() != null) { Result result = mapping.getResult(); result.execute(proxy.getInvocation()); } else { proxy.execute(); } // If there was a previous value stack then set it back onto the request if (!nullStack) { request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, stack); } } catch (ConfigurationException e) { logConfigurationException(request, e); sendError(request, response, HttpServletResponse.SC_NOT_FOUND, e); } catch (Exception e) { if (handleException || devMode) { sendError(request, response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e); } else { throw new ServletException(e); } } finally { UtilTimerStack.pop(timerKey); } }
在上面的try语句里面我们可以得知,struts是怎么通过命名空间,读取action的配置,以及action的method方法,
尤其是如何从容器中获得ActionProxyFactory代理工厂 ,是怎么创建ActionProxy来执行一个特定的命名空间和动作的。
以及怎么通过我们在struts2中设定的跳转方法,跳转到指定页面的。
//执行execute方法,并转向结果 if (mapping.getResult() != null) { Result result = mapping.getResult(); result.execute(proxy.getInvocation()); } else { proxy.execute(); }
至此,对于Struts2是如何工作的,怎么读取xml的,如何搭建在web应用中基本叙述完了,更详细的实在说不完,各位大佬可以看下下面这位大神的博客,很给力,就是看起来太繁琐。
另外,本文部分来源于该大神,未通知,深表歉意。http://blog.csdn.net/yuan_xw/article/details/7838123