CAT 源码分析之 MVC 框架
在分析 CAT
的 MVC
框架之前,先将自己脑子中有关 MVC
的那点存货,晾干,这样等剖析完 CAT
的 MVC
可以做一个公正性的对比。
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
模型的本质思想逻辑webwork
对MVC
模型另外一种 实现产品。Springmvc
依赖Spring 的基础组件 IOC 和 AOP
对MVC
模型的另外一种实现。- 其他小众的,不在说了
MVC 思想的研究
- 理解 MVC(Model-View-Controller)模式 - 视图 - 控制器 book.cakephp 网(外文网)
- MVC 架构探究及其源码实现 (1)- 理论基础 CSDN 博客 [引用日期 2012-12-13]
- ASP.NET MVC CodePlex 网(外文网)[ 引用日期 2012-12-13]
- 细说 MVC 框架的几大困惑 51com 网 [ 引用日期 2012-12-13]
- Joomla MVC 组件开发 dongxi 网 [ 引用日期 2012-12-13]
- Struts VS Spring 两种 MVC 框架比较 天极网 [ 引用日期 2012-12-17]
- ASP.NET MVC 官网 .ASP.NET MVC[ 引用日期 2013-04-13]
- 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-framework
是test
的框架,封装了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.Handler
和JSP
页面解释器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
默认请求入参方法标记 , 当前Handler
中handleInbound
的,注释InboundActionMeta
的name
值是 要求的值Transition
默认事务ErrorAction
发生异常时,默认的通过的配置值default
这些注释的效果,同Spring
的Controller
的配置。
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
注解,发现Handler
的handleInbound
的InboundActionMeta
的 值就是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 运行 正常服务请求流程