MVC不是一种像C、JAVA的编程语言,也不是一种像Ajax,Servlet的技术,只是一种如同面向对象一样编程思想。近年来MVC一直很火,赞者批者有之,然后大篇幅的文章很多,可是简明扼要的简单MVC的例子几乎没有。在JSP领域一直向鼓风机地猛吹SSH如何如何地好,一直怒批JSP,Servlet等如何如何差。其实使用JSP+Servlet同样可以利用MVC思想来完成一个系统。下面用一个烂得不能再烂的例子,你步入网页编程必须学会的东西,登陆、注册、修改密码系统,来说明这种编程思想。
一、基本目标
在一个form.jsp页面有三个表单,分别是登陆系统、注册系统、修改密码系统,不同情况给出不同的信息,如下图:
处理Servlet,也就是那些Java文件,是不可以通过直接输入网址来访问的。
二、基本思想
网站目录结构如下图所示,输入表单的页面form.jsp就是所谓的View层,单击go!之后提交到的处理Java文件登陆系统的login.java,注册系统的register.java,修改密码系统的update.java里面不允许有任何操作数据库的动作,它们只能通过form.jsp发送过来的信息,构造相应的sql语句到dbDAO.java,在dbDAO.java同一操作数据库,这些java文件就是所谓的Controller层,然后dbDAO.java就是所谓的处于JavaBean,Java容器下的DAO组件,什么业务逻辑的狗屁抽象东西了,也就是Model层了。
MVC的编程思想,在一开始写程序是最蛋疼的,其在最初始的开发速度不如直接写出Java语句操作数据库,你所想即所得当然写得比起你又要把操作数据库的东西抽象出来放在一个dbDAO.java里面快,但是在你开发完一个登陆系统之后,随后的用户注册系统、修改密码系统很快就能够完成了,因为所有的东西都被你抽象好了,改起来非常好改。这就是现在一些企业严格要求MVC去编程的原因,首先第一个分工明确,第二个是以后改起来好改,最重要的是他们可以利用一些技术不让你看到dbDAO.java,你只管开发View层或者Controller就能不完全掌握它们底层的数据库是什么。甚至一些专门写Model的程序员,他们根本就不知道数据库里面的一堆数据用来干什么。只有核心高管才知道整个程序的流程。他管你程序难写不好写,不会写请滚粗。
MVC编程思想有好有不好,这里不再讨论,关键是你要会。这是一道从低级程序猿进阶到中级程序猿的坎。下面使用Servlet说明一下怎么同时实现这三个系统的。不采用复杂SSH搞得一头雾水了。
这东西真的不难,请不要给那些大篇幅的东西吓到,只是我此前讨论过的JDBC技术《【Mysql】Java中对Mysql数据库的增删改查、Java的System类》(点击打开链接)与Servlet技术《【Servlet】最简单的Servlet JavaWeb程序》(点击打开链接)的指示再现而已。
三、制作过程
1、首先还是有一个test数据库,里面有一张用户信息表usertable,还是如下图经典的配置,主键、用户名、密码,
在表里面已经存在几个用户信息,如下图,按理说,这里的密码应该通过SHA-1的编码再存放的,但是为了避免太复杂,这里先用明文存放:
2、新建一个eclipse工程,在lib文件夹,放入javax.servlet-api-3.1.0.jar,防止一些来路不明的Tomcat不支持Servlet,这东西超过3.0就可以,还有所谓的mysqlJDBC数据源mysql-connector-java-5.1.32.jar,这东西超过5.0即可。就两个Jar包,网上一大堆,摆入lib文件夹就完事。
3、配置好Servlet,在web.xml文件写入如下的代码,表示/login的域名就跳到根目录下的login.java处理,其余如此类推。
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5"> <!-- 用户登录 --> <servlet> <servlet-name>login</servlet-name> <servlet-class>login</servlet-class> </servlet> <servlet-mapping> <servlet-name>login</servlet-name> <url-pattern>/login</url-pattern> </servlet-mapping> <!-- 用户注册 --> <servlet> <servlet-name>register</servlet-name> <servlet-class>register</servlet-class> </servlet> <servlet-mapping> <servlet-name>register</servlet-name> <url-pattern>/register</url-pattern> </servlet-mapping> <!-- 用户修改密码 --> <servlet> <servlet-name>update</servlet-name> <servlet-class>update</servlet-class> </servlet> <servlet-mapping> <servlet-name>update</servlet-name> <url-pattern>/update</url-pattern> </servlet-mapping> </web-app>
4、之后MVC先写哪个都没有关系,我习惯先写V,因为V最简单,就一个表示层,不用怎么思考,然后用户会产生什么变量你写完V就能够明确。也就是那个form.jsp,里面实际上就三张简单得不能再简单的表单了。放一段jsp语句,检查是否存在提示信息,如果有则输出。
<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>login</title> </head> <body> <div style="color: red;"> <strong> <% if(request.getAttribute("errmsg")!=null){ out.println(request.getAttribute("errmsg")); } %> </strong> </div> 用户登录:<br /> <form method="post" action="login"> username:<input type="text" name="username" /><br /> password:<input type="password" name="password" /><br /> <input type="submit" value="go!" /><br /> </form> 用户注册:<br /> <form method="post" action="register"> username:<input type="text" name="username" /><br /> password:<input type="password" name="password" /><br /> passwordAgain:<input type="password" name="passwordAgain" /><br /> <input type="submit" value="go!" /><br /> </form> 修改密码:<br /> <form method="post" action="update"> username:<input type="text" name="username" /><br /> password:<input type="password" name="password" /><br /> newpassword:<input type="password" name="newpassword" /><br /> newpasswordAgain:<input type="password" name="newpasswordAgain" /><br /> <input type="submit" value="go!" /><br /> </form> </body> </html>按理说,这段jsp也应该省去,使得V层是纯粹的V层,全部都是前端代码,应该直接利用ajax技术去完成错误信息的输出,使用Dwr或者jqueryAjax什么的javascript前端技术,这里为了不搞太复杂,还是用回简单的jsp代码来说明,同时验证两次密码是否一致,也应该用去《【JavaScript】表单即时验证,不成功不让提交》( 点击打开链接)做的,但是,同样为了简单一点,放到C层去处理吧。理解了MVC思想之后,大家慢慢改。
5、再写M,操作数据库的动作抽象出来,把连接数据库的动作放在构造函数,把关闭数据库的动作放在析构函数,以后在C层连con.close也省了,别告诉我Java没有析构函数,protected void finalize()与严格意义上的析构函数,得到的实际结果是没有任何区别,只有概念上的繁琐区别,然后比如查询、插入、修改数据,一般不用写删除,因为习惯设置标志位伪删除。关闭数据的时候,先看看是否现在是连接数据库的。
这个所谓的持久层,一般写好就不用改了。你只需要不停写C去调用M层,返回结果给V层。
import java.sql.*; public class dbDAO { private Connection con; // 构造函数,连接数据库 public dbDAO() throws Exception { String dburl = "jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&useOldAliasMetadataBehavior=true"; String dbusername = "root"; String dbpassword = "root"; Class.forName("com.mysql.jdbc.Driver"); this.con = DriverManager.getConnection(dburl, dbusername, dbpassword); } // 执行查询 public ResultSet query(String sql, Object... args) throws Exception { PreparedStatement ps = con.prepareStatement(sql); for (int i = 0; i < args.length; i++) { ps.setObject(i + 1, args[i]); } return ps.executeQuery(); } // 执行插入 public boolean insert(String sql, Object... args) throws Exception { PreparedStatement ps = con.prepareStatement(sql); for (int i = 0; i < args.length; i++) { ps.setObject(i + 1, args[i]); } if (ps.executeUpdate() != 1) { return false; } return true; } // 执行修改 public boolean modify(String sql, Object... args) throws Exception { PreparedStatement ps = con.prepareStatement(sql); for (int i = 0; i < args.length; i++) { ps.setObject(i + 1, args[i]); } if (ps.executeUpdate() != 1) { return false; } return true; } // 析构函数,中断数据库的连接 protected void finalize() throws Exception { if (!con.isClosed() || con != null) { con.close(); } } }这里与此前讨论过的JDBC技术《【Mysql】Java中对Mysql数据库的增删改查、Java的System类》( 点击打开链接)没有任何区别,只是注意这里新增了一点点的小东西,通过《【Java】JDK1.5以后新型的泛型参数传递方法Object...args》( 点击打开链接)传递一个sql语句,与一个Object数组过来,里面有字符串String与整形int,这里是为了配合setObject的方法,比如传递过来的sql语句是:
update userTable set password=? where username=?
两个Object参数是字符串11与22,通过:
for (int i = 0; i < args.length; i++) { ps.setObject(i + 1, args[i]); }它会自动把?替换按顺序替换成你传递过来的Object参数,形成出一条完整的sql语句,供你之后的插入、修改、删除方法executeUpdate()或者查询方法executeQuery()使用:
update userTable set password=‘11‘ where username=’22‘如果传递过来的是整形,它也会自动把引号去掉,以构造出一条完整的语句。所谓的PreparedStatement对象也就是这个意思了!
直接强调插入、修改、删除方法与查询方法在JDBC里面是不同的,前者是executeUpdate(),后者是executeQuery(),那是因为前者都是没有返回值,后者有返回值的缘故啊!当然,封装好的asp与php就另当别论。
通过上面的方法,现在不管你传递一个什么的东西过来,都能够查询了。
6、最后写C,这是最难的一步,有了M的基础与V的基础,才能把两部分连接起来。这里的Servlet不能通过一个service方法把doGet与doPost合起来,恰恰是把它们分开防止用户直接输入网址访问此Servlet,因为用户直接输入网址访问此Servlet是调用doGet方法,通过表单直接传递过来的是doPost方法,因为我再表单早就注明了。
(1)首先是login.java,登陆的思想没什么好说的。把此用户名在数据库对应的密码与用户输入的密码比对,是此成功,否此失败,查不到就是失败了。
可以明显看到,这里没有任何操作的数据库的东西。
import java.io.*; import java.sql.*; import javax.servlet.*; import javax.servlet.http.*; public class login extends HttpServlet { // 防止用户直接输入网址访问此Servlet protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { PrintStream out = new PrintStream(response.getOutputStream()); response.setContentType("text/html;charSet=utf-8"); out.print("请正常打开此页"); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException { String errmsg = ""; //获取用户输入的东西 String username = request.getParameter("username"); String password = request.getParameter("password"); try { //构造操作数据库的语句 dbDAO db = new dbDAO(); ResultSet rs = db .query("select password from userTable where username=?", username); //根据不同的查询结果的,返回不同的结果到View层 if (rs.next()) { if (rs.getString("password").equals(password)) { HttpSession session = request.getSession(); session.setAttribute("username", username); request.getRequestDispatcher("/welcome.jsp").forward( request, response); } else { errmsg = "密码错误!"; request.setAttribute("errmsg", errmsg); request.getRequestDispatcher("/form.jsp").forward(request, response); } } else { errmsg = "用户名不存在!"; request.setAttribute("errmsg", errmsg); request.getRequestDispatcher("/form.jsp").forward(request, response); } } catch (Exception e) { e.printStackTrace(); } } }这里的错误信息仅仅存在request对象,它在Servlet返回结果给form.jsp之后就消息了,以后在任何页面用request.getAttribute("errmsg")都拿不到此次请求的返回的提示消息了。如果登陆成功,则把用户名存放在session对象,这个对象默认是用户不关闭这个浏览器都不会消失的,一直存在的。登陆成功,用户不关闭浏览器,在任何页面利用session.getAttribute("username")都能拿到它的用户名,除非有语句设置这个session的生存时间或者立即死亡。
(2)之后的register.java的开发就快了,你可以比对login.java代码几乎一样,如果不使用MVC则要把操作数据库大改特改,甚至如果你直接写在jsp页面,就像php与asp开发那样,不是自己去改的话很蛋疼的,思想还是那个思想,先比对用户输入的密码是否一致,然后查询是否有这个用户,没有就在数据库插入这条信息。
import java.io.*; import java.sql.*; import javax.servlet.*; import javax.servlet.http.*; public class register extends HttpServlet { // 防止用户直接输入网址访问此Servlet protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { PrintStream out = new PrintStream(response.getOutputStream()); response.setContentType("text/html;charSet=utf-8"); out.print("请正常打开此页"); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException { String errmsg = ""; String username = request.getParameter("username"); String password = request.getParameter("password"); String passwordAgain = request.getParameter("passwordAgain"); try { if (password.equals(passwordAgain)) { dbDAO db = new dbDAO(); ResultSet rs = db.query( "select username from userTable where username=?", username); if (!rs.next()) { db.insert( "insert into userTable(username,password) values(?,?)", username, password); errmsg = "注册成功!"; request.setAttribute("errmsg", errmsg); request.getRequestDispatcher("/form.jsp").forward(request, response); } else { errmsg = "用户名已存在!"; request.setAttribute("errmsg", errmsg); request.getRequestDispatcher("/form.jsp").forward(request, response); } } else { errmsg = "两次输入的密码不一致"; request.setAttribute("errmsg", errmsg); request.getRequestDispatcher("/form.jsp").forward(request, response); } } catch (Exception e) { e.printStackTrace(); } } }
import java.io.*; import java.sql.*; import javax.servlet.*; import javax.servlet.http.*; public class update extends HttpServlet { // 防止用户直接输入网址访问此Servlet protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { PrintStream out = new PrintStream(response.getOutputStream()); response.setContentType("text/html;charSet=utf-8"); out.print("请正常打开此页"); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException { String errmsg = ""; String username = request.getParameter("username"); String password = request.getParameter("password"); try { dbDAO db = new dbDAO(); ResultSet rs = db .query("select password from userTable where username=?", username); if (rs.next()) { if (rs.getString("password").equals(password)) { String newpassword = request.getParameter("newpassword"); String newpasswordAgain = request.getParameter("newpasswordAgain"); if(newpassword.equals(newpasswordAgain)){ db.modify("update userTable set password=? where username=?", newpassword,username); errmsg = "密码修改成功!"; request.setAttribute("errmsg", errmsg); request.getRequestDispatcher("/form.jsp").forward(request, response); } else{ errmsg = "两次输入密码不一致!"; request.setAttribute("errmsg", errmsg); request.getRequestDispatcher("/form.jsp").forward(request, response); } } else { errmsg = "密码错误!"; request.setAttribute("errmsg", errmsg); request.getRequestDispatcher("/form.jsp").forward(request, response); } } else { errmsg = "用户名不存在!"; request.setAttribute("errmsg", errmsg); request.getRequestDispatcher("/form.jsp").forward(request, response); } } catch (Exception e) { e.printStackTrace(); } } }
通过上面的介绍,你可以看到MVC的优点与缺点,这东西的代码比平时多了一倍,还多了分层、建立持久层,也就是dbDAO.java的思考,但是你打好M的基础之后,以后要加新功能,你只要在web.xml加东西,加C层的java,查询数据库的语句统一变成了几个方法query,insert,modify,连接数据库,关闭数据库也不用你做了。与其说是一个新的编程模式,不如是面向对象OO编程的思想进一步深化而已。这要求你对变量的传递更加地熟悉。