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 

注:本文转载自https://my.oschina.net/u/1778239/blog/1585383,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责。如有侵权行为,请联系我们,我们会及时删除。


评论

赞助商