在上一篇中,我们完成了对MVC的Controller 的基本信息的初始化。现在我们注册一个Servlet 来拦截我们的请求然后我们根据请求路径来匹配找到对应的 Controller 的某一个方法。
public class DelegatingServlet extends HttpServlet { private static final long serialVersionUID = -3941041507536315510L; @Inject private MvcHandler mvcHandler; private static final Logger logger = LoggerFactory .getLogger(DelegatingServlet.class); @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doHandleRequest(req, resp, RequestMethod.GET); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doHandleRequest(req, resp, RequestMethod.POST); } public void doHandleRequest(HttpServletRequest request, HttpServletResponse response, RequestMethod requestMethod) { RequestContext context = new RequestContext(getServletContext(), request, response, requestMethod); logger.debug("Servlet delegating the {} request from {}", requestMethod, request.getRequestURI()); mvcHandler.handleRequest(context); } }
然后,我们在 web.xml 添加配置,拦截所有 /cdi/* 下面的请求。我们对一次请求里面所涉及到的 对象数据 定义了一个实体。
public class RequestContext { private final ServletContext servletContext; private final HttpServletRequest request; private final HttpServletResponse response; private final RequestMethod requestMethod; private Object controller; // 对应的 controller private ControllerMethod method; // controller 的相关参数 private Object outcome; // controller 的返回值 // gets, sets }
然后把这个实体传给 MvcHandler 处理, MvcHandler 通过请求路径和我们初始化好的Controller 来匹配找出对应的Controller。
public void handleRequest(RequestContext context) { context.setMethod(matcher.findMatching(controllerInfo, context)); //查找出Controller if (context.getMethod() != null) { Object controller = locateController(context.getMethod() .getControllerClass()); context.setController(controller); // start executing the controller method executeControllerMethod(context); // 找到对应的Controller后,开始执行对应的方法 handleResponse(context); } else { throw new RuntimeException("Unable to find method for " + context.getRequestMethod() + " request with url " + context.getPath()); } }
public ControllerMethod findMatching(ControllerInfo info, RequestContext context) { for (ControllerMethod method : info.getControllerMethods()) { if (matches(method, context)) { return method; } } return null; } protected boolean matches(ControllerMethod methodToTest, RequestContext context) { String path = context.getPath(); boolean result = methodToTest.matchesRequestMethod(context.getRequestMethod()) && (methodToTest.getPrefix() == null || path.startsWith(methodToTest.getPrefix())) && (methodToTest.getSuffix() == null || path.endsWith(methodToTest.getSuffix())); logger.debug("match result = " + result); return result; }
找到对应的Controller后,我们开始调用方法。这里分为带参数和不带参数的两种情况。目前参数还是不支持实体。
private void executeControllerMethod(RequestContext context) {
ControllerMethod controllerMethod = context.getMethod();
Method javaMethod = controllerMethod.getMethod();
Object outcome = null;
try {
if (!controllerMethod.getArgs().isEmpty()) {
// 处理带参数的 Controller 方法
HttpServletRequest request = context.getRequest();
outcome = javaMethod.invoke(context.getController(),
handleMethodArgs(controllerMethod,request));
} else {
outcome = javaMethod.invoke(context.getController());
}
} catch (Exception e) {
e.printStackTrace();
}
context.setOutcome(outcome);
}
处理带参数的方法
private Object[] handleMethodArgs(ControllerMethod controllerMethod,HttpServletRequest request) throws InstantiationException, IllegalAccessException, IllegalArgumentException, SecurityException, InvocationTargetException, NoSuchMethodException{
List<Map<String, Class<?>>> args = controllerMethod.getArgs();
List<Object> postArgs = new ArrayList<Object>();
for (Map<String, Class<?>> map : args) {
for (Iterator<String> iterator = map.keySet().iterator(); iterator
.hasNext();) {
String key = iterator.next();
String postValue = request.getParameter(key);
if (postValue == null
|| postValue.equals("")) {
if(map.get(key).isPrimitive()){
throw new ClassCastException("null value can not be cast to " + map.get(key)+" : " + key);
}
postArgs.add(map.get(key).newInstance());
}else{
if (map.get(key).isPrimitive()) { // 这里对基本数据类型处理,需要先获得其包装类
Class<?> wrapClass = ReflectionUtil.getWrapClass(map.get(key));
postArgs.add(wrapClass.getMethod("valueOf",
String.class).invoke(wrapClass,
request.getParameter(key)));
} else {
postArgs.add(map.get(key).cast(
request.getParameter(key)));
}
}
}
}
return postArgs.toArray();
}
执行完 Controller 方法,我们根据其返回的 View 的路径来跳转到对应的页面。
private void handleResponse(RequestContext context) { if (context.getOutcome() instanceof String) { String outcome = (String) context.getOutcome(); String view = "/" + outcome + ".jsp"; logger.debug("resolved outcome {} to view {}", outcome, view); context.forwardTo(view); } } public void forwardTo(String url) { if(url == null){ throw new NullPointerException(); } if (!url.startsWith("/")) { url = "/" + url; } try { servletContext.getRequestDispatcher(url).forward(request, response); } catch (Exception e) { e.printStackTrace(); } }
这样,我们就完成了 MVC 一个处理过程。但是我们 Controller 如何向 View 传值呢, CDI 本身是支持 EL 表达式的,所以我们这里可以直接使用 EL 表达式来把 Controller 的数据在 View 上面展示出来。
编写我们的Controller,如果需要通过 EL 来传值给 View ,那么就需要加上 @Named 注解。并且为变量添加get方法。
@Controller @RequestMapping("/user/") @Named @RequestScoped public class UserController { private String username; private int age; @Inject public void Page_Load(){ username = "Feng J"; } @RequestMapping(value = "userinfo") public String userInfo() { return "userInfo"; } @RequestMapping(value = "edit", method = RequestMethod.POST) public String userInfo(@Param("username") String _username,@Param("age") int _age) { System.out.println(_username + ":" + _age); username = _username; age = _age; return "viewUser"; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }
我们首先访问 /cdi/user/userinfo 。MVC就会帮我们跳转到userInfo.jsp页面。
<%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
</head>
<body>
<form method="post" action="user/edit">
姓名<br/>
<input value="${userController.username}" name="username"><br/>
年龄<br/>
<input name="age"><br/>
<input type="submit" value="Update" />
</form>
</body>
</html>
我们看到姓名已经有值,说明通过 EL 表达式拿到了后台 Controller 的值。然后我们修改提交,这里Form的Action是user/edit ,会执行 UserController 的 userInfo 方法,并将参数传过去。然后跳转到 viewUser.jsp 页面。
<%@ page language="java" import="java.util.*" pageEncoding="utf-8"%> <% String path = request.getContextPath(); String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/"; %> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> </head> <body> ${userController.username} : ${userController.age} </body> </html>
在这个页面上面显示修改后的 username 和 age 的值。
提交Form:
这样我们就完成了一个简单的MVC 处理,当然功能还是比较弱,下面会加上 对整个Form提交的支持,POJO实体提交,返回普通字符串,返回JSon。如果可能的话,还有对 freemarker 这样的支持。