我们通过前面的章节可以看到:
能否将两者结合起来,发挥各自的优点,避免各自的缺点?
答案是肯定的。我们来看一个具体的例子。
假设我们已经编写了几个JavaBean:
public class User {
public long id;
public String name;
public School school;
}
public class School {
public String name;
public String address;
}
在UserServlet中,我们可以从数据库读取User、School等信息,然后,把读取到的JavaBean先放到HttpServletRequest中,再通过forward()传给user.jsp处理:
@WebServlet(urlPatterns = "/user")
public class UserServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 假装从数据库读取:
School school = new School("No.1 Middle School", "101 South Street");
User user = new User(123, "Bob", school);
// 放入Request中:
req.setAttribute("user", user);
// forward给user.jsp:
req.getRequestDispatcher("/WEB-INF/user.jsp").forward(req, resp);
}
}
在user.jsp中,我们只负责展示相关JavaBean的信息,不需要编写访问数据库等复杂逻辑:
<%@ page import="com.itranswarp.learnjava.bean.*"%>
<%
User user = (User) request.getAttribute("user");
%>
<html>
<head>
<title>Hello World - JSP</title>
</head>
<body>
<h1>Hello <%= user.name %>!</h1>
<p>School Name:
<span style="color:red">
<%= user.school.name %>
</span>
</p>
<p>School Address:
<span style="color:red">
<%= user.school.address %>
</span>
</p>
</body>
</html>
请注意几点:
我们在浏览器访问http://localhost:8080/user,请求首先由UserServlet处理,然后交给user.jsp渲染:
我们把UserServlet看作业务逻辑处理,把User看作模型,把user.jsp看作渲染,这种设计模式通常被称为MVC:Model-View-Controller,即UserServlet作为控制器(Controller),User作为模型(Model),user.jsp作为视图(View),整个MVC架构如下:
使用MVC模式的好处是,Controller专注于业务处理,它的处理结果就是Model。Model可以是一个JavaBean,也可以是一个包含多个对象的Map,Controller只负责把Model传递给View,View只负责把Model给“渲染”出来,这样,三者职责明确,且开发更简单,因为开发Controller时无需关注页面,开发View时无需关心如何创建Model。
MVC模式广泛地应用在Web页面和传统的桌面程序中,我们在这里通过Servlet和JSP实现了一个简单的MVC模型,但它还不够简洁和灵活,后续我们会介绍更简单的Spring MVC开发。
小结
MVC模式是一种分离业务逻辑和显示逻辑的设计模式,广泛应用在Web和桌面应用程序。
通过结合Servlet和JSP的MVC模式,我们可以发挥二者各自的优点:
但是,直接把MVC搭在Servlet和JSP之上还是不太好,原因如下:
能不能通过普通的Java类实现MVC的Controller?类似下面的代码:
public class UserController {
@GetMapping("/signin")
public ModelAndView signin() {
...
}
@PostMapping("/signin")
public ModelAndView doSignin(SignInBean bean) {
...
}
@GetMapping("/signout")
public ModelAndView signout(HttpSession session) {
...
}
}
上面的这个Java类每个方法都对应一个GET或POST请求,方法返回值是ModelAndView,它包含一个View的路径以及一个Model,这样,再由MVC框架处理后返回给浏览器。
如果是GET请求,我们希望MVC框架能直接把URL参数按方法参数对应起来然后传入:
@GetMapping("/hello")
public ModelAndView hello(String name) {
...
}
如果是POST请求,我们希望MVC框架能直接把Post参数变成一个JavaBean后通过方法参数传入:
@PostMapping("/signin")
public ModelAndView doSignin(SignInBean bean) {
...
}
为了增加灵活性,如果Controller的方法在处理请求时需要访问HttpServletRequest、HttpServletResponse、HttpSession这些实例时,只要方法参数有定义,就可以自动传入:
@GetMapping("/signout")
public ModelAndView signout(HttpSession session) {
...
}
以上就是我们在设计MVC框架时,上层代码所需要的一切信息。
设计MVC框架
如何设计一个MVC框架?在上文中,我们已经定义了上层代码编写Controller的一切接口信息,并且并不要求实现特定接口,只需返回ModelAndView对象,该对象包含一个View和一个Model。实际上View就是模板的路径,而Model可以用一个Map
public class ModelAndView {
Map<String, Object> model;
String view;
}
比较复杂的是我们需要在MVC框架中创建一个接收所有请求的Servlet,通常我们把它命名为DispatcherServlet,它总是映射到/,然后,根据不同的Controller的方法定义的@Get或@Post的Path决定调用哪个方法,最后,获得方法返回的ModelAndView后,渲染模板,写入HttpServletResponse,即完成了整个MVC的处理。
这个MVC的架构如下:
其中,DispatcherServlet以及如何渲染均由MVC框架实现,在MVC框架之上只需要编写每一个Controller。
我们来看看如何编写最复杂的DispatcherServlet。首先,我们需要存储请求路径到某个具体方法的映射:
@WebServlet(urlPatterns = "/")
public class DispatcherServlet extends HttpServlet {
private Map<String, GetDispatcher> getMappings = new HashMap<>();
private Map<String, PostDispatcher> postMappings = new HashMap<>();
}
空
实现渲染
有的童鞋对如何使用模板引擎进行渲染有疑问,即如何实现上述的ViewEngine?
小结
一个MVC框架是基于Servlet基础抽象出更高级的接口,使得上层基于MVC框架的开发可以不涉及Servlet相关的HttpServletRequest等接口,处理多个请求更加灵活,并且可以使用任意模板引擎,不必使用JSP。