深入詳解美團點評CAT跨語言服務監控(九)CAT管理平臺MVC框架
在第2章我們講到,伺服器在初始化CatServlet 之後, 會初始化 MVC,MVC也是繼承自AbstractContainerServlet , 同樣也是一個 Servlet 容器,這是一個非常古老的MVC框架,當時Spring MVC 還並不成熟,但是所有MVC框架的核心思想都是一致的。
在初始化完CatServlet之後,我們就會呼叫 MVC 的父類 AbstractContainerServlet 去初始化MVC容器,初始化最核心的步驟是呼叫MVC子類的initComponents(...) 函式去建立請求週期管理器(RequestLifecycle),並將容器上下文(ServletContext)的指標傳遞給 RequestLifecycle。容器類RequestLifecycle 的配置資訊位於檔案
public class MVC extends AbstractContainerServlet { protected void initComponents(ServletConfig config) throws Exception { if(this.m_handler == null) { String contextPath = config.getServletContext().getContextPath(); String path = contextPath != null && contextPath.length() != 0?contextPath:"/"; this.getLogger().info("MVC is starting at " + path); this.initializeCat(config); this.initializeModules(config); this.m_handler = (RequestLifecycle)this.lookup(RequestLifecycle.class, "mvc"); this.m_handler.setServletContext(config.getServletContext()); config.getServletContext().setAttribute("mvc-servlet", this); this.getLogger().info("MVC started at " + path); } } }
從上面類圖, 我們看到MVC 擁有DefaultRequestLifecycle的一個例項,用於完成每次請求的生命週期內的所有工作,例如URL的解析,路由的實現,異常處理等等。DefaultRequestLifecycle 擁有3個重要的成員變數,m_servletContext 用於維護servlet容器的上下文,m_builder用於建立請求上下文,它是管理請求解析、路由的核心,m_actionHandlerManager 物件用於管理負責頁面請求的動作執行器,他會被plexus容器例項化。
下面我展示下業務頁面相關的類,如下,然後分析下 MVC 的核心邏輯。
接下來我們先來具體看看 RequestContextBuilder 物件的功能以及初始化的邏輯,如下類圖。
RequestContextBuilder 利用 ModelManager 來管理模組(Module)與動作模型(Model), 模組與動作模型是請求上下文中2個非常重要的概念,不要搞混淆,模組是大的頁面分類,目前整個CAT頁面平臺只有2個模組,分別是報表模組(ReportModule)和系統模組(SystemModule),動作模型則是兩大模組下面具體的頁面路由資訊,他維護具體頁面處理器(Handler)的指標、以及處理的函式,以便在請求到來的時候,根據URL找到相應頁面處理器,將控制權交給頁面處理器(Handler),由頁面處理器進行頁面邏輯處理,比如獲取報表資料、登入、配置修改、頁面展示等等,Module物件的指標和動作模型的資訊都會存在於ModuleModel物件中,ModuleModel相當於MVC框架的路由轉發控制核心,而Handler相當於MVC中的Controller層。接下來我們詳細剖析。
ModelManager 的 ModuleRegistry 物件負責對模組的例項化。ModuleRegistry類實現了Initializable方法,所以在plexus例項化ModuleRegistry之後,遍會呼叫他的initialize()方法例項化ReportModule和SystemModule物件了,如下:
public class ModuleRegistry extends ContainerHolder implements Initializable {
private String m_defaultModuleName;
private Module m_defaultModule;
private List<Module> m_modules;
@Override
public void initialize() throws InitializationException {
if (m_defaultModuleName != null) {
m_defaultModule = lookup(Module.class, m_defaultModuleName);
}
m_modules = lookupList(Module.class);
}
}
我們來看兩個頁面URL:
儲存報表頁面: http://localhost:8080/cat/r/t
配置管理頁面:http://localhost:8080/cat/s/config
上面URL中的 "r" 代表的就是ReportModule,所有的報表相關的頁面都在ReportModule中定義,"s" 則代表SystemModule,系統相關的頁面都在SystemModule中定義,上面引數的"t"和"config"是路由動作(Action),後續將會詳細講解,這裡的 "r"、"s" 實際上是模組名稱,該引數在哪裡定義的呢? 在模組(Module)的java定義檔案中,我們看到模組元資料(ModuleMeta) 的定義如下:
@ModuleMeta(name = "s", defaultInboundAction = "config", defaultTransition = "default", defaultErrorAction = "default")
@ModuleMeta(name = "r", defaultInboundAction = "home", defaultTransition = "default", defaultErrorAction = "default")
在元資料中,就有名稱資訊,除此之外,元資料還定義了模組的預設路由動作,報表預設動作是"home",即我們一進去看到的首頁, 而系統預設動作則是"config"。在兩個模組的java定義檔案中,除了Module元資料的定義之外,還有對該模組下所有頁面處理器(Handler)的定義:
系統頁面處理器:
@ModulePagesMeta({
com.dianping.cat.system.page.login.Handler.class,
com.dianping.cat.system.page.config.Handler.class,
com.dianping.cat.system.page.plugin.Handler.class,
com.dianping.cat.system.page.router.Handler.class
})
報表頁面處理器:
@ModulePagesMeta({
com.dianping.cat.report.page.home.Handler.class,
com.dianping.cat.report.page.problem.Handler.class,
com.dianping.cat.report.page.transaction.Handler.class,
com.dianping.cat.report.page.event.Handler.class,
com.dianping.cat.report.page.heartbeat.Handler.class,
com.dianping.cat.report.page.logview.Handler.class,
com.dianping.cat.report.page.model.Handler.class,
com.dianping.cat.report.page.dashboard.Handler.class,
com.dianping.cat.report.page.matrix.Handler.class,
com.dianping.cat.report.page.cross.Handler.class,
com.dianping.cat.report.page.cache.Handler.class,
com.dianping.cat.report.page.state.Handler.class,
com.dianping.cat.report.page.metric.Handler.class,
com.dianping.cat.report.page.dependency.Handler.class,
com.dianping.cat.report.page.statistics.Handler.class,
com.dianping.cat.report.page.alteration.Handler.class,
com.dianping.cat.report.page.monitor.Handler.class,
com.dianping.cat.report.page.network.Handler.class,
com.dianping.cat.report.page.web.Handler.class,
com.dianping.cat.report.page.system.Handler.class,
com.dianping.cat.report.page.cdn.Handler.class,
com.dianping.cat.report.page.app.Handler.class,
com.dianping.cat.report.page.alert.Handler.class,
com.dianping.cat.report.page.overload.Handler.class,
com.dianping.cat.report.page.database.Handler.class,
com.dianping.cat.report.page.storage.Handler.class,
com.dianping.cat.report.page.activity.Handler.class,
com.dianping.cat.report.page.top.Handler.class
})
我們回過頭來再看ModelManager的初始化過程,在兩個模組被例項化之後 ModelManager 將呼叫 register() 方法註冊模組(Module), 並放入 Map<String, List<ModuleModel>> 物件中, Map 的key是String型別,表示的就是Module名稱, Map的 value 則是 ModuleModel 。
public class ModelManager extends ContainerHolder implements Initializable {
@Inject
private ModuleRegistry m_registry;
private Map<String, List<ModuleModel>> m_modules = new HashMap();
private ModuleModel m_defaultModule;
public void initialize() throws InitializationException {
Module defaultModule = this.m_registry.getDefaultModule();
List<Module> modules = this.m_registry.getModules();
Iterator i$ = modules.iterator();
while(i$.hasNext()) {
Module module = (Module)i$.next();
this.register(module, defaultModule == module);
}
}
void register(Module module, boolean defaultModule) {
ModuleModel model = this.build(module);
String name = model.getModuleName();
List<ModuleModel> list = (List)this.m_modules.get(name);
if(list == null) {
list = new ArrayList();
this.m_modules.put(name, list);
}
((List)list).add(model);
if(defaultModule) {
...
this.m_defaultModule = model;
}
}
}
ModuleModel在什麼時候會被建立、初始化? 在模組註冊函式regiter()中,註冊的第一步,就是呼叫 build() 函式建立 ModuleModel,現在我們詳細剖析整個build的流程,build函式首先提取Module元資料(ModuleMeta),然後呼叫 buildModule() 建立ModuleModel物件,並初始化ModuleModel資訊, 例如設定模組名、模組類資訊、預設動作、以及模組物件(Module)指標,然後呼叫 buildModuleFromMethods 建立路由動作模型(Model)。
public class ModelManager extends ContainerHolder implements Initializable {
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);
} else {
ModuleModel model = this.buildModule(module, moduleMeta);
this.validateModule(model);
return model;
}
}
private ModuleModel buildModule(Module module, ModuleMeta moduleMeta) {
ModuleModel model = new ModuleModel();
Class<?> moduleClass = module.getClass();
model.setModuleName(moduleMeta.name());
model.setModuleClass(moduleClass);
model.setDefaultInboundActionName(moduleMeta.defaultInboundAction());
model.setDefaultTransitionName(moduleMeta.defaultTransition());
model.setDefaultErrorActionName(moduleMeta.defaultErrorAction());
model.setActionResolverInstance(this.lookupOrNewInstance(moduleMeta.actionResolver()));
model.setModuleInstance(module);
this.buildModuleFromMethods(model, moduleClass.getMethods(), model.getModuleInstance());
Class<? extends PageHandler<?>>[] pageHandlers = module.getPageHandlers();
if(pageHandlers != null) {
this.buildModuleFromHandlers(model, pageHandlers);
}
return model;
}
}
路由動作模型(Model)分為4類,分別是InboundActionModel、OutboundActionModel、TransitionModel、ErrorModel,InboundActionModel主要負責變更,OutboundActionModel負責頁面展示,絕大多數頁面都是展示目的,TransitionModel負責轉場,ErrorModel則負責異常處理。
每個動作模型(Model) 中都包含路由頁面處理器(Handler)的指標,上面類圖盡列舉了部分Handler,Handler物件的指標在Model中由 m_moduleInstance 變數維護、以及路由到頁面處理器中的具體哪個方法(Method),在Model中由m_actionMethod 變數維護。路由規則由誰來定?在各個頁面處理器類中,我們看到了四類註解,InboundActionMeta、OutboundActionMeta、TransitionMeta、ErrorActionMeta,他們將決定哪個頁面處理器的哪個函式會被路由到。
public class ModelManager extends ContainerHolder implements Initializable {
private void buildModuleFromHandlers(ModuleModel module, Class<? extends PageHandler<?>>[] handlerClasses) {
Class[] arr$ = handlerClasses;
int len$ = handlerClasses.length;
for(int i$ = 0; i$ < len$; ++i$) {
Class<? extends PageHandler<?>> handlerClass = arr$[i$];
PageHandler<?> handler = (PageHandler)this.lookup(handlerClass);
this.buildModuleFromMethods(module, handlerClass.getMethods(), handler);
}
}
private void buildModuleFromMethods(ModuleModel module, Method[] methods, Object instance) {
Method[] arr$ = methods;
int len$ = methods.length;
for(int i$ = 0; i$ < len$; ++i$) {
Method method = arr$[i$];
int modifier = method.getModifiers();
if(!Modifier.isStatic(modifier) && !Modifier.isAbstract(modifier) && !method.isBridge() && !method.isSynthetic()) {
InboundActionMeta inMeta = (InboundActionMeta)method.getAnnotation(InboundActionMeta.class);
PreInboundActionMeta preInMeta = (PreInboundActionMeta)method.getAnnotation(PreInboundActionMeta.class);
OutboundActionMeta outMeta = (OutboundActionMeta)method.getAnnotation(OutboundActionMeta.class);
TransitionMeta transitionMeta = (TransitionMeta)method.getAnnotation(TransitionMeta.class);
ErrorActionMeta errorMeta = (ErrorActionMeta)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) {
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) {
InboundActionModel inbound = this.buildInbound(module, method, inMeta, preInMeta);
inbound.setModuleInstance(instance);
module.addInbound(inbound);
} else if(outMeta != null) {
OutboundActionModel outbound = this.buildOutbound(module, method, outMeta);
outbound.setModuleInstance(instance);
module.addOutbound(outbound);
} else if(transitionMeta != null) {
TransitionModel transition = this.buildTransition(method, transitionMeta);
transition.setModuleInstance(instance);
if(!module.getTransitions().containsKey(transition.getTransitionName())) {
module.addTransition(transition);
}
} else {
if(errorMeta == null) {
throw new RuntimeException("Internal error!");
}
ErrorModel error = this.buildError(method, errorMeta);
error.setModuleInstance(instance);
if(!module.getErrors().containsKey(error.getActionName())) {
module.addError(error);
}
}
}
}
}
}
}
buildModule(...)函式將會從兩個方面建立路由動作模型,一方面在 buildModule(...) 函式中,有一行程式碼: this.buildModuleFromMethods(model, moduleClass.getMethods(), model.getModuleInstance()); 這行程式碼他會遍歷ReportModule和SystemModule類的所有成員函式,找到被註解的函式,僅有兩個模組的父類 AbstractModule 擁有TransitionMeta、ErrorActionMeta 兩類註解,所有Transition動作將被路由到handleTransition(...)函式內執行,異常將由onError(...)函式處理,事實上這兩個動作沒有做任何事情,如下:
public abstract class AbstractModule implements Module {
@TransitionMeta(name = "default")
public void handleTransition(ActionContext<?> ctx) {
// simple cases, nothing here
}
@ErrorActionMeta(name = "default")
public void onError(ActionContext<?> ctx) {
// ignore error, leave MVC to handle it
}
}
另一方面,在buildModule(...)函式中,module.getPageHandlers() 呼叫可以獲取模組(Module)下的所有頁面處理器(Handler),具體有哪些Handler在之前講解 @ModulePagesMeta 的時候有列舉,大家可以翻前面看看,然後去遍歷這些Handler的成員函式,檢視是否有上面講過的那些註解,如果有,就將被註解的成員函式資訊以及頁面處理器的指標寫入相應的動作模型(Model)中,不同動作模型會被分別加入ModuleModel的如下4個Map成員變數中,Map的 key 是 String型別,指代動作名稱。
public class ModuleModel extends BaseEntity<ModuleModel> {
private Map<String, InboundActionModel> m_inbounds = new LinkedHashMap();
private Map<String, OutboundActionModel> m_outbounds = new LinkedHashMap();
private Map<String, TransitionModel> m_transitions = new LinkedHashMap();
private Map<String, ErrorModel> m_errors = new LinkedHashMap();
}
例如下面的Transaction報表頁面處理器(transaction\Handler) 的handleInbound和handleOutbound 函式,分別被InboundActionMeta 和 OutboundActionMeta 註解了, 那麼對於url : http://localhost:8080/cat/r/t,我們就可以根據 "r" 找到對應的ModuleModel物件,然後根據動作名稱 "t" ,找到對應的路由動作模型, 然後程式控制權將會先後交給InboundActionModel、TransitionModel、OutboundActionModel 維護的頁面處理器(Handler)的執行函式。捕獲的異常將由 ErrorModel 維護的執行函式處理。下面transaction\Handler 的成員函式 handleInbound(...) 和 handleOutbound(...) ,將會先後被執行。
package com.dianping.cat.report.page.transaction;
public class Handler implements PageHandler<Context> {
@Override
@PayloadMeta(Payload.class)
@InboundActionMeta(name = "t")
public void handleInbound(Context ctx) throws ServletException, IOException {
}
@Override
@OutboundActionMeta(name = "t")
public void handleOutbound(Context ctx) throws ServletException, IOException {
...
}
}
除此之外,從程式碼中我們還看到一個PreInboundActionMeta註解,有些動作可能會包含一個或者多個前置動作。而 PreInboundActionMeta 註解就用於標識某個動作的前置動作。
請求處理流程
當有http請求過來,請進入 DefaultRequestLifecycle 的handle(HttpServletRequest request, HttpServletResponse response) 進行處理,該函式首先呼叫 RequestContextBuilder 的 build 函式建立請求上下文, 然後將請求上下文交給動作執行管理物件(ActionHandlerManager) 管理的 InboundActionHandler、TransitionHandler、OutboundActionHandler先後執行,如果捕獲到異常交給 ErrorHandler 處理。
public class DefaultRequestLifecycle implements RequestLifecycle, LogEnabled {
private RequestContextBuilder m_builder;
private ActionHandlerManager m_actionHandlerManager;
private ServletContext m_servletContext;
public void handle(HttpServletRequest request, HttpServletResponse response) throws IOException {
RequestContext context = this.m_builder.build(request);
try {
if(context == null) {
this.showPageNotFound(request, response);
} else {
this.handleRequest(request, response, context);
}
} finally {
if(context != null) {
this.m_builder.reset(context);
}
}
}
}
先看看build(...)函式中上下文的建立邏輯,首先將url請求引數放入請求引數提供物件(ParameterProvider),然後從引數中提取模組名,隨後建立動作解析器,通過解析請求引數。
例如URL:http://localhost:8080/cat/r/e?domain=cat&ip=All&date=2018041918&reportType=day&op=view 解析之後得到如下引數,其中,模組名就是"r",對應ReportModule,動作名是 "t"。
我們通過解析獲得的模組名稱、動作名稱,就可以找到對應的ModuleModel,從中獲取路由動作模型InboundActionModel、TransitionModel、OutboundActionModel、ErrorModel的指標,我們將引數資訊、模組資訊、動作模型資訊全部放入請求上下文(RequestContext) 中, 交給相應的動作執行器去執行。
public class DefaultRequestContextBuilder extends ContainerHolder implements RequestContextBuilder {
@Override
public RequestContext build(HttpServletRequest request) {
ParameterProvider provider = buildParameterProvider(request);
String requestModuleName = provider.getModuleName();
ActionResolver actionResolver = (ActionResolver) m_modelManager.getActionResolver(requestModuleName);
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;
}
}
現在我們回到DefaultRequestLifecycle 的請求處理函式handle(...),在建立完成請求上下文之後,我們便呼叫handleRequest(...)函式來處理請求了,當然,如果請求上下文為空,我們會呼叫showPageNotFound展示404頁面。在handleRequest(...)函式中,我們首先從請求上下文中獲取ModuleModel,以及InboundActionModel,然後建立動作上下文(ActionContext)。
public class DefaultRequestLifecycle implements RequestLifecycle, LogEnabled {
private void handleRequest(final HttpServletRequest request, final HttpServletResponse response,
RequestContext requestContext) throws IOException {
ModuleModel module = requestContext.getModule();
InboundActionModel inboundAction = requestContext.getInboundAction();
ActionContext<?> actionContext = createActionContext(request, response, requestContext, inboundAction);
request.setAttribute(CatConstants.CAT_PAGE_URI, actionContext.getRequestContext().getActionUri(inboundAction.getActionName()));
try {
InboundActionHandler handler = m_actionHandlerManager.getInboundActionHandler(module, inboundAction);
handler.preparePayload(actionContext);
if (!handlePreActions(request, response, module, requestContext, inboundAction, actionContext)) {
return;
}
handleInboundAction(module, actionContext);
if (actionContext.isProcessStopped()) {
return;
}
handleTransition(module, actionContext);
handleOutboundAction(module, actionContext);
} catch (Throwable e) {
handleException(request, e, actionContext);
}
}
}
動作上下文(ActionContext) 會攜帶一些與該動作相關的資訊, 包含HttpServletRequest、HttpServletResponse、請求上下文(RequestContext)、Servlet上下文(ServletContext)、Payload,Payload是URL後面帶的引數,是一種資料傳輸物件(DTO),例如上面URL的 domain=cat&ip=All&date=2018041918&reportType=day&op=view,具體生成哪個DTO類,由InboundActionModel的一個成員變數 m_payloadClass決定,這個成員變數在MVC初始化時,建立動作模型InboundActionModel的時候,即在buildInbound(...)函式內被賦值,如下程式碼,在下方inbound 函式上,被 PayloadMeta 註解的類就是該上下文的DTO類,上面類圖僅列舉了部分Payload。
每個動作,都有對應的動作上下文,具體生成哪個上下文類,是由InboundActionModel的一個成員變數 m_contextClass 決定,也是在buildInbound(...)函式內被賦值,如下程式碼,他將inbound函式的第一個引數的型別賦給m_contextClass,即再下面程式碼中handleInbound(Context ctx) 函式的引數型別Context, outbound函式與inbound函式引數相同,就這樣,Handler通過上下文 Context 與 MVC 容器之間交換引數資料、請求、應答等資訊。
public class ModelManager extends ContainerHolder implements Initializable {
private Class<?> m_contextClass;
private Class<?> m_payloadClass;
private InboundActionModel buildInbound(ModuleModel module, Method method, InboundActionMeta inMeta, PreInboundActionMeta preInMeta) {
...
inbound.setContextClass(method.getParameterTypes()[0]);
if (preInMeta != null) {
inbound.setPreActionNames(preInMeta.value());
}
PayloadMeta payloadMeta = method.getAnnotation(PayloadMeta.class);
if (payloadMeta != null) {
inbound.setPayloadClass(payloadMeta.value());
}
...
}
}
public class Handler implements PageHandler<Context> {
@Override
@PayloadMeta(Payload.class)
@InboundActionMeta(name = "t")
public void handleInbound(Context ctx) throws ServletException, IOException {
}
@Override
@OutboundActionMeta(name = "t")
public void handleOutbound(Context ctx) throws ServletException, IOException {
...
}
}
接下來,通過 InboundActionHandler 去執行 inbound 邏輯,我們將會在首次使用 InboundActionHandler 的時候例項化,每個模組的每個動作都對應一個InboundActionHandler,如下:
public class DefaultActionHandlerManager extends ContainerHolder implements ActionHandlerManager {
public InboundActionHandler getInboundActionHandler(ModuleModel module, InboundActionModel inboundAction) {
String key = module.getModuleName() + ":" + inboundAction.getActionName();
InboundActionHandler actionHandler = (InboundActionHandler)this.m_inboundActionHandlers.get(key);
if(actionHandler == null) {
... //執行緒安全
actionHandler = (InboundActionHandler)this.lookup(InboundActionHandler.class);
actionHandler.initialize(inboundAction);
this.m_inboundActionHandlers.put(key, actionHandler);
}
return actionHandler;
}
}
例項化之後便呼叫 InboundActionHandler 的 initialize(...) 函式初始化,初始化的核心,在於通過PayloadProvider註冊PayloadModel,之前提到,Payload是一種資料傳輸物件(DTO),每個頁面動作都有各自的Payload,我們會通過 PayloadProvider 將各個Payload描述資訊,載入到 PayloadModel物件中,然後寫入DefaultPayloadProvider的成員變數 m_payloadModels中,該變數是個 Map 型別, Map的 key 是類通配泛型Class<?>,表示Payload類,具體是哪個類,之前有說明過。
每個Payload都有可能由3種類型的欄位組成,普通欄位(Field), 物件欄位(Object)和路徑(Path), 分別由 FieldMeta、ObjectMeta、PathMeta 三個註解所描述,如下config頁面的Payload資料(實際上這個Payload沒有Path,為了示範我加了進來),他們的描述資訊將會被分別寫入 PayloadModel 的 m_fields、m_objects、m_paths 三個列表裡。
package com.dianping.cat.system.page.config;
public class Payload implements ActionPayload<SystemPage, Action> {
@FieldMeta("op")
private Action m_action;
private SystemPage m_page;
@ObjectMeta("project")
private Project m_project = new Project();
@ObjectMeta("patternItem")
private PatternItem m_patternItem = new PatternItem();
...
@FieldMeta("domain")
private String m_domain;
@FieldMeta("domains")
private String[] m_domains = new String[100];
@FieldMeta("from")
private String m_from;
@FieldMeta("id")
private int m_id;
...
@PathMeta("path")
private String[] m_path;
}
在InboundActionHandler 被初始化之後,我們將首先用它來呼叫她的 preparePayload(...) 函式處理URL引數資訊,如domain=cat&ip=All&date=2018041918&reportType=day&op=view,PayloadProvider 的process 方法會遍歷InboundActionHandler初始化時註冊的PayloadModel中的m_fields、m_objects、m_paths 三個列表,按照描述資訊,將URL引數資訊寫入Payload物件,之後將 payload 裝入動作上下文(ActionContext),帶入Action函式。
public class DefaultInboundActionHandler extends ContainerHolder implements InboundActionHandler, LogEnabled {
@Override
public void preparePayload(ActionContext ctx) {
if (m_payloadClass != null) {
RequestContext requestContext = ctx.getRequestContext();
ActionPayload payload = createInstance(m_payloadClass);
payload.setPage(requestContext.getAction());
m_payloadProvider.process(requestContext.getUrlMapping(), requestContext.getParameterProvider(), payload);
payload.validate(ctx);
ctx.setPayload(payload);
}
}
}
載入Payload之後,RequestLifecycle 會呼叫 handlePreActions(...) 去執行 inbound 動作的前置動作,但是僅配置(config)相關的頁面會有前置動作(login),如下,即必須是已登陸的使用者才可以修改配置,這裡我不做詳細講解,
public class Handler implements PageHandler<Context> {
@Override
@PreInboundActionMeta("login")
@PayloadMeta(Payload.class)
@InboundActionMeta(name = "config")
public void handleInbound(Context ctx) throws ServletException, IOException {
// display only, no action here
}
@Override
@PreInboundActionMeta("login")
@OutboundActionMeta(name = "config")
public void handleOutbound(Context ctx) throws ServletException, IOException {
...
}
}
然後RequestLifecycle將會通過inbound動作執行器去執行inbound方法,這個過程有很多的校驗工作,其實最核心的就是下面一行程式碼,他利用java反射機制,去呼叫在 InboundActionModel 中註冊的 Handler 物件的 inbound 方法,並將動作上下文(ActionContext) 作為引數傳遞過去。接下來的transition、outbound動作的呼叫,異常處理的 ErrorAction 也都是這種方式。
public class DefaultInboundActionHandler extends ContainerHolder implements InboundActionHandler, LogEnabled {
private InboundActionModel m_inboundAction;
public void handle(ActionContext ctx) throws ActionException {
...
ReflectUtils.invokeMethod(this.m_inboundAction.getActionMethod(), this.m_inboundAction.getModuleInstance(), new Object[]{ctx});
...
}
}
最後在Handler處理完成之後,DefaultRequestLifecycle將呼叫 this.m_builder.reset(context) 來重置請求上下文。
頁面處理器Handler