很久没有写博客了,主要的原因还是因为懒惰…前些日子上课讲到了一个简单的注册登录案例;
虽然案例本身需求并不复杂,但因为老师将MVC的基本思想和JavaWeb的分层模型思想加入到里面,让这个小案例有了很多可以咀嚼的地方,特此做个记录;
小生水平有限,不足之处望多多交流:)
基础知识:
MVC模型和JavaWeb分层思想;
MVC模型;
控制器Controller:对请求进行处理,负责请求转发;
视图View:界面设计人员进行图形界面设计;
模型Model:程序编写程序应用的功能(实现算法等等)、数据库管理;
这是一个设计思想,具体可以Google;按照我的理解,这是一个将整个工程划分为三个部分的设计思想;
M层— 数据模型层;
这一层主要与数据打交道,结合JDBC等实现可以从代码层面完成和数据库的联动,从而实现通过代码对数据库进程操作;另外很多具体的数据实体也可以分到这一层;
总而言之,这个层面直接面对数据;
V层— 视图层;
我最不感兴趣的一层,就个人而言感觉这层不那么有趣(请前端攻城狮轻拍);这一层主要和用户打交道,主要用于数据的表示层;在M层对数据的操作和处理结果都会反馈到这一层,而这一层提交的对数据的请求也会由C层进行控制并联合M层进行操控;
简言之,这一层主要用户数据显示;
C层— 控制层;
据说是最复杂的一层;
这一层主要负责对M层和V层的行为掌控,由V层提交的数据访问请求经过处理之后传达给M层,并且当M层完成数据初六之后再返回到V层;这一系列的操作都需要由C层来进行操作和主导;
简言之,这一层主要负责对请求的处理和转发;
下图是MVC的示意图:
参考资料:崔希凡老师笔记(内部);
JavaWeb
相比MVC,JavaWeb的模型层划分更加细致,二者之间有联系,但不能单纯的认为MVC模型就是JavaWeb数据模型层;要知道在除开web工程,很多软件模型也使用了MVC分层思想;
所谓的JavaWeb三层框架,主要分为web层,业务层和实体层;
WEB层:包含JSP和Servlet等与WEB相关的内容;
业务层:业务层中不包含JavaWeb API,它只关心业务逻辑;
数据层:封装了对数据库的访问细节;
WEB层:
这层主要负责web方面内容,包括HTML和servlet,由于目前没有学到SSH三大框架,暂时无法使用action,所以控制器(C层)方面基本由servlet负责,这也是容易和MVC混淆的地方;需要了解的是,严格意义上的WEB层之负责和它名字相关的部分,剩下的业务逻辑和数据访问等信息都是管的;而在MVC模型中,C层和V层是分开的,这是两者最大的区别;另外,WEB层依赖于业务层;
业务层:
这层主要负责业务逻辑,只负责具体的业务流程和实现,而对于web层的相关信息则不予理睬,它主要和数据层打交道,负责完成业务逻辑中的相关操作,同时需要访问数据层;这一层依赖于数据层;
数据层:
这一层主要负责数据操作,有点类似于MVC中的M层,但依然不能混淆;同时这层当中似乎还包括实体层;这层还需要和数据库打交道;
下图是JavaWeb的示意图;
简单的注册登录案例
需求分析:
完成一个简单的注册登录界面;
在服务端对数据进行验证,其中包括空用户名,空密码,密码不匹配和数据格式不符合要求等;
在注册界面进行注册,将请求界面的用户信息封装存入数据库;
另外需要设计简单的友好提示,用于处理用户误操作的情况;
项目的基本结构;
简要说明:
数据库为MySQL;
Dao包负责数据模型层;
Domai包负责实体层,存放UserBean;
Service包负责业务逻辑;
Web.servlect包负责控制层;
V层由jsp组成;
Utils和test包提供对dao层和bean进行操作的工具类;
开发流程:
在这个案例之前,因为还没有学习到数据库,暂时歇了一个用XML文件做数据存储的案例,其他层完全一样,由此也可以体现出MVC模型的强大和灵活之处,在这个案例中,只需要简单的将数据访问层的配置文件更改即可完成从XML存储到MYSQL的切换;下面是之前的流程图,在本案例中将XML部分替换为数据库即可;
开发前期准备;
在实际开发中,有些重复性的工作是不需要每次都做的;所以一般会先准备一些工具包;
首先是jar包
commons-beanutils-1.8.3.jar
commons-logging-1.1.1.jar
jaxen-1.1-beta-6.jar
mysql-connector-java-5.0.8-bin.jar
说实话是不是都需要我忘了,因为这些部分是直接Copy来的,但并不影响最后效果;
然后是一些不需要手写的工具类和实体类;
timescript.com.domain
User.java
timescript.com.utils
BeanFactory.java
JdbcUtil.java
WebUtils.java
Timescript.com.exception
UserExistException.java
Timescript.com.web.form
UserFormBean.java
bean.properties
Jdbccfg.properties
需要知道的是,至少在这个案例中,数据的流向和开发的步骤是相反的;按照上面的流程图所示,最开始准备的应该是数据库;
那么开始吧 —
数据库建表信息;
create table user(
username varchar(20),
password varchar(16),
email varchar(50),
birthday datetime
);
创建DAO接口和其实现类;
Timescript.com.dao
Timescript.com.dao.impl
这一层完成后可以使用单元测试验证一下数据是否能够添加到数据库当中;
package timescript.com.dao;
import timescript.com.domain.User;
/**
这是dao层接口;
*/
public interface UserDao {
/*
* 将一个指定的用户添加到数据库;
*/
public void addUser(User user);
/*
* 根据用户名和密码进行查询;
*/
public User getUserByUsernameAndPassword(String username,String password);
/*
* 根据用户名进行查询;
*/
public User getUserByUsername(String username);
}
package timescript.com.dao.impl;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import timescript.com.dao.UserDao;
import timescript.com.domain.User;
import timescript.com.utils.JdbcUtil;
/**
这是dao层接口的实现类;
*/
public class UserMySqlDaoImpl implements UserDao {
@Override
public void addUser(User user) {
Connection con = null;// 定义一个连接;
PreparedStatement st = null;// 定义SQL语句对象;
try {
con = JdbcUtil.getConnection();
String sql = "insert into user value(?,?,?,?)";
st = con.prepareStatement(sql);
st.setString(1, user.getUsername());// 依次对不同字段的属性进行赋值;
st.setString(2, user.getPassword());
st.setString(3, user.getEmail());
st.setDate(4, new java.sql.Date(user.getBirthday().getTime()));
// 因为日期类型比较特殊所以需要转换一下;
st.executeUpdate();// 更新数据;
} catch (Exception e) {
e.printStackTrace();
} finally {
JdbcUtil.release(null, st, con);
// 无论操作是否成功,都需要释放资源;
}
}
// 用户登录方法;
@Override
public User getUserByUsernameAndPassword(String username, String password) {
Connection con = null;
PreparedStatement st = null;
ResultSet rs = null;
User user = null;
try {
con = JdbcUtil.getConnection();// 获取连接;
String sql = "select * from user where username=?";
// 定义查询用户名的sql语句;
if (password != null) {// 避免用户使用空密码登录情况;
sql += "and password=?";
}
System.out.println(sql);
// 创建发送和执行sql语句的对象;
st = con.prepareStatement(sql);
// 为第一个占位符赋值为username
st.setString(1, username);
if (password != null && !"".equals(password)) {
// 如果密码不是空字符串则对第二个占位符赋值为password;
st.setString(2, password);
}
// 对查询结果进行处理;
rs = st.executeQuery();
if (rs.next()) {
//如果有下一条,则说明用户名和密码在记录表中存在,则将结果集封装到UserBean当中
user = new User(rs.getString("username"),
rs.getString("password"),
rs.getString("email"),
rs.getDate("birthday"));
}
} catch (Exception e) {
e.printStackTrace();
}
return user;
}
@Override
public User getUserByUsername(String username) {
return getUserByUsernameAndPassword(username, null);
}
}
这一层完成后可以使用单元测试验证一下数据是否能够添加到数据库当中;
创建service接口和其实现类;
timescript.com.service
Timescript.com.service.impl
package timescript.com.service;
import timescript.com.domain.User;
import timescript.com.exception.UserExistException;
/**
* 业务层接口
*/
public interface UserService {
/**
* 用于实现用户登录功能;
*/
public User login(String username, String password);
/**
* 用于实现注册功能;
*/
public void register(User user)throws UserExistException;
}
package timescript.com.service.impl;
import timescript.com.dao.UserDao;
import timescript.com.domain.User;
import timescript.com.exception.UserExistException;
import timescript.com.service.UserService;
import timescript.com.utils.BeanFactory;
public class UserServiceImpl implements UserService {
//通过工厂创建数据层对象;
private UserDao dao= BeanFactory.getInstance().getUserDaoInstance();
@Override
public User login(String username, String password) {
//返回数据层调用登录方法获取的结果;
return dao.getUserByUsernameAndPassword(username, password);
}
@Override
public void register(User user) throws UserExistException {
//通过数据层调用方法对数据库进行查找,看注册用户是否存在;
User u= dao.getUserByUsername(user.getUsername());
if(u==null){
//如过用户不存在则调用注册方法进行注册;
dao.addUser(user);
}else{
//否则抛出用户已存在异常;
throw new UserExistException();
}
}
}
创建ControllerServlet完成控制功能;
Timescript.com.web.servlet
package timescript.com.web.servlet;
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 javax.servlet.http.HttpSession;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.ConvertUtils;
import org.apache.commons.beanutils.locale.converters.DateLocaleConverter;
import timescript.com.domain.User;
import timescript.com.exception.UserExistException;
import timescript.com.service.UserService;
import timescript.com.service.impl.UserServiceImpl;
import timescript.com.utils.WebUtils;
import timescript.com.web.form.UserFormBean;
public class ControllerServlet extends HttpServlet {
public void destroy() {
super.destroy();
}
private UserService us = new UserServiceImpl();
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 解决浏览器乱码问题
request.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset= UTF-8");
// 获取一个输出流对象
PrintWriter out = response.getWriter();
// 获取请求界面参数名为“op”的参数值;
// 并根据该值调用相应的函数进行操作;
String op = request.getParameter("op");
if ("login".equals(op)) {
login(request, response);
} else if ("register".equals(op)) {
register(request, response);
} else if ("logout".equals(op)) {
logout(request, response);
} else {
out.write("该功能尚在开发当中,2秒后返回主界面");
response.setHeader("Refresh", "2;URL=" + request.getContextPath());
}
}
// 注销的操作
private void logout(HttpServletRequest request, HttpServletResponse response)
throws IOException {
// 获取到session对象;
HttpSession hs = request.getSession();
// 获取一个输入流用于打印提示信息;
PrintWriter out = response.getWriter();
hs.removeAttribute("user");// 移除user的session;
hs.invalidate();// 销毁session;
out.write("注销成功,2秒后返回首页");
response.setHeader("Refresh", "2;URL=" + request.getContextPath());
}
// 注册操作;
private void register(HttpServletRequest request,
HttpServletResponse response) throws IOException, ServletException {
PrintWriter out = response.getWriter();
// 将请求界面传入的参数封装到bean当中;
UserFormBean bean = WebUtils.fillBean(request, UserFormBean.class);
if (!bean.validate()) {
// 如果验证方法返回值不为空,则说明用户的注册信息填写有误;
// 保存用户已填写的信息,然后跳回注册界面;
request.setAttribute("bean", bean);
request.getRequestDispatcher("/register.jsp").forward(request,
response);
return;
}
// 注册一个日期转换器;
ConvertUtils.register(new DateLocaleConverter(), Date.class);
// 创建一个用户对象
User user = new User();
try {
// 通过beanutils的方法将bean当中的数据封装回UserBean当中;
BeanUtils.copyProperties(user, bean);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("转换数据失败");
}
try {
us.register(user);// 调用业务层的方法进行注册;
out.write("注册成功,2秒后跳转到主页;");
response.setHeader("Refresh", "2;URL="+request.getContextPath());
} catch (UserExistException e) {
// 如果捕获了同名异常则进行提示并跳回注册界面;
bean.getErrorMsg().put("username", "用户名已存在");
request.setAttribute("bean", bean);
request.getRequestDispatcher("/register.jsp").forward(request,
response);
}
}
// 登录操作;
private void login(HttpServletRequest request, HttpServletResponse response)
throws IOException {
PrintWriter out = response.getWriter();
// 获取请求参数中的具体值;
String username = request.getParameter("username");
String password = request.getParameter("password");
//调用业务层的方法进行登录;
User user = us.login(username, password);
if (user != null) {
//如果user实例对象不为空,则说明登录成功;
//则将用户信息保存到session当中;
request.getSession().setAttribute("user", user);
response.sendRedirect(request.getContextPath());
} else {
out.write("登录失败,2秒后返回登录界面");
response.setHeader("Refresh", "2;URL" + request.getContextPath()
+ "/login.jsp");
}
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
this.doGet(request, response);
}
public void init() throws ServletException {
}
}
需要注意的是,上面的步骤没有包含已经实现准备好的类似于UserBean这样的实体类的创建;
不过每一段代码的注释都足够详细,可以参考了解整个业务逻辑和数据访问的具体实现;
小结和收获;
需要承认的是,到目前为止对于这整个模型的掌握程度还根本不够;英语所造成的阻碍已经越发明显;其实在目前阶段,很多方法的名字都非常直白;但只因为看不懂英语,导致整个开发过程很痛苦;另外需要注意的是对错误的排查,MyEclipse至少目前看来对于错误的提示还是比较准确,基本能够达到看懂意思就能发现错误所在的地方;不过需要注意的是字符串的拼写和URL地址的错误填写,这样的错误MyEclipse无法检测出来,在调试的过程中也很花费时间;按照老师的建议,复制粘贴确实不失为一种手段;
不过最大的问题还是练习的不足;其实到目前为止,就像在web阶段老师说的那样,还没有遇到过什么太过于复杂的问题,大部分都是单词拼写错误或者采用自动完成时出现的导错包或者是方法的权限错误等等;
但经过半天的思考和练习,对MVC和JavaWeb的结构有了深一层的了解,日后需要学习到的其他技术,大概也不会脱离这个基本框架;只会在不同的部分进行添加补齐;