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就搞定,而且也符合约定优先配置的设计思想。