开发模式的介绍
在web开发模式中,有两个主要的开发结构,称为模式一和模式二。
相关概念:
- DAO(Data Access Object):主要对数据的操作,增加,修改,删除等原子性操作。
- Web层:界面+控制器,也就是说JSP【界面】+Servlet【控制器】。
- Service业务层:将多个原子性的DAO操作进行组合,组合成一个完整的业务逻辑。
- 控制层:主要使用Servlet进行控制。
- 数据访问层:使用DAO、Hibernate、JDBC技术实现对数据的增删改查。
- JavaBean用于封装数据,处理部分核心逻辑,每一层中都用到。
模式一
模式一指的就是在开发中将显示层、控制层、数据层的操作统一交给JSP或者JavaBean来进行处理。
模式一有两种情况:
1)完全使用JSP做开发
- 优点:开发速度快,只要写JSP就行了,JavaBean和Servlet都不用设计;小幅度修改代码方便,直接修改JSP页面交给WEB容器就行了,不像Servlet还有编译成.class文件再交给服务器。
- 缺点:程序的可读行差、复用性低、代码复杂。
2)使用JSP+JavaBean做开发
- 优点:程序的可读性较高,大部分的代码都写在JavaBean上,不会和HTML代码混合在一起,可读性还行的;可重复利用高,核心的代码都由JavaBean开发了,JavaBean的设计就是用来重用、封装、大大减少编写重复代码的工作。
- 缺点:没有流程控制,程序中的JSP页面都需要检查请求的参数是否正确,异常发生时的处理。显示操作和业务逻辑代码工作会紧密耦合在一起的。日后维护会困难。
应用实例:
使用JSP+JavaBean开发一个简易的计算器:
首先开发JavaBean对象
public class Calculator { private double firstNum; private double secondNum; private char operator='+'; private double result; public void calculate() { switch (this.operator) { case '+': this.result = this.firstNum + this.secondNum; break; case '-': this.result = this.firstNum - this.secondNum; break; case '*': this.result = this.firstNum * this.secondNum; break; case '/': if (this.secondNum == 0) { throw new RuntimeException("除数不能为0"); } this.result = this.firstNum / this.secondNum; break; default: throw new RuntimeException("传入非法字符"); } } public double getFirstNum() { return firstNum; } public void setFirstNum(double firstNum) { this.firstNum = firstNum; } public double getSecondNum() { return secondNum; } public void setSecondNum(double secondNum) { this.secondNum = secondNum; } public char getOperator() { return operator; } public void setOperator(char operator) { this.operator = operator; } public double getResult() { return result; } public void setResult(double result) { this.result = result; } }
在开发显示页面
<%@ page contentType="text/html;charset=UTF-8" language="java" pageEncoding="utf-8" %> <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <html> <head> <title>Title</title> <jsp:useBean id="calculator" class="com.company.Calculator" scope="page"></jsp:useBean> <jsp:setProperty name="calculator" property="*"></jsp:setProperty> <jsp:scriptlet> calculator.calculate(); </jsp:scriptlet> <c:out value="计算得出的结果是:"/> <jsp:getProperty name="calculator" property="firstNum"/> <jsp:getProperty name="calculator" property="operator"/> <jsp:getProperty name="calculator" property="secondNum"/> <c:out value="="/> <jsp:getProperty name="calculator" property="result"/> </head> <body> <form action="Calculator.jsp" method="post"> <table border="1"> <tr> <td colspan="2">简单计数器</td> </tr> <tr> <td>第一个参数:</td> <td><input type="text" name="firstNum"></td> </tr> <tr> <td>运算符</td> <td> <select name="operator"> <option value="+">+</option> <option value="-">-</option> <option value="*">*</option> <option value="/">/</option> </select> </td> </tr> <tr> <td>第二个参数:</td> <td><input type="text" name="secondNum"></td> </tr> <tr> <td colspan="2"> <input type="submit" value="提交"> </td> </tr> </table> </form> </body> </html>
效果:

只用了一个JSP页面和一个JavaBean就完成了一个简易计算器的开发。
模式一适合小型的开发,复杂程度低的开发,因为模式一的特点就是开发速度快,但在进行维护的时候就要付出更大的代价。
模式二
模式二中所有的开发都是以Servlet为主体展开的,由Servlet接收所有的客户端请求,然后根据请求调用相应的JavaBean,并所有的显示结果交给JSP完成,也就是俗称的MVC设计模式。

MVC设计模式
- 显示层(View):主要负责接收Servlet传递的内容,调用JavaBean,将内容显示给用户。
- 控制层(Controller):主要负责所有用户的请求参数,判断请求参数是否合法,根据请求的类型调用JavaBean,将最终的处理结果交给显示层显示。
- 模型层(Model):模型层包括了业务层,DAO层。
应用实例:
使用MVC模式开发一个简单的用户注册登陆的案例,作为一个简单的用户登陆注册,直接使用XML文档当做小型数据库。
搭建开发环境
- 导入相对应的开发包:(dom4j-1.6.1.jar和jaxen-1.2.0.jar)操作XML文件,servlet-api.jar 处理Servlet,(commons-beanutils-1.8.3.jar和commons-logging-api-1.1.jar)将表单数据填充到对象中,
- 创建程序的包名。
- 创建XML文件,当做小型的数据库。

开发实体User
public class User { private String id; private String username; private String password; private String email; private Date birthday; public String getId() { return id; } public void setId(String id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public Date getBirthday() { return birthday; } public void setBirthday(Date birthday) { this.birthday = birthday; } @Override public String toString() { return "User{" + "id=" + id + ", username='" + username + '\'' + ", password='" + password + '\'' + ", email='" + email + '\'' + ", birthday=" + birthday + '}'; } }
开发Dao
注册(外界传递一个User对象进来,可以在XML文档中新增一条信息)。登陆(外界传递用户名和密码过来,就在XML文档中查找有没有改用户名和密码,如果有就返回一个User对象)。
定义一个接口
public interface UserDao { //登陆时调用该方法 User find(String username, String password); //注册时调用该方法 void register(User user); }
实现上面的接口
public class UserImplDao implements UserDao { @Override public User find(String username, String password) { try { //得到XML文档的流对象 InputStream inputStream = UserImplDao.class.getClassLoader().getResourceAsStream("user.xml"); //得到dom4j的解析器对象 SAXReader saxReader = new SAXReader(); //解析XML文档 Document read = saxReader.read(inputStream); //使用XPATH技术,查找XML文档中是否有传递进来的username和password Element element = (Element) read.selectSingleNode("//user[@username='" + username + "'and@password='"+password+"']"); if (element == null) { return null; } //如果有,就把XML查出来的的节点信息封装到User对象,返回出去 User user = new User(); user.setId(element.attributeValue("id")); user.setUsername(element.attributeValue("username")); user.setPassword(element.attributeValue("password")); user.setEmail(element.attributeValue("email")); //生日需要转换一下,XML文档保存的是字符串,User对象需要的是Date类型 SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd"); Date birthday = simpleDateFormat.parse(element.attributeValue("birthday")); user.setBirthday(birthday); //返回User对象 return user; } catch (DocumentException e) { e.printStackTrace(); throw new RuntimeException("初始化时候出错了"); } catch (ParseException e) { e.printStackTrace(); throw new RuntimeException("查询的时候出错了"); } return null; } @Override public void register(User user) { try { //获取XML文档路径 String path = UserImplDao.class.getClassLoader().getResource("user.xml").getPath(); //获取dom4j的解析器,解析XML文档 SAXReader saxReader = new SAXReader(); Document document = saxReader.read(path); //在XML文档中创建新的节点 Element userElement = DocumentHelper.createElement("user"); userElement.addAttribute("id", String.valueOf(user.getId())); userElement.addAttribute("username", user.getUsername()); userElement.addAttribute("password", user.getPassword()); userElement.addAttribute("emial", user.getEmail()); //日期返回的是指定格式的日期 SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd"); String date = simpleDateFormat.format(user.getBirthday()); userElement.addAttribute("birthday", date); //把新创建的节点增加到父节点上 document.getRootElement().add(userElement); //把XML内容中文档的内容写到硬盘文件上 OutputFormat outputFormat = OutputFormat.createPrettyPrint(); outputFormat.setEncoding("UTF-8"); org.dom4j.io.XMLWriter xmlWriter = new org.dom4j.io.XMLWriter(new FileWriter(path), outputFormat); xmlWriter.write(document); xmlWriter.close(); System.out.println("保存成功"); } catch (DocumentException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }
读取XML文件使用的类装载器的方法,在编译后,按照WEB的结构目录,XML文件的读写是在WEB-INF的classes目录下的。
开发service层
service层就是将多个原子性的DAO操作进行组合,组合成一个完整的业务逻辑。简单来说:对web层提供所有的业务服务的。
public class UserService { private UserImplDao userImplDao=new UserImplDao(); public void register(User user){ userImplDao.register(user); } public User find(String username,String password){ User user=userImplDao.find(username,password); return user; } }
开发web层
注册界面
开发注册界面的jsp:register.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <body> <h1>欢迎来到注册界面</h1> ${pageContext.request.contextPath} <form action="${pageContext.request.contextPath}/RegisterServlet" method="post"> <table> <tr> <td>用户名</td> <td> <input type="text" name="username"/> </td> </tr> <tr> <td>密码</td> <td> <input type="text" name="password"/> </td> </tr> <tr> <td>用户名</td> <td> <input type="text" name="pasword"/> </td> </tr> <tr> <td>邮箱</td> <td> <input type="text" name="email"/> </td> </tr> <tr> <td>生日</td> <td> <input type="text" name="birthday"/> </td> </tr> <tr> <td> <input type="submit" name="提交"/></td> <td> <input type="reset" name="重置"/> </td> </tr> </table> </form> </body> </html>
效果:

开发处理jsp的servlet
protected void doPost(javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException, IOException { try { //获取页面参数 String username = request.getParameter("username"); String password=request.getParameter("password"); String email=request.getParameter("email"); String date=request.getParameter("birthday"); //对生日参数进行处理转换为日期类型 SimpleDateFormat simpleDateFormat=new SimpleDateFormat("yyyy-MM-dd"); Date birthday = simpleDateFormat.parse(date); //将参数封装到User对象中 User user=new User(); String id=Generator.makeId(); user.setId(id); user.setUsername(username); user.setPassword(password); user.setEmail(email); user.setBirthday(birthday); //注册操作 UserService userService=new UserService(); userService.register(user); } catch (ParseException e) { e.printStackTrace(); } }
使用反射机制中的BeanUtils开发包,为了更好地重用,将它写成一个工具类。
/* *将Parameter参数的数据封装到Bean中,为了外边不用强制,这里就使用泛型了。 *@request 由于要获取的是Parameter参数的信息,所以需要有request对象。 *@tClass 本身是不知道封装什么对象的,所以用class */ public static <T> T requestToBean(HttpServletRequest request, Class<T> tClass) { try { //创建tClass的对象 T bean = tClass.newInstance(); //日期转换器确保日期参数转换bean中对应日期属性 ConvertUtils.register(new DateLocaleConverter(),Date.class); /* ConvertUtils.register(new Converter() { @Override public Object convert(Class aClass, Object o) { SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd"); try { return simpleDateFormat.parse(o.toString()); } catch (ParseException e) { // TODO Auto-generated catch block e.printStackTrace(); } return null; } }, Date.class);*/ //获取得到Parameter中全部的参数的名称 Enumeration<String> parameterNames = request.getParameterNames(); //遍历上面的集合 while (parameterNames.hasMoreElements()) { //获取得到每一个带过来参数的名字 String name = (String) parameterNames.nextElement(); //获取参数对应的值 String value = request.getParameter(name); //把数据封装到Bean对象中 BeanUtils.setProperty(bean, name, value); } return bean; } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } return null; }
如果没有日期转换器,日期不能直接封装到Bean对象中,会直接报出异常。当BeanUtils的setProperty()方法检测到日期时,会自动调用日期转换器对日期进行转换,从而实现封装。
//日期转换器 ConvertUtils.register(new DateLocaleConverter(), Date.class);
还有一个问题,用户的id不是自己输入的,是由程序生成的。避免id的重复,就使用UUID生成用户的id,为了更好的重用,把它封装成一个方法。
public static String makeId(){ return UUID.randomUUID().toString(); }
上面的代码是不够完善的(没有检验用户输入的信息、注册成功或失败都没有给出提示等等)
校验用户输入的信息,如果用户输入的信息不合法,那就直接跳转回注册的界面。之前,用BeanUtils把Parameter的信息全部封装到User对象中,但现在要验证用户提交表单的数据,也应该把表单的数据用一个对象保存着【面向对象的思想、封装、重用】。流程:当用户提交表单数据的时候,就把表单数据封装到我们设计的表单对象上,调用表单对象的方法,验证数据是否合法。
开发一个表单对象,定义一个validate()方法。
public class FormBean { private String username; private String password; private String password2; private String email; private String birthday; private HashMap<String,String> errors=new HashMap<>(); public HashMap<String, String> getErrors() { return errors; } public void setErrors(HashMap<String, String> errors) { this.errors = errors; } //判断表单数据是否合法 public boolean validate(){ if(this.username==null||this.username.trim().equals("")){ errors.put("username","用户名不能为空,且长度为3到8的字符"); return false; }else{ if(!this.username.matches("[a-zA-Z]{3,8}")){ errors.put("username","用户名不能为空,且长度为3到8的字符"); return false; } } if(this.password==null||this.password.trim().equals("")){ errors.put("password", "密码不能为空,并且要是3-8的数字"); return false; }else{ if(!this.password.matches("\\d{3,8}")){ errors.put("password", "密码不能为空,并且要是3-8的数字"); return false; } } if(this.password2!=null&&!this.password2.equals(this.password)){ errors.put("password2", "两次密码要一致"); return false; } if(this.email!=null&&!this.email.trim().equals("")&&this.email.matches("\\w+@\\w+(\\.\\w+)+")){ errors.put("email", "邮箱不合法!"); System.out.println("邮箱格式错误"); return false; } if(this.birthday!=null&&!this.birthday.trim().equals("")){ try { DateLocaleConverter dateLocaleConverter=new DateLocaleConverter(); dateLocaleConverter.convert(this.birthday); } catch (Exception e) { System.out.println("日期格式错误"); errors.put("birthday", "日期不合法!"); e.printStackTrace(); } } return true; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getPassword2() { return password2; } public void setPassword2(String password2) { this.password2 = password2; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String getBirthday() { return birthday; } public void setBirthday(String birthday) { this.birthday = birthday; } }
当提交的表单数据中,日期没有填写,就抛出错误。原因很简单:表单数据提交给Servlet,Servlet将表单的数据(Parameter中的数据)用BeanUtils封装到User对象中,当封装到日期的时候,发现日期为null,无法转化成日期对象。
首先,设定的时候,已经允许了email和birthday可以为空,那么在DAO层UserImplDao.java就应该有相应的逻辑判断email和birthday是否为空。
if(user.getEmail()==null){ userElement.addAttribute("email",""); }else{ userElement.addAttribute("email", user.getEmail()); } if(user.getBirthday()!=null) { SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd"); String date = simpleDateFormat.format(user.getBirthday()); userElement.addAttribute("birthday", date); }else{ userElement.addAttribute("birthday",""); }
解决方法:BeanUtil.java Parameter中的数据如果是"",就不把数据封装到User对象中,执行下一次循环。
if(value==""){ continue; }else { BeanUtils.setProperty(bean, name, value); }
将数据封装到User对象中还有另外一个方法:
BeanUtils有个copyProperties()方法,可以将某个对象的成员数据拷贝到另外一个对象的成员变量数据上(前提是成员变量的名称相同),FormBean对象的成员变量名称和User对象的成员变量的名称是一致的。并且,前面在验证的时候,已经把parameter中带过来的数据封装到了FormBean对象中了,所以可以使用copyProperties()方法。

处理表单的Servlet完整代码如下:
FormBean formBean = BeanUtil.requestToBean(request, FormBean.class); if(!formBean.validate()){ request.setAttribute("formbean",formBean); request.getRequestDispatcher("Register.jsp").forward(request,response); return; } //方法一 /*User user = new User(); user.setId(WebUtils.makeId()); BeanUtils.copyProperties(user,formBean);*/ //方法二 User user = BeanUtil.requestToBean(request, User.class); user.setId(Generator.makeId()); UserService userService=new UserService(); userService.register(user); response.setContentType("text/html;charset=UTF-8"); response.getWriter().write("注册成功了");
当发现用户输入的信息不合法时,把错误的信息记录下来,等到返回注册页面,就提示用户哪里出错了。

前面写的代码,但页面出错,并没有一次性标记出来给用户。要改也十分简单:在验证的时候,用一个布尔变量标记,最后返回布尔变量即可。
public boolean validate(){ boolean flag=true; if(this.username==null||this.username.trim().equals("")){ errors.put("username","用户名不能为空,且长度为3到8的字符"); flag=false; }else{ if(!this.username.matches("[a-zA-Z]{3,8}")){ errors.put("username","用户名不能为空,且长度为3到8的字符"); flag=false; } } if(this.password==null||this.password.trim().equals("")){ errors.put("password", "密码不能为空,并且要是3-8的数字"); flag=false; }else{ if(!this.password.matches("\\d{3,8}")){ errors.put("password", "密码不能为空,并且要是3-8的数字"); flag=false; } } if(this.password2!=null&&!this.password2.equals(this.password)){ errors.put("password2", "两次密码要一致"); flag=false; } if(this.email!=null&&!this.email.trim().equals("")&&this.email.matches("\\w+@\\w+(\\.\\w+)+")){ errors.put("email", "邮箱不合法!"); System.out.println("邮箱格式错误"); flag=false; } if(this.birthday!=null&&!this.birthday.trim().equals("")){ try { DateLocaleConverter dateLocaleConverter=new DateLocaleConverter(); dateLocaleConverter.convert(this.birthday); } catch (Exception e) { System.out.println("日期格式错误"); errors.put("birthday", "日期不合法!"); e.printStackTrace(); } } return flag; }
登陆界面
Login.jsp
<h1>这是登陆界面</h1>
<form method="post" action="${pageContext.request.contextPath}/LoginServlet">
<table>
<tr>
<td>用户名</td>
<td>
<input type="text" name="username"/>
</td>
</tr>
<tr>
<td>密码</td>
<td>
<input type="password" name="password"/>
</td>
</tr>
<tr>
<td>
<input type="submit" name="登陆"/>
</td>
<td>
<input type="reset" name="重置"/>
</td>
</tr>
</table>
</form>
处理登陆表单的Servlet
String username = request.getParameter("username");
String password = request.getParameter("password");
UserService userService = new UserService();
User user = userService.find(username, password);
response.setContentType("text/html;charset=UTF-8");
if (user == null) {
response.getWriter().write("登陆失败");
} else {
response.getWriter().write("登陆成功");
}
把注册和登陆都放在首页
<h1>这是首页!</h1> <a href="${pageContext.request.contextPath}/LoginUIServlet">登陆</a> <a href="${pageContext.request.contextPath}/RegisterUIServlet">注册</a> </body>

总结
- 使用JSP+JavaBean开发一个简单计算器,是非常容易的,显示页面和请求都是交由JSP来做。
- MVC模式开发使用Servlet来做处理请求,代码量略大,但层次清晰。
- 使用BeanUtils开发组件可以将request请求的参数封装到JavaBean对象中,Date属性要另外处理。
- 校验的功能也是使用一个JavaBean来完成,目的就是为了可重用性,职责分工。同时,可以在该JavaBean设置一个Map集合来保存错误的信息,以便在前台上展示错误的信息。
注:程式报错:m4j java.lang.NoClassDefFoundError: org/jaxen/JaxenException,使用dom4j解析XML时,要快速获取某个节点的数据,使用XPath是个不错的方法,dom4j的快速手册也建议使用这种方式。但会报出异常,因为不光要有dom4j包,还要有jaxen包。
posted on
浙公网安备 33010602011771号