TeaFramework——MVC框架的实现

    web MVC模式拆解来看,就做了以下几件事:

    1、将web页面传过来的零散数据赋值给Model,这里的model就是普通java对象,如pojo、domain、vo等等。

    2、控制返回值,返回值可以是普通的视图,例如jsp、freemark、html等视图,返回值也可以是json、xml等数据实体。

    3、传递动态参数,动态参数通常是放在request、response、session等域中。

    下来请看,TeaFramework MVC框架怎么实现的。

    首先需要对每个controller标记一个url前缀,谈到标记,我们自然想到注解,定义了Namespace注解,来标记url前缀,那么用法就是这样@Namespace("/userManage")

@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface Namespace {
	public String value();
}

    对于在controller类写的public方法,可以直接访问,不必映射url关系,例如UserController上有个@Namespace("/userManage"),UserController里有个addUser方法,那么前端就可以直接通过/userManage/addUser来访问。

    对于八大基本类型+Date、String,定义了一个绑定参数注解Param。

@Target({ ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
public @interface Param {
	public String value();
}

    如果前端参数由实体对象接收,那就不必加注解,只要参数的name与对象的属性名称对应上,就可以自动赋值了,如果需要将参数返回给前端,可以直接在方法中定义一个Map即可。对于HttpServletRequest、HttpServletResponse的引用,直接写在方法即可,这一点与SpringMVC是相同的。如果要返回的数据需要转化为JSON,那就要加上JSON注解。下面有一个示例

@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对象,然后执行该对象里的方法呢?我们需要做两件事。

    1、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);
	}
}

    2、通过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 

猜你喜欢

转载自my.oschina.net/u/1778239/blog/1585383
今日推荐