在設計完API后,我們就需要實(shí)現這個(gè)MVC框架。MVC框架的核心是一個(gè)DispatcherServlet,用于接收所有的HTTP請求,并根據URL選擇合適的Action對其進(jìn)行處理。在這里,和Struts不同的是,所有的組件均被IoC容器管理,因此,DispatcherServlet需要實(shí)例化并持有Guice IoC容器,此外,DispatcherServlet還需要保存URL映射和Action的對應關(guān)系,一個(gè)Interceptor攔截器鏈,一個(gè)ExceptionResolver處理異常。DispatcherServlet定義如下:
package com.javaeedev.lightweight.mvc;
/**
* Core dispatcher servlet.
*
* @author Xuefeng
*/
public class DispatcherServlet extends HttpServlet {
private Log log = LogFactory.getLog(getClass());
private Map<String, ActionAndMethod> actionMap;
private Interceptor[] interceptors = null;
private ExceptionResolver exceptionResolver = null;
private ViewResolver viewResolver = null;
private Injector injector = null; // Guice IoC容器
...
}
Guice的配置完全由Java 5注解完成,而在DispatcherServlet中,我們需要主動(dòng)從容器中查找某種類(lèi)型的Bean,相對于客戶(hù)端被動(dòng)地使用IoC容器(客戶(hù)端甚至不能感覺(jué)到IoC容器的存在),DispatcherServlet需要使用ServiceLocator模式主動(dòng)查找Bean,寫(xiě)一個(gè)通用方法:
private List<Key<?>> findKeysByType(Injector inj, Class<?> type) {
Map<Key<?>, Binding<?>> map = inj.getBindings();
List<Key<?>> keyList = new ArrayList<Key<?>>();
for(Key<?> key : map.keySet()) {
Type t = key.getTypeLiteral().getType();
if(t instanceof Class<?>) {
Class<?> clazz = (Class<?>) t;
if(type==null || type.isAssignableFrom(clazz)) {
keyList.add(key);
}
}
}
return keyList;
}
DispatcherServlet初始化時(shí)就要首先初始化Guice IoC容器:
public void init(ServletConfig config) throws ServletException {
String moduleClass = config.getInitParameter("module");
if(moduleClass==null || moduleClass.trim().equals(""))
throw new ServletException("Cannot find init parameter in web.xml: <servlet>"
+ "<servlet-name>?</servlet-name><servlet-class>"
+ getClass().getName()
+ "</servlet-class><init-param><param-name>module</param-name><param-value>"
+ "put-your-config-module-full-class-name-here</param-value></init-param></servlet>");
ServletContext context = config.getServletContext();
// init guice:
injector = Guice.createInjector(Stage.PRODUCTION, getConfigModule(moduleClass.trim(), context));
...
}
然后,從IoC容器中查找Action和URL的映射關(guān)系:
private Map<String, ActionAndMethod> getUrlMapping(List<Key<?>> actionKeys) {
Map<String, ActionAndMethod> urlMapping = new HashMap<String, ActionAndMethod>();
for(Key<?> key : actionKeys) {
Object obj = safeInstantiate(key);
if(obj==null)
continue;
Class<Action> actionClass = (Class<Action>) obj.getClass();
Annotation ann = key.getAnnotation();
if(ann instanceof Named) {
Named named = (Named) ann;
String url = named.value();
if(url!=null)
url = url.trim();
if(!"".equals(url)) {
log.info("Bind action [" + actionClass.getName() + "] to URL: " + url);
// link url with this action:
urlMapping.put(url, new ActionAndMethod(key, actionClass));
}
else {
log.warn("Cannot bind action [" + actionClass.getName() + "] to *EMPTY* URL.");
}
}
else {
log.warn("Cannot bind action [" + actionClass.getName() + "] because no @Named annotation found in config module. Using: binder.bind(MyAction.class).annotatedWith(Names.named(\"/url\"));");
}
}
return urlMapping;
}
我們假定客戶(hù)端是以如下方式配置Action和URL映射的:
public class MyModule implements Module {
public void configure(Binder binder) {
// bind actions:
binder.bind(Action.class)
.annotatedWith(Names.named("/start.do"))
.to(StartAction.class);
binder.bind(Action.class)
.annotatedWith(Names.named("/register.do"))
.to(RegisterAction.class);
binder.bind(Action.class)
.annotatedWith(Names.named("/signon.do"))
.to(SignonAction.class);
...
}
}
即通過(guò)Guice提供的一個(gè)注解Names.named()指定URL。當然還可以用其他方法,比如標注一個(gè)@Url注解可能更方便,下一個(gè)版本會(huì )加上。
Interceptor,ExceptionResolver和ViewResolver也是通過(guò)查找獲得的。
下面討論DispatcherServlet如何真正處理用戶(hù)請求。第一步是根據URL查找對應的Action:
String contextPath = request.getContextPath();
String url = request.getRequestURI().substring(contextPath.length());
if(log.isDebugEnabled())
log.debug("Handle for URL: " + url);
ActionAndMethod am = actionMap.get(url);
if(am==null) {
response.sendError(HttpServletResponse.SC_NOT_FOUND); // 404 Not Found
return;
}
沒(méi)找到Action就直接給個(gè)404 Not Found,找到了進(jìn)行下一步,實(shí)例化一個(gè)Action并填充參數:
// init ActionContext:
HttpSession session = request.getSession();
ServletContext context = session.getServletContext();
ActionContext.setActionContext(request, response, session, context);
// 每次創(chuàng )建一個(gè)新的Action實(shí)例:
Action action = (Action) injector.getInstance(am.getKey());
// 把HttpServletRequest的參數自動(dòng)綁定到Action的屬性中:
List<String> props = am.getProperties();
for(String prop : props) {
String value = request.getParameter(prop);
if(value!=null) {
am.invokeSetter(action, prop, value);
}
}
注意,為了提高速度,所有的set方法已經(jīng)預先緩存了,因此避免每次請求都用反射重復查找Action的set方法。
然后要應用所有的Interceptor以便攔截Action:
InterceptorChainImpl chains = new InterceptorChainImpl(interceptors);
chains.doInterceptor(action);
ModelAndView mv = chains.getModelAndView();
實(shí)現InterceptorChain看上去復雜,其實(shí)就是一個(gè)簡(jiǎn)單的遞歸,大家看InterceptorChainImpl代碼就知道了:
package com.javaeedev.lightweight.mvc;
/**
* Used for holds an interceptor chain.
*
* @author Xuefeng
*/
class InterceptorChainImpl implements InterceptorChain {
private final Interceptor[] interceptors;
private int index = 0;
private ModelAndView mv = null;
InterceptorChainImpl(Interceptor[] interceptors) {
this.interceptors = interceptors;
}
ModelAndView getModelAndView() {
return mv;
}
public void doInterceptor(Action action) throws Exception {
if(index==interceptors.length)
// 所有的Interceptor都執行完畢:
mv = action.execute();
else {
// 必須先更新index,再調用interceptors[index-1],否則是一個(gè)無(wú)限遞歸:
index++;
interceptors[index-1].intercept(action, this);
}
}
}
把上面的代碼用try ... catch包起來(lái),就可以應用ExceptionResolver了。
如果得到了ModelAndView,最后一步就是渲染View了,這個(gè)過(guò)程極其簡(jiǎn)單:
// render view:
private void render(ModelAndView mv, HttpServletRequest reqest, HttpServletResponse response) throws ServletException, IOException {
String view = mv.getView();
if(view.startsWith("redirect:")) {
// 重定向:
String redirect = view.substring("redirect:".length());
response.sendRedirect(redirect);
return;
}
Map<String, Object> model = mv.getModel();
if(viewResolver!=null)
viewResolver.resolveView(view, model, reqest, response);
}
最簡(jiǎn)單的JspViewResolver的實(shí)現如下:
package com.javaeedev.lightweight.mvc.view;
/**
* Let JSP render the model returned by Action.
*
* @author Xuefeng
*/
public class JspViewResolver implements ViewResolver {
/**
* Init JspViewResolver.
*/
public void init(ServletContext context) throws ServletException {
}
/**
* Render view using JSP.
*/
public void resolveView(String view, Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
if(model!=null) {
Set<String> keys = model.keySet();
for(String key : keys) {
request.setAttribute(key, model.get(key));
}
}
request.getRequestDispatcher(view).forward(request, response);
}
}
至此,MVC框架的核心已經(jīng)完成。
聯(lián)系客服