在第2章我們講到,服務器在初始化CatServlet 之后, 會初始化 MVC,MVC也是繼承自AbstractContainerServlet , 同樣也是一個 Servlet 容器,這是一個非常古老的MVC框架,當時Spring MVC 還並不成熟,但是所有MVC框架的核心思想都是一致的。
在初始化完CatServlet之后,我們就會調用 MVC 的父類 AbstractContainerServlet 去初始化MVC容器,初始化最核心的步驟是調用MVC子類的initComponents(...) 函數去創建請求周期管理器(RequestLifecycle),並將容器上下文(ServletContext)的指針傳遞給 RequestLifecycle。容器類RequestLifecycle 的配置信息位於文件 web-framework-4.0.0.jar/META-INF/plexus/components-web-framework.xml 中。
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