.net到Java那些事儿--structs做了那些事(二)

2024-01-31 04:50
文章标签 java net structs 事儿

本文主要是介绍.net到Java那些事儿--structs做了那些事(二),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一、跟着项目先来看下structs怎么执行的

     首先看下web.xml配置文件,下面有如下代码

  <filter>
<filter-name>struts2</filter-name>
<filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
View Code

    这是一个过滤器,过滤器不管是Java还是.net的人都清楚,简单说一下拦截Http的请求,所以上面这段配置就很清楚启动一个Filter来拦截自与客户端的请求,这里我们先不关心源码方面的东西就在表面上走,之后在下面说Struts2内部怎么处理的;

    接下来我们看下struts.xml下面的配置

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.3//EN"
"http://struts.apache.org/dtds/struts-2.3.dtd">
<struts>
<constant name="struts.objectFactory" value="spring" />
<constant name="struts.ui.theme" value="simple"/>
<constant name="struts.devMode" value="true" />
<constant name="struts.i18n.encoding" value="UTF-8" />
<package name="store" namespace="/" extends="struts-default">
<action name="login" method="login" class="userAction">
<result name="success">/WEB-INF/login.jsp</result>
</action>
</package>
</struts>
View Code

    这里我也不讲那些属性是那些意思,网上很多自己查,我们就看下action配置,这里我们是通过Spring集成的所以我们那个class属性放入了bean那个id的名字,当然这里我就不进行替换成文件的全限定名了,我就解释下下面这段话的意思,查找一个叫做userAction类下面有个叫llogin方法名的方法,如果返回success就转发到login.jsp的页面上。

    这个地方加一下Struts页面跳转方式的介绍吧:

    1.dispather这个是默认的配置转发,页面跳转属于同一个线程,Action中数据始终存在,简单一点说这是一个请求;

    2.redirect这个当接收到状态码的时候,重新请求服务器资源返回到客户端,这个地方意味是产生两个请求,原来请求的数据是无法访问到的;

    3.redirectAction 这个相当于重新定向到另外一个Action,这个与redirect方式都是重新生成一个新的请求,但是这个redirectAction是使用ActionMapperFactory提供的ActionMapper来重定向请求。

    4.chain这个可以在多个action之间跳转,处于chain中的action属于同一个http请求,共享一个ActionContext,由于是同一个请求所以请求参数和属性都可以保留,可以理解为2和3的增强版,新增功能保留请求的数据。

    5.stream 这个文件下载时候使用的

    

     基本上就是上面这个样子,中间少了一个过滤器,没事下面重点就是他;

二、structs内部是怎么匹配的

     上面我们再看web.xml配置的时候我们看到过滤器配置,现在我们就把他解剖一下;

     // 这里继承Filter进行实现,那么我们就按照Filter的方式来读一下这个源码

public class StrutsPrepareAndExecuteFilter implements StrutsStatics, Filter {
protected PrepareOperations prepare;
protected ExecuteOperations execute;
protected List<Pattern> excludedPatterns = null;
//init实现初始化配置信息
public void init(FilterConfig filterConfig) throws ServletException {
InitOperations init = new InitOperations();
Dispatcher dispatcher = null;//这个是初始配置时候的重点对象
try {//主要用来存储filter
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();
}
}
protected void postInit(Dispatcher dispatcher, FilterConfig filterConfig) {
}
*/
public Dispatcher initDispatcher( HostConfig filterConfig ) {
Dispatcher dispatcher = createDispatcher(filterConfig);
//主要用来加载配置文件 dispatcher.init();
return dispatcher; } /** * Initializes the static content loader with the filter configuration */ public StaticContentLoader initStaticContentLoader( HostConfig filterConfig, Dispatcher dispatcher ) { StaticContentLoader loader = dispatcher.getContainer().getInstance(StaticContentLoader.class); loader.setHostConfig(filterConfig); return loader; }/** * Create a {@link Dispatcher} */ private Dispatcher createDispatcher( HostConfig filterConfig ) { Map<String, String> params = new HashMap<String, String>();
//将参数存放到Map中
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); }
    /**
* Load configurations, including both XML and zero-configuration strategies,
* and update optional settings, including whether to reload configurations and resource files.
*/

//这个主要初始化加载一些文件,我是感觉这里写的代码真好起码比我写的好太多了,一个人是否优秀在代码确实能看出很多来我自愧不如 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) { if (LOG.isErrorEnabled()) LOG.error("Dispatcher initialization failed", ex); throw new StrutsException(ex); } }

     以上基本上加载配置文件的过程,这里面有几个值得学习的方法,日志加载的抽象类可以兼容日志插件,还有init方法中清晰明了一步一步,我感觉这可能就是看源码的好处;最近正在Bob大叔的代码的整洁之道,在这里我算是看到了。

      接下来我们看重点,请求拦截到以后Struts帮我们做了什么吧!

    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
try {//这个地方有配置就放行
if (excludedPatterns != null && prepare.isUrlExcluded(request, excludedPatterns)) {
chain.doFilter(request, response);
} else {
prepare.setEncodingAndLocale(request, response);//设置编码
prepare.createActionContext(request, response);//创建Action请求的上下文
prepare.assignDispatcherToThread();//将这个对象放入线程
request = prepare.wrapRequest(request);//包装请求对象
ActionMapping mapping = prepare.findActionMapping(request, response, true);//查找action
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);
}
}

     设置编码这块就不要看了挺简单的封装,我们这里看创建action这个作者玩的高招,学习下哈哈

  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这个对象,并做相同的初始化,改地方使用了ThreadLocal
ActionContext oldContext = ActionContext.getContext();
if (oldContext != null) {
// 这个地方我认为可能是分开部署的时候这个可能是跳转过来的Action
ctx = new ActionContext(new HashMap<String, Object>(oldContext.getContextMap()));
} else {
//这里创建值栈 ValueStack stack
= dispatcher.getContainer().getInstance(ValueStackFactory.class).createValueStack();
//创建ContextMap,并加入值栈, stack.getContext().putAll(dispatcher.createContextMap(request, response,
null)); ctx = new ActionContext(stack.getContext());//使用值栈创建上下文 } request.setAttribute(CLEANUP_RECURSION_COUNTER, counter); ActionContext.setContext(ctx);//绑定到本地线程 return ctx; }

      以上创建请求上下文基本是可以了,通过使用ThreadLocal保证线程安全,主要是为了将Dispatcher绑定到本地线程

    public ActionMapping findActionMapping(HttpServletRequest request, HttpServletResponse response, boolean forceLookup) {
ActionMapping mapping = (ActionMapping) request.getAttribute(STRUTS_ACTION_MAPPING_KEY);
if (mapping == null || forceLookup) {
try {
//通过反射去容器中查找action创建ActionMapping对象 mapping
= dispatcher.getContainer().getInstance(ActionMapper.class).getMapping(request, dispatcher.getConfigurationManager()); if (mapping != null) { request.setAttribute(STRUTS_ACTION_MAPPING_KEY, mapping); } } catch (Exception ex) { dispatcher.sendError(request, response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex); } } return mapping; }

     这里需要说一下ActionMapping这个对象,对比下下面2段代码,相信你瞬间就明白了这个对象的作用;

    @Override
public String toString() {
return "ActionMapping{" +
"name='" + name + '\'' +
", namespace='" + namespace + '\'' +
", method='" + method + '\'' +
", extension='" + extension + '\'' +
", params=" + params +
", result=" + (result != null ? result.getClass().getName() : "null") +
'}';
}
    <package name="store" namespace="/" extends="struts-default">
<action name="userAction" method="login" class="userAction">
<result name="success">/WEB-INF/login.jsp</result>
</action>
</package>

      这里简单总结下这个对象作用,主要功能是管理Action信息和管理Action执行完以后的重定向,都是通过初始化配置容器中查找到的。说到这里我感觉就对Struts有了一个整体上的认知了,Struts就是帮我们造了一个巨大容器,里面放了几张网,每张网的作用就是为了找到合适自己的对象,找到以后就展示出来,基本都这样就后面的Spring也是这样。

      最后就是显示到网页上了呗,我们看下这里也玩的比较好,这块玩了一个代理工厂;

    public void serviceAction(HttpServletRequest request, HttpServletResponse response, ActionMapping mapping)
throws ServletException {
Map<String, Object> extraContext = createContextMap(request, response, mapping);
// 这个地方如果有就创建一个新的值栈,然后将值栈的信息放入extraContext中
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();//这2个就是获取属性的值然后到下面的代理中找到自己action中的方法
//代理工厂找见合适的action
ActionProxy proxy = getContainer().getInstance(ActionProxyFactory.class).createActionProxy(
namespace, name, method, extraContext, true, false);
//更新对应值栈属性
request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, proxy.getInvocation().getStack());
// 如果有就执行就好了
if (mapping.getResult() != null) {
Result result = mapping.getResult();
result.execute(proxy.getInvocation());
} else {
proxy.execute();
}
// 如果有值栈,设置值栈的值
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);
}
}

       这里的重点就在ActionFactory了,来来我们看看这里搞了什么,这里通过ActionProxyFactory这个工厂反射创建,我们看下这个接口是有DeafultActionFactory这个类实现的,那我们看下这里面做了什么好玩的事情;

    public ActionProxy createActionProxy(String namespace, String actionName, String methodName, Map<String, Object> extraContext, boolean executeResult, boolean cleanupContext) {
ActionInvocation inv = new DefaultActionInvocation(extraContext, true);
container.inject(inv);
return createActionProxy(inv, namespace, actionName, methodName, executeResult, cleanupContext);
}
public ActionProxy createActionProxy(ActionInvocation inv, String namespace, String actionName, boolean executeResult, boolean cleanupContext) {
return createActionProxy(inv, namespace, actionName, null, executeResult, cleanupContext);
}
public ActionProxy createActionProxy(ActionInvocation inv, String namespace, String actionName, String methodName, boolean executeResult, boolean cleanupContext) {
//这里重点
DefaultActionProxy proxy = new DefaultActionProxy(inv, namespace, actionName, methodName, executeResult, cleanupContext);
container.inject(proxy);
proxy.prepare();//这里事执行
return proxy;
}

    看到这里会发现我们真正的重点就要到来了,DedaultActionProxy这个实现了ActionProxy这个接口的类,里面是怎么找到Action,我们把重点放到这里;

    protected void prepare() {
String profileKey = "create DefaultActionProxy: ";
try {
UtilTimerStack.push(profileKey);
//这里主要事根据命名空间和Action寻找到正确的Action config
= configuration.getRuntimeConfiguration().getActionConfig(namespace, actionName); if (config == null && unknownHandlerManager.hasUnknownHandlers()) {
//寻找未知的Action config
= unknownHandlerManager.handleUnknownAction(namespace, actionName); } if (config == null) { throw new ConfigurationException(getErrorMessage()); } //寻找方法 resolveMethod(); if (!config.isAllowedMethod(method)) { throw new ConfigurationException("Invalid method: " + method + " for action " + actionName); } //初始化创建 invocation.init(this); } finally { UtilTimerStack.pop(profileKey); } }
    private void resolveMethod() {
// 没有就执行默认的execite方法
if (StringUtils.isEmpty(this.method)) {
this.method = config.getMethodName();
if (StringUtils.isEmpty(this.method)) {
this.method = ActionConfig.DEFAULT_METHOD;
}
methodSpecified = false;
}
}
    public void init(ActionProxy proxy) {
this.proxy = proxy;
Map<String, Object> contextMap = createContextMap();
// Setting this so that other classes, like object factories, can use the ActionProxy and other
// contextual information to operate
ActionContext actionContext = ActionContext.getContext();
if (actionContext != null) {
actionContext.setActionInvocation(this);
}
//创建Action类
createAction(contextMap);
if (pushAction) {
stack.push(action);
contextMap.put("action", action);
}
invocationContext = new ActionContext(contextMap);
invocationContext.setName(proxy.getActionName());
// 生成一个迭代器列表
List<InterceptorMapping> interceptorList = new ArrayList<InterceptorMapping>(proxy.getConfig().getInterceptors());
interceptors = interceptorList.iterator();
}
    protected void createAction(Map<String, Object> contextMap) {
// load action
String timerKey = "actionCreate: " + proxy.getActionName();
try {
UtilTimerStack.push(timerKey);
action = objectFactory.buildAction(proxy.getActionName(), proxy.getNamespace(), proxy.getConfig(), contextMap);
} catch (InstantiationException e) {
throw new XWorkException("Unable to intantiate Action!", e, proxy.getConfig());
} catch (IllegalAccessException e) {
throw new XWorkException("Illegal access to constructor, is it public?", e, proxy.getConfig());
} catch (Exception e) {
String gripe;
if (proxy == null) {
gripe = "Whoa!  No ActionProxy instance found in current ActionInvocation.  This is bad ... very bad";
} else if (proxy.getConfig() == null) {
gripe = "Sheesh.  Where'd that ActionProxy get to?  I can't find it in the current ActionInvocation!?";
} else if (proxy.getConfig().getClassName() == null) {
gripe = "No Action defined for '" + proxy.getActionName() + "' in namespace '" + proxy.getNamespace() + "'";
} else {
gripe = "Unable to instantiate Action, " + proxy.getConfig().getClassName() + ",  defined for '" + proxy.getActionName() + "' in namespace '" + proxy.getNamespace() + "'";
}
gripe += (((" -- " + e.getMessage()) != null) ? e.getMessage() : " [no message in exception]");
throw new XWorkException(gripe, e, proxy.getConfig());
} finally {
UtilTimerStack.pop(timerKey);
}
if (actionEventListener != null) {
action = actionEventListener.prepare(action, stack);
}
}

      所有的准备工作都完成了,接下来就是执行方法了

    public String execute() throws Exception {
ActionContext nestedContext = ActionContext.getContext();
ActionContext.setContext(invocation.getInvocationContext());
String retCode = null;
String profileKey = "execute: ";
try {
UtilTimerStack.push(profileKey);
//这里就是执行,,这里面又玩一朵花
retCode = invocation.invoke();
} finally {
if (cleanupContext) {
ActionContext.setContext(nestedContext);
}
UtilTimerStack.pop(profileKey);
}
return retCode;
}

      这里将拦截遍历一遍,最后输出Action的实例方法,然后在返回拦截器的情况,这个地方我不解释了,我也是看了一位大神得解释明悟哈哈

    public String invoke() throws Exception {
String profileKey = "invoke: ";
try {
UtilTimerStack.push(profileKey);
if (executed) {
throw new IllegalStateException("Action has already executed");
}
if (interceptors.hasNext()) {
final InterceptorMapping interceptor = interceptors.next();
String interceptorMsg = "interceptor: " + interceptor.getName();
UtilTimerStack.push(interceptorMsg);
try {
resultCode = interceptor.getInterceptor().intercept(DefaultActionInvocation.this);
}
finally {
UtilTimerStack.pop(interceptorMsg);
}
} else {
resultCode = invokeActionOnly();
}
// this is needed because the result will be executed, then control will return to the Interceptor, which will
// return above and flow through again
if (!executed) {
if (preResultListeners != null) {
for (Object preResultListener : preResultListeners) {
PreResultListener listener = (PreResultListener) preResultListener;
String _profileKey = "preResultListener: ";
try {
UtilTimerStack.push(_profileKey);
listener.beforeResult(this, resultCode);
}
finally {
UtilTimerStack.pop(_profileKey);
}
}
}
// now execute the result, if we're supposed to
if (proxy.getExecuteResult()) {
executeResult();
}
executed = true;
}
return resultCode;
}
finally {
UtilTimerStack.pop(profileKey);
}
}

三、总结

 

      推荐博客:http://blog.csdn.net/fcbayernmunchen/article/details/8441210

      看了这个我感觉我理解得就是小儿科,这个大神上一篇也值得看下。结合整体得架构图,加上我中间提到得几个重要得对象,相信应该基本对Struts会有个整体印象;

      这里欢迎大家加一下我的群438836709,欢迎大家一起学习!

 

这篇关于.net到Java那些事儿--structs做了那些事(二)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/662592

相关文章

SpringBoot中四种AOP实战应用场景及代码实现

《SpringBoot中四种AOP实战应用场景及代码实现》面向切面编程(AOP)是Spring框架的核心功能之一,它通过预编译和运行期动态代理实现程序功能的统一维护,在SpringBoot应用中,AO... 目录引言场景一:日志记录与性能监控业务需求实现方案使用示例扩展:MDC实现请求跟踪场景二:权限控制与

在.NET平台使用C#为PDF添加各种类型的表单域的方法

《在.NET平台使用C#为PDF添加各种类型的表单域的方法》在日常办公系统开发中,涉及PDF处理相关的开发时,生成可填写的PDF表单是一种常见需求,与静态PDF不同,带有**表单域的文档支持用户直接在... 目录引言使用 PdfTextBoxField 添加文本输入域使用 PdfComboBoxField

Java NoClassDefFoundError运行时错误分析解决

《JavaNoClassDefFoundError运行时错误分析解决》在Java开发中,NoClassDefFoundError是一种常见的运行时错误,它通常表明Java虚拟机在尝试加载一个类时未能... 目录前言一、问题分析二、报错原因三、解决思路检查类路径配置检查依赖库检查类文件调试类加载器问题四、常见

Java注解之超越Javadoc的元数据利器详解

《Java注解之超越Javadoc的元数据利器详解》本文将深入探讨Java注解的定义、类型、内置注解、自定义注解、保留策略、实际应用场景及最佳实践,无论是初学者还是资深开发者,都能通过本文了解如何利用... 目录什么是注解?注解的类型内置注编程解自定义注解注解的保留策略实际用例最佳实践总结在 Java 编程

Java 实用工具类Spring 的 AnnotationUtils详解

《Java实用工具类Spring的AnnotationUtils详解》Spring框架提供了一个强大的注解工具类org.springframework.core.annotation.Annot... 目录前言一、AnnotationUtils 的常用方法二、常见应用场景三、与 JDK 原生注解 API 的

Java controller接口出入参时间序列化转换操作方法(两种)

《Javacontroller接口出入参时间序列化转换操作方法(两种)》:本文主要介绍Javacontroller接口出入参时间序列化转换操作方法,本文给大家列举两种简单方法,感兴趣的朋友一起看... 目录方式一、使用注解方式二、统一配置场景:在controller编写的接口,在前后端交互过程中一般都会涉及

Java中的StringBuilder之如何高效构建字符串

《Java中的StringBuilder之如何高效构建字符串》本文将深入浅出地介绍StringBuilder的使用方法、性能优势以及相关字符串处理技术,结合代码示例帮助读者更好地理解和应用,希望对大家... 目录关键点什么是 StringBuilder?为什么需要 StringBuilder?如何使用 St

使用Java将各种数据写入Excel表格的操作示例

《使用Java将各种数据写入Excel表格的操作示例》在数据处理与管理领域,Excel凭借其强大的功能和广泛的应用,成为了数据存储与展示的重要工具,在Java开发过程中,常常需要将不同类型的数据,本文... 目录前言安装免费Java库1. 写入文本、或数值到 Excel单元格2. 写入数组到 Excel表格

Java并发编程之如何优雅关闭钩子Shutdown Hook

《Java并发编程之如何优雅关闭钩子ShutdownHook》这篇文章主要为大家详细介绍了Java如何实现优雅关闭钩子ShutdownHook,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起... 目录关闭钩子简介关闭钩子应用场景数据库连接实战演示使用关闭钩子的注意事项开源框架中的关闭钩子机制1.

Maven中引入 springboot 相关依赖的方式(最新推荐)

《Maven中引入springboot相关依赖的方式(最新推荐)》:本文主要介绍Maven中引入springboot相关依赖的方式(最新推荐),本文给大家介绍的非常详细,对大家的学习或工作具有... 目录Maven中引入 springboot 相关依赖的方式1. 不使用版本管理(不推荐)2、使用版本管理(推