TeaFramework——MVC框架的實現

    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 

相關文章
相關標籤/搜索