JSP
JSP(全称JavaServer Pages)是由Sun Microsystems公司主导创建的一种动态网页技术标准。
-
JSP部署于网络服务器上,可以响应客户端发送的请求,并根据请求内容动态地生成HTML、XML或其他格式文档的Web网页,然后返回给请求者。
-
JSP技术以Java语言作为脚本语言,为用户的HTTP请求提供服务,并能与服务器上的其它Java程序共同处理复杂的业务需求。
-
JSP将Java代码和特定变动内容嵌入到静态的页面中,实现以静态页面为模板,动态生成其中的部分内容。
-
JSP引入了被称为“JSP动作”的XML标签,用来调用内建功能。另外,可以创建JSP标签库,然后像使用标准HTML或XML标签一样使用它们。标签库能增强功能和服务器性能,而且不受跨平台问题的限制。
-
JSP文件在运行时会被其编译器转换成更原始的Servlet代码。JSP编译器可以把JSP文件编译成用Java代码写的Servlet,然后再由Java编译器来编译成能快速执行的二进制机器码,也可以直接编译成二进制码。
-
jsp 的主要作用是代替 Servlet 程序回传 html 页面的数据,因为 Servlet 程序回传 html 页面数据是一件非常繁锁的事情。开发成本和维护成本都极高。
jsp 页面本质上是一个 Servlet 程序。
当我们第一次访问 jsp 页面的时候,我们可以在文件的工程目录下看到,Tomcat 服务器会帮我们把 jsp 页面翻译成为一个 java 源文件。并且对它进行编译成 为.class 字节码程序!
打开java 源文件不难发现其里面的内容是:
跟踪原代码发现,HttpJspBase 类。它直接地继承了 HttpServlet 类。
也就是说,jsp 翻译出来的 java 类,它间接了继承了 HttpServlet 类,也就是一个 Servlet 程序。
JSP的四个域对象
域对象的作用:
用于保存数据,获取数据,在不同资源之间共享数据。
域对象的方法:
-
setAttribute(name,object) ; 保存数据方法
-
getAttribute(name) 获取数据
romveAttribute(name) 清除数据
四个域对象的作用范围:
-
page域: 处于同一个jsp页面中数据共享是有效的
-
request域:处于同一个请求中数据共享是有效的
-
session域:处于同一个会话中数据共享是有效的
-
application域:处于同一个web应用中数据共享是有效的
书城项目
第一阶段 表单验证
验证用户名:必须由字母,数字和下划线组成,并且长度为5到12位
* 获取用户名输入框里的内容* 创建正则表达式* 使用test方法测试* 提示用户结果
验证密码:必须由字母,数字和下划线组成,并且长度为5到12位
- 获取密码输入框里的内容
- 创建正则表达式
- 使用test方法测试
- 提示用户结果
验证确认密码:和密码相同
- 获取确认密码输入框里的内容
- 与密码输入框中的内容进行比较
邮箱验证:xxxxx@xxx.com
- 获取邮箱输入框里的内容
- 创建正则表达式
- 使用test方法测试
- 提示用户结果
验证码:现在只需要验证用户
- 获取验证码输入框里的内容
- 验证码不能为空
- 提示用户结果
注意: 假设现在第一次输入了不合法的用户名,页面提示了错误,当我们再次输入一个合法的用户名时进行提交,而网速较慢时,页面显示的错误仍会存在!于是进行验证之后应该将错误信息重置!
<script>
$(document).ready(function () {
// 给验证按钮绑定单击事件
$("#sub_btn").click(function () {
// 验证用户名:必须由字母,数字和下划线组成,并且长度为5到12位
var $btnOnj = $("#username").val()
var patt = /^\w{5,12}$/
if ( ! patt.test($btnOnj)) {
$("span.errorMsg").text("用户名不合法");
return false;
}
// 验证密码:必须由字母,数字和下划线组成,并且长度为5到12位
var $pwOnj = $("#password").val()
var pwpatt = /^\w{5,12}$/
if (!pwpatt.test($pwOnj)) {
$("span.errorMsg").text("密码不合法");
return false;
}
// 验证确认密码:和密码相同
var $repwdObj = $("#repwd").val()
if ($pwOnj != $repwdObj) {
$("span.errorMsg").text("两次密码不一致");
return false;
}
// 邮箱验证:xxxxx@xxx.com
var $emailObj = $("#email").val()
var emailpatt = /^[A-Za-zd0-9]+([-_.][A-Za-zd]+)*@([A-Za-zd]+[-.])+[A-Za-zd]{2,5}$/;
if (! emailpatt.test($emailObj)) {
$("span.errorMsg").text("邮箱不合法");
return false;
}
// 验证码:现在只需要验证用户
var $code = $("#code").val()
// 去掉验证码中输入的空格!
$code = $code.trim();
if ($code == null || $code == "") {
$("span.errorMsg").text("验证码不能为空");
return false;
}
})
$("span.errorMsg").text(""); // 将错误信息重置
})
</script>
注意: 验证码的输入内容需要去除空格,可以使用正则表达式来进行验证,也可以使用JQuery中提供的工具方法trim()方法!
第二阶段 用户注册和登录
JavaEE的三层架构
分层的目的是为了解耦,解耦就是为了降低代码的耦合度,方便项目后期的维护和升级!
环境项目搭建
web 层 com.atguigu.web/servlet/controller
service 层 com.atguigu.service Service 接口包
com.atguigu.service.impl Service 接口实现类
dao 持久层 com.atguigu.dao Dao 接口包
com.atguigu.dao.impl Dao 接口实现类
实体 bean 对象 com.atguigu.pojo/entity/domain/bean JavaBean 类
测试包 com.atguigu.test/junit
工具类 com.atguigu.utils
代码流程
1. 创建数据库表和表
创建与注册页面所需数据对应的表:
由上图可得,数据库中所对应的用户表(t_user),应该要有用户名称(username),用户密码(password),用户邮箱(email)这三个字段!
CREATE DATABASE book
USE book;
CREATE TABLE t_user(
`id` INT AUTO_INCREMENT,
`username` VARCHAR(20) NOT NULL,
`password` VARCHAR(32) NOT NULL,
`email` VARCHAR(200) NOT NULL,
PRIMARY KEY `id`(`id`),
UNIQUE KEY `username`(`username`)
);
INSERT INTO t_user(`username`,`password`,`email`)
VALUE('admin','admin','admin@atayin.com');
2. 编写数据库表对应的JavaBean类
在创建完数据库表之后,我们需要在存放JavaBean类的包下写一个与数据库表对应的User类:
public class User {
private Integer id;
private String username;
private String password;
private String email;
public User() {
}
public User(Integer id, String username, String password, String email) {
this.id = id;
this.username = username;
this.password = password;
this.email = email;
}
public Integer getId() {
return id;
}
public void setId(Integer 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;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
", email='" + email + '\'' +
'}';
}
}
3. 编写Dao持久层
一般而言,在编写Dao持久层之前,项目经理会提前搭建好持久层所需的项目工具类,但此时的项目是由我们独自完成,于是需要先编写工具类(JdbcUtils)!
编写JdbcUtils工具类:实现连接数据库
public class JdbcUtils {
private static DruidDataSource dataSource;
static {
try {
Properties properties = new Properties();
// 读取 jdbc.properties属性配置文件
InputStream inputStream = JdbcUtils.class.getClassLoader().getResourceAsStream("jdbc.properties");
// 从流中加载数据
properties.load(inputStream);
// 创建 数据库连接 池
dataSource = (DruidDataSource) DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 获取数据库连接池中的连接
* @return 如果返回null,说明获取连接失败<br/>有值就是获取连接成功
*/
public static Connection getConnection(){
Connection conn = null;
try {
conn = dataSource.getConnection();
} catch (Exception e) {
e.printStackTrace();
}
return conn;
}
/**
* 关闭连接,放回数据库连接池
* @param conn
*/
public static void close(Connection conn){
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
关于数据库连接池所需要的jar包,以及Java操作数据库所需要的jar包,如下:
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.27</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.8</version>
</dependency>
</dependencies>
编写创建 数据库连接 池的配置文件:
username=root
password=12345
url = jdbc:mysql://localhost:3306/book?useUnicode=true&characterEncoding=utf8&useSSL=true
driver=com.mysql.cj.jdbc.Driver
initialSize=5
maxActive=10
我们可以在测试包下创建一个JdbcUtils的测试类,以此来测试JdbcUtils是否能获取连接:
import com.atayin.utils.JdbcUtils;
import org.junit.Test;
/**
* Created by Ayin
* Date 2021/11/10 18:12
* Description: 测试JdbcUtils工具类
*/
public class JdbcUtilsTest {
@Test
public void testJdbcUtils() {
System.out.println(JdbcUtils.getConnection());
}
}
注意:我们在配置文件中配置了数据库连接池中的最大连接数是10,也就是说,我们即使获取100次连接,也只会返回10次连接,所以我们需要在使用完之后及时关闭连接!
编写BaseDao:
以下代码实例可以获得数据库中的数据,也就是sql语句中所对应的参数值:
<!-- https://mvnrepository.com/artifact/commons-dbutils/commons-dbutils -->
<dependency>
<groupId>commons-dbutils</groupId>
<artifactId>commons-dbutils</artifactId>
<version>1.7</version>
</dependency>
public abstract class BaseDao {
QueryRunner queryRunner = new QueryRunner();
/**
* update() 执行sql语句中的insert、update、delete语句
* @param sql
* @param args
* @return 返回-1则执行失败;返回其他表示影响的行数
*/
public int update(String sql, Object...args) {
Connection connection = JdbcUtils.getConnection();
try {
return queryRunner.update(connection, sql, args);
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
JdbcUtils.close(connection);
}
return -1;
}
/**
* 查询返回一个JavaBean的sql语句
* @param type 返回的对象类型
* @param sql 执行的sql语句
* @param args sql对应的参数值
* @param <T> 返回类型的泛型
* @return
*/
public <T> T queryForOne(Class<T> type, String sql, Object...args ) {
Connection connection = JdbcUtils.getConnection();
try {
return queryRunner.query(connection, sql, new BeanHandler<T>(type), args);
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
JdbcUtils.close(connection);
}
return null;
}
/**
* 查询返回多个JavaBean的sql语句
* @param type 返回的对象类型
* @param sql 执行的sql语句
* @param args sql对应的参数值
* @param <T> 返回类型的泛型
* @return
*/
public <T> List<T> queryForList(Class<T> type, String sql, Object...args ) {
Connection connection = JdbcUtils.getConnection();
try {
return queryRunner.query(connection, sql, new BeanListHandler<T>(type), args);
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
JdbcUtils.close(connection);
}
return null;
}
/**
* 执行返回一行的sql语句
* @param sql 执行的sql语句
* @param args sql对应的参数值
* @return
*/
public Object queryForSingleValue(String sql, Object...args) {
Connection connection = JdbcUtils.getConnection();
try {
return queryRunner.query(connection, sql, new ScalarHandler<>(), args);
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
JdbcUtils.close(connection);
}
return null;
}
}
编写UserDao:
public interface UserDao {
/**
* 根据用户名查询信息
* @param username 用户名
* @return 如果返回null,说明没有这个用户
*/
public User queryUserByUsername(String username);
/**
* 根据用户名和密码查询信息
* @param username 用户名
* @param password 密码
* @return 如果返回null,说明用户名或密码错误
*/
public User queryUserByUsernameAndPassword(String username, String password);
/**
*插入新的用户并返回所影响的行数
* @param user 新用户
* @return 如果返回-1,说明添加新用户失败
*/
public int saveUser(User user);
}
编写UserDaoImpl:
UserDaoImpl需要实现UserDao接口并继承BaseDao类!
public class UserDaoImpl extends BaseDao implements UserDao {
@Override
public User queryUserByUsername(String username) {
String sql = "SELECT `id`,`username`,`password`,`email` FROM `t_user` WHERE `username` = ?";
return queryForOne(User.class, sql, username);
}
@Override
public User queryUserByUsernameAndPassword(String username, String password) {
String sql = "SELECT `id`,`username`,`password`,`email` FROM `t_user` WHERE `username` = ? AND `password` = ?";
return queryForOne(User.class, sql, username, password);
}
@Override
public int saveUser(User user) {
String sql = "INSERT INTO `t_user`(`username`,`password`,`email`) VALUES(?, ?, ?)";
return update(sql, user.getUsername(), user.getPassword(), user.getEmail());
}
}
完成以上步骤之后,我们可以对UserDaoImpl类进行测试,IDEA可以通过快捷键Ctrl+Shift+T来快速创建一个测试方法类!
public class UserDaoImplTest {
@Test
public void queryUserByUsername() {
UserDao userDao = new UserDaoImpl();
System.out.println(userDao.queryUserByUsername("admin"));
}
@Test
public void queryUserByUsernameAndPassword() {
}
@Test
public void saveUser() {
UserDao userDao = new UserDaoImpl();
int i = userDao.saveUser(new User(null, "testsave", "123456", "testsave@atayin.com"));
System.out.println(i);
}
}
4. 编写Service层
完成Dao持久层的编写之后,应该进入Service层的编写,按照书城项目的用户注册和登录界面,我们可以解析得到,这两个页面需要实现以下三个业务:
- 用户注册(registUser)
- 用户登录(loginUser)
- 用户名验证(existUsername)
编写UserService接口:
public interface UserService {
/**
* 注册用户
* @param user
*/
public void registUser(User user);
/**
* 登录用户
* @param user
* @return 如果返回null,说明用户名或密码错误
*/
public User loginUser(User user);
/**
* 判断用户名是否可用
* @param username
* @return
*/
public boolean existUsername(String username);
}
编写UserServiceImpl实现类:
public class UserServiceImpl implements UserService {
UserDao userDao = new UserDaoImpl();
@Override
public void registUser(User user) {
userDao.saveUser(user);
}
@Override
public User loginUser(User user) {
return userDao.queryUserByUsernameAndPassword(user.getUsername(), user.getPassword());
}
@Override
public boolean existUsername(String username) {
if (userDao.queryUserByUsername(username) == null) {
return false;
}
return true;
}
}
5. 编写web层
用户注册
以上为图解用户注册的流程,我们需要按照以上流程,使用servlet来实现用户注册的功能!
当然,在使用servlet之前,需要导入所需的jar包:
<!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
修改regist和regist_succes界面:
编写base标签,永远固定相对路径跳转的结果:
<base href="http://localhost:8080/ruibook/">
当编写base标签之后,会对页面产生影响,需要将页面中所有的相对路径进行修改,我们可以在浏览器的控制台中查看被影响的标签,并对其进行修改!
注意:启动tomcat服务器时的路径需要与base标签设定的路径一致!
修改表单的提交地址和提交方式:
<form action="regist" method="post">
需要注意的是,action属性所填写的路径填写的是在web.xml中注册的路径!
编写web包下的RegistServlet类:
创建完RegistServlet之后,需要在web.xml进行注册:
<servlet>
<servlet-name>RegistServlet</servlet-name>
<servlet-class>com.atayin.web.RegistServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>RegistServlet</servlet-name>
<url-pattern>/regist</url-pattern>
</servlet-mapping>
当表单提交之后,会进入/regist路径,并开始执行RegistServlet类中的方法:
public class RegistServlet extends HttpServlet {
UserService userService = new UserServiceImpl();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 1. 获取请求的参数
String username = req.getParameter("username");
String password = req.getParameter("password");
String email = req.getParameter("email");
String code = req.getParameter("code");
// 2. 检查验证码是否正确,由于并没有使用服务器生成验证码,于是将验证码写死
if ("abcde".equalsIgnoreCase(code)) {
// 验证码正确
// 3. 检查用户名是否可用
if (userService.existUsername(username)) {
// 不可用
System.out.println("用户名已存在!");
req.getRequestDispatcher("/pages/user/regist.html").forward(req, resp);
} else {
// 可用
System.out.println("用户已注册!");
userService.registUser(new User(null, username, password, email));
req.getRequestDispatcher("/pages/user/regist_success.html").forward(req, resp);
}
} else {
// 验证码错误,跳转至注册界面
System.out.println("验证码错误!");
req.getRequestDispatcher("/pages/user/regist.html").forward(req, resp);
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
上述代码实例中,需要注意:web层不可以直接调用Dao层,所以需要一开始实例化UserService!
- 通过req.getParameter()方法获得表单doGet() / doPost()提交的参数
- 通过req.getRequestDispatcher()方法进行请求转发,并跳转页面
用户登录
以上为图解用户登录的流程!
注册路径:
<servlet>
<servlet-name>LoginServlet</servlet-name>
<servlet-class>com.atayin.web.LoginServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>LoginServlet</servlet-name>
<url-pattern>/login</url-pattern>
</servlet-mapping>
编写web包下的LoginServlet类:
public class LoginServlet extends HttpServlet {
UserService userService = new UserServiceImpl();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String username = req.getParameter("username");
String password = req.getParameter("password");
User loginUser = userService.loginUser(new User(null, username, password, null));
if (loginUser == null) {
System.out.println("用户名或密码错误");
req.getRequestDispatcher("/pages/user/login.html").forward(req, resp);
} else {
req.getRequestDispatcher("/pages/user/login_success.html").forward(req, resp);
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
页面jsp动态化
修改jsp页面:
1、在 html 页面顶行添加 page 指令。
2、修改文件后缀名为:.jsp
3、使用 IDEA中,点击已经修改过的jsp文件,使用快捷键:Ctrl+Shift+R,搜索替换.html 为.jsp
动态修改base标签中的ip地址:
<%
String basePath = request.getScheme()
+ "://"
+ request.getServerName()
+ ":"
+ request.getServerPort()
+ request.getContextPath()
+ "/";
%>
<%=basePath%>
<!--写 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>
将众多jsp页面中所重复的代码字段提取出来,封装在一个jsp文件中,以上代码实例就是每个页面中所重复的内容,需要用到该重复代码字段的jsp页面可以直接使用静态包含来调用!
以下为其他页面代码中重复的部分:
每个页面的页脚:
<div id="bottom">
<span>
尚硅谷书城.Copyright ©2015
</span>
</div>
登录成功后的菜单:
<div>
<span>欢迎<span class="um_span">韩总</span>光临尚硅谷书城</span>
<a href="../order/order.jsp">我的订单</a>
<a href="../../index.jsp">注销</a>
<a href="../../index.jsp">返回</a>
</div>
manager模块的菜单:
<div>
<a href="book_manager.jsp">图书管理</a>
<a href="order_manager.jsp">订单管理</a>
<a href="../../index.jsp">返回商城</a>
</div>
登录、注册错误提示以及表单回显
当用户进行进行登录或者注册时,有可能会发生以下几种错误:
- 登录时,用户名或密码输入错误
- 注册时,用户名重复
- 注册时,验证码错误
当用户进行业务操作时,发生了以上几种错误,页面需要将错误进行提示,并展现在页面上!同时,页面中用户已输入的信息不应该被清除,而是保留在页面中,所以需要表单执行回显操作。
在处理登录业务的Servlet程序中,通过req.setAttribute()方法在request域中存放用户信息和报错信息,并在页面中使用静态包含调用:
public class LoginServlet extends HttpServlet {
UserService userService = new UserServiceImpl();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String username = req.getParameter("username");
String password = req.getParameter("password");
User loginUser = userService.loginUser(new User(null, username, password, null));
if (loginUser == null) {
// 把错误信息,和回显的表单项信息,保存到Request域中
req.setAttribute("msg","用户或密码错误!");
req.setAttribute("username", username);
req.getRequestDispatcher("/pages/user/login.jsp").forward(req, resp);
} else {
req.getRequestDispatcher("/pages/user/login_success.jsp").forward(req, resp);
}
}
}
jsp中使用静态包含调用存放在request域中的数据:
<div class="msg_cont">
<b></b>
<span class="errorMsg">
<%=request.getAttribute("meg") == null ? "" : request.getAttribute("msg")%>
</span>
</div>
同样的,用户名称也可以使用类似的方法进行回显操作:
<label>用户名称:</label>
<input class="itxt" type="text" placeholder="请输入用户名"
autocomplete="off" tabindex="1" name="username"
value="<%=request.getAttribute("username") == null ? "" : request.getAttribute("username")%>" />
处理注册业务的Servlet程序以及对应的jsp页面也同理:
public class RegistServlet extends HttpServlet {
UserService userService = new UserServiceImpl();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 1. 获取请求的参数
String username = req.getParameter("username");
String password = req.getParameter("password");
String email = req.getParameter("email");
String code = req.getParameter("code");
// 2. 检查验证码是否正确,由于并没有使用服务器生成验证码,于是将验证码写死
if ("abcde".equalsIgnoreCase(code)) {
// 验证码正确
// 3. 检查用户名是否可用
if (userService.existUsername(username)) {
// 不可用
System.out.println("用户名已存在!");
// 把回显信息,保存到Request域中
req.setAttribute("msg", "用户名已存在!!");
req.setAttribute("password", password);
req.setAttribute("username", username);
req.setAttribute("email", email);
req.getRequestDispatcher("/pages/user/regist.jsp").forward(req, resp);
} else {
// 可用
System.out.println("用户名可用!");
userService.registUser(new User(null, username, password, email));
req.getRequestDispatcher("/pages/user/regist_success.jsp").forward(req, resp);
}
} else {
// 验证码错误,跳转至注册界面
System.out.println("验证码错误!");
// 把回显信息,保存到Request域中
req.setAttribute("msg", "验证码错误!!");
req.setAttribute("username", username);
req.setAttribute("password", password);
req.setAttribute("email", email);
req.getRequestDispatcher("/pages/user/regist.jsp").forward(req, resp);
}
}
}
页面回显邮箱:
<input class="itxt" type="text" placeholder="请输入邮箱地址"
value="<%=request.getAttribute("email") == null ? "" : request.getAttribute("email")%>"
autocomplete="off" tabindex="1" name="email" id="email" />
页面回显用户以及密码:
<label>用户名称:</label>
<input class="itxt" type="text" placeholder="请输入用户名"
value=“<%=request.getAttribute("username") == null ? "" : request.getAttribute("username")%>”
autocomplete="off" tabindex="1" name="username" id="username" />
<br />
<br />
<label>用户密码:</label>
<input class="itxt" type="password" placeholder="请输入密码"
value="<%=request.getAttribute("password") == null ? "" : request.getAttribute("password")%>"
autocomplete="off" tabindex="1" name="password" id="password" />
<br />
页面显示错误信息:
<div class="tit">
<h1>注册会员</h1>
<span class="errorMsg">
<%=request.getAttribute("msg") == null ? "" : request.getAttribute("msg")%>
</span>
</div>
代码优化
合并登录Servlet类以及注册Servlet类
在实际的项目开发中,一个模块,一般只使用一个 Servlet 程序。
比如说,用户注册(RegistServlet)和用户登录(LoginServlet)都为用户类的业务,所以需要将这两者进行合并,如何合并,需要使用页面中的隐藏域!
<input type="hidden" name="action" value="regist">
<input type="hidden" name="action" value="login" />
以上代码分别出现在用户注册页面和用户登录页面,一般而言,这种隐藏域是在页面中看不到的,隐藏在表单标签之后的下一行。
编写UserServlet类:
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String action = req.getParameter("action");
// 处理注册的要求
if ("regist".equals(action)) {
regist(req, resp);
// 处理登录的要求
} else if ("login".equals(action)) {
login(req, resp);
}
}
web.xml中注册UserServlet类:
<servlet>
<servlet-name>UserServlet</servlet-name>
<servlet-class>com.atayin.web.UserServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>UserServlet</servlet-name>
<url-pattern>/userServlet</url-pattern>
</servlet-mapping>
假设此时将之前的RegistServlet类和LoginServelet类直接复制粘贴进doGet方法中,会显得代码非常臃肿,于是可以创建regist方法和login方法写在UserServlet类中!
通过反射优化大量else-if代码
但是,假设日后业务逐渐增多,例如用户类业务需要进行用户名修改、用户密码修改、注销用户等一系列复杂的操作,这就代表着业务需要进行处理,同时在doGet方法中也需要增添else if模块,有没有一种方法,可以在处理业务的时候,不需要去判断,自动去寻找对应的方法执行?
答案当然是有的!
Java可以通过反射机制来获取对应方法对象并执行!
创建一个测试类,测试通过反射获取与字符串同名的方法对象:
public class UserServletTest {
public static void main(String[] args) {
String action = "login";
try {
Method method = UserServletTest.class.getDeclaredMethod(action);
// System.out.println(method);
method.invoke(new UserServletTest());
} catch (Exception e) {
e.printStackTrace();
}
}
@Test
public void login() {
System.out.println("login方法被调用!");
}
@Test
public void regist() {
System.out.println("regist方法被调用!");
}
}
运行以上测试类后,可以发现login方法被调用,所以通过反射来获取方法对象的方案是可行的,按照同样的逻辑应用在UserServlet类中的doGet/dePost方法中:
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String action = req.getParameter("action");
// 处理注册的要求
try {
Method method = this.getClass().getDeclaredMethod(action, HttpServletRequest.class, HttpServletRequest.class);
method.invoke(this, req, resp);
} catch (Exception e) {
e.printStackTrace();
}
}
抽取BaseServlet程序
假设此时有许多Servlet程序来处理业务,比如UserServlet程序来处理用户类业务,BookServlet程序来处理图书类业务,两者都需要获取action参数值,并通过反射获取action参数值所对应的业务方法,最后再通过反射调用业务方法这一系列操作。
这些操作存在一定的重复性,可以将其抽取出来,放在BaseServelet程序中来统一处理,UserServlet程序和BookServlet程序只要继承BaseServlet程序即可!
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Z2qy1vex-1644134704853)(C:/Users/12709/AppData/Roaming/Typora/typora-user-images/image-20211118160041964.png)]
public abstract class BaseServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String action = req.getParameter("action");
// 处理注册的要求
try {
Method method = this.getClass().getDeclaredMethod(action, HttpServletRequest.class, HttpServletResponse.class);
method.invoke(this, req, resp);
} catch (Exception e) {
e.printStackTrace();
}
}
}
Tips:抽象类通过abstract关键字来实现,当abstract关键字用来修饰一个方法,那这个方法就是抽象方法,意思是一个约束,这个方法只有方法名字,而内部可以不写任何代码,而抽象类的方法将会由继承该类的子类来具体实现,除非该子类也是抽象类,那该方法的实现将会丢给子类的子类来实现。
注意:UserServlet需要继承的是BaseServlet类,而不是原来的HttpServlet类!
数据的封装和抽取 BeanUtils 的使用
观察UserServlet类可以发现,该类的业务方法在执行时会获取客户端通过req回传的参数,虽然在该类中,参数比较少,所以代码也相对比较少,但是在实例业务中,所需要获取的参数有可能高达20多个,这时一一去获取参数就会使得代码显得臃肿无比!
BeanUtils 工具类,它可以一次性的把所有请求的参数注入到 JavaBean 中,经常用于把 Map 中的值注入到 JavaBean 中,或者是对象属性值的拷贝操作。
BeanUtils 它不是 Jdk 的类,而是第三方的工具类,所以需要导入对应的 jar 包: commons-beanutils-1.8.0.jar、commons-logging-1.1.1.jar!
<!-- https://mvnrepository.com/artifact/commons-logging/commons-logging -->
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-beanutils/commons-beanutils -->
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.9.4</version>
</dependency>
使用BeanUtils工具类进行封装:
User user = new User();
try {
System.out.println("注入之前" + user);
// 将请求的参数注入在user中
BeanUtils.populate(user, req.getParameterMap());
System.out.println("注入之后" + user);
} catch (Exception e) {
e.printStackTrace();
}
将这段代码放在Servlet程序中,可以发现,当程序执行业务方法时,req.getParameterMap()方法会把获取的参数按照键值对的形式存储在map集合中,而BeanUtils工具类可以将map集合中的键值对参数注入在User中!
但是,每一个Servlet程序几乎都要请求参数,所以在每个程序都写上这样一段代码是非常繁琐的,为了让代码进一步变得简洁,可以将以上代码封装成一个工具类,在Servlet程序中直接调用即可!
public class WebUtils {
public static void copyParamToBean(HttpServletRequest req, Object bean) {
try {
System.out.println("注入之前" + bean);
// 将请求的参数注入在user中
BeanUtils.populate(bean, req.getParameterMap());
System.out.println("注入之后" + bean);
} catch (Exception e) {
e.printStackTrace();
}
}
}
// UserServlet
User user = new User();
WebUtils.copyParamToBean(req, user);
通过调用WebUtils工具类,也可以达成将参数注入在User中的效果。
进一步分析BeanUtils类的工作原理,是分析了map集合中键值并调用对应的set方法,例如map集合中有这样一个键值对(username, “ayin”),那么,BeanUtils将会调用username所对应的setUsername方法,并将username设置为"ayin"!
进行到这一步,代码看似非常完善,但还是有很多细节可以进一步优化!
在WebUtils工具类中,业务方法请求的参数是HttpServletRequest,如果将其更改为Map,在UserServlet中调用业务方法时传入req.getParameterMap(),该业务依然可以执行!
这两种有何区别?
简单而言,在工具类中,调用的参数是JavaEE三层架构中的web层才会调用的HttpServletRequest,但这就代表着Dao层和Service层并不能调用,但如果将参数设置为Map,那么在三层架构中都可以进行使用!而且,使用HttpServletRequest作为参数也会使得web层的耦合度高!
即使到了这一步,代码仍然可以继续优化,例如将UserServlet的两行代码合并成一行!
// UserServlet
User user = (User)WebUtils.copyParamToBean(req, new User());
但是,这也代表着WebUtils工具类需要一个Object的返回值,而且转型在某些情况下也会有一定风险,于是工具类可以使用泛型将转型也省去!
public class WebUtils {
public static <T> T copyParamToBean(Map value, T bean) {
try {
System.out.println("注入之前" + bean);
// 将请求的参数注入在user中
BeanUtils.populate(bean, value);
System.out.println("注入之后" + bean);
} catch (Exception e) {
e.printStackTrace();
}
return bean;
}
}
使用EL表达式实现表单回显
在表单回显功能的实现中,使用jsp标签固然可行,不过也有更方便更简单的方法,就是EL表达式:
${requestScope.msg}
以上的表达式可以获取request域中存放的数据,如果不存在则会默认返回一个空串!
${empty requestScope.msg ? "请输入用户名和密码" : requestScope.msg}
也可以通过empty关键字来定义数据不存在所返回的内容!