CAT源码分析10 - MVC实现原理

深度解析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匹配例子如下:
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值