Struts核心代码ActionServlet的运行漫游

    早在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的运行轨迹已经没有关系了,大家也没必要看了,

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值