然后完成以下需求
字母、数字、下划线组成,且长度是5-12,
由正则表达式表示为
var patt = /^\w{5,12}$/;
if(patt.test(usernameText)){
alert("用户名合法!");
}else {
alert("用户名不合法!");
}
邮箱部分也需要由正则表达式表示,可查看jQuery文档的 “其他” 里面
<head>
<meta charset="UTF-8">
<title>尚硅谷会员注册页面title>
<link type="text/css" rel="stylesheet" href="../../static/css/style.css" >
<style type="text/css">
.login_form{
height:420px;
margin-top: 25px;
}
style>
<script type="text/javascript" src="../../static/script/jquery-1.7.2.js">script>
<script type="text/javascript">
$(function () {
$("#sub_btn").click(function () {
//验证用户名
var patt = /^\w{5,12}$/;
var usernameText = $("#username").val();
if(!patt.test(usernameText)) {
$("span.errorMsg").text("用户名不合法")
return false
}
//验证密码
var passwordText = $("#password").val();
if(!patt.test(passwordText)) {
$("span.errorMsg").text("密码不合法")
return false
}
//验证确认密码
var repwdText = $("#repwd").val();
if(!(repwdText == passwordText)){
$("span.errorMsg").text("密码不一致")
return false
}
//邮箱验证
var emailText = $("#email").val();
var emailpatt = /^[a-z\d]+(\.[a-z\d]+)*@([\da-z](-[\da-z])?)+(\.{1,2}[a-z]+)+$/;
if(!emailpatt.test(emailText)) {
$("span.errorMsg").text("邮箱不合法")
return false
}
//验证码
var codeText = $("#code").val();
//注意去掉前后空格
var trim = $.trim(codeText);
if(trim == null || trim == "") {
$("span.errorMsg").text("验证码为空")
return false
}
//注意当合法注册的时候注意需要清空提示的span里面的文本
$("span.errorMsg").text("")
})
})
script>
head>
<body>
<div id="login_header">
<img class="logo_img" alt="" src="../../static/img/logo.gif" >
div>
<div class="login_banner">
<div id="l_content">
<span class="login_word">欢迎注册span>
div>
<div id="content">
<div class="login_form">
<div class="login_box">
<div class="tit">
<h1>注册尚硅谷会员h1>
<span class="errorMsg">span>
div>
<div class="form">
<form action="regist_success.html">
<label>用户名称:label>
<input class="itxt" type="text" placeholder="请输入用户名" autocomplete="off" tabindex="1" name="username" id="username" />
<br />
<br />
<label>用户密码:label>
<input class="itxt" type="password" placeholder="请输入密码" autocomplete="off" tabindex="1" name="password" id="password" />
<br />
<br />
<label>确认密码:label>
<input class="itxt" type="password" placeholder="确认密码" autocomplete="off" tabindex="1" name="repwd" id="repwd" />
<br />
<br />
<label>电子邮件:label>
<input class="itxt" type="text" placeholder="请输入邮箱地址" autocomplete="off" tabindex="1" name="email" id="email" />
<br />
<br />
<label>验证码:label>
<input class="itxt" type="text" style="width: 150px;" id="code"/>
<img alt="" src="../../static/img/code.bmp" style="float: right; margin-right: 40px">
<br />
<br />
<input type="submit" value="注册" id="sub_btn" />
form>
div>
div>
div>
div>
div>
<div id="bottom">
<span>
尚硅谷书城.Copyright ©2015
span>
div>
body>
注意点:
需求 1:用户注册
1)访问注册页面
2)填写注册信息,提交给服务器
3)服务器应该保存用户
4)当用户已经存在----提示用户注册失败,用户名已存在
5)当用户不存在-----注册成功
需求 2:用户登陆
1)访问登陆页面
2)填写用户名密码后提交
3)服务器判断用户是否存在
4)如果登陆失败 —>>>> 返回用户名或者密码错误信息
5)如果登录成功 —>>>> 返回登陆成功信息
分层的目的是为了解耦。解耦就是为了降低代码的耦合度。方便项目后期的维护和升级
第三方 jar 包放在 web 目录的 WEB-INF 文件夹下
编写数据库表对应的 JavaBean 对象
public class User {
private int id;
private String username;
private String password;
private String email;
}
使用 Druid 数据库连接池,导入 jar 包
在 src 源码目录下编写 jdbc.properties 属性配置文件
工具类 JdbcUtils 中封装数据库链接和关闭操作
注意,在连接操作中,如果使用ClassLoader.getSystemResourceAsStream(“文件名”),在后面项目运行的时候会报空指针异常
要使用jdbcUtils.class.getClassLoader().getResourceAsStream(“druid.properties”)
package com.atguigu.utils;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import com.alibaba.druid.pool.DruidPooledConnection;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
public class jdbcUtils {
private static DruidDataSource dataSource;
static {
try {
Properties pro = new Properties();
pro.load(jdbcUtils.class.getClassLoader().getResourceAsStream("druid.properties"));
dataSource = (DruidDataSource) DruidDataSourceFactory.createDataSource(pro);
} catch (Exception e) {
e.printStackTrace();
}
}
//获取连接(注意获取连接的操作不允许抛异常)
//@return 如果返回null,说明获取连接失败
public static Connection getconn(){
DruidPooledConnection connection = null;
try {
connection = dataSource.getConnection();
} catch (SQLException e) {
e.printStackTrace();
}
return connection;
}
//关闭连接
public static void close(Connection conn){
if(conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
如果获取了数据库连接池的连接没有及时释放,就只能获取最大连接数的连接
但是如果及时的释放了连接,那获取几个连接都没有问题
package com.atguigu.DAO;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.apache.commons.dbutils.handlers.ScalarHandler;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
public class BaseDAO<T> {
private Class<T> clazz = null;
{
ParameterizedType parameterized = (ParameterizedType) this.getClass().getGenericSuperclass();
Type[] type = parameterized.getActualTypeArguments();
clazz = (Class<T>) type[0];
}
private QueryRunner qr = new QueryRunner();
//通用的增删改操作
public void update(Connection conn,String sql,Object...args){
try {
int i = qr.update(conn, sql, args);
System.out.println("影响了" + i + "条数据");
} catch (SQLException e) {
e.printStackTrace();
}
}
//通用的查询一条数据操作
public T queryForOne(Connection conn,String sql,Object...args){
T t = null;
try {
t = qr.query(conn,sql,new BeanHandler<T>(clazz),args);
return t;
} catch (SQLException e) {
e.printStackTrace();
}
return t;
}
//通用的查询多条数据操作
public List<T> queryForList(Connection conn,String sql,Object...args){
List<T> list = null;
try {
list = qr.query(conn, sql, new BeanListHandler<T>(clazz), args);
return list;
} catch (SQLException e) {
e.printStackTrace();
}
return list;
}
//通用的查询特殊值的操作
public Object queryForSingleValue(Connection conn,String sql,Object...args){
Object o = null;
try {
o = qr.query(conn, sql, new ScalarHandler(), args);
} catch (SQLException e) {
e.printStackTrace();
}
return o;
}
}
package com.atguigu.DAO;
import com.atguigu.bean.User;
import java.sql.Connection;
public interface UserDAO {
//根据用户名查询用户信息
User queryUserByUsername(Connection connection, String username);
//根据用户名和密码查询用户信息
User queryUserByUsernameAndPassword(Connection connection,String username,String password);
//保存用户信息(就是插一条用户信息数据)
void saveUser(Connection connection,User user);
}
package com.atguigu.DAO.impl;
import com.atguigu.DAO.BaseDAO;
import com.atguigu.DAO.UserDAO;
import com.atguigu.bean.User;
import java.sql.Connection;
public class UserDAOImpl extends BaseDAO<User> implements UserDAO {
@Override
public User queryUserByUsername(Connection connection, String username) {
String sql = "select * from `t_user` where username = ?";
User user = queryForOne(connection, sql, username);
return user;
}
@Override
public User queryUserByUsernameAndPassword(Connection connection, String username, String password) {
String sql = "select * from `t_user` where username = ? and password = ?";
User user = queryForOne(connection, sql, username, password);
return user;
}
@Override
public void saveUser(Connection connection, User user) {
String sql = "insert into `t_user`(`id`,`username`,`password`,`email`) values(?,?,?,?)";
update(connection,sql,user.getId(),user.getUsername(),user.getPassword(),user.getEmail());
}
}
该层编写的是具体的业务
比如,登录、注册、验证用户名是否重复
package com.atguigu.service;
import com.atguigu.bean.User;
public interface UserService {
//这里是业务层
//注册业务
void registerUser(User user);
//登录业务
User login(User user);
//检查用户名是否已经存在,若已存在,说明不可用
boolean existsUsername(String username);
}
package com.atguigu.service.impm;
import com.atguigu.DAO.UserDAO;
import com.atguigu.DAO.impl.UserDAOImpl;
import com.atguigu.bean.User;
import com.atguigu.service.UserService;
import com.atguigu.utils.jdbcUtils;
import java.sql.Connection;
public class UesrServiceImpl implements UserService {
private UserDAO userDAO = new UserDAOImpl();
@Override
public void registerUser(User user) {
Connection conn = jdbcUtils.getconn();
userDAO.saveUser(conn,user);
jdbcUtils.close(conn);
}
@Override
public User login(User user) {
Connection conn = jdbcUtils.getconn();
User user1 = userDAO.queryUserByUsernameAndPassword(conn, user.getUsername(), user.getPassword());
jdbcUtils.close(conn);
return user1;
}
@Override
public boolean existsUsername(String username) {
Connection conn = jdbcUtils.getconn();
User user = userDAO.queryUserByUsername(conn, username);
jdbcUtils.close(conn);
return user != null;
}
}
web.xml 文件中的配置
<servlet>
<servlet-name>RegistServletservlet-name>
<servlet-class>com.atguigu.web.RegistServletservlet-class>
servlet>
<servlet>
<servlet-name>LoginServletservlet-name>
<servlet-class>com.atguigu.web.LoginServletservlet-class>
servlet>
<servlet-mapping>
<servlet-name>RegistServletservlet-name>
<url-pattern>/registServleturl-pattern>
servlet-mapping>
<servlet-mapping>
<servlet-name>LoginServletservlet-name>
<url-pattern>/loginServleturl-pattern>
servlet-mapping>
选择 POST 请求方式,是因为在转入网址之后,不会显现密码的内容
注册成功或者注册失败的时候,跳转页面使用的是 响应的请求重定向
package com.atguigu.web;
import com.atguigu.bean.User;
import com.atguigu.service.impm.UesrServiceImpl;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class RegistServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//获取请求的参数
String username = request.getParameter("username");
String password = request.getParameter("password");
String email = request.getParameter("email");
String code = request.getParameter("code");
//验证验证码是否正确
//本案例中将验证码写死,写成固定的字段abc,忽略大小写
if("abc".equalsIgnoreCase(code)){
//检查用户名是否可用
UesrServiceImpl uesrService = new UesrServiceImpl();
if(uesrService.existsUsername(username)){
System.out.println("用户名已存在");
response.sendRedirect("http://localhost:8080/book02/pages/user/regist.html");
}else {
//执行注册功能,保存数据
User user = new User(username, password, email);
uesrService.registerUser(user);
//跳转到注册成功页面
response.sendRedirect("http://localhost:8080/book02/pages/user/regist_success.html");
}
}else {
System.out.println("验证码不正确");
response.sendRedirect("http://localhost:8080/book02/pages/user/regist.html");
}
}
}
登录成功或者登录失败的时候,跳转页面使用的是 响应的请求重定向
package com.atguigu.web;
import com.atguigu.bean.User;
import com.atguigu.service.impm.UesrServiceImpl;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class LoginServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//获取请求的参数
String username = request.getParameter("username");
String password = request.getParameter("password");
UesrServiceImpl uesrService = new UesrServiceImpl();
if(uesrService.login(new User(username,password,null)) != null){
response.sendRedirect("http://localhost:8080/book02/pages/user/login_success.html");
}else {
System.out.println("登录失败");
response.sendRedirect("http://localhost:8080/book02/pages/user/login.html");
}
}
}
登录页面的 html 文件中表单的提交链接
<form action="http://localhost:8080/book02/loginServlet" method="post">
注册页面的 html 文件中表单的提交链接
<form action="http://localhost:8080/book02/registServlet" method="post">
<base href="http://localhost:8080/book/">
<link type="text/css" rel="stylesheet" href="static/css/style.css" >
<script type="text/javascript" src="static/script/jquery-1.7.2.js">
script>
前面两个阶段已经处理好了注册页面和登录页面,但是注册、登陆失败的时候仅仅是返回注册页面、登录页面,没有动态的提示
第三部分是优化前面已经做好的部分,使其动态化
注意:
当进行到第三部分的时候,已经将前面第二部分涉及到的绝对路径,都更改为相对路径,并在每个 html 文件中设置了 base 标签,标签地址指向工程路径 http://localhost:8080/book02/
1、在 html 页面顶行添加 page 指令
2、修改文件后缀名为:.jsp
3、使用 IDEA 搜索替换,将文件内部涉及到路径的地方,.html 为.jsp
<div>
<span>欢迎<span class="um_span">小草莓span>光临尚硅谷书城span>
<a href="pages/order/order.jsp">我的订单a>
<a href="index.jsp">注销a>
<a href="index.jsp">返回a>
div>
创建新文件,将上面相同的内容放进新文件
然后将原有文件中,内容的位置替换为如下:
<%--动态包含,登录成功后的欢迎菜单页面--%>
<jsp:include page="/pages/common/welcome_menu.jsp">jsp:include>
相同内容如下:
<div id="bottom">
<span>
小草莓书城.LittleStrawberry ©2021
span>
div>
然后将原有文件中,内容的位置替换为如下:
<%--动态包含,每个页面的页脚--%>
<jsp:include page="/pages/common/footer.jsp">jsp:include>
相同内容如下:
<div>
<a href="pages/manager/book_manager.jsp">图书管理a>
<a href="pages/manager/order_manager.jsp">订单管理a>
<a href="index.jsp">返回商城a>
div>
然后将原有文件中,内容的位置替换为如下:
<%--动态包含,manager 模块的菜单--%>
<jsp:include page="/pages/common/manager_menu.jsp">jsp:include>
相同内容如下:
<base href="http://localhost:8080/book02/">
<link type="text/css" rel="stylesheet" href="static/css/style.css" >
<script type="text/javascript" src="static/script/jquery-1.7.2.js">script>
然后将原有文件中,内容的位置替换为如下:
<%--动态包含,head 中 css样式、jquery文件、base标签--%>
<jsp:include page="/pages/common/css_base_jquery.jsp">jsp:include>
此时要注意 动态 base 标签 的问题,如果直接将 上面 localhost 的地址放进 jsp 文件,那么在换一个 IP 访问项目的时候,就会有下面问题
比如换同一个网络下的手机进行访问该网页,会报错
更改如下:
以下是文件 css_base_jquery.jsp 的内容
<body>
<%
//request.getScheme() 获取协议名称:http
//request.getServerName() 获取服务器ip或域名
//request.getServerPort() 获取服务器端口号
//request.getContextPath() 获取当前工程路径
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+request.getContextPath()+"/";
%>
<%--将base标签的地址指向动态的地址--%>
<base href="<%=basePath%>">
<link type="text/css" rel="stylesheet" href="static/css/style.css" >
<script type="text/javascript" src="static/script/jquery-1.7.2.js">script>
body>
在第一部分表单验证的实现里面,仅仅实现了注册页面输入内容的提示
现在需要的是注册或登录时候提示的错误
如:登录时候用户或密码不正确,注册时候验证码不正确或用户名已存在
需求:
代码:
此时需要将用户已经输入过的信息保存在 request 对象的域值中,将错误信息也保存到 请求域对象的域值当中,然后在 注册页面获取域值即可
前面在 Web 层里面的 servlet 程序里面,跳转页面使用的是请求重定向,但是请求重定向是两次请求,会清空请求域对象里面的阈值
所以现将请求重定向操作更改为请求中转,这样页面跳转的时候是一次请求,就能在跳转之后仍然获取到请求域对象保存的域值
下面是修改后 注册servlet 的代码
public class RegistServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//获取请求的参数
String username = request.getParameter("username");
String password = request.getParameter("password");
String email = request.getParameter("email");
String code = request.getParameter("code");
//验证验证码是否正确
//本案例中将验证码写死,写成固定的字段abc,忽略大小写
if("abc".equalsIgnoreCase(code)){
//检查用户名是否可用
UesrServiceImpl uesrService = new UesrServiceImpl();
if(uesrService.existsUsername(username)){
//保存已经输入的用户信息,保存到请求对象的域值
request.setAttribute("registUsername",username);
request.setAttribute("registPassword",password);
request.setAttribute("registEmail",email);
request.setAttribute("registCode",code);
request.setAttribute("registMsg","用户名已存在");
//跳回注册页面
request.getRequestDispatcher("/pages/user/regist.jsp").forward(request,response);
}else {
//执行注册功能,保存数据
User user = new User(username, password, email);
uesrService.registerUser(user);
//跳转到注册成功页面
request.getRequestDispatcher("/pages/user/regist_success.jsp").forward(request,response);
}
}else {
//保存已经输入的用户信息,保存到请求对象的域值
request.setAttribute("registUsername",username);
request.setAttribute("registPassword",password);
request.setAttribute("registEmail",email);
request.setAttribute("registCode",code);
request.setAttribute("registMsg","验证码不正确");
//跳回注册页面
request.getRequestDispatcher("/pages/user/regist.jsp").forward(request,response);
}
}
}
在 注册页面regist.jsp 获取请求域对象的域值,如下:
在每个输入框内设置 value 属性值,这样在跳转回到注册页面的时候就会显示已经输入过的信息
span 标签没有 value 属性,所以错误信息的获取,就是在 span 前后标签之间获取域值
此处需要注意一点:
上图中使用的是 EL 表达式,因为 EL 表达式输出 null 值的时候,输出的是空串
如果使用 jsp 语句,那么输出 null 值的时候,输出的是null
即在第一次访问注册页面时候,此时还没有设置请求域对象的域值,所以获取到的就是null,那么这里就要加一个判断语句,以防在第一次访问的时候输出 null
<%=request.getAttribute("registUsername")==null?"":request.getAttribute("registUsername")%>
同理,更改登录页面
下面是修改后 登录servlet 的代码
public class LoginServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//获取请求的参数
String username = request.getParameter("username");
String password = request.getParameter("password");
UesrServiceImpl uesrService = new UesrServiceImpl();
if(uesrService.login(new User(username,password,null)) != null){
request.setAttribute("loginUsername",username);
request.setAttribute("loginPassword",password);
//跳转到登录成功页面
request.getRequestDispatcher("/pages/user/login_success.jsp").forward(request,response);
}else {
request.setAttribute("loginUsername",username);
request.setAttribute("loginPassword",password);
request.setAttribute("loginMsg","用户名或密码不正确");
//跳回登录页面
request.getRequestDispatcher("/pages/user/login.jsp").forward(request,response);
}
}
}
在 登录页面login.jsp 获取请求域对象的域值,如下:
注意,因为在首次访问登录页面的时候,提示的位置有文字,所以错误提示的地方还是需要进行一次判断
使用 jsp 语句的情况
<span class="errorMsg">
<%=request.getAttribute("loginMsg")==null?"请输入用户名和密码":request.getAttribute("loginMsg")%>
span>
使用 EL 表达式的情况
<span class="errorMsg">
${empty requestScope.registMsg?"请输入用户名和密码":requestScope.registMsg }
span>
在实际的项目开发中,一个模块,一般只使用一个 Servlet 程序
所以要合并 LoginServlet 和 RegistServlet 程序为 UserServlet 程序
思路:
因为要处理两个页面的数据,所以不妨在 登录页面、注册页面 里面设置隐藏域,设置不同的值
在表单提交到 UserServlet 程序的时候, UserServlet 程序判断是哪个页面发送的请求,然后根据不同的请求来处理不同的业务
可以将请求的业务封装到自定义方法中,然后在 if 语句中调用方法
UserServlet 的代码
这里将封装的两个方法的方法名设置为 隐藏域 value 值一致,方便后续讲解反射获取方法名
public class UserServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
if ("login".equals(request.getParameter("postName"))){
login(request,response);
}else if("regist".equals(request.getParameter("postName"))){
regist(request,response);
}
}
protected void login(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//获取请求的参数
String username = request.getParameter("username");
String password = request.getParameter("password");
UesrServiceImpl uesrService = new UesrServiceImpl();
if(uesrService.login(new User(username,password,null)) != null){
request.setAttribute("loginUsername",username);
request.setAttribute("loginPassword",password);
//跳转到登录成功页面
request.getRequestDispatcher("/pages/user/login_success.jsp").forward(request,response);
}else {
request.setAttribute("loginUsername",username);
request.setAttribute("loginPassword",password);
request.setAttribute("loginMsg","用户名或密码不正确");
//跳回登录页面
request.getRequestDispatcher("/pages/user/login.jsp").forward(request,response);
}
}
protected void regist(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//获取请求的参数
String username = request.getParameter("username");
String password = request.getParameter("password");
String email = request.getParameter("email");
String code = request.getParameter("code");
//验证验证码是否正确
//本案例中将验证码写死,写成固定的字段abc,忽略大小写
if("abc".equalsIgnoreCase(code)){
//检查用户名是否可用
UesrServiceImpl uesrService = new UesrServiceImpl();
if(uesrService.existsUsername(username)){
//保存已经输入的用户信息,保存到请求对象的域值
request.setAttribute("registUsername",username);
request.setAttribute("registPassword",password);
request.setAttribute("registEmail",email);
request.setAttribute("registCode",code);
request.setAttribute("registMsg","用户名已存在");
//跳回注册页面
request.getRequestDispatcher("/pages/user/regist.jsp").forward(request,response);
}else {
//执行注册功能,保存数据
User user = new User(username, password, email);
uesrService.registerUser(user);
//跳转到注册成功页面
request.getRequestDispatcher("/pages/user/regist_success.jsp").forward(request,response);
}
}else {
//保存已经输入的用户信息,保存到请求对象的域值
request.setAttribute("registUsername",username);
request.setAttribute("registPassword",password);
request.setAttribute("registEmail",email);
request.setAttribute("registCode",code);
request.setAttribute("registMsg","验证码不正确");
//跳回注册页面
request.getRequestDispatcher("/pages/user/regist.jsp").forward(request,response);
}
}
}
登录页面、注册页面 修改如下:
在 UserServlet 程序中,是使用 if 语句进行判断用户在访问哪种功能页面,而实际开发中,用户可以访问的不仅仅只有注册和登录两个功能页面,可能还会有注销、修改、新增 等等多个页面,此时再使用 if 语句,就比较忙麻烦
使用反射调用运行时类的指定方法(将隐藏域的value值设置为servlet程序内封装的方法名一致,就可以获取到指定方法名)
以下代码是将 UserServlet 程序里面的 doPost()
方法进行优化升级,用反射取代了 if 语句
public class UserServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
try {
this.getClass().getDeclaredMethod(request.getParameter("postName"),HttpServletRequest.class,HttpServletResponse.class).invoke(this,request,response);
} catch (Exception e) {
e.printStackTrace();
}
}
}
BaseServlet 程序代码:
继承HttpServlet
将 BaseServlet 类设置为抽象的是防止其实例化,同时也可以加大复用程度
不需要配置BaseServlet在web.xml里面的servlet程序,因为我们根本不去访问这个servlet程序
public abstract class BaseServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
try {
this.getClass().getDeclaredMethod(request.getParameter("postName"),HttpServletRequest.class,HttpServletResponse.class).invoke(this,request,response);
} catch (Exception e) {
e.printStackTrace();
}
}
}
修改 UserServlet 程序继承 BaseServlet 程序
在程序运行的时候,访问的是子类 UserServlet 程序,调用子类的 doPost() 方法,但是此时子类 UserServlet 程序里面没有重写此方法,那么就会向上去执行父类中重写的此方法
public class UserServlet extends BaseServlet {
//里面只有两个封装的方法
protected void login(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {}
protected void regist(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {}
}
BeanUtils 工具类,经常用于 把 Map 中的值注入到 JavaBean 中 ,或者是对象属性值的拷贝操作
因为获取请求参数有个返回 Map 的方法,所以可以使用 BeanUtils 工具类,一次性的把所有请求的参数注入到 JavaBean 中
BeanUtils 它不是 Jdk 的类。而是第三方的工具类。所以需要导包。
在登陆的方法里调用 BeanUtils 类的方法
BeanUtils.populate(Object bean, Map properties)
第一个参数就是要装载数据的 JavaBean 对象,第二个参数是要使用的数据,是 Map 类型
数据的输入不是根据对象的属性名,而是根据对象中创建的 set()
方法
所以请求参数的时候,表单输入框的(name)属性要和 JavaBean 对象的(setXxx)方法一致,否则就无法将数据封装进对象
类似情况还出现在 EL 表达式输出 Bean 的属性
//判断是否登陆成功,是将用户请求的参数封装进User对象
//使用 BeanUtils 代替单个的请求参数并使用构造器
User user = new User();
BeanUtils.populate(user,request.getParameterMap());
System.out.println(user);
因为此方法会在多处使用,为了方便维护,抽取为 WebUtils 工具类
public class WebUtils {
/**
* @param t 传入对象t
* @param map 将键值对的数据封装进对象t
* @param
* @return 返回满载数据的t
*/
public static <T> T mapToBean(T t, Map map){
try {
BeanUtils.populate(t, map);
} catch (Exception e) {
e.printStackTrace();
}
return t;
}
}
使用工具类
这样就算有多个请求参数,也只需要用一行代码就可以将所有参数封装进 JavaBean 对象
//判断是否登陆成功,需要将用户请求的参数封装进User对象
//使用 BeanUtils 代替单个的请求参数和使用构造器
User user = WebUtils.mapToBean(new User(), request.getParameterMap());
使用工具类,对代码进行优化
更改了 UserServlet 类
public class UserServlet extends BaseServlet {
protected void login(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
UesrServiceImpl uesrService = new UesrServiceImpl();
//判断是否登陆成功,需要将用户请求的参数封装进User对象
//使用 BeanUtils 代替单个的请求参数并使用构造器
//获取请求的参数,并封装进对象
User user = WebUtils.mapToBean(new User(), request.getParameterMap());
if(uesrService.login(user) != null){
request.setAttribute("loginUsername",user.getUsername());
request.setAttribute("loginPassword",user.getPassword());
//跳转到登录成功页面
request.getRequestDispatcher("/pages/user/login_success.jsp").forward(request,response);
}else {
request.setAttribute("loginUsername",user.getUsername());
request.setAttribute("loginPassword",user.getPassword());
request.setAttribute("loginMsg","用户名或密码不正确");
//跳回登录页面
request.getRequestDispatcher("/pages/user/login.jsp").forward(request,response);
}
}
protected void regist(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//获取请求的参数,并封装进对象
User user1 = WebUtils.mapToBean(new User(), request.getParameterMap());
String code = request.getParameter("code");
//验证验证码是否正确
//本案例中将验证码写死,写成固定的字段abc,忽略大小写
if("abc".equalsIgnoreCase(code)){
//检查用户名是否可用
UesrServiceImpl uesrService = new UesrServiceImpl();
if(uesrService.existsUsername(user1.getUsername())){
//保存已经输入的用户信息,保存到请求对象的域值
request.setAttribute("registUsername",user1.getUsername());
request.setAttribute("registPassword",user1.getPassword());
request.setAttribute("registEmail",user1.getEmail());
request.setAttribute("registCode",code);
request.setAttribute("registMsg","用户名已存在");
//跳回注册页面
request.getRequestDispatcher("/pages/user/regist.jsp").forward(request,response);
}else {
//执行注册功能,保存数据
uesrService.registerUser(user1);
//跳转到注册成功页面
request.getRequestDispatcher("/pages/user/regist_success.jsp").forward(request,response);
}
}else {
//保存已经输入的用户信息,保存到请求对象的域值
request.setAttribute("registUsername",user1.getUsername());
request.setAttribute("registPassword",user1.getPassword());
request.setAttribute("registEmail",user1.getEmail());
request.setAttribute("registCode",code);
request.setAttribute("registMsg","验证码不正确");
//跳回注册页面
request.getRequestDispatcher("/pages/user/regist.jsp").forward(request,response);
}
}
}
MVC 全称:Model 模型、 View 视图、 Controller 控制器
是 Web 层的分层
## 创建数据库表
CREATE TABLE t_book (
`id` INT PRIMARY KEY auto_increment,
`name` VARCHAR ( 100 ),
`price` DECIMAL ( 11, 2 ),
`author` VARCHAR ( 100 ),
`sales` INT,
`stock` INT,
`img_path` VARCHAR ( 200 )
);
## 插入初始化测试数据
INSERT INTO t_book ( `id`, `name`, `author`, `price`, `sales`, `stock`, `img_path` )
VALUES
( NULL, 'java 从入门到放弃', '国哥', 80, 9999, 9, 'static/img/default.jpg' ),
( NULL, '数据结构与算法', '严敏君', 78.5, 6, 13, 'static/img/default.jpg' ),
( NULL, '怎样拐跑别人的媳妇', '龙伍', 68, 99999, 52, 'static/img/default.jpg' ),
( NULL, '木虚肉盖饭', '小胖', 16, 1000, 50, 'static/img/default.jpg' ),
( NULL, 'C++编程思想', '刚哥', 45.5, 14, 95, 'static/img/default.jpg' ),
( NULL, '蛋炒饭', '周星星', 9.9, 12, 53, 'static/img/default.jpg' ),
( NULL, '赌神', '龙伍', 66.5, 125, 535, 'static/img/default.jpg' ),
( NULL, 'Java 编程思想', '阳哥', 99.5, 47, 36, 'static/img/default.jpg' ),
( NULL, 'JavaScript 从入门到精通', '婷姐', 9.9, 85, 95, 'static/img/default.jpg' ),
( NULL, 'cocos2d-x 游戏编程入门', '国哥', 49, 52, 62, 'static/img/default.jpg' ),
( NULL, 'C 语言程序设计', '谭浩强', 28, 52, 74, 'static/img/default.jpg' ),
( NULL, 'Lua 语言程序设计', '雷丰阳', 51.5, 48, 82, 'static/img/default.jpg' ),
( NULL, '西游记', '罗贯中', 12, 19, 9999, 'static/img/default.jpg' ),
( NULL, '水浒传', '华仔', 33.05, 22, 88, 'static/img/default.jpg' ),
( NULL, '操作系统原理', '刘优', 133.05, 122, 188, 'static/img/default.jpg' ),
( NULL, '数据结构 java 版', '封大神', 173.15, 21, 81, 'static/img/default.jpg' ),
( NULL, 'UNIX 高级环境编程', '乐天', 99.15, 210, 810, 'static/img/default.jpg' ),
( NULL, 'javaScript 高级编程', '国哥', 69.15, 210, 810, 'static/img/default.jpg' ),
( NULL, '大话设计模式', '国哥', 89.15, 20, 10, 'static/img/default.jpg' ),
( NULL, '人月神话', '刚哥', 88.15, 20, 80, 'static/img/default.jpg' );
public class Book {
private int id;
private String name;
private double price;
private String author;
private int sales;
private int stock;
//设置图片的默认值
private String img_path = "static/img/default.jpg";
}
注意,图片是有默认值的,那么在赋值的操作中可以判断一下
public interface BookDAO {
//插入图书信息数据
void addBook(Connection connection, Book book);
//修改图书数据
int updateBook(Connection connection, Book book);
//删除图书,根据id
void deleteBookById(Connection connection, int id);
//查询图书信息,根据id
Book queryBookById(Connection connection, int id);
//查看所有图书信息
List<Book> queryBooks(Connection connection);
}
public class BookDAOImpl extends BaseDAO<Book> implements BookDAO {
@Override
public void addBook(Connection connection, Book book) {
String sql = "insert into `t_book`(`name`, `author`, `price`, `sales`, `stock`, `img_path`) values(?,?,?,?,?,?)";
update(connection,sql,book.getName(),book.getAuthor(),book.getPrice(),book.getSales(),book.getStock(),book.getImg_path());
}
@Override
public int updateBook(Connection connection, Book book) {
String sql = "UPDATE `t_book` SET `name`=?,`author`=?,`price`=?, `sales`=?,`stock`=?,`img_path`=? where `id`=?";
int update = update(connection, sql, book.getName(), book.getAuthor(), book.getPrice(), book.getSales(), book.getStock(), book.getImg_path(), book.getId());
return update;
}
@Override
public void deleteBookById(Connection connection, int id) {
String sql = "delete from `t_book` where `id`=?";
update(connection,sql,id);
}
@Override
public Book queryBookById(Connection connection, int id) {
String sql = "select * from `t_book` where `id`=?";
return queryForOne(connection, sql, id);
}
@Override
public List<Book> queryBooks(Connection connection) {
String sql = "select * from `t_book`";
return queryForList(connection, sql);
}
}
public interface BookService {
//插入图书信息数据
void addBook(Book book);
//修改图书数据
void updateBook(Book book);
//删除图书,根据id
void deleteBookById(int id);
//查询图书信息,根据id
Book queryBookById(int id);
//查看所有图书信息
List<Book> queryBooks();
}
public class BookServiceImpl implements BookService {
private BookDAO bookDAO = new BookDAOImpl();
@Override
public void addBook(Book book) {
Connection conn = jdbcUtils.getconn();
bookDAO.addBook(conn,book);
jdbcUtils.close(conn);
}
@Override
public void updateBook(Book book) {
Connection conn = jdbcUtils.getconn();
int i = bookDAO.updateBook(conn, book);
if(i == 0){
System.out.println("更改失败");
}
jdbcUtils.close(conn);
}
@Override
public void deleteBookById(int id) {
Connection conn = jdbcUtils.getconn();
bookDAO.deleteBookById(conn,id);
jdbcUtils.close(conn);
}
@Override
public Book queryBookById(int id) {
Connection conn = jdbcUtils.getconn();
Book book = bookDAO.queryBookById(conn, id);
jdbcUtils.close(conn);
return book;
}
@Override
public List<Book> queryBooks() {
Connection conn = jdbcUtils.getconn();
List<Book> books = bookDAO.queryBooks(conn);
jdbcUtils.close(conn);
return books;
}
}
点击 “ 后台管理 ”manager.jsp,再点击 “ 图书管理 ”book_manager.jsp ,首先展示的就是 图书列表,展示库中所有图书信息
图解列表功能流程:
注意:
如果访问 jsp 无法直接获取到数据,那么可以让程序先访问 Servlet 程序,再通过请求中转转发
在配置文件 web.xml 中进行配置
下面是 BookServlet
程序的地址配置
修改图书管理的超链接,更改至 BookServlet
程序
在超链接标签中,可以使用 ?methonName=query
设置隐藏域参数
<div>
<a href="manager/bookServlet?methonName=query">图书管理a>
<a href="pages/manager/order_manager.jsp">订单管理a>
<a href="index.jsp">返回商城a>
div>
同时注意,这里图书管理的超链接,是 GET 的请求方式,所以在 BaseServlet 程序里面还要重写 doGet() 方法
public abstract class BaseServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request,response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
try {
this.getClass().getDeclaredMethod(request.getParameter("methodName"),HttpServletRequest.class,HttpServletResponse.class).invoke(this,request,response);
} catch (Exception e) {
e.printStackTrace();
}
}
}
编写 BookServlet
程序中关于图书列表展示的代码
public class BookServlet extends BaseServlet {
protected void query(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
BookServiceImpl bookService = new BookServiceImpl();
List<Book> books = bookService.queryBooks();
request.setAttribute("booklist",books);
request.getRequestDispatcher("/pages/manager/book_manager.jsp").forward(request,response);
}
}
修改 pages/manager/book_manager.jsp 页面
使用 JSTL 标签实现对 request 域中的图书数据进行遍历输出
<c:forEach items="${requestScope.booklist}" var="book">
<tr>
<td>${book.name}td>
<td>${book.price}td>
<td>${book.author}td>
<td>${book.sales}td>
<td>${book.stock}td>
<td><a href="pages/manager/book_edit.jsp">修改a>td>
<td><a href="#">删除a>td>
tr>
c:forEach>
在 book_edit.jsp
页面,修改表单 提交的按钮,更改至 BookServlet
程序
并设置隐藏域
在 BookServlet
程序中,编写 add() 方法,完成如下操作:
1、获取请求参数,并封装到 Book 对象
2、调用 bookService.addBook() 方法
3、跳转到图书列表页面,显示添加成功后新的页面信息
public class BookServlet extends BaseServlet {
protected void add(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
BookServiceImpl bookService = new BookServiceImpl();
Book book = WebUtils.mapToBean(new Book(), request.getParameterMap());
bookService.addBook(book);
//添加成功,跳转到图书列表,并展示添加之后的图书数据
//request.getRequestDispatcher("/manager/bookServlet?methodName=query").forward(request,response);
//也可以选择直接调用query(request,response)方法
//query(request,response);
//------------以上两句都是请求转发,会有bug,需要使用请求重定向---------------
response.sendRedirect(request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+request.getContextPath()+"/"+"/manager/bookServlet?methodName=query");
}
}
注意两点:
1、获取请求参数并封装的时候,需要 JavaBean 对象里面的 set() 方法名字与 输入框的 name 属性值一致
2、在上面代码中,如果使用请求中转,那么从用户输入,提交到add()方法,再到query()查询,都是一次请求。
当用户提交完请求,浏览器会记录下最后一次请求的全部信息。当用户按下功能键 F5,就会发起浏览器记录的最后一次 请求
那么此时执行最后一次请求,就会添加数据并且查询,就有了重复添加问题
所以在 BookServlet
程序中,从添加成功,跳转到图书列表,只可以使用请求重定向
不需要共享请求数据的跳转,通通使用重定向即可
图解删除流程
修改删除的超链接,更改至 BookServlet
程序
在超链接标签中,可以使用 ?methonName=delete
设置隐藏域参数
因为删除的方法是根据对象的 id 进行删除,所以需要在跳转的时候设置请求参数:对象的 id
<a href="manager/bookServlet?methodName=delete&bookid=${book.id}">删除</a>
在 BookServlet
程序中,编写 delete() 方法
public class BookServlet extends BaseServlet {
protected void delete(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
BookServiceImpl bookService = new BookServiceImpl();
// 获取请求的id
Integer bookid = Integer.valueOf(request.getParameter("bookid"));
bookService.deleteBookById(bookid);
// 跳转回图书列表,使用请求重定向
response.sendRedirect(request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+request.getContextPath()+"/"+"/manager/bookServlet?methodName=query");
}
}
到此,删除的功能就完成了,但是还可以优化一下
1、在 BookServlet
程序中,第一步获取请求参数的 id 时候,可以在 WebUtils 工具类中编写一个方法,如果 id 强转失败,就返回默认值 0,这个功能后续也会用到
2、在删除的时候,浏览器弹出窗口提示 “ 确定删除XXX吗? ”
在 WebUtils
工具类中
public class WebUtils {
/**
* 如果转换失败,则返回默认值 defaultid
*/
public static int parseInt(String id,int defaultid){
try {
return Integer.parseInt(id);
} catch (NumberFormatException e) {
e.printStackTrace();
}
return defaultid;
}
}
在 BookServlet
程序中
public class BookServlet extends BaseServlet {
protected void delete(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
BookServiceImpl bookService = new BookServiceImpl();
// 获取请求的 id
int bookid = WebUtils.parseInt(request.getParameter("bookid"), 0);
//调用 BookServiceImpl 的 deleteBookById() 方法
bookService.deleteBookById(bookid);
// 跳转回图书列表
response.sendRedirect(request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+request.getContextPath()+"/"+"/manager/bookServlet?methodName=query");
}
}
在 book_manager.jsp
页面中,添加删除弹窗确认操作
<script type="text/javascript">
$(function () {
//删除超链接
$("a.adeleteclass").click(function () {
//获取当前点击的 删除超链接 所在的行标签tr
var aa = $(this).parent().parent();
//获取当前 删除超链接 所控制删除的书名
var $1 = aa.children(":first").text()
//confirm()
//当用户点击了确定,该方法就会返回true,当用户点击了取消,就返回false
return confirm("确定删除《"+$1+"》吗?");
//当return false的时候, 删除超链接 的点击事件就不会执行
})
})
</script>
当你点击修改,跳转进 book_edit.jsp
页面,会显示你点击要修改的图书的全部信息
类似登录失败或注册失败时候的表单回显信息的操作
修改修改的超链接,更改至 BookServlet
程序
在超链接标签中,可以使用 ?methonName=getBook
设置请求参数,使其执行 getBook 方法
因为表单回显操作是根据对象的 id 来查询当前的图书数据,所以需要在跳转的时候设置请求参数:对象的 id
在 BookServlet
程序中,编写 getBook() 方法
在请求对象的域值设置的是一个对象时候request.setAttribute("book",book);
,在jsp文件中也可以获取到对象里面的各个属性值 ${requestScope.book.name}
public class BookServlet extends BaseServlet {
protected void getBook(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
BookServiceImpl bookService = new BookServiceImpl();
// 获取请求的 id,就是当前要修改的图书的id
int bookid = WebUtils.parseInt(request.getParameter("bookid"), 0);
//由 id 查询图书信息,就是当前要修改的图书的信息
Book book = bookService.queryBookById(bookid);
//设置为请求对象的域值
request.setAttribute("book",book);
request.getRequestDispatcher("/pages/manager/book_edit.jsp").forward(request,response);
}
}
在 book_edit.jsp
页面,展示请求对象域值里封装的图书对象的信息
到此,表单回显功能实现
在 book_edit.jsp
页面,点击提交的时候,既要实现添加操作,又要实现修改操作,区别这两个功能的是表单隐藏域的值,隐藏域的值可以决定表单提交到 servlet 程序后执行哪一个方法/功能
有三个解决方法
1、就是在添加按钮、修改的超链接点击的时候(发送请求的时候
),设置各自的请求参数,然后在 book_edit.jsp
页面来获取这个参数,以区分两种不同的功能
2、因为添加超链接是直接跳转至 book_edit.jsp
页面,没有参数,而修改超链接是设置了请求参数bookid=${book.id}
那么可以判断一下请求参数里面有没有bookid参数,如果有就说明是修改操作,没有就说明是添加操作
value = "${empty param.bookid ? "add" : "update"}"
3、此方案与第二种方案类似,就是判断 请求对象的域(Request 域)中是否包含图书对象。如果有就说明是修改操作,没有就说明是添加操作
value = "${empty requestScope.book ? "add" : "update"}"
(因为修改操作经过了getBook方法,Request 域中有一个图书对象book )
注意:
在 BookServlet
程序中,编写 update() 方法
public class BookServlet extends BaseServlet {
protected void update(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
BookServiceImpl bookService = new BookServiceImpl();
// 获取请求参数,封装为对象
Book book = WebUtils.mapToBean(new Book(), request.getParameterMap());
book.setId(WebUtils.parseInt(request.getParameter("bookid"), 0));
System.out.println(book);
bookService.updateBook(book);
// 跳转回图书列表
response.sendRedirect(request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+request.getContextPath()+"/"+"/manager/bookServlet?methodName=query");
}
}