早在3年前就开始用Struts了,因该也算用的比较早的一批人。菜鸟的时候也曾经看过别人写的Struts的运行机制,也看过部分代码,但都是草草了事,自己明白了大致方向就结束了。我经常问别人我们写的Action如UserAction是不是Serlvet的,是不是单例的。其实我也没看过实际代码,这样问别人的时候,内心也经常惶恐,惟恐怕从网上看见的资料有假。
在公司担任技术经理也很长时间了,虽然公司希望自己在开发深度上有所突破,但是本身作为小公司,自己涉及的事情又太多了,时而搞搞bea的portal,时而出去吹吹牛,还经常带几个什么都不会的菜鸟做项目,这几年做的东西也不算少,eai soa eip bmp都有实际的项目经验,但是哪个都没有坚持一路走下去,今天领导要我给公司做java时间比较短的人讲讲struts,正长是个机会,可以把Struts的运行机制,自己好好看看。因为我觉得如果给大家讲hello world,估计大家也不会满意。说了半天废话(我真的很爱说废话诶)下面进入主题吧。
Struts两大功能,我自己的体会,Struts一个很实用的功能是它的jsp tag,这个是个独立的模块,我们的页面都用他的tag ,虽然现在的extremecomponents也是不错。
Struts的另一个主要的功能是业务数据流的传递 jsp---->form—》action,这个是整个Struts的核心流程。
除了这两个主要的功能,剩下的都,都是些具体的基类和接口,方便我们自己的使用,从Struts1.0 1.1 1.2 1.3我们不难看出Struts的趋势也是越来越简单,越来越使用,也不是很一味的追求概念的真理化了。
其实Struts的运行机制,只要看看ActionServlet就ok了,今天下午花了2个小时看了一边,也算略有收获吧,把原来的一些猜测的内容看清楚出处了。 如果你在次之前,对 单例和多线程不了解,我建议你 补充一下基础概念,因为所有的开源代码里这2个概念都是核心概念,代码也都是围绕着这2个概念去进行的。
因为Struts也是jsp Servlet机制运行的,所以我们的入口点还先从Servlet的源头开始看,我们打开一个Struts的web项目。打开web.xml文件。
<servlet>
<servlet-name>action</servlet-name>
<servlet-class>org.apache.struts.action.ActionServlet</servlet-class>
<init-param>
<param-name>config</param-name>
<param-value>/WEB-INF/struts-config.xml</param-value>
</init-param>
<init-param>
<param-name>debug</param-name>
<param-value>3</param-value>
</init-param>
<init-param>
<param-name>detail</param-name>
<param-value>3</param-value>
</init-param>
<load-on-startup>0</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>action</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
</web-app>
我们发现Struts需要在我们的项目里配置一个Servlet ActionServlet它有几个初始化参数,和监听url里包含.do的url
我们打开ActionServlet类.先看看它的init()方法,因为这个是启动tomcat时就会执行的代码,看看它一开始做了些什么工作
public void init() throws ServletException {
initInternal();
initOther();
initServlet();
// Initialize modules as needed
getServletContext().setAttribute(Globals.ACTION_SERVLET_KEY, this);
ModuleConfig moduleConfig = initModuleConfig("", config);
initModuleMessageResources(moduleConfig);
initModuleDataSources(moduleConfig);
initModulePlugIns(moduleConfig);
moduleConfig.freeze();
Enumeration names = getServletConfig().getInitParameterNames();
while (names.hasMoreElements()) {
String name = (String) names.nextElement();
if (!name.startsWith("config/")) {
continue;
}
String prefix = name.substring(6);
moduleConfig = initModuleConfig
(prefix, getServletConfig().getInitParameter(name));
initModuleMessageResources(moduleConfig);
initModuleDataSources(moduleConfig);
initModulePlugIns(moduleConfig);
moduleConfig.freeze();
}
destroyConfigDigester();
}
里面都是些初始化的操作,其实不看代码,我们用脚都能想出来,它肯定是把struts-config.xml文件的内容装载到它封状好的配置信息类里,至于配置信息类,我就不介绍了,无非是些工厂模式或者单例的java类,
我们先看 initInternal()
internal = MessageResources.getMessageResources(internalName); //不用具体看了,是把资源文件装载到类里 if (defaultFactory == null)
defaultFactory = MessageResourcesFactory.createFactory();
return defaultFactory.createResources(config); 工厂模式,基本上init里都是这么操作的
initOther() 这个方法读取web.xml里配置的
<init-param>
<param-name>config</param-name>
<param-value>/WEB-INF/struts-config.xml</param-value>
</init-param>
并把这个属性值方法actionSerlvet的 protected String config = "/WEB-INF/struts-config.xml";里,因为servlet是单例的,这个属性也就理所当然的成为全局变量了,其实不是特殊位置,没必要在web.xml里配置了,因为这个属性 有默认值。 initOther() 方法对里convertNull也做了处理,如果你在web,xml 配置的话,它就会给beanutil注册默认转化信息。
initServlet() 也是把web.xml里的信息初始化了一下,放到 getServletContext()中了。对整个流程没影响。
getServletContext().setAttribute(Globals.ACTION_SERVLET_KEY, this); 这句话,很有意思,自己把自己注册到
getServletContext().中,在struts中,经常看见这样的用法。
oduleConfig moduleConfig = initModuleConfig("", config);
initModuleMessageResources(moduleConfig);
initModuleDataSources(moduleConfig);
initModulePlugIns(moduleConfig);
moduleConfig.freeze();
读取struts-config.xml文件的内容,到类里,看名字也能知道都初始化了什么。就不多说了。
Enumeration names = getServletConfig().getInitParameterNames();
while (names.hasMoreElements()) {
String name = (String) names.nextElement();
if (!name.startsWith("config/")) {
continue;
}
String prefix = name.substring(6);
moduleConfig = initModuleConfig
(prefix, getServletConfig().getInitParameter(name));
initModuleMessageResources(moduleConfig);
initModuleDataSources(moduleConfig);
initModulePlugIns(moduleConfig);
moduleConfig.freeze();
}
这部分代码,就是把web.xml配置的多个模块的信息读入,重复刚才的初始化信息,因为默认的config已经初始化了,就continue过去了,呵呵,感觉这样写的好处是把config第一初始化,然后再初始化其他模块,但是不明白这样的顺序有什么关系,懒的理了,还有它的初始话,基本上都是把配置信息读到类里,然后再getServletContext().setAttribute,这样就加速了struts的运行期速度,如果有人要用Struts做业务平台,自动产生代码的话,这些东东估计都要重写了。
Servlet的init工作结束了,省下的我们来看一下运行期的代码轨迹。我们简单建了action
public class UserAction extends DispatchAction {
public ActionForward hello(
ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response) {
UserForm userForm = (UserForm) form;
System.out.println("hhhhhhhhhhhhhhhhhhh");
return mapping.findForward("tt");
}
} 然后启动tomcat,请求hello的方法http://localhost:7001/struts1.1/user.do?do=hello
通过web.xml我们知道了,actionServlet mapping了,do的url,所以这个请求肯定到了actionserlvet的get方法,我们看代码,发现无论是get还是post方法都是调用了process(request, response);我们进入这个方法 RequestUtils.selectModule(request, getServletContext());
getRequestProcessor(getModuleConfig(request)).process
(request, response);
呵呵,比较简单,只有2句话,其实可以猜测出这两句话,一个是把url转换成对应的类的调用情况,然后再调用相应的类的相应的方法,我们分别进入,仔细看一下,RequestUtils.selectModule(request, getServletContext());
RequestUtils这个类看名字都知道它的用处了,这个方法先是检查这个请求是属于哪个配置文件里的,我们这里没有其他的配置文件只有一个,所有没有前缀。这个方法的主要作用是把init方法读入的那些信息从ServletContext中再读去到request对象中。以备下面的方法调用,原因目前不详。不过看代码这些细节不必一次全部搞的特别清楚,能明确大的流程就行了,呵呵。
getRequestProcessor(getModuleConfig(request)).process
(request, response);
先是执行getModuleConfig(request))方法,拿到config的真正实现类,也是从request中拿的,如果request没有,则去ServletContext去拿。直接的类是ModuleConfigImpl,再把这个类,作为参数传入actionserlvet的getRequestProcessor方法,这个方法有关键字synchronized,通过这个关键字,来处理Struts的多线程的问题。这个方法是实力化RequestProcessor,如果有的话,就不实例化了,其实也是个单例的应用,并对这个类多了init无非是把那些配置信息又加载到这个类里,下面我们看关键的process方法,我们已经加载了很多配置信息,接下来,肯定要做的就是把这些配置信息和url做匹配,找到真正的类的运行方法,其实也就是找到我们的action类,根据java的面向对象的特性,它肯定是初始化了基类,再调用我们的业务代码。
request = processMultipart(request); 封装request对象,平时一般的请求,直接返回request对象。 String path = processPath(request, response); 拿到url中的/user 通过这个参数 实例化一个ActionMapping 的类,
这个类是把 <action
attribute="userForm"
name="userForm"
parameter="do"
path="/user"
scope="request"
type="com.nbw.struts.action.UserAction"
validate="false">
<forward
name="tt"
path="/tt.jsp"
contextRelative="true" />
</action> 取取到对象中。再作为参数 实例化ActionForm form = processActionForm(request, response, mapping); 然后processPopulate(request, response, form, mapping);把请求中的request对象参数值,复制到对应的form 的参数中,这里用到了beanutil的开源jar,Struts和beanutil结合的还是比较密切的,它在1.2的版本里也给beanutil捐献了一些基类,但是beanutil本身存在一些问题,这有可能是个隐患。
Action action = processActionCreate(request, response, mapping);根据mapping 对象实例了action类,我们知道,我们的所有的action都是继承过来的,这个方法里的有意思的地方是,它先去HashMap actions 里取寻找类的实例。如果没有再实例,再放到hashmap中,这行代码我们知道了action的子类其实是个单例,这叫要我们写业务代码的人,注意到这点,action里的变量其实是全局变量,是多人共享的。
processActionPerform(request, response,
action, form, mapping)
利用类的反射机智,执行actio的hello方法,
processForwardConfig(request, response, forward);进行跳转,到页面显示的jsp
再jsp里Struts有封装了很多的tag方便显示。不过那些和Struts的运行轨迹已经没有关系了,大家也没必要看了,