在Web开发模式中,有两个主要的开发结构,称为模式一(Mode I)和模式二(Mode II).
首先我们来理清一些概念吧:
模式一指的就是在开发中将显示层、控制层、数据层的操作统一交给JSP或者JavaBean来进行处理!
模式一有两种情况:
我们使用JavaBean+JSP开发一个简易的计算器吧,效果如图下:
public class Calculator {
private double firstNum;
private double secondNum;
private char Operator = '+';
private double result;
//JavaBean提供了计算的功能
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) {
Operator = operator;
}
public double getResult() {
return result;
}
public void setResult(double result) {
this.result = result;
}
}
<%--开发用户界面--%>
<form action="/zhongfucheng/1.jsp" method="post">
<table border="1">
<tr>
<td colspan="2">简单计数器</td>
<td></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>
<td></td>
</tr>
</table>
</form>
<%--获取得到Bean对象--%>
<jsp:useBean id="calculator" class="domain.Calculator" scope="page"/>
<%--设置Bean对象的数据--%>
<jsp:setProperty name="calculator" property="*"/>
<%--调用Caculator的方法计算出值--%>
<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"/>
开发这个简易的计算器,只用了一个JSP页面和一个JavaBean完成!
总的来说,Mode I 适合小型的开发,复杂程序低的开发,因为Mode I 的特点就是开发速度快,但在进行维护的时候就要付出更大的代价!
Mode II 中所有的开发都是以Servlet为主体展开的,由Servlet接收所有的客户端请求,然后根据请求调用相对应的JavaBean,并所有的显示结果交给JSP完成!,也就是俗称的MVC设计模式!
MVC设计模式:
我们使用MVC模式开发一个简单的用户登陆注册的案例吧!作为一个简单的用户登陆注册,这里就直接使用XML文档当作小型数据库吧!
private int id;
private String username;
private String password;
private String email;
private Date birthday;
//....各种setter、getter
//外界传递用户名和密码进来,我要在XML文档中查找是否有该条记录
public User find(String username, String password) {
//得到XML文档的流对象
InputStream inputStream = UserImplXML.class.getClassLoader().getResourceAsStream("user.xml");
//得到dom4j的解析器对象
SAXReader saxReader = new SAXReader();
try {
//解析XML文档
Document document = saxReader.read(path);
//使用XPATH技术,查找XML文档中是否有传递进来的username和password
Element element = (Element) document.selectSingleNode("//user[@username='" + username + "' and@password='" + password + "']");
if (element == null) {
return null;
}
//如果有,就把XML查出来的节点信息封装到User对象,返回出去
User user = new User();
user.setId(Integer.parseInt(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("yy-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("查询的时候出错啦!");
}
}
private String username = "zhongfucheng";
private String password = "123";
@Test
public void testLogin() {
UserImplXML userImplXML = new UserImplXML();
User user = userImplXML.find(username, password);
System.out.println(user.getBirthday());
System.out.println(user.getEmail());
System.out.println(user.getId());
System.out.println(user.getUsername());
System.out.println(user.getPassword());
}
3.2注册功能
//注册功能,外界传递一个User对象进来。我就在XML文档中添加一条信息
public void register(User user) {
//获取XML文档路径!
String path = UserImplXML.class.getClassLoader().getResource("user.xml").getPath();
try {
//获取dom4j的解析器,解析XML文档
SAXReader saxReader = new SAXReader();
Document document = saxReader.read(path);
//在XML文档中创建新的节点
Element newElement = DocumentHelper.createElement("user");
newElement.addAttribute("id", String.valueOf(user.getId()));
newElement.addAttribute("username", user.getUsername());
newElement.addAttribute("email", user.getEmail());
newElement.addAttribute("password", user.getPassword());
//日期返回的是指定格式的日期
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yy-MM-dd");
String date = simpleDateFormat.format(user.getBirthday());
newElement.addAttribute("birthday",date);
//把新创建的节点增加到父节点上
document.getRootElement().add(newElement);
//把XML内容中文档的内容写到硬盘文件上
OutputFormat outputFormat = OutputFormat.createPrettyPrint();
outputFormat.setEncoding("UTF-8");
XMLWriter xmlWriter = new XMLWriter(new FileWriter(path),outputFormat);
xmlWriter.write(document);
xmlWriter.close();
} catch (DocumentException e) {
e.printStackTrace();
throw new RuntimeException("注册的时候出错了!!!");
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException("注册的时候出错了!!!");
}
}
@Test
public void testRegister() {
UserImplXML userImplXML = new UserImplXML();
//这里我为了测试的方便,就添加一个带5个参数的构造函数了!
User user = new User(10, "nihao", "123", "sina@qq.com", new Date());
userImplXML.register(user);
}
service层的开发就非常简单了!上面已经说了,service层就是:将多个原子性的DAO操作进行组合,组合成一个完整的业务逻辑。简单来说:对web层提供所有的业务服务的!
在逻辑代码不是非常复杂的情况下,我们可以没有service层的,这里还是演示一下吧!
public class UserServiceXML {
//Service层就是调用Dao层的方法,我们就直接在类中创建Dao层的对象了
UserDao userImplXML = new UserImplXML();
public void register(User user) {
userImplXML.register(user);
}
public void login(String username, String password) {
userImplXML.find(username, password);
}
}
public class RegisterUIServlet extends javax.servlet.http.HttpServlet {
protected void doPost(javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException, IOException {
//直接跳转到显示注册界面的JSP
request.getRequestDispatcher("/WEB-INF/register.jsp").forward(request, response);
}
protected void doGet(javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException, IOException {
this.doPost(request, response);
}
}
<h1>欢迎来到注册界面!</h1>
<%--提交给处理注册的处理Servlet--%>
<form method="post" action="${pageContext.request.contextPath}/RegisterServlet">
<table>
<%--对于id来讲,是服务器分配的!不需要用户自己输入--%>
<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="password">
</td>
</tr>
<tr>
<td>邮箱</td>
<td>
<input type="text" name="email">
</td>
</tr>
<tr>
<td>生日</td>
<td>
<input type="text " name="birethday">
</td>
</tr>
<tr>
<td>
<input type="submit" value="提交">
</td>
<td>
<input type="reset" value="重置!">
</td>
</tr>
</table>
</form>
//首先要接受Parameter的参数,封装到User里面去
String username = request.getParameter("username");
String password = request.getParameter("password");
//......如果参数过多,我们就要写好多好多类似的代码了...
/*
* 将Parameter参数的数据封装到Bean中,为了外边不用强转,这里就使用泛型了!
*
* @request 由于要获取的是Parameter参数的信息,所以需要有request对象
* @tClass 本身是不知道封装什么对象的,所以用class
*
* */
public static <T> T request2Bean(HttpServletRequest httpServletRequest, Class<T> tClass) {
try {
//创建tClass的对象
T bean = tClass.newInstance();
//获取得到Parameter中全部的参数的名字
Enumeration enumeration = httpServletRequest.getParameterNames();
//遍历上边获取得到的集合
while (enumeration.hasMoreElements()) {
//获取得到每一个带过来参数的名字
String name = (String) enumeration.nextElement();
//获取得到值
String value = httpServletRequest.getParameter(name);
//把数据封装到Bean对象中
BeanUtils.setProperty(bean, name, value);
}
return bean;
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("封装数据到Bean对象中出错了!");
}
}
//日期转换器
ConvertUtils.register(new DateLocaleConverter(), Date.class);
/*生成ID*/
public static int makeId() {
return Integer.parseInt(UUID.randomUUID().toString());
}
User user = WebUtils.request2Bean(request, User.class);
user.setId(WebUtils.makeId());
//调用service层的注册方法,实现注册
ServiceBussiness serviceBussiness = new UserServiceXML();
serviceBussiness.register(user);
上面的代码是不够完善的(没有校验用户输入的信息、注册成功或失败都没有给出提示..等等)
public class FormBean {
//表单提交过来的数据全都是String类型的,birthday也不例外!
private String username;
private String password;
private String password2;
private String email;
private String birthday;
/*用于判断表单提交过来的数据是否合法*/
public boolean validate() {
return false;
}
//......各种setter、getter方法
}
public boolean validate() {
//用户名不能为空,并且要是3-8的字符 abcdABcd
if (this.username == null || this.username.trim().equals("")) {
return false;
} else {
if (!this.username.matches("[a-zA-Z]{3,8}")) {
return false;
}
}
//密码不能为空,并且要是3-8的数字
if (this.password == null || this.password.trim().equals("")) {
return false;
} else {
if (!this.password.matches("\\d{3,8}")) {
return false;
}
}
//两次密码要一致
if (this.password2 != null && !this.password2.trim().equals("")) {
if (!this.password2.equals(this.password)) {
return false;
}
}
//邮箱可以为空,如果为空就必须合法
if (this.email != null && !this.email.trim().equals("")) {
if (!this.email.matches("\\w+@\\w+(\\.\\w+)+")) {
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("日期错误了!");
return false;
}
}
//如果上面都没有执行,那么就是合法的了,返回true
return true;
}
//将表单的数据封装到formBean中
FormBean formBean = WebUtils.request2Bean(request, FormBean.class);
//验证表单的数据是否合法,如果不合法就跳转回去注册的页面
if(formBean.validate()==false){
request.getRequestDispatcher("/WEB-INF/register.jsp").forward(request, response);
return;
}
try {
//将表单的数据封装到User对象中
User user = WebUtils.request2Bean(request, User.class);
user.setId(WebUtils.makeId());
//调用service层的注册方法,实现注册
ServiceBussiness serviceBussiness = new UserServiceXML();
serviceBussiness.register(user);
} catch (Exception e) {
e.printStackTrace();
}
它抛出了错误!原因也非常简单:表单数据提交给Servlet,Servlet将表单的数据(Parameter中的数据)用BeanUtils封装到User对象中,当封装到日期的时候,发现日期为null,无法转换成日期对象!
那我们现在要怎么解决呢?
首先我们要明确:因为我们在设定的时候,已经允许了email和birthday可以为空,那么在DAO层就应该有相应的逻辑判断email和birthday是否为空!
if (user.getEmail() == null) {
newElement.addAttribute("email", "");
} else {
newElement.addAttribute("email", user.getEmail());
}
//如果不是空才格式化信息
if (user.getBirthday() != null) {
//日期返回的是指定格式的日期
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
String date = simpleDateFormat.format(user.getBirthday());
newElement.addAttribute("birthday", date);
} else {
newElement.addAttribute("birthday", "");
}
解决办法:
public static <T> T request2Bean(HttpServletRequest httpServletRequest, Class<T> tClass) {
try {
//创建tClass的对象
T bean = tClass.newInstance();
//获取得到Parameter中全部的参数的名字
Enumeration enumeration = httpServletRequest.getParameterNames();
//日期转换器
ConvertUtils.register(new DateLocaleConverter(), Date.class);
//遍历上边获取得到的集合
while (enumeration.hasMoreElements()) {
//获取得到每一个带过来参数的名字
String name = (String) enumeration.nextElement();
//获取得到值
String value = httpServletRequest.getParameter(name);
//如果Parameter中的数据为"",那么我就不封装到User对象里边去!执行下一次循环
if (value == "") {
continue;
} else {
//把数据封装到Bean对象中
BeanUtils.setProperty(bean, name, value);
}
}
return bean;
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("封装数据到Bean对象中出错了!");
}
}
将数据封装到User对象中还有另外一个办法:
//将表单的数据封装到formBean中
FormBean formBean = WebUtils.request2Bean(request, FormBean.class);
//验证表单的数据是否合法,如果不合法就跳转回去注册的页面
if(formBean.validate()==false){
request.getRequestDispatcher("/WEB-INF/register.jsp").forward(request, response);
return;
}
try {
//这是第一种--------------------------
/*User user = new User();
user.setId(WebUtils.makeId());
BeanUtils.copyProperties(user,formBean);*/
//------------------------------------------
//这是第二种
User user1 = WebUtils.request2Bean(request,User.class);
user1.setId(WebUtils.makeId());
//-----------------------------------
//调用service层的注册方法,实现注册
ServiceBussiness serviceBussiness = new UserServiceXML();
serviceBussiness.register(user1);
} catch (Exception e) {
e.printStackTrace();
}
现在还有问题,如果我填写信息不合法,提交给服务器验证以后,服务器应该告诉用户哪个信息不合法,而不是直接把跳转回注册界面,把所有的信息全部清空,让用户重新填写!
我们应该这样做:当发现用户输入的信息不合法时,把错误的信息记录下来,等到返回注册页面,就提示用户哪里出错了!
//表单提交过来的数据全都是String类型的,birthday也不例外!
private String username;
private String password;
private String password2;
private String email;
private String birthday;
//记录错误的信息
private HashMap<String, String> error = new HashMap<>();
/*用于判断表单提交过来的数据是否合法*/
public boolean validate() {
//用户名不能为空,并且要是3-8的字符 abcdABcd
if (this.username == null || this.username.trim().equals("")) {
error.put("username", "用户名不能为空,并且要是3-8的字符");
return false;
} else {
if (!this.username.matches("[a-zA-Z]{3,8}")) {
error.put("username", "用户名不能为空,并且要是3-8的字符");
return false;
}
}
//密码不能为空,并且要是3-8的数字
if (this.password == null || this.password.trim().equals("")) {
error.put("password", "密码不能为空,并且要是3-8的数字");
return false;
} else {
if (!this.password.matches("\\d{3,8}")) {
error.put("password", "密码不能为空,并且要是3-8的数字");
return false;
}
}
//两次密码要一致
if (this.password2 != null && !this.password2.trim().equals("")) {
if (!this.password2.equals(this.password)) {
error.put("password2", "两次密码要一致");
return false;
}
}
//邮箱可以为空,如果为空就必须合法
if (this.email != null && !this.email.trim().equals("")) {
if (!this.email.matches("\\w+@\\w+(\\.\\w+)+")) {
error.put("email", "邮箱不合法!");
return false;
}
}
//日期可以为空,如果为空就必须合法
if (this.birthday != null && !this.birthday.trim().equals("")) {
try {
DateLocaleConverter dateLocaleConverter = new DateLocaleConverter();
dateLocaleConverter.convert(this.birthday);
} catch (Exception e) {
error.put("birthday", "日期不合法!");
return false;
}
}
//如果上面都没有执行,那么就是合法的了,返回true
return true;
}
//.....各种的setter和getter
//验证表单的数据是否合法,如果不合法就跳转回去注册的页面
if(formBean.validate()==false){
//在跳转之前,把formbean对象传递给注册页面
request.setAttribute("formbean", formBean);
request.getRequestDispatcher("/WEB-INF/register.jsp").forward(request, response);
return;
}
做到这里,还是有丢丢的问题,我们不应该把用户输入的数据全部清空的!你想想,如果用户注册需要输入多个信息,仅仅一个出错了,就把全部信息清空,要他重新填写,这样是不合理的!
还没有完善,细心的朋友可以发现,上面图的日期也是错误的,但是没一次性标记出来给用户!要改也十分简单:在验证的时候,不要先急着return false 用一个布尔型变量记住,最后返回布尔型的变量即可
无论注册成功还是失败都需要给用户一个友好界面的!
登陆和注册是类似的,我们按着注册的步骤来写就对了!
首先写一个提供登陆界面的Servlet
//直接跳转到登陆界面
request.getRequestDispatcher("/WEB-INF/login.jsp").forward(request, response);
<h1>这是登陆界面</h1>
<form action="${pageContext.request.contextPath}/LoginServlet" method="post">
<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" value="提交"></td>
<td><input type="reset" name="重置"></td>
</tr>
</table>
</form>
//获取提交过来的数据
String username = request.getParameter("username");
String password = request.getParameter("password");
//调用service层的方法,去查询数据库(XML)是否有该条记录
try {
ServiceBussiness serviceBussiness = new UserServiceXML();
User user = serviceBussiness.login(username, password);
if (user == null) {
request.setAttribute("message", "用户名或密码是错的");
} else {
request.setAttribute("message","登陆成功");
}
} catch (Exception e) {
e.printStackTrace();
request.setAttribute("message","登陆失败咯");
}
request.getRequestDispatcher("/message.jsp").forward(request, response);
<h1>这是首页!</h1>
<a href="${pageContext.request.contextPath}/LoginUIServlet">登陆</a>
<a href="${pageContext.request.contextPath}/RegisterUIServlet">注册</a>
</body>