CAT 源码分析之 MVC 框架

在分析 CATMVC 框架之前,先将自己脑子中有关 MVC 的那点存货,晾干,这样等剖析完 CATMVC 可以做一个公正性的对比。

MVC 到底是个什么东西,即 禅

首先,说的是一个框架概念,一种思想, 一种哲学

说它是一个概念

主要是,抛弃了之前软件开发领域中繁琐的数据链条主义,最为熟悉的 JavaWeb,刚刚兴起的时候,主要在 web.xml 中通过配置一个个的 servlet 进行页面流程或数据的转发与处理,效率低下,配置繁多,一个下来,web.xml 的代码配置量很大,难以翻阅。 MVC 不是在这个时候出现,而是在软件提供给非专业人员操作的时候,就已经存在了。只是在 软件 普及化,民众化,发展了,延伸了。它的模型更加完善,

说它是一种思想

真的,一点都不假,从 软件视图化 进程中,从开始到现在,框架不停的进行迭代升级,从最初 功能简单,操作简单,使用简单,到 功能强大但复杂,操作步骤开始繁多起来,使用上在人可以接受或者被强行接受的程度,在进一步的发展到,功能强大,设计简单,使用便捷。更加适合人的最佳原始的感知认知上。软件的过程经历展示它背后思想的发展阶段。

说它是一种哲学

一种视图的哲学,从看不到,就想看到,到能看到,渴望看到更多,发现能到的很多,但是不能突然中心,找不到重点,回到想看到 重要的实用的 。事物发展的过程。回归到视图最为本质的东西上 – 关注最为重要的,本质的点上。

MVC 模型

整个 MVC 思想 / 学说的发展,其实就是这个 MVC 模型发展、合理、演变趋近完美的过程。

简单说一下 MVC 每个单词代表什么

  • M 数据逻辑层的对象实体
  • V 界面展示页面的数据视图
  • C 负责调配管理 MV 的控制逻辑层。

一级模型阶段 V

单细胞生物,无脑阶段,本能响应反应。

二级模型阶段 MV

多细胞生物,大脑发育不完整,只有本能的反应。通过视图的事件调用数据层的数据进行展示,但是 V 的 感性漠然,表情严肃,不生动。

三级模型阶段 MVC

控制层 C 的加入模型,进化高级生物,物种繁多。主要体现在进行功能管理控制与协调,直接效果是 V 层,表情丰富,生动,多变。

MVC 模型产品

MVC 高速发展的 10 年间,很在技术历史洪流留下一笔的产品有:

  • JSP ,整合 HTML 的页面组件,进行后台的翻译,解释,反馈浏览器一个 HTML 描述文档。
  • JSF,丰富了 JSP ,在 V 发展的,出现了 C 控制的逻辑,解放一些 M 中掺杂的 C 层逻辑
  • Struct 1.xx–2.xxx , 彻底的将 MVC 三层模型定位,并且比较好的诠释 MVC 模型的本质思想逻辑
  • webworkMVC 模型另外一种 实现产品。
  • Springmvc 依赖 Spring 的基础组件 IOC 和 AOPMVC 模型的另外一种实现。
  • 其他小众的,不在说了

MVC 思想的研究

  1. 理解 MVC(Model-View-Controller)模式 - 视图 - 控制器 book.cakephp 网(外文网)
  2. MVC 架构探究及其源码实现 (1)- 理论基础 CSDN 博客 [引用日期 2012-12-13]
  3. ASP.NET MVC CodePlex 网(外文网)[ 引用日期 2012-12-13]
  4. 细说 MVC 框架的几大困惑 51com 网 [ 引用日期 2012-12-13]
  5. Joomla MVC 组件开发 dongxi 网 [ 引用日期 2012-12-13]
  6. Struts VS Spring 两种 MVC 框架比较 天极网 [ 引用日期 2012-12-17]
  7. ASP.NET MVC 官网 .ASP.NET MVC[ 引用日期 2013-04-13]
  8. Mvc 控件工具 gcpowertools[ 引用日期 2013-02-07]

构建一个完美的产品 MVC 需要做什么

  • URL 请求处理器 – 包含过滤,拦截器
  • 视图数据翻译与解析
  • 控制逻辑与业务代码逻辑的解耦

unidal 的 MVC 框架

公司的内部的框架,是对 MVC 思想的实现的产品方案。

项目框架 maven 依赖

<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>3.1.0</version>
</dependency>
<dependency>
    <groupId>javax.servlet.jsp</groupId>
    <artifactId>javax.servlet.jsp-api</artifactId>
    <version>2.3.1</version>
</dependency>
<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.3.2</version>
</dependency>
<dependency>
    <groupId>org.unidal.framework</groupId>
    <artifactId>foundation-service</artifactId>
    <version>4.0.0</version>
</dependency>
<dependency>
    <groupId>org.unidal.framework</groupId>
    <artifactId>web-framework</artifactId>
    <version>4.0.0</version>
</dependency>
<dependency>
    <groupId>org.unidal.framework</groupId>
    <artifactId>test-framework</artifactId>
    <version>4.0.0</version>
</dependency>

** 说明 **

  • web-framework web 框架的 jar 包, 需要依赖 foundation-service
  • test-frameworktest 的框架,封装了 web 运行测试 jetty

HelloWorld 示例

配置 web.xml

<servlet>
    <servlet-name>mvc-servlet</servlet-name>
    <servlet-class>org.unidal.web.MVC</servlet-class>
    <init-param>
        <param-name>cat-client-xml</param-name>
        <param-value>client.xml</param-value>
    </init-param>
    <init-param>
        <param-name>init-modules</param-name>
        <param-value>false</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>mvc-servlet</servlet-name>
    <url-pattern>/r/*</url-pattern>
</servlet-mapping>

** 说明 **

  • 使用 unidal web 框架的启动 Servlet
  • 配置 请求处理的 URL-Pattern/r/*

配置 服务组件

这个服务可以从代码的方式直接生成, 关注 相关系列的文章,有单独的说明。

配置文件的路径 src/main/resources/META-INF/plexus/components-web.xml

<?xml version="1.0" encoding="UTF-8"?>
<plexus>
    <components>
        <component>
            <role>org.unidal.web.mvc.model.ModuleRegistry</role>
            <implementation>org.unidal.web.mvc.model.ModuleRegistry</implementation>
            <configuration>
                <defaultModule>org.dal.demo.report.ReportModule</defaultModule>
            </configuration>
        </component>
        <component>
            <role>org.unidal.web.mvc.Module</role>
            <role-hint>org.dal.demo.report.ReportModule</role-hint>
            <implementation>org.dal.demo.report.ReportModule</implementation>
        </component>
        <component>
            <role>org.dal.demo.report.page.hello.Handler</role>
            <implementation>org.dal.demo.report.page.hello.Handler</implementation>
            <requirements>
                <requirement>
                    <role>org.dal.demo.report.page.hello.JspViewer</role>
                </requirement>
            </requirements>
        </component>
        <component>
            <role>org.dal.demo.report.page.hello.JspViewer</role>
            <implementation>org.dal.demo.report.page.hello.JspViewer</implementation>
            <requirements>
                <requirement>
                    <role>org.unidal.web.mvc.view.model.ModelHandler</role>
                </requirement>
            </requirements>
        </component>
    </components>
</plexus>       

** 解释说明 **

  • org.unidal.web.mvc.model.ModuleRegistry 实现的注册,其目的是为了 org.dal.demo.report.ReportModule 作为参数注入到 org.unidal.web.mvc.model.ModuleRegistry 类的变量 defaultModule 上; 简单说就是赋值操作,从这说没有 Spring 的从通过属性方式注入便捷
  • org.unidal.web.mvc.Module 是 实现类 org.dal.demo.report.ReportModule 的注册。
  • 控制器 org.dal.demo.report.page.hello.HandlerJSP 页面解释器 org.dal.demo.report.page.hello.JspViewer 组件注册
  • 其中,最为核心的 org.dal.demo.report.page.hello.Handler 下面会进行详细的解释。

服务组件类文件

配置文件涉及到类有

org.dal.demo.report.ReportModule

** 源代码 **

package org.dal.demo.report;

import org.unidal.web.mvc.AbstractModule;
import org.unidal.web.mvc.annotation.ModuleMeta;
import org.unidal.web.mvc.annotation.ModulePagesMeta;

@ModuleMeta(name = "r", defaultInboundAction = "hello", defaultTransition = "default", defaultErrorAction = "default")
@ModulePagesMeta({org.dal.demo.report.page.hello.Handler.class})
public class ReportModule extends AbstractModule {


}

** 说明 **

  • 注解 ModuleMeta 的 注解参数 defaultxxxx 定义的 全局 此模块 的 默认情况的参数
    • InboundAction 默认请求入参方法标记 , 当前 HandlerhandleInbound 的,注释 InboundActionMetaname 值是 要求的值
    • Transition 默认事务
    • ErrorAction 发生异常时,默认的通过的配置值 default 这些注释的效果,同 SpringController 的配置。

org.dal.demo.report.page.hello.Handler

** 源代码 **

package org.dal.demo.report.page.hello;

import org.dal.demo.report.ReportPage;
import org.unidal.lookup.annotation.Inject;
import org.unidal.web.mvc.PageHandler;
import org.unidal.web.mvc.annotation.InboundActionMeta;
import org.unidal.web.mvc.annotation.OutboundActionMeta;
import org.unidal.web.mvc.annotation.PayloadMeta;

import javax.servlet.ServletException;
import java.io.IOException;

public class Handler implements PageHandler<Context> {

    @Inject
    private JspViewer m_jspViewer;

    @Override
    @PayloadMeta(Payload.class)
    @InboundActionMeta(name = "hello")
    public void handleInbound(Context ctx) throws ServletException, IOException {System.out.println("Hello handle Inbound");
    }

    @Override
    @OutboundActionMeta(name = "hello")
    public void handleOutbound(Context ctx) throws ServletException, IOException {System.out.println("Hello handle Outbound");

        Model model = new Model(ctx);
        Payload payload = ctx.getPayload();

        model.setAction(payload.getAction());
        model.setPage(ReportPage.HELLO);
        model.setDate(payload.getDate());
        model.setDomain(payload.getDomain());

        model.setData("业务逻辑结果");

        m_jspViewer.view(ctx, model);
    }

}

** 说明 **

  • JspViewer m_jspViewer 通过 注入 方式提供实现
  • 结合上面说的 ReportModule 类中 defaultInboundAction 注解,发现 HandlerhandleInboundInboundActionMeta 的 值就是 hello。这样整理 流程 串联 起来。

org.dal.demo.report.page.hello.JspViewer

** 源代码 **

package org.dal.demo.report.page.hello;

import org.dal.demo.report.ReportPage;
import org.unidal.web.mvc.view.BaseJspViewer;

public class JspViewer extends BaseJspViewer<ReportPage, Action, Context, Model> {

    @Override
    protected String getJspFilePath(Context ctx, Model model) {Action action = model.getAction();

        switch (action) {
            case VIEW:
            case CHECKPOINT:
            case THREAD_DUMP:
                return JspFile.VIEW.getPath();}

        throw new RuntimeException("Unknown action:" + action);
    }
}

关联类 org.dal.demo.report.page.hello.JspFile
package org.dal.demo.report.page.hello;

public enum JspFile {VIEW("/hello.jsp"),

    ;

    private String m_path;

    private JspFile(String path) {m_path = path;}

    public String getPath() {return m_path;}
}

** 说明 **

这里就是一个 界面跳转的分发器

org.dal.demo.report.page.hello.Context

源代码

package org.dal.demo.report.page.hello;

import org.dal.demo.report.ReportContext;

public class Context extends ReportContext<Payload> {


}

说明

这个类的目的,承载 请求的 入参和出参 的 参数面板

org.dal.demo.report.page.Payload

内容面板管理的 参数承载对象,就是 页面的参数承载对象,或者称做 页面参数实体

package org.dal.demo.report.page.hello;

import org.dal.demo.mvc.AbstractReportPayload;
import org.dal.demo.report.ReportPage;
import org.unidal.web.mvc.ActionContext;
import org.unidal.web.mvc.payload.annotation.FieldMeta;

public class Payload extends AbstractReportPayload<Action, ReportPage> {@FieldMeta("op")
    private Action m_action;

    public Payload() {super(ReportPage.HELLO);
    }

    @Override
    public Action getAction() {return m_action;}

    public void setAction(String action) {m_action = Action.getByName(action, Action.VIEW);
    }

    @Override
    public void setPage(String page) {m_page = ReportPage.getByName(page, ReportPage.HELLO);
    }

    @Override
    public void validate(ActionContext<?> ctx) {if (m_action == null) {m_action = Action.VIEW;}
    }
}

org.dal.demo.report.page.hello.Action

请求动作的分发器,就是一个枚举

源代码

package org.dal.demo.report.page.hello;

public enum Action implements org.unidal.web.mvc.Action{CHECKPOINT("checkpoint"),

    THREAD_DUMP("threadDump"),

    VIEW("view");

    private String name;

    public static Action getByName(String name, Action defaultAction) {for (Action action : Action.values()) {if (action.getName().equals(name)) {return action;}
        }

        return defaultAction;
    }

    private Action(String name) {this.name = name;}

    @Override
    public String getName() {return this.name;}


}

JSP 文件

路径 src/main/webapp/hello.jsp

源代码

<%@ page import="org.dal.demo.report.page.hello.Payload" %>
<%@ page import="org.dal.demo.report.page.hello.Model" %>
<%@ page import="com.google.gson.Gson" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Hello 请求 </title>
</head>
<body>
hello 请求界面
<%
    Payload payload = (Payload)request.getAttribute("payload") ;
    Model model = (Model)request.getAttribute("model");

    out.println(payload.getPage().getPath());
    out.println("<br/>");
    out.println(model.getDomain());
    out.println("<br/>");
    out.println(model.getData());
%>
</body>
</html>

HelloWorld 测试

测试运行 Jetty 服务
源代码

package test.org.dal.demo;

import org.eclipse.jetty.servlets.GzipFilter;
import org.eclipse.jetty.webapp.WebAppContext;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.unidal.test.jetty.JettyServer;

import javax.servlet.DispatcherType;
import java.util.EnumSet;

@RunWith(JUnit4.class)
public class TestServer extends JettyServer {

    @Override
    protected String getContextPath() {return "/demo";}

    @Override
    protected int getServerPort() {return 2281;}

    @Override
    protected void postConfigure(WebAppContext context) {context.addFilter(GzipFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST));
    }

    @Test
    public void startWebApp() throws Exception {
        // open the page in the default browser
        display("/demo");
        waitForAnyKey();}
}

运行效果

其中 封装代码类

AbstractReportModel

package org.dal.demo.mvc;

import org.unidal.web.mvc.Action;
import org.unidal.web.mvc.ActionContext;
import org.unidal.web.mvc.Page;
import org.unidal.web.mvc.ViewModel;

import java.text.SimpleDateFormat;
import java.util.*;

public abstract class AbstractReportModel<A extends Action, P extends Page, M extends ActionContext<?>> extends
      ViewModel<P, A, M> {

    private Date m_creatTime;

    private String m_customDate;

    private long m_date;

    private SimpleDateFormat m_dateFormat = new SimpleDateFormat("yyyyMMddHH");

    private SimpleDateFormat m_dayFormat = new SimpleDateFormat("yyyyMMdd");

    private String m_displayDomain;

    private Throwable m_exception;

    private String m_ipAddress;

    private String m_reportType;


    public AbstractReportModel(M ctx) {super(ctx);
    }

    public String getBaseUri() {return buildPageUri(getPage().getPath(), null);
    }

    public Date getCreatTime() {return m_creatTime;}

    // required by report tag
    public Date getCurrentTime() {return new Date();
    }

    public String getCustomDate() {return m_customDate;}

    // required by report tag
    public String getDate() {if (m_reportType != null && m_reportType.length() > 0) {return m_dayFormat.format(new Date(m_date));
        }
        return m_dateFormat.format(new Date(m_date));
    }

    public String getDisplayDomain() {return m_displayDomain;}

    public String getDisplayHour() {Calendar cal = Calendar.getInstance();
        cal.setTimeInMillis(m_date);
        int hour = cal.get(Calendar.HOUR_OF_DAY);
        if (hour < 10) {return "0" + Integer.toString(hour);
        } else {return Integer.toString(hour);
        }
    }

    public abstract String getDomain();

    public abstract Collection<String> getDomains();

    // required by report tag
    public Throwable getException() {return m_exception;}

    public String getIpAddress() {return m_ipAddress;}

    public List<String> getIps() {return new ArrayList<String>();
    }

    public long getLongDate() {return m_date;}


    public String getReportType() {return m_reportType;}

    public void setCreatTime(Date creatTime) {m_creatTime = creatTime;}

    public void setCustomDate(Date start, Date end) {SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
        StringBuilder sb = new StringBuilder();

        sb.append("&startDate=").append(sdf.format(start)).append("&endDate=").append(sdf.format(end));
        m_customDate = sb.toString();}

    public void setDisplayDomain(String displayDomain) {m_displayDomain = displayDomain;}

    public void setException(Throwable exception) {m_exception = exception;}

    public void setIpAddress(String ipAddress) {m_ipAddress = ipAddress;}

    public void setDate(long date) {m_date = date;}

    public void setReportType(String reportType) {m_reportType = reportType;}
}

AbstractReportPayload

package org.dal.demo.mvc;

import org.unidal.lookup.util.StringUtils;
import org.unidal.web.mvc.Action;
import org.unidal.web.mvc.ActionPayload;
import org.unidal.web.mvc.Page;
import org.unidal.web.mvc.payload.annotation.FieldMeta;

import java.text.SimpleDateFormat;

public abstract class AbstractReportPayload<A extends Action, P extends Page> implements ActionPayload<P, A> {

    protected P m_page;


    @FieldMeta("domain")
    private String m_domain = "demo";

    @FieldMeta("date")
    protected long m_date;

    private SimpleDateFormat m_hourlyFormat = new SimpleDateFormat("yyyyMMddHH");

    private SimpleDateFormat m_dayFormat = new SimpleDateFormat("yyyyMMdd");

    protected P m_defaultPage;

    public AbstractReportPayload(P defaultPage) {m_defaultPage = defaultPage;}

    @Override
    public P getPage() {return m_page;}


    public void setPage(P page) {m_page = page;}

    public String getDomain() {return m_domain;}

    public void setDomain(String domain) {if (StringUtils.isNotEmpty(domain)) {m_domain = domain;}
    }

    public long getDate() {return this.m_date;}

    public void setDate(String date) {if(null != date && 0 < date.length()){this.m_date = Long.valueOf(date);
        }

    }
}

ReportContext

package org.dal.demo.report;

import org.unidal.web.mvc.Action;
import org.unidal.web.mvc.ActionContext;
import org.unidal.web.mvc.ActionPayload;
import org.unidal.web.mvc.Page;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;

public class ReportContext<T extends ActionPayload<? extends Page, ? extends Action>> extends ActionContext<T> {

    @Override
    public void initialize(HttpServletRequest request, HttpServletResponse response) {super.initialize(request, response);
    }

}

ReportContext

package org.dal.demo.report;

import org.unidal.web.mvc.Page;
import org.unidal.web.mvc.annotation.ModuleMeta;

public enum ReportPage implements Page {

   ...

   HELLO("hello", "hello", "Hello", "Hello Page", true),

  .....
  ;

   private String m_name;

   private String m_path;

   private String m_title;

   private String m_description;

   private boolean m_standalone;

   private ReportPage(String name, String path, String title, String description, boolean standalone) {
      m_name = name;
      m_path = path;
      m_title = title;
      m_description = description;
      m_standalone = standalone;
   }

   public static ReportPage getByName(String name, ReportPage defaultPage) {for (ReportPage action : ReportPage.values()) {if (action.getName().equals(name)) {return action;}
      }

      return defaultPage;
   }

   public String getDescription() {return m_description;}

   public String getModuleName() {ModuleMeta meta = ReportModule.class.getAnnotation(ModuleMeta.class);

      if (meta != null) {return meta.name();
      } else {return null;}
   }

   @Override
   public String getName() {return m_name;}

   @Override
   public String getPath() {return m_path;}

   public String getTitle() {return m_title;}

   public boolean isStandalone() {return m_standalone;}

   public ReportPage[] getValues() {return ReportPage.values();
   }
}

MVC 的流程

核心组件架构装载预置流程

MVC 运行 正常服务请求流程

posted @ 2021-02-07 22:13  可可逗豆  阅读(332)  评论(0)    收藏  举报