说明:这是一个实现用户注册和登录的web应用,只是一个很小的demo,同时最初我们只是使用xml来代替数据库,之后才会加入数据库,主要是用来说明MVC开发模式。
(工程day09_user
)
一、搭建开发环境
1.1 导入相关的jar包
操作xml文档: dom4j-1.6.1.jar
操作xpath: jaxen-1.1-beta-6.jar
将数据封装到javabean中: commons-beanutils-1.9.2.jar
包含log4j日志包: commons-logging-1.2.jar
标签包:javax.servlet.jsp.jstl.jar、jstl-impl-1.2.2.jar
如果使用MyEclipse开发那么这两个包MyEclipse已经帮我加入了,无需自己加了。
1.2 创建程序开发用要的包
实体类存放包:cn.itcast.domain
dao层接口:cn.itcast.dao
dao层实现:cn.itcast.dao.impl
业务(service)层接口:cn.itcast.service
业务实现:cn.itcast.service.impl
web层业务控制:cn.itcast.web.controller
web层UI控制:cn.itcast.web.UI
工具包:cn.itcast.utils
测试包:junit.test
自定义异常包:cn.itcast.exception
表单数据包:cn.itcast.web.formbean
jsp包:WEB-INF/jsp
说明:
- 1.包比较多,刚开始可能建的不全,我们可以在后面进行添加。
- 2.对于web层,我们要知道,其实servlet有两种,一种是实际处理业务类的servlet,另一种是为用户提供相关的界面jsp的,因为jsp我们需要保护起来,不能让用户随便访问。所以放在了
WEB-INF
目录下。 - 3.对于异常处理包,我们知道在应用中我们有时候可能需要通过异常来传递相关的异常消息,所以需要一个异常处理包。
- 4.由于数据表单一般来说和实体类是不太一致的,所以需要专门建包存放。
二、开发实体
User.java
public class User {
private String id;
private String username ;
private String password ;
private String email ;
private Date birthday ;
...
}
三、开发dao层工具类
XmlUtils.java
package cn.itcast.utils;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.SAXReader;
import org.dom4j.io.XMLWriter;
//注意:使用dom4j操作xml文档我们可以直接去查文档,在quick start就可以查到
public class XmlUtils {
private static String filename = "users.xml";
public static Document getDocument() throws DocumentException{
//得到资源文件的URL地址
URL url = XmlUtils.class.getClassLoader().getResource(filename);
//得到资源文件的真实路径
String realpath = url.getPath();
//操作xml文档
SAXReader reader = new SAXReader();
return reader.read(realpath);//工具类的异常一般抛给其调用者
}
public static void write2Xml(Document document) throws IOException{
URL url = XmlUtils.class.getClassLoader().getResource(filename);
String realpath = url.getPath();
//使用格式化输出器进行输出
OutputFormat format = OutputFormat.createPrettyPrint();
XMLWriter writer = new XMLWriter(new FileOutputStream(realpath), format );
writer.write( document );
writer.close();
}
}
说明:这里我们由于使用的是xml存放数据,所以需要新建一个操作xml文档的工具类,在后面我们换成MySQL或其他数据库之后同样也需要这样一个工具类来获取数据库的链接等。
四、开发dao层
UserDaoXmlImpl.java
package cn.itcast.dao.impl;
import java.text.SimpleDateFormat;
import org.dom4j.Document;
import org.dom4j.Element;
import cn.itcast.dao.UserDao;
import cn.itcast.domain.User;
import cn.itcast.utils.XmlUtils;
public class UserDaoXmlImpl implements UserDao {
//使用用户名和密码去查找,用于用户登录
@Override
public User find(String username , String password) {
try {
//这里的异常可能是资源文件读取不到,所以抛出去是没有意义的,而且不能
//直接打印,因为我们需要让上一层知道这里出现了异常,这里最好的方式是自己定义一个异常,后面再说
Document document = XmlUtils.getDocument();
//下面使用了xpath
Element e = (Element) document.selectSingleNode("//user[@username='"+username+"'and @password='"+password+"']");
if(e == null){
return null;
}
User user = new User();
user.setId(e.attributeValue("id"));
user.setUsername(e.attributeValue("username"));
user.setPassword(e.attributeValue("password"));
user.setEmail(e.attributeValue("email"));
String birth = e.attributeValue("birthday");//这里取到的是一个日期字符串,需要转换成日期
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
user.setBirthday(sdf.parse(birth));
return user;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
//使用用户名去查找,用于用户注册,查看系统中此用户名是否被使用
@Override
public User find(String username){
try {
Document document = XmlUtils.getDocument();
//下面使用了xpath
Element e = (Element) document.selectSingleNode("//user[@username='"+username+"']");
if(e == null){
return null;
}
User user = new User();
user.setId(e.attributeValue("id"));
user.setUsername(e.attributeValue("username"));
user.setPassword(e.attributeValue("password"));
user.setEmail(e.attributeValue("email"));
String birth = e.attributeValue("birthday");//这里取到的是一个日期字符串,需要转换成日期
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
user.setBirthday(sdf.parse(birth));
return user;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
//添加用户
@Override
public void add(User user){
try {
Document document = XmlUtils.getDocument();
Element root = document.getRootElement();
Element user_node = root.addElement("user");//创建user节点,并挂到root节点上
//向新节点中赋值
user_node.setAttributeValue("id", user.getId());
user_node.setAttributeValue("username", user.getUsername());
user_node.setAttributeValue("password", user.getPassword());
user_node.setAttributeValue("email", user.getEmail());
user_node.setAttributeValue("birthday", user.getBirthday().toLocaleString());
XmlUtils.write2Xml(document);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
说明:
- 1.这里我们只是提供用户注册和登录,所以只有查找和添加方法。如果以后的项目中业务比较复杂可能还需要删除和修改等操作。
- 2.时间属性一定要进行处理,这里需要将字符串转换成一个日期对象。
- 3.在登录的时候我们需要用户的用户名和密码,而在注册的时候我们首先要查看数据库中此用户名是否已经存在,所以这里我们需要提供两个查找的方法。
测试
开发一层之后我们需要立即进行测试。
UserDaoTest.java
package junit.test;
import java.util.Date;
import org.junit.Test;
import cn.itcast.dao.UserDao;
import cn.itcast.dao.impl.UserDaoXmlImpl;
import cn.itcast.domain.User;
public class UserDaoTest {
@Test
public void testAdd(){
User user = new User();
user.setId("2222");
user.setUsername("bbb");
user.setPassword("2222");
user.setEmail("[email protected]");
user.setBirthday(new Date());
UserDao dao = new UserDaoXmlImpl();
dao.add(user);
//注意:测试通过后我们发现工程下的users.xml没有改变,其实这里改变的是classes中的那个users.xml,而不是
//工程src下的这个文件,因为我们编译之后src下的users.xml是发布到classes目录下的,我们可以看到工程最下面
//多了一个users.xml文件
}
@Test
public void testFind(){
UserDao dao = new UserDaoXmlImpl();
//User user = dao.find("aaa", "123");
User user = dao.find("aaa");
}
}
说明:测试add方法时我们直接new一个User对象,然后设置好相关的属性值,直接调用add方法进行测试。而查找方法我们直接调用测试即可。
五、开发service层
BusinessServiceImpl.java
package cn.itcast.service.impl;
import cn.itcast.dao.UserDao;
import cn.itcast.dao.impl.UserDaoXmlImpl;
import cn.itcast.domain.User;
import cn.itcast.exception.UserExistException;
import cn.itcast.service.BusinessService;
public class BusinessServiceImpl implements BusinessService {
//有两个需求,一个是注册一个是登录
UserDao dao = new UserDaoXmlImpl();
//提供注册服务
@Override
public void registerUser(User user) throws UserExistException{
if(dao.find(user.getUsername()) != null){
//在应用开发中一般都是抛运行时异常,但是如果我们想将异常当成一个返回值来让上一层处理的话
//那么需要抛编译时异常,比如这里的给用户一个友好提示(注册的用户已存在)。最好的做法就是自定义
//异常,而编译时异常需要在方法上申明
throw new UserExistException("注册的用户名已存在");
}
dao.add(user);
}
//提供登录服务
@Override
public User loginUser(String username , String password){
return dao.find(username, password);
}
}
说明:很明显我们可以看到在用户注册的时候如果此用户已经存在,那么我们需要向上一层提供一个信息,而在开发中一般我们使用异常向上一层提供这类信息。于是我们需要开发一个专门的异常包用来存放各类异常。最后注意抽取接口。
cn.itcast.exception/UserExistException.java
package cn.itcast.exception;
//当用户已经存在的时候抛此异常,继承Exception表示是编译时异常,如果是运行时异常则继承RuntimeException
public class UserExistException extends Exception {
//自定义异常肯定是用来封装信息,那一般使用构造函数来封装信息,从父类继承
public UserExistException() {
super();
// TODO Auto-generated constructor stub
}
public UserExistException(String message, Throwable cause,
boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
// TODO Auto-generated constructor stub
}
public UserExistException(String message, Throwable cause) {
super(message, cause);
// TODO Auto-generated constructor stub
}
public UserExistException(String message) {
super(message);
// TODO Auto-generated constructor stub
}
public UserExistException(Throwable cause) {
super(cause);
// TODO Auto-generated constructor stub
}
}
说明:一定要注意程序中对异常的一些说明。异常类中的方法都是直接从父类中继承的,我们一般通过构造方法来设置异常信息。
六、web层
6.1注册
web层的开发中我们需要知道,为了网站的安全性,其实工程中的相关jsp文件是不能随便让用户直接访问的,于是我们将相关的jsp放在
WEB-INF
目录中。那如何为用户提供相关的页面呢?这里我们使用servlet为用户提供相关的jsp页面,而这类servlet不是真正处理用户业务请求的servlet,于是还有另一类servlet专门处理用户的业务请求。这里我们将前者放在cn.itcast.web.UI
包中,后者放在cn.itcast.web.controller
包中。而当用户注册等请求的时候需要将表单数据提交给后台进行处理,而表单数据我们是需要进行校验的,同时表单数据往往和真实实体类的属性是不太一致的,所以我们需要一个专门的包存放表单数据类。这里是
cn.itcast.web.formbean
。
RegisterFormBean.java
package cn.itcast.web.formbean;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
//表单中的数据一般都是String类型
public class RegisterFormBean {
private String username ;//用户名不能为空,并且要是3-8为的字符
private String password ;//密码不能为空,并且要是3-8位的数字
private String password2 ;//两次的密码必须一致
private String email ;//可以为空,不为空的时候必须是一个合法的邮箱
//这里的生日必须是字符串类型,而后面我们需要校验此字符串是否是一个合法的日期
private String birthday ;//可以为空,不为空时要是一个合法的日期
private Map errors = new HashMap();//用来保存校验失败信息
public boolean validate(){
boolean isOk = true;
if(this.username==null || this.username.trim().equals("") ){
isOk = false;
errors.put("username", "用户名不能为空!!");
}else{
if(!this.username.matches("[a-zA-Z]{3,8}")){
isOk = false;
errors.put("username", "用户名必须是3-8位的字母!!");
}
}
if(this.password==null || this.password.trim().equals("")){
isOk = false;
errors.put("password", "密码不能为空!!");
}else{
if(!this.password.matches("\\d{3,8}")){
isOk = false;
errors.put("password", "密码必须是3-8位的数字!!");
}
}
//private String password2; 两次密码要一致
if(this.password2!=null){
if(!this.password2.equals(this.password)){
isOk = false;
errors.put("password2", "两次密码不一致!!");
}
}
//private String email; 可以为空,不为空要是一个合法的邮箱
// [email protected]
if(this.email!=null){
if(!this.email.matches("\\w+@\\w+(\\.\\w+)+")){
isOk = false;
errors.put("email", "邮箱不是一个合法邮箱!!");
}
}
//private String birthday; 可以为空,不为空时,要是一个合法的日期
//校验日期比较简单,如果字符串能被转换成日期就表示是合法,如果抛出
//异常则表示非法,但是不能使用SimpleDateFormat转,这个类不太严
//格,我们使用beanuitls中的转换器
if(this.birthday!=null){
try{
DateLocaleConverter conver = new DateLocaleConverter();
conver.convert(this.birthday);
}catch (Exception e) {
isOk = false;
errors.put("birthday", "生日必须要是一个日期!!");
}
}
return isOk;
}
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;
}
}
说明:这里需要注意的是表单数据类中的属性一般都是字符串,不管是数字还是日期,在表单数据类中都是String类型。而表单数据类中还提供了对数据的校验工作,于是需要一个属性来封装校验信息。
注册页面(WEB-INF/JSP/register.jsp
)
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
register page
提供注册页面的servlet(RegisterUIServlet.java
)
package cn.itcast.web.UI;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
//为用户提供注册界面的servlet
public class RegisterUIServlet extends HttpServlet{
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.getRequestDispatcher("/WEB-INF/jsp/register.jsp").forward(request, response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
处理用户业务请求的servlet(RegisterServlet.java
)
package cn.itcast.web.controller;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.InvocationTargetException;
import java.util.Date;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.ConvertUtils;
import org.apache.commons.beanutils.locale.converters.DateLocaleConverter;
import cn.itcast.domain.User;
import cn.itcast.exception.UserExistException;
import cn.itcast.service.BusinessService;
import cn.itcast.service.impl.BusinessServiceImpl;
import cn.itcast.utils.WebUtils;
import cn.itcast.web.formbean.RegisterFormBean;
//处理用户注册请求
public class RegisterServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//RegisterFormBean formbean = new RegisterFormBean();
//formbean.setBirthday(request.getParameter("birthday"));
//....太多,使用工具类
RegisterFormBean formbean = WebUtils.request2Bean(request, RegisterFormBean.class);
if(!formbean.validate()){
//formbean中带有了校验等信息,需要设置到request中
request.setAttribute("formbean", formbean);
//校验失败回到首页,同时显示错误信息,需要修改注册页面
request.getRequestDispatcher("/WEB-INF/jsp/register.jsp").forward(request, response);
}
//把表单数据填充到javabean中
User user = new User();
try {
//注册字符串到日期的转换器
ConvertUtils.register(new DateLocaleConverter(), Date.class);
BeanUtils.copyProperties(user, formbean);//复制formbean数据到javabean中
user.setId(WebUtils.makeId());
BusinessService service = new BusinessServiceImpl();
service.registerUser(user);
request.setAttribute("message", "注册成功");
request.getRequestDispatcher("/message.jsp").forward(request, response);
} catch (UserExistException e) {
formbean.getErrors().put("username", "注册用户已存在");
request.setAttribute("formbean", formbean);
request.getRequestDispatcher("/WEB-INF/jsp/register.jsp").forward(request, response);
}catch (Exception e) {
e.printStackTrace();//后台也需要记录
request.setAttribute("message", "对不起,注册失败");
request.getRequestDispatcher("/message.jsp").forward(request, response);
}
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
说明:
- 1.首先我们需要将formbean中的数据存放到request中去,但是一个一个设置显然太麻烦,而之后我们可能还有其他的formbean,于是这里我们需要一个工具类
WebUtils
。 - 2.然后我们需要需要校验数据,校验如果失败我们返回到注册界面,但是返回之后我们需要在表单中显示错误信息,同时还需要回显数据,所以需要对注册界面进行修改。
- 3.如果校验成功,那么此时我们需要将数据复制到javabean中去,同时存到数据库中,不管成功还是失败我们都需要向用户发送一条提示信息,这里我们需要一个全局消息显示页面
message.jsp
。 - 4.如果用户已经存在则我们跳转到注册界面,同时在表单中显示相关信息。
- 5.对于日期我们不能直接复制到javabean中,因为两个对象的日期类型不一致,我们要使用一个转换器,这里可能会出现异常 ,需要导入包
commons-collections-3.2.1.jar
,我导入高版本的不行,这个版本可以通过,同时用户id我们需要一个工具来产生,这里我们使用uuid。
工具类:WebUtils.java
package cn.itcast.utils;
import java.util.Enumeration;
import java.util.UUID;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.beanutils.BeanUtils;
public class WebUtils {
//将request中的数据封装到bean中,那以后我们可能不知道封装到哪个bean中,这里使用泛型
public static T request2Bean(HttpServletRequest request, Class clazz ){
try{
T bean = clazz.newInstance();
Enumeration e = request.getParameterNames();//拿到所有请求参数的名称
while(e.hasMoreElements()){
String name = (String) e.nextElement();
String value = request.getParameter(name);
BeanUtils.setProperty(bean, name, value);//将属性值封装到bean中
}
return bean;
}catch(Exception e){
throw new RuntimeException(e);
}
}
public static String makeId(){
//id使用uuid,唯一的id号
return UUID.randomUUID().toString();
}
}
修改后的注册页面:(register.jsp
)
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8" isELIgnored="false"%>
register page
全局消息显示页面:(webRoot/message.jsp
)
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
全局消息显示页面
${message }
6.2登录
首先当然还是需要一个servlet为用户提供一个登录界面
LoginUIServlet.java
package cn.itcast.web.UI;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
//为用户提供登录界面
public class LoginUIServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.getRequestDispatcher("/WEB-INF/jsp/login.jsp").forward(request, response);
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
注册界面(login.jsp
)
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
用户登录
处理用户登录业务请求的servlet
LoginServlet.java
package cn.itcast.web.controller;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import cn.itcast.domain.User;
import cn.itcast.service.BusinessService;
import cn.itcast.service.impl.BusinessServiceImpl;
public class LoginServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String username = request.getParameter("username");
String password = request.getParameter("password");
BusinessService service = new BusinessServiceImpl();
User user = service.loginUser(username, password);
if(user == null){
request.setAttribute("message", "用户名或密码有误");
request.getRequestDispatcher("/message.jsp").forward(request, response);
return ;
}
request.getSession().setAttribute("user", user);
request.setAttribute("message", "恭喜您:" + user.getUsername() + "登录成功,本页面将在5秒钟后跳到首页"
+ "
说明:当用户登录成功了我们先跳到全局消息显示页面,同时我们还向用户发送了一个头,让用户登录成功之后在全局消息显示页面上停留5秒钟之后跳转到首页上,这里本来不应该将地址写死的。用户登录成功之后一定记得在session中存入用户数据。
首页(index.jsp
)
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
首页
xx网站
注册
登录
${user.username }登录成功
说明:当用户登录成功之后我们就不要再显示注册和登录按钮了,这里使用了标签对其进行控制。当用户成功之后我们还需要在后面加上一个推出按钮,用于推出业务请求。
用户推出请求业务(LogoutServlet.java
)
package cn.itcast.web.controller;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class LogoutServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.getSession().removeAttribute("user");
request.setAttribute("message", "注销成功,本页面将在5秒钟后跳到首页"
+ "
最后:
到此我们就完成了一个简单的用户注册和登录的web小demo,我们主要要学会这种MVC的开发模式。在后面我们会正真使用数据库进行开发,同时我们是在此工程的基础上进行修改即可。