基于JavaWeb实现的书城项目:阶段一至阶段三

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 源文件不难发现其里面的内容是:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-haZRud0Z-1644134506638)(C:/Users/12709/AppData/Roaming/Typora/typora-user-images/image-20211114165257474.png)]

跟踪原代码发现,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的三层架构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-q1z4I0d7-1644134646639)(C:/Users/12709/AppData/Roaming/Typora/typora-user-images/image-20211110164907949.png)]

分层的目的是为了解耦,解耦就是为了降低代码的耦合度,方便项目后期的维护和升级!

环境项目搭建

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. 创建数据库表和表

创建与注册页面所需数据对应的表:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eRDV3oew-1644134628467)(C:/Users/12709/AppData/Roaming/Typora/typora-user-images/image-20211110172108792.png)]

由上图可得,数据库中所对应的用户表(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层
用户注册

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZolVZDX6-1644134628467)(C:/Users/12709/AppData/Roaming/Typora/typora-user-images/image-20211113154152695.png)]

以上为图解用户注册的流程,我们需要按照以上流程,使用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()方法进行请求转发,并跳转页面
用户登录

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-i03IzM9h-1644134628468)(C:/Users/12709/AppData/Roaming/Typora/typora-user-images/image-20211113172528504.png)]

以上为图解用户登录的流程!

注册路径:

<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 &copy;2015
    </span>
</div>

登录成功后的菜单:

<div>
    <span>欢迎<span class="um_span">韩总</span>光临尚硅谷书城</span>
    <a href="../order/order.jsp">我的订单</a>
    <a href="../../index.jsp">注销</a>&nbsp;&nbsp;
    <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>

登录、注册错误提示以及表单回显

当用户进行进行登录或者注册时,有可能会发生以下几种错误:

  1. 登录时,用户名或密码输入错误
  2. 注册时,用户名重复
  3. 注册时,验证码错误

当用户进行业务操作时,发生了以上几种错误,页面需要将错误进行提示,并展现在页面上!同时,页面中用户已输入的信息不应该被清除,而是保留在页面中,所以需要表单执行回显操作。

在处理登录业务的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关键字来定义数据不存在所返回的内容!

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值