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