仿Struts2自己写个MVC

发现struts2要配的东西还不少,但对于一般的小项目 ,其实没有必要用到那么多功能。特别struts2和现在的好多云平台(BAE,GAE)都不是很好兼容。所以自己写的了一个MVC的东东代替STRTUS2,其实也是自己用习惯了。一般小项目主要用到还是STRUTS ACTION相关的功能。

先说MVC的一个流程吧:

1, 初始化Action相关的配置。(只要做一次就好了)

2, 截获相应的请求并根据配置初始化Action

3, 把对应的Request里的内容填充到Action。

4. 执行Action。

5, 根据Action的返回的结果填充Request并跳转。

1, 初始化Action相关的配置。(只要做一次就好了)

 Struts2 conversion很好用,所以这个一定要仿,其实就是扫到相应包里的Class,再把他们做成Action.因为后面很多工作都要用到反射,所以把第一次反射后拿到的方法缓存起来。动手吧:

初始化配,因为还WEB应用,所以在web.xml里面用listener加载 相应配置:

    <listener>
        <listener-class>
            com.demo.config.LifecycleInit
        </listener-class>
    </listener>

 在这个类里扫描ACTION的包并加到配置类里去:

        Set<Class<?>> classes = Utils.getClasses(Constants.ACTION_PACKAGE);
        for (Class c : classes) {
            MyConfig.getMyConfig().addConfigByClass(c);
        }

 getClasses方法不多说了,可以看源代码,就是扫描到包里所有的类。

MyConfig是一个单例,因为配置类只要一份就OK了。addConfigByClass里会做更细的配置。

        if (StringUtils.endsWith(clazz.getName(), Constants.ACTION_SUFFIX)) {
            ActionConfig actionConfig = ActionConfig.buildByClass(clazz);
            if (actionConfigs.get(actionConfig.getUrlName()) != null)
                throw new RuntimeException("duplicate action class found" + clazz.getName());
            actionConfigs.put(actionConfig.getUrlName().toLowerCase(), actionConfig);
        }

 ActionConfig.buildByClassu将创建具体的Action配置类。

        Method[] methods = clazz.getDeclaredMethods();
        //缓存方法
        for (Method method : methods) {
            String methodName = method.getName();
            if (StringUtils.startsWith(methodName, "get")) {//缓存数据方法
                if (!StringUtils.endsWith(methodName,"Dao") )  {
                    String proNameTmp = StringUtils.removeStart(methodName,"get" );
                    String proName  = proNameTmp.substring(0,1).toLowerCase()+proNameTmp.substring(1);
                    this.getMethod.put(proName, method);
                }
            } else { //缓存Action方法
                this.methodMap.put(method.getName().toLowerCase(), method);
            }
        }

 到这里配置就完成了。

2, 截获相应的请求并根据配置初始化Action

还是学Struts用filter来做url截获吧,自己写个ActionFilter

    <filter>
        <filter-name>actionFilter</filter-name>
        <filter-class>com.demo.utils.ActionFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>actionFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

接下来就是这个filter里会根据URL和之前的配置类组装Action.对了Spring还是很好用的,所以里面还是用到了Spring

        //根据名子得到Action
        MyConfig myConfig = MyConfig.getMyConfig();
        ActionConfig actionConfig = myConfig.getActionConfigByName(actionName);

        //从SPRING里拿到Action bean
        ServletContext servletContext = request.getSession().getServletContext();
        BasicAction actionClazz = (BasicAction) servletfindBean(servletContext, actionName.toLowerCase() + Constants.ACTION_SUFFIX);
        
        //初始化Action 的上下文
        actionClazz.setActionConfig(actionConfig);
        actionClazz.setCurMethodName(methodName);
        actionClazz.setRequest(request);
        actionClazz.setResponse(response);
        
        //调用Action dispatch方法
        actionClazz.dispatchUrl();

3, 把对应的Request里的内容填充到Action。

这里自己还是想偷下懒,用到ognl类里的表达式,这样可以很快的request里的数据自动装载到Action里面去

            //云平台一般都要把SecurityManager设置成NULL
            OgnlRuntime.setSecurityManager(null);
            //因为属性是NULL的时候我们要自动构建对应的类
            OgnlRuntime.setNullHandler(Object.class, new BasicAction.OnglNullHandler());
            Map<String, Object> parMap = request.getParameterMap();
            for (String name : parMap.keySet()) {
                Ognl.setValue(name, this, parMap.get(name));
            }

4. 执行Action。

这个简单了:之前就找在filter里找到了Action方法,这里动态调用一下就OK了。

            Method curMethod = actionConfig.getMethodByName(curMethodName);
            String result = (String) curMethod.invoke(this);

5, 根据Action的返回的结果填充Request并跳转。

最后就要把相应的属性放回到request里面去。还记得之前缓存过属性对应的方法,这里正好用得上:

        for (String key : actionConfig.getGetMethod().keySet()) {
            Object value = actionConfig.getGetMethod().get(key).invoke(this);
            if (value != null) {
                request.setAttribute(key, actionConfig.getGetMethod().get(key).invoke(this));
            }
        }

 还差一步就是跳到对应的地方,里面可自己再定义更多的跳法,里面主要有仿TILES,redirect,和直接到JSP。

        if (StringUtils.indexOf(result, Constants.ACTION_RESULT_SPLIT) > -1) {
            String[] results = StringUtils.split(result, Constants.ACTION_RESULT_SPLIT);

            if (results.length == 1) { //跳到默认模板
                request.setAttribute("contentPageJsp", results[0]);
                request.getRequestDispatcher(StringUtils.removeEnd(Constants.ACTION_RESULT_DEFAULT, Constants.ACTION_RESULT_SPLIT)).forward(request, response);
            } else {
                if (StringUtils.removeEnd(Constants.ACTION_RESULT_REDIRECT, Constants.ACTION_RESULT_SPLIT).equals(results[0])) {  //redirect
                    response.sendRedirect(results[1]);
                } else {  //跳到指定模板
                    request.setAttribute("contentPageJsp", results[1]);
                    request.getRequestDispatcher(results[0]).forward(request, response);
                }
            }
        } else {  //跳到JSP
            request.getRequestDispatcher(result).forward(request, response);
        }

再说一下我的JSP里如果是TILES的怎么写吧。其实很简单写INCLUDE就好了,如有必要全写成变量也OK,

        <div id="together">
                <div id="ggw_header" class="ggw_tile"><jsp:include page="/jsp/layout/header.jsp" /> </div>
            <div id="ggw_userheader" class="ggw_tile" style="z-index: 5;">
                <jsp:include page="/jsp/layout/userheader.jsp" />
            </div>
            <div id="ggw_content" class="ggw_tile" style="z-index: 0;"><jsp:include page="${contentPageJsp}"/></div>
            <div id="ggw_footer_transition" class="ggw_tile"></div>
        </div>
        <div id="ggw_footer"><jsp:include page="/jsp/layout/footer.jsp"/></div>

这里也可年到我用的JSTL EL去显示相量,用自己的MVC感觉也还OK,很多东西自己好控制了,不用为了struts2的BUG再烦了,有问题自己修。

用这个写了例子用baidu app engine:http://yijushequ.duapp.com/

猜你喜欢

转载自zlhades.iteye.com/blog/1857017