2.7.6 使用Servlet作为控制器
正如前面见到的,使用Servlet
作为表现层的工作量太大,所有的HTML
标签都需要使用页面输出流生成。因此,使用Servlet
作为表现层有如下三个劣势
- 开发效率低,所有的
HTML
标签都需使用页面输出流完成。
- 不利于团队协作开发,美工人员无法参与
Servlet
界面的开发
- 程序可维护性差,即使修改一个按钮的标题,都必须重新编辑
Java
代码,并重新编译
在标准的MVC
模式中,Servlet
仅作为控制器使用。JavaEE
应用架构正是遵循MVC
模式的,对于遵循MVC
模式的Java EE
应用而言,JSP
仅作为表现层(view
)技术,其作用有两点:
- 负责收集用户请求参数。
- 将应用的处理结果、状态数据呈现给用户。
Servlet
则仅充当控制器(Controller
)角色,它的作用类似于调度员:所有用户请求都发送给Servlet
,Servlet
调用Model
来处理用户请求,并调用JSP
来呈现处理结果;或者Servlet
直接调用JSP
将应用的状态数据呈现给用户。
Model
通常由JavaBean
来充当,所有业务逻辑、数据访问逻辑都在Model
中实现。实际上隐藏在Model
下的可能还有很多丰富的组件,例如DAO
组件、领域对象等。
程序示例 简单登录验证
下面介绍一个使用Servlet
作为控制器的MVC
应用,该应用演示了一个简单的登录验证。
login.jsp
下面是本应用的登录页面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| <%@ page contentType="text/html; charset=UTF-8" language="java" errorPage="" %> <!DOCTYPE html> <html> <head> <title> 用户登录 </title> </head> <body> <!-- 输出出错提示 --> <span style="color:red;font-weight:bold"> <% if (request.getAttribute("err") != null) { out.println(request.getAttribute("err") + "<br/>"); } %> </span> 请输入用户名和密码: <!-- 登录表单,该表单提交到一个Servlet --> <form id="login" method="post" action="login"> 用户名:<input type="text" name="username"/><br/> 密  码:<input type="password" name="pass"/><br/> <input type="submit" value="登录"/><br/> </form> </body> </html>
|
以上页面使用JSP
脚本输出错误提示,该页面其实是一个简单的表单页面,用于收集用户名及密码,并将请求提交到指定Servlet
,该Servlet
充当控制器角色。
根据严格的MVC
规范,上面的login.jsp
页面也不应该被客户端直接访问,客户的请求应该先发送到指定Servlet
,然后由Servlet
将请求forward
到该JSP
页面。
LoginServlet.java
控制器Servlet
的代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
| package lee;
import javax.servlet.*; import javax.servlet.http.*; import javax.servlet.annotation.*; import java.io.PrintWriter; import java.io.IOException; import java.sql.*;
@WebServlet( name = "login", urlPatterns = {"/login"}) public class LoginServlet extends HttpServlet { public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, java.io.IOException { String errMsg = ""; RequestDispatcher rd; String username = request.getParameter("username"); String pass = request.getParameter("pass"); try { DbDao dd = new DbDao("com.mysql.cj.jdbc.Driver", "jdbc:mysql://localhost:3306/liuyan?serverTimezone=GMT", "root", "root");
ResultSet rs = dd.query( "select pass from user_inf" + " where name = ?", username); if (rs.next()) { if (rs.getString("pass").equals(pass)) { HttpSession session = request.getSession(true); session.setAttribute("name", username); rd = request.getRequestDispatcher("/welcome.jsp"); rd.forward(request, response); } else { errMsg += "您的用户名密码不符合,请重新输入"; } } else { errMsg += "您的用户名不存在,请先注册"; } } catch (Exception e) { e.printStackTrace(); } if (errMsg != null && !errMsg.equals("")) { rd = request.getRequestDispatcher("/login.jsp"); request.setAttribute("err", errMsg); rd.forward(request, response); } } }
|
控制器负责接收客户端的请求,它既不直接对客户端输出响应,也不处理用户请求,只调用JavaBean
来处理用户请求,如程序中粗体字代码所示;JavaBean
处理结束后,Servlet
根据处理结果,调用不同的JSP
页面向浏览器呈现处理结果。
上面Servlet
使用@WebServlet
注解为该Servlet
配置了URL
为/login
,因此向/login
发送的请求将会交给该Servlet
处理。
DbDao.java
下面是本应用中DaDao
的源代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102
| package lee;
import java.sql.*;
public class DbDao { private Connection conn; private String driver; private String url; private String username; private String pass;
public DbDao() { }
public DbDao(String driver, String url, String username, String pass) { this.driver = driver; this.url = url; this.username = username; this.pass = pass; }
public void setDriver(String driver) { this.driver = driver; }
public void setUrl(String url) { this.url = url; }
public void setUsername(String username) { this.username = username; }
public void setPass(String pass) { this.pass = pass; }
public String getDriver() { return (this.driver); }
public String getUrl() { return (this.url); }
public String getUsername() { return (this.username); }
public String getPass() { return (this.pass); }
public Connection getConnection() throws Exception { if (conn == null) { Class.forName(this.driver); conn = DriverManager.getConnection(url, username, this.pass); } return conn; }
public boolean insert(String sql, Object... args) throws Exception { PreparedStatement pstmt = getConnection().prepareStatement(sql); for (int i = 0; i < args.length; i++) { pstmt.setObject(i + 1, args[i]); } if (pstmt.executeUpdate() != 1) { return false; } pstmt.close(); return true; }
public ResultSet query(String sql, Object... args) throws Exception { PreparedStatement pstmt = getConnection().prepareStatement(sql); for (int i = 0; i < args.length; i++) { pstmt.setObject(i + 1, args[i]); } return pstmt.executeQuery(); }
public void modify(String sql, Object... args) throws Exception { PreparedStatement pstmt = getConnection().prepareStatement(sql); for (int i = 0; i < args.length; i++) { pstmt.setObject(i + 1, args[i]); } pstmt.executeUpdate(); pstmt.close(); }
public void closeConn() throws Exception { if (conn != null && !conn.isClosed()) { conn.close(); } } }
|
上面DaDao
负责完成查询、插入、修改等操作。从上面这个应用的结构来看,整个应用的流程非常清晰,下面是MVC
中各个角色的对应组件:
M
:Model
,即模型,对应JavaBean
V
:View
,即视图,对应JSP
页面。
C
:Controller
,即控制器,对应Servlet
data.sql
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| DROP DATABASE IF EXISTS liuyan; CREATE DATABASE liuyan;
USE liuyan;
CREATE TABLE user_inf ( id INT AUTO_INCREMENT PRIMARY KEY, NAME VARCHAR(255), pass VARCHAR(255) );
INSERT INTO user_inf VALUES(NULL,'crazyit','123'); INSERT INTO user_inf VALUES(NULL,'tiger','123'); ;
|