web MVC模式拆解來看,就作了如下幾件事:css
一、將web頁面傳過來的零散數據賦值給Model,這裏的model就是普通java對象,如pojo、domain、vo等等。html
二、控制返回值,返回值能夠是普通的視圖,例如jsp、freemark、html等視圖,返回值也能夠是json、xml等數據實體。前端
三、傳遞動態參數,動態參數一般是放在request、response、session等域中。java
下來請看,TeaFramework MVC框架怎麼實現的。git
首先須要對每一個controller標記一個url前綴,談到標記,咱們天然想到註解,定義了Namespace註解,來標記url前綴,那麼用法就是這樣@Namespace("/userManage")web
@Target({ ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) public @interface Namespace { public String value(); }
對於在controller類寫的public方法,能夠直接訪問,沒必要映射url關係,例如UserController上有個@Namespace("/userManage"),UserController裏有個addUser方法,那麼前端就能夠直接經過/userManage/addUser來訪問。json
對於八大基本類型+Date、String,定義了一個綁定參數註解Param。數組
@Target({ ElementType.PARAMETER }) @Retention(RetentionPolicy.RUNTIME) public @interface Param { public String value(); }
若是前端參數由實體對象接收,那就沒必要加註解,只要參數的name與對象的屬性名稱對應上,就能夠自動賦值了,若是須要將參數返回給前端,能夠直接在方法中定義一個Map便可。對於HttpServletRequest、HttpServletResponse的引用,直接寫在方法便可,這一點與SpringMVC是相同的。若是要返回的數據須要轉化爲JSON,那就要加上JSON註解。下面有一個示例session
@Namespace("/testUrl") @Component public class TestController { public String test(@Param("id") Long id, Map<String, Object> map, HttpServletRequest request, HttpServletResponse response) { map.put("user", new User()); return "/test.jsp"; } } @Namespace("/userManage") @Component public class UserController { @Inject private UserService userService; @JSON public User addUser(User user) { return userService.addUser(user); } }
下面說道了一個關鍵問題,經過請求怎麼找到了對應的controller對象,而後執行該對象裏的方法呢?咱們須要作兩件事。app
一、bean容器啓動時將namespace和controller對象放入map映射,在BeanContainerInitialization中init方法末尾有這樣一段代碼。
Namespace namespace = clazz.getAnnotation(Namespace.class); if (namespace != null) { NamespaceBeanMapping.putController(namespace.value(), bean); Method[] methods = bean.getClass().getMethods(); for (Method method : methods) { if (!method.getDeclaringClass().equals(java.lang.Object.class)) { NamespaceBeanMapping.putControllerMethod(namespace.value(), method); } } }
掃描到類上有Namespace註解,變把Namespace與實體對象、Namespace與實體對象的method造成映射。
public class NamespaceBeanMapping { private static Map<String, Object> NAMESPACE_BEAN_MAPPING = new ConcurrentHashMap<String, Object>(200); private static Map<String, Map<String, Method>> NAMESPACE_METHOD_MAPPING = new ConcurrentHashMap<String, Map<String, Method>>( 200); public static void putController(String namespace, Object bean) { if (NAMESPACE_BEAN_MAPPING.containsKey(namespace)) { throw new TeaWebException("已存在相同的Namespace:" + namespace); } NAMESPACE_BEAN_MAPPING.put(namespace, bean); } public static <T> T getController(String namespace) { return (T) NAMESPACE_BEAN_MAPPING.get(namespace); } public static void putControllerMethod(String namespace, Method method) { if (NAMESPACE_METHOD_MAPPING.get(namespace) == null) { Map<String, Method> methodMapping = new ConcurrentHashMap<String, Method>(); methodMapping.put(method.getName(), method); NAMESPACE_METHOD_MAPPING.put(namespace, methodMapping); } else { if (NAMESPACE_METHOD_MAPPING.get(namespace).get(method.getName()) != null) { throw new TeaWebException(namespace + "下已經存在相同的方法:" + method.getName()); } NAMESPACE_METHOD_MAPPING.get(namespace).put(method.getName(), method); } } public static Method getControllerMethod(String namespace, String methodName) { return NAMESPACE_METHOD_MAPPING.get(namespace).get(methodName); } }
二、經過Filter將請求移交給對應的controller處理,這裏定義一個TeaDispatcherFilter來移交請求
public class TeaDispatcherFilter implements Filter { private static List<String> NOT_INTERCEPT_LIST = new ArrayList<String>(); private static final String notIntercept = "notIntercept"; private static String characterEncoding = null; private static final String ENCODING = "encoding"; @Override public void init(FilterConfig filterConfig) throws ServletException { String notInterceptParam = filterConfig.getInitParameter(notIntercept); characterEncoding = filterConfig.getInitParameter(ENCODING); if (notInterceptParam != null) { String[] params = notInterceptParam.split(","); for (String param : params) { NOT_INTERCEPT_LIST.add(param); } } } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) servletRequest; HttpServletResponse response = (HttpServletResponse) servletResponse; if (characterEncoding != null) { request.setCharacterEncoding(characterEncoding); response.setCharacterEncoding(characterEncoding); } String uri = request.getRequestURI(); if (isPass(uri) || "/".equals(uri)) { chain.doFilter(request, response); return; } else { String namespace = uri.substring(0, uri.lastIndexOf("/")); String methodName = uri.substring(uri.lastIndexOf("/") + 1); Object bean = NamespaceBeanMapping.getController(namespace); if (bean == null) { response.sendError(HttpServletResponse.SC_NOT_FOUND, "沒找到對應的controller"); return; } else { Method method = NamespaceBeanMapping.getControllerMethod(namespace, methodName); if (method == null) { response.sendError(HttpServletResponse.SC_NOT_FOUND, bean.getClass().getName() + "不存在方法:" + methodName); return; } else { ActionProcessor.processor(bean, method, request, response); } } } } private boolean isPass(String uri) { boolean result = false; for (String suffix : NOT_INTERCEPT_LIST) { if (uri.endsWith(suffix)) { result = true; } } return result; } @Override public void destroy() { } }
配置這個過濾器時,能夠配置資源文件不過濾,如css、圖片等等。也能夠設置編碼
<filter> <filter-name>TeaDispatcherFilter</filter-name> <filter-class>org.teaframework.web.filter.TeaDispatcherFilter</filter-class> <init-param> <param-name>notIntercept</param-name> <param-value>.jsp,.png,.gif,.jpg,.js,.css,.jspx,.jpeg,.swf,.ico</param-value> </init-param> </filter> <filter-mapping> <filter-name>TeaDispatcherFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
最後當controller對象和method驗證經過以後,就進入ActionProcessor.processor(bean, method, request, response),將請求移交給對應controller去處理了。
ActionProcessor中processor方法以下
public static void processor(Object bean, Method method, HttpServletRequest request, HttpServletResponse response) { try { Object[] params = bindParameters(method, request, response); Object result = method.invoke(bean, params); if (method.getAnnotation(JSON.class) != null) { PrintWriter writer = response.getWriter(); writer.write(com.alibaba.fastjson.JSON.toJSONString(result)); writer.flush(); writer.close(); return; } if (method.getReturnType().equals(String.class)) { String pageUrl = (String) result; if (pageUrl.startsWith(REDIRECT_PREFIX)) { response.sendRedirect(request.getContextPath() + pageUrl.replace(REDIRECT_PREFIX, "")); } else { Map<String, Object> returnMap = getReturnMap(params); if (returnMap != null) { for (Map.Entry<String, Object> entry : returnMap.entrySet()) { request.setAttribute(entry.getKey(), entry.getValue()); } } request.getRequestDispatcher(pageUrl).forward(request, response); } } } catch (Exception e) { throw new TeaWebException(e); } }
第一步:先反射controller中對應方法的參數列表,將前端傳的參數與參數列表參數對應起來,造成一個參數數組Object[] params。這個params就是要傳給對應method了。
第二步:控制返回,若是有json註解,則轉化爲json對象經過response寫回前端。若是method的返回值是String,那麼這時候就是要返回視圖頁面了,這裏只支持到jsp。固然若是有參數要回傳給jsp頁面,將封裝在request域中返回。
這裏也有不足,附件上傳沒有進行封裝,後期慢慢補上。
項目地址:https://git.oschina.net/lxkm/teaframework
博客:https://my.oschina.net/u/1778239/blog