mvc框架的一个重要的作用就是根据用户的url请求,来调用相应的方法。
首先自然是对url进行解析了,这里有两种方法一是采用filter方式,另一种则是servlet方式。
采用servlet方式的需要在web.xml进行如下配置:
<servlet> <servlet-name>nutServlet</servlet-name> <servlet-class>org.nutz.mvc.NutServlet</servlet-class> <init-param> <param-name>modules</param-name> <param-value>org.nutz.controller.HelloController</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>nutServlet</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping>
这样请求过来后就得先经过NutServlet了。
主要的方法是init和service。init进行初始化,将定义好的controller和url的映射关系保存起来,然后在service方法中就可以根据request的path来获取相应的controller的方法处理了。
NutServlet init方法
public void init() throws ServletException { if (log.isInfoEnabled()) { URL me = Thread.currentThread() .getContextClassLoader() .getResource(NutServlet.class.getName().replace('.', '/') + ".class"); log.infof("Nutz Version : %s in %s", Nutz.version(), me); } config = new ServletNutConfig(getServletConfig()); Loading ing = Inits.init(config, false); urls = ing.getUrls(); ok = true; }
NutServlet service方法
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { if (null == urls) { if (log.isErrorEnabled()) log.error("!!!This servlet is destroyed!!! Noting to do!!!"); return; } Mvcs.updateRequestAttributes(req); String path = Mvcs.getRequestPath(req); if (log.isInfoEnabled()) log.info("HttpServletRequest path = " + path); // get Url and invoke it ActionInvoking ing = urls.get(path); if (null == ing || null == ing.getInvoker()) resp.setStatus(404); else ing.invoke(config.getServletContext(), req, resp); }
代码中可以看到contoller和url对应的关系保存到了UrlMap urls对象中。init的关键方法在
Loading ing = Inits.init(config, false); urls = ing.getUrls();
Inits 的init方法的基本思想就是遍历所有的controller的方法,再根据At注解中定义的响应url来建立url和controller中各个方法的关联关系。
这里有个问题就是nutz必须要个maincontroller,其他的controller必须在这个controller里定义,系统才能找到。在上面的web.xml中helloController就是。我们公司的mvc框架则采用其他的方式来寻找系统中的Controller,就是通过一个properties将所有的Controller类定义到里面,init里面遍历这个properties就行了。
Inits的init方法:
...... Class<? extends Loading> loadingType; LoadingBy lb = mainModule.getAnnotation(LoadingBy.class); if (null != lb) loadingType = lb.value(); else loadingType = DefaultLoading.class; if (log.isDebugEnabled()) log.debug("Loading by " + loadingType); // Here, we load all Nutz.Mvc configuration Loading ing = Mirror.me(loadingType).born(); ing.load(config, mainModule); ........
我们没有写loading的实现,就只得使用defaultLoading了。 进入DefaultLoading类。
在这里我们又看到了UrlMap urls变量了,组装好这个变量,NutServlet的 urls就能用了。
DefaultLoading 的load方法 还是干了很多事的
this.mainModule = mainModule; createContent(config); if (log.isDebugEnabled()) log.debug("Loading configuration..."); loadIoc(config); loadSubModules(config); loadLocalization(config); setupServer(config); saveResult2Context(config);
这里主要看loadSubModules。
protected void loadSubModules(NutConfig config) throws Throwable { Views vms = mainModule.getAnnotation(Views.class); // Prepare view makers List<ViewMaker> makers = new ArrayList<ViewMaker>(); if (null != vms) for (Class<? extends ViewMaker> type : vms.value()) makers.add(type.newInstance()); makers.add(new DefaultViewMaker());// 优先使用用户自定义 // Load modules if (log.isDebugEnabled()) log.debugf("MainModule: <%s>", mainModule.getName()); urls = makeUrlMap(config, context, mainModule); Set<Class<?>> moduleSet = new HashSet<Class<?>>(); // Add default module moduleSet.add(mainModule); // Then try to load sub-modules Modules modules = mainModule.getAnnotation(Modules.class); Class<?>[] moduleRefers; if (null == modules || null == modules.value() || modules.value().length == 0) moduleRefers = new Class<?>[]{mainModule}; else moduleRefers = modules.value(); // 扫描所有的 boolean isNeedScanSubPackages = null == modules ? false : modules.scanPackage(); for (Class<?> module : moduleRefers) { // 扫描这个类同包,以及所有子包的类 if (isNeedScanSubPackages) { if (log.isDebugEnabled()) log.debugf(" > scan '%s'", module.getPackage().getName()); List<Class<?>> subs = Scans.me().scanPackage(module); for (Class<?> sub : subs) { if (isModule(sub)) { if (log.isDebugEnabled()) log.debugf(" >> add '%s'", sub.getName()); moduleSet.add(sub); } else if (log.isTraceEnabled()) { log.tracef(" >> ignore '%s'", sub.getName()); } } } // 仅仅加载自己 else { if (isModule(module)) { if (log.isDebugEnabled()) log.debugf(" > add '%s'", module.getName()); moduleSet.add(module); } else if (log.isTraceEnabled()) { log.tracef(" > ignore '%s'", module.getName()); } } } for (Class<?> module : moduleSet) { if (log.isDebugEnabled()) log.debugf("Module: <%s>", module.getName()); urls.add(makers, module); } config.setAttributeIgnoreNull(UrlMap.class.getName(), urls); }
程序先生成个UrlMap,然后就挨个扫描,把所有的Controller找到,然后再每个Controller单独处理,其实就是读At注解,把要处理的url和method关联起来,这个是在urls.add(makers, module);的时候做的。
里面的代码还是挺烦的,看到NutServlet 的service方法里面的ActionInvoking 对象了吗,urls 添加 module的时候就会为moudule中所有有At注解的方法,生成个这个类型的对象,并和At注解里面定义的path关联起来。
Nutz 的Mvc框架大体流程就是这样了。个人觉得还是有些复杂,如果做的更轻量点的话,可以这么做:
直接将url分成两个部分moudle和method。
比如url为如下格式:http://www.test.com/home/index.do
这样的url会调用HomeController(module)的index(method)来处理。这样path对应于方法的映射关系很简单,一个HashMap就搞定,而且也符合约定优先配置的设计思想。