简要的对struts ActionServlet init()方法以及 struts RequestProcessor类做源码分析
首先要确定的是Servlet至始至终只有一个对象以及init()方法会在Servlet第一次加载时被执行一次也是唯
一的一次,所以Servlet的初始化工作一般在init()方法进行
一、解析ModuleConfig
ModuleConfig封装了struts-config的所有配置信息
actionConfigs(ActionMapping)/actionConfigList 、exceptions 、formBeans(ActionForm)、forwards
(ActionForward)、messageResources、plugIns等
// 初始化ModuleConfig配置工厂
initModuleConfigFactory();
// 由配置工厂实例化一个ModuleConfig的对象
ModuleConfig moduleConfig = initModuleConfig("", config);
initModuleConfig(String prefix, String paths)方法做了一下事情
1、 ModuleConfigFactory factoryObject = ModuleConfigFactory.createFactory();
由生成的配置工厂生成一个工厂实例
2、ModuleConfig config = factoryObject.createModuleConfig(prefix);
创建ModuleConfig实例createModuleConfig()方法会有DefaultModuleConfigFactory 执行
这是在配置文件中指定的工厂类
方法内部new 出ModuleConfig对象
在ModuleConfig构造方法内对以下参数进行了初始化
this.prefix = prefix;
this.actionConfigs = new HashMap(); //ActionMapping集合
this.actionConfigIds = new HashMap();
this.actionConfigList = new ArrayList();
this.actionFormBeanClass = "org.apache.struts.action.ActionFormBean";
this.actionMappingClass = "org.apache.struts.action.ActionMapping";
this.actionForwardClass = "org.apache.struts.action.ActionForward";
this.configured = false;
this.controllerConfig = null;
this.exceptions = new HashMap();
this.formBeans = new HashMap(); //ActionForm集合
this.forwards = new HashMap(); //ActionForward集合
this.messageResources = new HashMap();
this.plugIns = new ArrayList();
到此config构造完成
Digester digester = initConfigDigester();
initConfigDigester()方法添加解析struts文件的解析规则
3、循环struts配置文件并把所有标签封装为相应对象填充到config对象相应的map集合中中
List urls = splitAndResolvePaths(paths);
URL url;
for (Iterator i = urls.iterator(); i.hasNext();) {
url = (URL) i.next();
digester.push(config);
this.parseModuleConfigFile(digester, url);
}
最后getServletContext().setAttribute(Globals.MODULE_KEY
+ config.getPrefix(), config);
把ModuleConfig设入ServletContext中
至此config初始化工作全部完成
struts每个模块都对应一个ModuleConfig对象个,在init方法内被初始化,内部封装了模块xml文
件中的配置
二、struts核心类RequestProcessor类解析
ActionServlet做为前端控制器当有请求被接收时,会调用process(request, response)方法
1、ModuleConfig config = getModuleConfig(request);
通过Servlet上下文找到ModuleConfig对象
2、RequestProcessor processor = getProcessorForModule(config);
通过Servlet上下文找到RequestProcessor对象 --所以RequestProcessor类也是单例的
if (processor == null) {
processor = getRequestProcessor(config);
}
如果为空 也就说明是服务器端第一次接收客户端的接收请求
那么执行getRequestProcessor(config)方法生成RequestProcessor并设入ServletContext里
此方法为同步方法,借此我猜测此方法为生成RequestProcessor的唯一方法
方法内部对RequestProcessor调用了init()方法,进行了RequestProcessor的初始化并设入ServletContext
里。
为什么RequestProcessor要做成单例,原因就是RequestProcessor类内部有
HashMap actions = new HashMap();
这样一个集合封装了所有的控制器。
这样的设计是面向对象的。当ActionServlet接收到请求时它需要对请求分发到相应控制器中
此时它获取中央控制器,而这个中央控制器内部拥有所有控制器的引用。
/*
这里我的疑惑是为什么要通过反射生成RequestProcessor,此类并没有继承实现任何接口或类。
process方法最后调用了RequestProcessor的process()方法。此方法为RequestProcessor的核心方法
*/原因经过学习发现RequestProcessor并不是单例的,而实际因为struts的多模块应用模式,导致
RequestProcessor类是多例的
三、解析RequestProcessor类核心方法process
1、 String path = processPath(request, response);
截取客户端请求字符串相当于<action>标签的path属性
2、ActionMapping mapping = processMapping(request, response, path);
获取控制器相对的ActionMapping 对象
processMapping(request, response, path)
1、ActionMapping mapping =
(ActionMapping) moduleConfig.findActionConfig(path);
方法内部调用了moduleConfig.findActionConfig(path);
ModuleConfig对象前面已做过解析,在findActionConfig方法内部已path属性做为Key值,直接
到ActionConfigHashMap集合内寻找ActionMapping,原因见一.3
2.if (mapping != null) {
request.setAttribute(Globals.MAPPING_KEY, mapping);
return (mapping);
}
如果找到mapping那么放入request中,并返回
3、 ActionConfig[] configs = moduleConfig.findActionConfigs();
for (int i = 0; i < configs.length; i++) {
if (configs[i].getUnknown()) {
mapping = (ActionMapping) configs[i];
request.setAttribute(Globals.MAPPING_KEY, mapping);
return (mapping);
}
}
//ActionMapping说明,和Action的unknow属性有关
3、if (mapping == null) {
return;
}
容错处理,说明客户端请求的path路径并没有被配置
4、ActionForm form = processActionForm(request, response, mapping);
processActionForm(request, response, mapping);
1、ActionForm instance =
RequestUtils.createActionForm(request, mapping, moduleConfig,
servlet);
1、String name = mapping.getName();
获得mapping name属性也就是映射的相对ActionForm的name
2、FormBeanConfig config = moduleConfig.findFormBeanConfig(name);
已name做为Key值在缓存的HashMap内获取相对(FormBeanConfig)对象
<!--对这里的一些继承关系做下说明
struts Config类的基类 BaseConfig
ForwardConfig从BaseConfig继承
FormBeanConfig从BaseConfig继承
ActionConfig从BaseConfig继承
ActionMapping从ActionConfig继承
-->
3、if (config == null) {
log.warn("No FormBeanConfig found under '" + name + "'");
return (null);
}
如果为空也就说明并没有配置相对ActionForm,而这是合法的所以返回空
4、ActionForm instance =
lookupActionForm(request, attribute, mapping.getScope());
此方法试图在request,session内寻找ActionForm
5、if ((instance != null) && config.canReuse(instance)) {
return (instance);
}
如果找到那么返回
6、return createActionForm(config, servlet);
如果没有找到则进行创建,方法传递了config,因为config内封装了formbean标签的所
有配置信息。方法可以根据类名进行反射生成对象。
2、if ("request".equals(mapping.getScope())) {
request.setAttribute(mapping.getAttribute(), instance);
} else {
HttpSession session = request.getSession();
session.setAttribute(mapping.getAttribute(), instance);
}
根据配置信息,把ActionForm设入相应作用域内
此方法作用就是获取ActionForm
5、processPopulate(request, response, form, mapping);
此方法用于做ActionForm的数据收集工作
1、form.reset(mapping, request);
此方法用于做数据重置,程序员可以重写此方法
2、RequestUtils.populate(form, mapping.getPrefix(), mapping.getSuffix(),
request);
调用工具类进行数据收集
1、if ((contentType != null)
&& (contentType.startsWith("multipart/form-data"))
&& (method.equalsIgnoreCase("POST"))) {
判断是否是数据上传
2、if (!isMultipart) {
names = request.getParameterNames();
}
如果不是获取request里所有参数参数名。
3、 while (names.hasMoreElements()) {
String name = (String) names.nextElement(); //循环获取参数名
String stripped = name;
Object parameterValue = null;
...
if (isMultipart) {
parameterValue = multipartParameters.get(name);
parameterValue = rationalizeMultipleFileProperty(bean, name,
parameterValue);
} else { //不是上传 则获取此参数名的所有参数值
parameterValue = request.getParameterValues(name);
}
if (!(stripped.startsWith("org.apache.struts."))) {
properties.put(stripped, parameterValue);
}
}
方法最后把参数名做为Key,所对应的所有参数值做为键值放入hashmap内
5、BeanUtils.populate(bean, properties);
由第三方工具类根据ActionForm以及所有参数名参数值进行数据收集
3、if (!processValidate(request, response, form, mapping)) {
return;
}
进行数据验证,方法内部会调用ActionForm的validate方法,程序员可以重写此方法
6、Action action = processActionCreate(request, response, mapping);
获取控制器
1、方法首先获取Action的类名
2、然后此方法对actions进行了同步处理,原因RequestProcessor是多例的,actions是一个成
员变量,缓存了所有action。
3、instance = (Action) actions.get(className);
if (instance != null) {
if (log.isTraceEnabled()) {
log.trace(" Returning existing Action instance");
}
return (instance);
}
试图在集合内取出action,如果不为空 则返回。此时说明此Action已被执行过。
4、instance = (Action) RequestUtils.applicationInstance(className);
如果没有反射创建Action引用。
5、actions.put(className, instance);
最后把action放入集合,并返回。
7、ActionForward forward =
processActionPerform(request, response, action, form, mapping);
此方法内部调用Action的execte方法,获取转向类。
<!--说明类组织关系
在他的父类里有 HashMap forwards = new HashMap();这样一个集合
这里的类组织关系是这样的,ActionMapping从ActionConfig继承,
ActionConfig拥有forwards 集合,
而ModuleConfig内部拥有ActionConfig集合,
这和struts-config标签的组织关系是完全类似的.
ModuleConfig模块配置类对应一个struts配置文件,
ActionConfig控制器配置类对应一个Action标签,
ForwardConfig转向配置类对应Forward标签,
而ModuleConfig在ActionServlet初始化时被加载。
这里可以看出大师对类的组织的合理,每个标签都有相应的类。
-->
综上所述,由于forward标签属于ActionMapping标签所以在程序里,能用findForward()方法找到
ActionForward.
而在ActionConfig内部有这样一个方法addForwardConfig(ForwardConfig config);它已ForwardConfig 的
name属性做为Key,ForwardConfig 类实例做为值添加入缓存中。
这也就是为什么我们可以在findForward()方法内通过传name属性获取相应的ActionForward实例。
8、processForwardConfig(request, response, forward);
在process方法的最后调用了processForwardConfig()方法。完成转向操作。
在这个方法内部,他根据Redirect()是否设置为true选择是进行转发还是重定向。如果没有设置,默认是进
行转发。
说明:
此篇学习笔记算是阅读尚学堂王勇老师struts视频的读后感吧,当时小弟刚学习struts,也算是个j2ee的新人就斗胆写了这篇源码的分析,今天发上来并未做任何删减,错误是难免的,还希望各位大虾阅读时,多多指正,不吝赐教!!
最后感谢由衷的感谢王勇老师,感谢尚学堂!!