在上一篇中,我们完成了对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
执行完 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 上面展示出来。
基本的MVC demo
编写我们的Controller,如果需要通过 EL 来传值给 View ,那么就需要加上 @Named 注解。并且为变量添加get方法。
@Controller@RequestMapping("/user/")@Named@RequestScopedpublic 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+"/";%>
我们看到姓名已经有值,说明通过 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+"/";%> ${userController.username} : ${userController.age}
在这个页面上面显示修改后的 username 和 age 的值。
提交Form:
这样我们就完成了一个简单的MVC 处理,当然功能还是比较弱,下面会加上 对整个Form提交的支持,POJO实体提交,返回普通字符串,返回JSon。如果可能的话,还有对 freemarker 这样的支持。