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://blog.csdn.net/dong_lxkm