TeaFramework - Implementation of MVC Framework

    From the disassembly of the web MVC pattern, the following things are done:

    1. Assign the scattered data from the web page to the Model, where the model is a common java object, such as pojo, domain, vo, etc.

    2. Control the return value. The return value can be a common view, such as jsp, freemark, html and other views, and the return value can also be a data entity such as json and xml.

    3. Pass dynamic parameters. Dynamic parameters are usually placed in fields such as request, response, and session.

    Let's see how the TeaFramework MVC framework is implemented.

    First of all, we need to mark a url prefix for each controller. When it comes to marking, we naturally think of annotations and define the Namespace annotation to mark the url prefix, so the usage is like this @Namespace("/userManage")

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

    For the public method written in the controller class, it can be accessed directly without mapping the url relationship. For example, there is @Namespace("/userManage") on UserController, and there is an addUser method in UserController, then the front end can directly pass /userManage/addUser to access.

    For the eight basic types +Date and String, a binding parameter annotation Param is defined.

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

    If the front-end parameters are received by the entity object, there is no need to add annotations. As long as the name of the parameter corresponds to the attribute name of the object, it can be automatically assigned. If you need to return the parameter to the front-end, you can directly define a Map in the method. . For the reference of HttpServletRequest and HttpServletResponse, you can write it directly in the method, which is the same as SpringMVC. If the data to be returned needs to be converted into JSON, then add JSON annotations. Below is an example

@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);
	}
}

    The following is a key question, how to find the corresponding controller object through the request, and then execute the method in the object? We need to do two things.

    1. When the bean container starts, the namespace and controller objects are put into the map map. There is such a piece of code at the end of the init method in BeanContainerInitialization.

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

	}
}

    There is a Namespace annotation on the scanned class, and the Namespace is mapped to the entity object, and the Namespace to the method of the entity object.

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. The request is handed over to the corresponding controller through the Filter, where a TeaDispatcherFilter is defined to hand over the request

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() {
	}

}

    When configuring this filter, you can configure resource files not to be filtered, such as css, images, etc. You can also set the encoding

<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>

    Finally, when the controller object and method are verified, it enters ActionProcessor.processor(bean, method, request, response), and the request is handed over to the corresponding controller for processing.

    The processor method in ActionProcessor is as follows

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

    The first step: first reflect the parameter list of the corresponding method in the controller, and correspond the parameters passed from the front end to the parameters of the parameter list to form a parameter array Object[] params. This params is to be passed to the corresponding method.

    Step 2: Control the return. If there is a json annotation, it will be converted into a json object and written back to the front end through the response. If the return value of method is String, then it is time to return to the view page. Only jsp is supported here. Of course, if there are parameters to be returned to the jsp page, they will be encapsulated in the request field and returned.

    There are also deficiencies here. The attachment upload is not packaged, and it will be added later.

     Project address: https://git.oschina.net/lxkm/teaframework
     Blog: https://my.oschina.net/u/1778239/blog 

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325437439&siteId=291194637