深度解析Cat源码系列专栏点击访问
持续更新中
文章目录
CAT源码分析10 - MVC实现原理
1. 概述
CAT没有使用SpringMVC作为MVC架构的实现框架,而使用了一套自研的MVC框架。虽然与Spring MVC实现不同,但MVC的框架基本原理都非常接近。即通过统计入口拦截请求,读取相关配置,完成参数的读取封装,请求的分发调用。
2. MVC配置详解
2.1 JavaBean + Annocation配置
CAT中的MVC相关配置,如URL方法映射关系,参数映射关系,视图映射关系,都通过JavaBean和Annocation组合的方式进行配置。主要配置类包括
- ReportModule/SystemModule:模块,相关配置的载体,管理所有的处理器Handler
- PagesHandler:具有多种实现,相当于SpringMVC中的controller。接口指定定义了两个方法(handleInBound/handleOutBound),对应请求的输入/输出
- Payload:参数载体,封装请求参数
- JspViewer:view层渲染类,通过Plexus容器component.xml注入到handler中
- JspFile:枚举类,定义在JspViewer中,配置handler返回的所有jsp页面路径
- @ModuleMeta注解:标注在Module类上,用来配置module的name等默认信息
- @ModulePagesMeta注解:标注在Module类上,用来配置该module可以调用的所有Handler
- @PreInboundActionMeta注解:标注在handler的方法上,在inaction之前使用,只用于login方法
- @InActionMeta/@OutActionMeta注解:标注在handler实现类的对应方法上,表明请求路径name
- @PayloadMeta注解:标注在handleInbound方法上,用于说明请求参数类型
- @FieldMeta注解:标注在Payload实现类的属性上,映射参数名
配置之间的关系如下图所示
除了以上配置相关的类,MVC中还有两个跟请求过程相关的类ViewModel和RequestContext。
前者是view渲染时,服务端返回的参数,具有多种实现类,在jsp中通过el表达式获取;
后者是请求时封装的RequestLifeCycle创建的上下文,其中管理请求相关数据,可以创建出Payload
2.2 配置管理和解析
以上是配置类的关系,配置类完成相关配置后,应用的解析主要逻辑如下:
- 在应用启动时,通过Plexus容器初始化ModuleRegistry,该类初始化载入ReportModule/SystemModule两个实现类
- ModelManager自动初始化时,从ModuleRegistry中读取两个Module类,解析类上的注解等配置
- 获取配置后,通过build方法,创建所需的数据和实例
逻辑关系图如下所示:
2.3 解析逻辑
ModuleManager配置解析的代码很多,下面只介绍核心的一些逻辑:
- 初始化,获取所有module,并执行注册Module
- 注册Module时,完成Module中所有Model数据的初始化后,将model和module添加到Manager的集合中
- 解析出所有的handler中的InboundAction/OutboundAction等方法信息,保存到MethodModel中,添加到ModuleModel
完成所有方法信息的初始化,在内存中缓存管理。
// 1. 初始化
public void initialize() throws InitializationException {
// 默认ReportModule
Module defaultModule = m_registry.getDefaultModule();
//通过容器读取所有Module实现类 ReportModule / SystemModule
List<Module> modules = m_registry.getModules();
for (Module module : modules) {
// 注册Module
register(module, defaultModule == module);
}
}
// 2. 注册
void register(Module module, boolean defaultModule) {
// 为Module创建model
ModuleModel model = build(module);
String name = model.getModuleName();
// 获取管理的Module集合,并添加
List<ModuleModel> list = m_modules.get(name);
if (list == null) {
list = new ArrayList<ModuleModel>();
m_modules.put(name, list);
}
list.add(model);
// 其他代码
}
// 3. 创建model
ModuleModel build(Module module) {
Class<?> moduleClass = module.getClass();
// 获取@ModuleMeta注解
ModuleMeta moduleMeta = moduleClass.getAnnotation(ModuleMeta.class);
if (moduleMeta == null) {
throw new RuntimeException(moduleClass + " must be annotated by " + ModuleMeta.class);
}
// 执行buildModule
ModuleModel model = buildModule(module, moduleMeta);
validateModule(model);
return model;
}
private ModuleModel buildModule(Module module, ModuleMeta moduleMeta) {
// 创建ModuleModel
ModuleModel model = new ModuleModel();
Class<?> moduleClass = module.getClass();
// 根据注解信息,设置module的name等数据,如ReportModule的“r”
model.setModuleName(moduleMeta.name());
model.setModuleClass(moduleClass);
model.setDefaultInboundActionName(moduleMeta.defaultInboundAction());
model.setDefaultTransitionName(moduleMeta.defaultTransition());
model.setDefaultErrorActionName(moduleMeta.defaultErrorAction());
model.setActionResolverInstance(lookupOrNewInstance(moduleMeta.actionResolver()));
// module实例
model.setModuleInstance(module);
// AbstractModule中的@TransitionMeta(name = "default") 方法的初始化
buildModuleFromMethods(model, moduleClass.getMethods(), model.getModuleInstance());
// org.unidal.web.mvc.AbstractModule#getPageHandlers 实现,获取@ModulePagesMeta设置的handlers
Class<? extends PageHandler<?>>[] pageHandlers = module.getPageHandlers();
if (pageHandlers != null) {
// 创建handlerModel, 调用buildModuleFromMethods
buildModuleFromHandlers(model, pageHandlers);
}
return model;
}
// 构建Method相关Model
private void buildModuleFromMethods(ModuleModel module, Method[] methods, Object instance) {
for (Method method : methods) {
int modifier = method.getModifiers();
// 过滤, ignore static, abstract and bridge methods
if (Modifier.isStatic(modifier) || Modifier.isAbstract(modifier) || method.isBridge()) {
continue;
}
// 获取方法上的相应注解
InboundActionMeta inMeta = method.getAnnotation(InboundActionMeta.class);
PreInboundActionMeta preInMeta = method.getAnnotation(PreInboundActionMeta.class);
OutboundActionMeta outMeta = method.getAnnotation(OutboundActionMeta.class);
TransitionMeta transitionMeta = method.getAnnotation(TransitionMeta.class);
ErrorActionMeta errorMeta = method.getAnnotation(ErrorActionMeta.class);
int num = (inMeta == null ? 0 : 1) + (outMeta == null ? 0 : 1) + (transitionMeta == null ? 0 : 1)
+ (errorMeta == null ? 0 : 1);
if (num == 0) {
// 没有任何注解,直接返回
continue;
} else if (num > 1) {
throw new RuntimeException(method + " can only be annotated by one of " + InboundActionMeta.class + ", "
+ OutboundActionMeta.class + ", " + TransitionMeta.class + " or " + ErrorActionMeta.class);
}
// 构建每个方法信息和参数配置信息
if (inMeta != null) {
// buildInbound主要解析方法上的注解,将相关类型保存到Model中
InboundActionModel inbound = buildInbound(module, method, inMeta, preInMeta);
inbound.setModuleInstance(instance);
// 添加到moduleModel
module.addInbound(inbound);
} else if (outMeta != null) {
// 代码
} else if (transitionMeta != null) {
// 代码
} else if (errorMeta != null) {
// 代码
} else {
throw new RuntimeException("Internal error!");
}
}
}
3. MVC流程简介
3.1 Servlet拦截入口
web.xml文件配置入口servlet和拦截的URL,所有的控制台MVC请求都被其拦截。CAT使用org.unidal.web.MVC作为入口的Servlet,代码如下,org.unidal.web.MVC将拦截所有 /r/*
/s/*
url的请求
<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>2</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>mvc-servlet</servlet-name>
<url-pattern>/r/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>mvc-servlet</servlet-name>
<url-pattern>/s/*</url-pattern>
</servlet-mapping>
3.2 MVC类
初始化,MVC继承AbastractServlet,继承HttpServlet。当init时,AbasctractServlet首先初始化Plexus容器,然后调用子类的initComponents完成实现具体初始化逻辑。MVC初始化时
- 根据web.xml配置初始化CAT客户端。cat服务端consumer也受自身的监控,且服务端使用老版本client
- 初始化modules,配置默认false不执行
- 通过容器实例化RequestLifeCycle,该类是所有请求的处理器
代码如下
@Override
protected void initComponents(ServletConfig config) throws Exception {
String contextPath = config.getServletContext().getContextPath();
String path = contextPath == null || contextPath.length() == 0 ? "/" : contextPath;
getLogger().info("MVC is starting at " + path);
// 初始化cat客户端
initializeCat(config);
// 初始化module,实际配置没用
initializeModules(config);
// 实例化请求处理器
m_handler = lookup(RequestLifecycle.class, "mvc");
// 设置servlet上下文
m_handler.setServletContext(config.getServletContext());
config.getServletContext().setAttribute(ID, this);
getLogger().info("MVC started at " + path);
}
private void initializeCat(ServletConfig config) {
// 读配置文件进行初始化
String catClientXml = config.getInitParameter("cat-client-xml");
File clientXmlFile;
if (catClientXml == null) {
clientXmlFile = new File(Cat.getCatHome(), "config/client.xml");
} else if (catClientXml.startsWith("/")) {
clientXmlFile = new File(catClientXml);
} else {
clientXmlFile = new File(Cat.getCatHome(), catClientXml);
}
Cat.initialize(getContainer(), clientXmlFile);
}
@SuppressWarnings("unchecked")
private void initializeModules(ServletConfig config) throws ServletException {
String initModules = config.getInitParameter("init-modules");
if (!"false".equals(initModules)) {
// 默认false,不会执行
}
}
3.3 Service方法
MVC的service方法执行拦截处理所有的请求,经过RequestLifeCycle对请求进行分发处理。
RequestLifeCycle主要逻辑是首先通过RequestContextBuilder对请求进行封装,将其封装为RequestContext,包含了请求的基本信息,并解析、保存了应当跳转的Model层处理器,完成了参数的读取封装;之后,按照PreInbound,Inbound、Transition、Outbound、Execption的逻辑顺序,完成请求的整个生命周期流程。
代码如下:
public void handle(final HttpServletRequest request, final HttpServletResponse response) throws IOException {
// 创建请求上下文,其中已经创建了Module,包含所有的配置映射关系
RequestContext context = m_builder.build(request);
try {
// 处理请求
handleRequest(request, response, context);
} finally {
// 清理上下文
m_builder.reset(context);
}
}
private void handleRequest(final HttpServletRequest request, final HttpServletResponse response,
RequestContext requestContext) throws IOException {
if (requestContext == null) {
showPageNotFound(request, response);
return;
}
// 根据上下文获取Model层实例,并获取输入、输出等Modle类
ModuleModel module = requestContext.getModule();
InboundActionModel inboundAction = requestContext.getInboundAction();
ActionContext<?> actionContext = createActionContext(request, response, requestContext, inboundAction);
Transaction t = Cat.getManager().getPeekTransaction();
if (t == null) { // in case of not CatFilter is configured
t = NullMessage.TRANSACTION;
}
request.setAttribute(CatConstants.CAT_PAGE_URI,
actionContext.getRequestContext().getActionUri(inboundAction.getActionName()));
try {
// 获取各种实现的handler,执行逻辑方法
InboundActionHandler handler = m_actionHandlerManager.getInboundActionHandler(module, inboundAction);
handler.preparePayload(actionContext);
// 处理@PreInboundAction注解标注的方法
if (!handlePreActions(request, response, module, requestContext, inboundAction, actionContext)) {
return;
}
// 处理输入逻辑
handleInboundAction(module, actionContext);
t.addData("module", module.getModuleName());
t.addData("in", actionContext.getInboundAction());
if (actionContext.isProcessStopped()) {
t.addData("processStopped=true");
return;
}
// 过度逻辑
handleTransition(module, actionContext);
t.addData("out", actionContext.getOutboundAction());
// 处理返回逻辑
handleOutboundAction(module, actionContext);
} catch (ActionException e) {
// 处理异常
handleException(request, e, actionContext);
} catch (Exception e) {
handleException(request, e, actionContext);
} catch (Error e) {
handleException(request, e, actionContext);
}
}
@Override
public RequestContext build(HttpServletRequest request) {
// 创建Provider ,Provider模式,一个接口,实现类分开
ParameterProvider provider = buildParameterProvider(request);
String requestModuleName = provider.getModuleName();
ActionResolver actionResolver = (ActionResolver) m_modelManager.getActionResolver(requestModuleName);
if (actionResolver == null) {
return null;
}
UrlMapping urlMapping = actionResolver.parseUrl(provider);
String action = urlMapping.getAction();
InboundActionModel inboundAction = m_modelManager.getInboundAction(requestModuleName, action);
if (inboundAction == null) {
return null;
}
RequestContext context = new RequestContext();
ModuleModel module = m_modelManager.getModule(requestModuleName, action);
urlMapping.setModule(module.getModuleName());
context.setActionResolver(actionResolver);
context.setParameterProvider(provider);
context.setUrlMapping(urlMapping);
context.setModule(module);
context.setInboundAction(inboundAction);
context.setTransition(module.findTransition(inboundAction.getTransitionName()));
context.setError(module.findError(inboundAction.getErrorActionName()));
return context;
}
调用具体的方法实现原理就是通过instance和method信息,执行反射,完成调用,不再赘述。
一个请求流程的URL匹配例子如下: