老杜 JavaWeb 讲解(十二) ——oa项目的改造(模板方法)
(十三)对单表操作的优化
对应视频:
在一个web应用中应该如何完成资源的跳转?
在一个web应用中通过两种方式,可以完成资源的跳转:
-
转发
-
重定向
转发和重定向有什么区别?
代码的区别:
- 转发:
// 获取请求转发器对象
RequestDispatcher dispatcher = request.getRequestDispatcher("/dept/list");
// 调用请求转发器对象的forward方法完成转发
dispatcher.forward(request, response);
// 合并一行代码
request.getRequestDispatcher("/dept/list").forward(request, response);
// 转发的时候是一次请求,不管你转发了多少次。都是一次请求。
// AServlet转发到BServlet,再转发到CServlet,再转发到DServlet,不管转发了多少次,都在同一个request当中。
// 这是因为调用forward方法的时候,会将当前的request和response对象传递给下一个Servlet。
- 重定向
//重定向
//重定向时的路径需要添加项目名。
//response对象将 equest.getContextPath()+"/b" 路径响应给浏览器。
//在这个过程中,浏览器又自发的向浏览器发送了一次请求:http://localhost:8080/equest.getContextPath()/b
//最终,浏览器地址栏上的地址会发生改变,显示最后一次的请求的路径。
response.sendRedirect(request.getContextPath()+"/b");
形式区别:
-
转发是一次请求。
-
重定向是两次请求。
本质区别:
- 转发:是由WEB服务器来控制的。A资源跳转到B资源,这个跳转动作是Tomcat服务器内部完成的。
- 重定向:是浏览器完成的。具体跳转到哪个资源,是浏览器说了算。
例子生动描述转发和重定向:
- 借钱(转发:发送了一次请求)
- 杜老师没钱了,找张三借钱,其实张三没有钱,但是张三够义气,张三自己找李四借了钱,然后张三把这个钱给了杜老师,杜老师不知道这个钱是李四的,杜老师只求了一个人。杜老师以为这个钱就是张三的。
- 借钱(重定向:发送了两次请求)
- 杜老师没钱了,找张三借钱,张三没有钱,张三有一个好哥们,叫李四,李四是个富二代,于是张三将李四的家庭住址告诉了杜老师,杜老师按照这个地址去找到李四,然后从李四那里借了钱。显然杜老师在这个过程中,求了两个人。并且杜老师知道最终这个钱是李四借给俺的。
转发和重定向应该如何选择?什么时候使用转发,什么时候使用重定向?
- 如果在上一个Servlet当中向request域当中绑定了数据,希望从下一个Servlet当中把request域里面的数据取出来,使用转发机制。
- 剩下所有的请求均使用重定向。(重定向使用较多。)
跳转的下一个资源有没有要求呢?必须是一个Servlet吗?
-
不一定,跳转的资源只要是服务器内部合法的资源即可。包括:Servlet、JSP、HTML.....
-
转发会存在浏览器的刷新问题(使用重定向可以避免)。
Servlet注解,简化配置
-
分析oa项目中的web.xml文件
- 现在只是一个单标的CRUD,没有复杂的业务逻辑,很简单的一丢丢功能。web.xml文件中就有如此多的配置信息。如果采用这种方式,对于一个大的项目来说,这样的话web.xml文件会非常庞大,有可能最终会达到几十兆。
- 在web.xml文件中进行servlet信息的配置,显然开发效率比较低,每一个都需要配置一下。
- 而且在web.xml文件中的配置是很少被修改的,所以这种配置信息能不能直接写到java类当中呢?可以的。
-
Servlet3.0版本之后,推出了各种Servlet基于注解式开发。优点是什么?
- 开发效率高,不需要编写大量的配置信息。直接在java类上使用注解进行标注。
- web.xml文件体积变小了。
-
并不是说注解有了之后,web.xml文件就不需要了:
- 有一些需要变化的信息,还是要配置到web.xml文件中。一般都是 注解+配置文件 的开发模式。
- 一些不会经常变化修改的配置建议使用注解。一些可能会被修改的建议写到配置文件中。
-
我们的第一个注解:jakarta.servlet.annotation.WebServlet
- 在Servlet类上使用:@WebServlet,WebServlet注解中有哪些属性呢?
- name属性:用来指定Servlet的名字。等同于:
- urlPatterns属性:用来指定Servlet的映射路径。可以指定多个字符串。
- loadOnStartUp属性:用来指定在服务器启动阶段是否加载该Servlet。等同于:
- value属性:当注解的属性名是value的时候,使用注解的时候,value属性名是可以省略的。
- 注意:不是必须将所有属性都写上,只需要提供需要的。(需要什么用
- 什么。)
- 注意:属性是一个数组,如果数组中只有一个元素,使用该注解的时候,属性值的大括号可以省略。
- name属性:用来指定Servlet的名字。等同于:
- 在Servlet类上使用:@WebServlet,WebServlet注解中有哪些属性呢?
-
注解对象的使用格式:
- @注解名称(属性名=属性值, 属性名=属性值, 属性名=属性值....)
通过反射获取注解信息:
package com.zwm.javaweb.servlet;
import jakarta.servlet.annotation.WebServlet;
/**
* @author 猪无名
* @date 2023/7/21 23 59
* discription:
*/
public class Test {
public static void main(String[] args) throws Exception{
//反射获取类
Class<?> helloServletClass = Class.forName("com.zwm.javaweb.servlet.HelloServlet");
//获取类上面的注解对象
//先判断这个类上有没有注解对象,有的话就获取。
// boolean annotationPresent = helloServletClass.isAnnotationPresent(WebServlet.class);
// System.out.println(annotationPresent);//true
if(helloServletClass.isAnnotationPresent(WebServlet.class)){
//获取这个类上面的注解对象
WebServlet annotation = helloServletClass.getAnnotation(WebServlet.class);
//获取注解的value属性值
String[] value = annotation.value();
for (String s : value) {
System.out.println(s);
}
}
}
}
使用模板方法设计模式优化oa项目
上面的注解解决了配置文件的问题。但是现在的oa项目仍然存在一个比较臃肿的问题。
- 一个单标的CRUD,就写了7个Servlet。如果一个复杂的业务系统,这种开发方式,显然会导致类爆炸。(类的数量太大。)
- 怎么解决这个类爆炸问题?可以使用模板方法设计模式。
怎么解决类爆炸问题?
- 以前的设计是一个请求一个Servlet类。1000个请求对应1000个Servlet类。导致类爆炸。
- 可以这样做:一个请求对应一个方法。一个业务对应一个Servlet类。
- 处理部门相关业务的对应一个DeptServlet。处理用户相关业务的对应一个UserServlet。处理银行卡卡片业务对应一个CardServlet。
更改之后的代码:
package com.zwm.javaweb.action;
import com.zwm.javaweb.utils.DBUtil;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* @author 猪无名
* @date 2023/7/22 00 20
* discription:
*/
//母版类
@WebServlet({"/dept/list","/dept/update","/dept/JDBCupdate","/dept/detail","/dept/delete","/dept/add","/dept/JDBCadd"})
public class DeptServlet extends HttpServlet {
//模板方法
//重写service方法
@Override
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String servletPath = request.getServletPath();
if("/dept/list".equals(servletPath)){
doList(request,response);
}else if("/dept/detail".equals(servletPath)){
doDetail(request,response);
}else if("/dept/delete".equals(servletPath)){
doDel(request,response);
}else if("/dept/update".equals(servletPath)){
doUpdate(request,response);
}else if("/dept/JDBCupdate".equals(servletPath)){
doJDBCupdate(request,response);
}else if("/dept/add".equals(servletPath)){
doAdd(request,response);
}else if("/dept/JDBCadd".equals(servletPath)){
doJDBCadd(request,response);
}
}
//部门列表
private void doList(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//获取应用的根路径
String contextPath = request.getContextPath();
//设置相应内容类型和字符集,防止中文乱码。
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
//在html页面中,固定不变的。
out.print("<!DOCTYPE html>");
out.print("<html>");
out.print(" <head>");
out.print(" <meta charset='utf-8'>");
out.print(" <title>部门列表页面</title>");
out.print(" <script type='text/javascript'>");
out.print(" function del(dno){");
out.print(" if(window.confirm('亲,删了不可恢复哦!')){");
out.print(" document.location.href = '"+contextPath+"/dept/delete?deptno=' + dno;");
out.print(" }");
out.print(" }");
out.print(" </script>");
out.print(" </head>");
out.print(" <body>");
out.print(" <h1 align='center'>部门列表</h1>");
out.print(" <hr>");
out.print(" <table border='1px' align='center' width='50%'>");
out.print(" <tr>");
out.print(" <th>序号</th>");
out.print(" <th>部门编号</th>");
out.print(" <th>部门名称</th>");
out.print(" <th>操作</th>");
out.print(" </tr>");
//连接数据库所需变量
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
//获取连接
conn = DBUtil.getConnection();
//获取预处理的数据库操作对象
String sql = "select deptno,dname,loc from dept";
ps=conn.prepareStatement(sql);
//执行SQL语句
rs = ps.executeQuery();
int number = 0;
//处理结果集
while(rs.next()){
String deptno = rs.getString("deptno");
String dname = rs.getString("dname");
String loc = rs.getString("loc");
out.print(" <tr>");
out.print(" <td>"+(++number)+"</td>");
out.print(" <td>"+deptno+"</td>");
out.print(" <td>"+dname+"</td>");
out.print(" <td>");
out.print(" <a href= 'javascript:void(0)' onclick='del("+deptno+")'>删除</a>");
out.print(" <a href='"+contextPath+"/dept/update?deptno="+deptno+"&dname="+dname+"&loc="+loc+"'>修改</a>");
out.print(" <a href='"+contextPath+"/dept/detail?deptno="+deptno+"'>详情</a>");
out.print(" </td>");
out.print(" </tr>");
}
} catch (SQLException e) {
throw new RuntimeException(e);
}finally {
//释放资源
DBUtil.close(conn,ps,rs);
}
//在html页面中,固定不变的。
out.print(" </table>");
out.print(" ");
out.print(" <hr>");
out.print(" <a href='"+contextPath+"/dept/add'>新增部门</a>");
// out.print(" <a href='"+contextPath+"/add. html'>新增部门</a>");
out.print(" </body>");
out.print("</html>");
}
//查看部门详情
private void doDetail(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException{
//设置相应内容类型和字符集,防止中文乱码。
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
out.print("<!DOCTYPE html>");
out.print("<html>");
out.print(" <head>");
out.print(" <meta charset='utf-8'>");
out.print(" <title>部门详情</title>");
out.print(" </head>");
out.print(" <body>");
out.print(" <h1>部门详情</h1>");
out.print(" <hr>");
//中文思路(思路来源于:你要做什么?目标:查看部门详细信息。)
// 第一步:获取部门编号
String deptno = request.getParameter("deptno");
// 第二步:根据部门编号查询数据库,获取该部门编号对应的部门信息。
//连接数据库所需变量
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
conn = DBUtil.getConnection();
//获取预处理的数据库操作对象
String sql = "select deptno,dname,loc from dept where deptno = ?";
ps=conn.prepareStatement(sql);
ps.setString(1,deptno);
//执行SQL语句
rs = ps.executeQuery();
if(rs.next()){
String dname = rs.getString("dname");
String loc = rs.getString("loc");
out.print(" 部门编号:"+deptno+" <br>");
out.print(" 部门名称:"+dname+" <br>");
out.print(" 部门位置:"+loc+" <br>");
}
} catch (SQLException e) {
throw new RuntimeException(e);
}finally {
//释放资源
DBUtil.close(conn,ps,rs);
}
out.print(" <input type='button' value='后退' onclick='window.history.back()'/>");
out.print(" ");
out.print(" </body>");
out.print("</html>");
}
//删除部门
private void doDel(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//设置相应内容类型和字符集,防止中文乱码。
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
String deptno = request.getParameter("deptno");
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
conn = DBUtil.getConnection();
String sql = "DELETE FROM dept WHERE deptno = ?";
ps=conn.prepareStatement(sql);
ps.setString(1,deptno);
//执行SQL语句
//返回值代表影响了数据库当中多少条数据。
int rowsAffected = ps.executeUpdate();
if (rowsAffected > 0) {
System.out.println("删除成功");
// 删除完成后重定向回原来的页面
response.sendRedirect(request.getContextPath()+"/dept/list");
} else {
System.out.println("删除失败");
response.sendRedirect(request.getContextPath()+"/dept/list");
}
} catch (SQLException e) {
throw new RuntimeException(e);
}finally {
//释放资源
DBUtil.close(conn,ps,rs);
}
}
//增加部门
private void doAdd(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException{
//获取应用的根路径
String contextPath = request.getContextPath();
//设置相应内容类型和字符集,防止中文乱码。
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
out.print(" <!DOCTYPE html>");
out.print(" <html>");
out.print(" <head>");
out.print(" <meta charset='utf-8'>");
out.print(" <title>新增部门</title>");
out.print(" </head>");
out.print(" <body>");
out.print(" <h1>新增部门</h1>");
out.print(" <hr>");
out.print(" <form action='"+contextPath+"/dept/JDBCadd' method='get'>");
out.print(" 部门编号:<input type='text' name='deptno'/><br>");
out.print(" 部门名称:<input type='text' name='deptname'/><br>");
out.print(" 部门位置:<input type='text' name='loc'/><br>");
out.print(" <input type='submit' value='提交'/><br>");
out.print(" </form>");
out.print(" </body>");
out.print(" </html>");
}
private void doJDBCadd(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException{
//设置相应内容类型和字符集,防止中文乱码。
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
String deptno = request.getParameter("deptno");
String deptname = request.getParameter("deptname");
String loc = request.getParameter("loc");
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
conn = DBUtil.getConnection();
String sql = "INSERT INTO dept (deptno, dname, loc) VALUES (?, ?, ?)";
ps=conn.prepareStatement(sql);
ps.setString(1,deptno);
ps.setString(2,deptname);
ps.setString(3,loc);
//执行SQL语句
//返回值代表影响了数据库当中多少条数据。
int rowsAffected = ps.executeUpdate();
if (rowsAffected > 0) {
System.out.println(rowsAffected + " 行已插入数据");
//request.getRequestDispatcher("/dept/list").forward(request, response);
response.sendRedirect(request.getContextPath()+"/dept/list");
} else {
System.out.println("未插入任何数据");
}
} catch (SQLException e) {
throw new RuntimeException(e);
}finally {
//释放资源
DBUtil.close(conn,ps,rs);
}
}
//修改部门
private void doUpdate(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException{
//获取应用的根路径
String contextPath = request.getContextPath();
//设置相应内容类型和字符集,防止中文乱码。
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
String deptno = request.getParameter("deptno");
String dname = request.getParameter("dname");
String loc = request.getParameter("loc");
out.print(" <!DOCTYPE html>");
out.print(" <html>");
out.print(" <head>");
out.print(" <meta charset='utf-8'>");
out.print(" <title>修改部门</title>");
out.print(" </head>");
out.print(" <body>");
out.print(" <h1>修改部门</h1>");
out.print(" <hr>");
out.print(" <form action='"+contextPath+"/dept/JDBCupdate' method='get'>");
out.print(" 部门编号:<input type='text' name='deptno' value='"+deptno+"' readonly/><br>");
out.print(" 部门名称:<input type='text' name='deptname' value='"+dname+"'/><br>");
out.print(" 部门位置:<input type='text' name='loc' value='"+loc+"'/><br>");
out.print(" <input type='submit' value='修改'/><br>");
out.print(" </form>");
out.print(" </body>");
out.print(" </html>");
}
private void doJDBCupdate(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException{
//设置相应内容类型和字符集,防止中文乱码。
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
String deptno = request.getParameter("deptno");
String deptname = request.getParameter("deptname");
String loc = request.getParameter("loc");
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
conn = DBUtil.getConnection();
String sql = "UPDATE dept SET dname = ?, loc = ? WHERE deptno = ?";
ps=conn.prepareStatement(sql);
ps.setString(1,deptname);
ps.setString(2,loc);
ps.setString(3,deptno);
//执行SQL语句
//返回值代表影响了数据库当中多少条数据。
int rowsAffected = ps.executeUpdate();
if (rowsAffected > 0) {
System.out.println("更改成功");
response.sendRedirect(request.getContextPath()+"/dept/list");
} else {
System.out.println("无匹配数据");
response.sendRedirect(request.getContextPath()+"/dept/list");
}
} catch (SQLException e) {
throw new RuntimeException(e);
}finally {
//释放资源
DBUtil.close(conn,ps,rs);
}
}
}
分析使用纯粹Servlet开发web应用的缺陷
-
在Servlet当中编写HTML/CSS/JavaScript等前端代码。存在什么问题?
-
java程序中编写前端代码,编写难度大,无法直观地看到前端代码的错误,麻烦。
-
java程序中编写前端代码,显然程序的耦合度非常高。
-
java程序中编写前端代码,代码非常不美观。
-
java程序中编写前端代码,维护成本太高。(非常难于维护)
修改小小的一个前端代码,只要有改动,就需要重新编译java代码,生成新的class文件,打一个新的war包,重新发布。
-
-
思考一下,如何解决这个问题?
- 上面的那个Servlet(Java程序)能不能不写了,让机器自动生成。我们程序员只需要写这个Servlet程序中的“前端的那段代码”,然后让机器将我们写的“前端代码”自动翻译生成“Servlet这种java程序”。然后机器再自动将“java”程序编译生成"class"文件。然后再使用JVM调用这个class中的方法。
- 对应技术JSP。