🥽 在一个web应用中应该如何完成资源的跳转
- 在一个web应用中通过两种方式,可以完成资源的跳转:
- 第一种方式:转发
- 第二种方式:重定向
- 转发和重定向有什么区别?
- 代码上有什么区别?
- 转发
// 获取请求转发器对象 // 告诉Tomcat下一次请求要发给哪个Servlet对象 RequestDispatcher dispatcher = request.getRequestDispatcher("/dept/list"); // 调用请求转发器对象的forward方法完成转发 // 传入request, response 保证为同一次请求,保证请求域相同 dispatcher.forward(request, response); // 合并一行代码 request.getRequestDispatcher("/dept/list").forward(request, response); // 转发的时候是一次请求,不管你转发了多少次。都是一次请求。 // AServlet转发到BServlet,再转发到CServlet,再转发到DServlet,不管转发了多少次,都在同一个request当中。 // 这是因为调用forward方法的时候,会将当前的request和response对象传递给下一个Servlet。
- 重定向
// 注意:路径上要加一个项目名。为什么? // 浏览器发送请求,请求路径上是需要添加项目名的。 // 以下这一行代码会将请求路径“/oa/dept/list”发送给浏览器 // 浏览器会自发的向服务器发送一次全新的请求:/oa/dept/list response.sendRedirect("/oa/dept/list");
- 形式上有什么区别?
- 转发(一次请求)
- 在浏览器地址栏上发送的请求是:http://localhost:8080/servlet10/a ,最终请求结束之后,浏览器地址栏上的地址还是这个。没变。
- 重定向(两次请求)
- 在浏览器地址栏上发送的请求是:http://localhost:8080/servlet10/a ,最终在浏览器地址栏上显示的地址是:http://localhost:8080/servlet10/b
- 转发和重定向的本质区别?
- 转发:是由WEB服务器来控制的。A资源跳转到B资源,这个跳转动作是Tomcat服务器内部完成的。
- 重定向:是浏览器完成的。具体跳转到哪个资源,是浏览器说了算。
- 使用一个例子去描述这个转发和重定向
- 借钱(转发:发送了一次请求)
- 杜老师没钱了,找张三借钱,其实张三没有钱,但是张三够义气,张三自己找李四借了钱,然后张三把这个钱给了杜老师,杜老师不知道这个钱是李四的,杜老师只求了一个人。杜老师以为这个钱就是张三的。
- 借钱(重定向:发送了两次请求)
- 杜老师没钱了,找张三借钱,张三没有钱,张三有一个好哥们,叫李四,李四是个富二代,于是张三将李四的家庭住址告诉了杜老师,杜老师按照这个地址去找到李四,然后从李四那里借了钱。显然杜老师在这个过程中,求了两个人。并且杜老师知道最终这个钱是李四借给俺的。
- 转发和重定向应该如何选择?什么时候使用转发,什么时候使用重定向?
- 如果在上一个Servlet当中向request域当中绑定了数据,希望从下一个Servlet当中把request域里面的数据取出来,使用转发机制。
- 剩下所有的请求均使用重定向。(重定向使用较多。)
- 跳转的下一个资源有没有要求呢?必须是一个Servlet吗?
- 不一定,跳转的资源只要是服务器内部合法的资源即可。包括:Servlet、JSP、HTML…
- 转发会存在浏览器的刷新问题。由于请求转发的前端路径不会发送变化,所以前端进行刷新会重新执行一次与之前相同的请求,如果之前执行的为向数据库插入信息的请求,则使用转发每次进行刷新都会重新执行一次插入操作。使用重定向则不会,重定向是发送一次与之前请求无关的一次新请求。
🥽 将oa项目中的资源跳转修改为合适的跳转方式
删除之后,重定向
// 判断成功或失败,进行页面的跳转 if (count >= 1) { //删除成功 //仍然跳转到部门列表页面 //部门列表页面的显示需要执行另一个Servlet。怎么办?转发。 // request.getRequestDispatcher("/dept/list").forward(request, response); response.sendRedirect(request.getContextPath() + "/dept/list"); } else { // 删除失败 // request.getRequestDispatcher("/error.html").forward(request, response); response.sendRedirect(request.getContextPath() + "/error.html"); }
修改之后,重定向
// 成功或失败 if (count >= 1) { // request.getRequestDispatcher("/dept/list").forward(request, response); response.sendRedirect(request.getContextPath() + "/dept/list"); } else { // request.getRequestDispatcher("/error.html").forward(request, response); response.sendRedirect(request.getContextPath() + "/error.html"); }
保存之后,重定向
// 成功或失败 if (count >= 1) { // request.getRequestDispatcher("/dept/list").forward(request, response); response.sendRedirect(request.getContextPath() + "/dept/list"); } else { // request.getRequestDispatcher("/error.html").forward(request, response); response.sendRedirect(request.getContextPath() + "/error.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属性名是可以省略的。
//@WebServlet(urlPatterns = {"/welcome1", "/welcome2"}) // 注意:当注解的属性是一个数组,并且数组中只有一个元素,大括号可以省略。 //@WebServlet(urlPatterns = "/welcome") // 这个value属性和urlPatterns属性一致,都是用来指定Servlet的映射路径的。 //@WebServlet(value = {"/welcome1", "/welcome2"}) // 如果注解的属性名是value的话,属性名也是可以省略的。 //@WebServlet(value = "/welcome1") //@WebServlet({"/wel", "/abc", "/def"}) @WebServlet("/wel") public class WelcomeServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html;charset=UTF-8"); PrintWriter out = response.getWriter(); out.print("欢迎学习Servlet。"); } }
- 注意:不是必须将所有属性都写上,只需要提供需要的。(需要什么用什么。)
- 注意:属性是一个数组,如果数组中只有一个元素,使用该注解的时候,属性值的大括号可以省略。
package com.bjpowernode.javaweb.servlet; import jakarta.servlet.ServletException; import jakarta.servlet.annotation.WebInitParam; 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.util.Enumeration; @WebServlet(name = "hello", urlPatterns = {"/hello1", "/hello2", "/hello3"}, loadOnStartup = 1, // 服务器部署启动时加载该类 // 初始化参数 initParams = {@WebInitParam(name="username", value="root"), @WebInitParam(name="password", value="123")}) public class HelloServlet extends HttpServlet { // 无参数构造方法 public HelloServlet() { System.out.println("无参数构造方法执行,HelloServlet加载完成"); } /*@WebServlet private String name;*/ @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html;charset=UTF-8"); PrintWriter out = response.getWriter(); // 获取Servlet Name String servletName = getServletName(); out.print("servlet name = " + servletName + "<br>"); // 获取servlet path String servletPath = request.getServletPath(); out.print("servlet path = " + servletPath + "<br>"); // 获取初始化参数 Enumeration<String> names = getInitParameterNames(); while (names.hasMoreElements()) { String name = names.nextElement(); String value = getInitParameter(name); out.print(name + "=" + value + "<br>"); } } }
- 注解对象的使用格式:
- @注解名称(属性名=属性值, 属性名=属性值, 属性名=属性值…)
🥽 通过反射获取注解
public class ReflectAnnotation { public static void main(String[] args) throws Exception{ // 使用反射机制将类上面的注解进行解析。 // 获取类Class对象 Class<?> welcomeServletClass = Class.forName("com.bjpowernode.javaweb.servlet.WelcomeServlet"); // 获取这个类上面的注解对象 // 先判断这个类上面有没有这个注解对象,如果有这个注解对象,就获取该注解对象。 //boolean annotationPresent = welcomeServletClass.isAnnotationPresent(WebServlet.class); //System.out.println(annotationPresent); if (welcomeServletClass.isAnnotationPresent(WebServlet.class)) { // 获取这个类上面的注解对象 WebServlet webServletAnnotation = welcomeServletClass.getAnnotation(WebServlet.class); // 获取注解的value属性值。 String[] value = webServletAnnotation.value(); for (int i = 0; i < value.length; i++) { System.out.println(value[i]); } } } }
🥽 使用模板方法设计模式优化oa项目
- 上面的注解解决了配置文件的问题。但是现在的oa项目仍然存在一个比较臃肿的问题。
- 一个单标的CRUD,就写了6个Servlet。如果一个复杂的业务系统,这种开发方式,显然会导致类爆炸。(类的数量太大。)
- 怎么解决这个类爆炸问题?可以使用模板方法设计模式。
- 怎么解决类爆炸问题?
- 以前的设计是一个请求一个Servlet类。1000个请求对应1000个Servlet类。导致类爆炸。
- 可以这样做:一个请求对应一个方法。一个业务对应一个Servlet类。
- 处理部门相关业务的对应一个DeptServlet。处理用户相关业务的对应一个UserServlet。处理银行卡卡片业务对应一个CardServlet。
package com.bjpowernode.oa.web.action; import com.bjpowernode.oa.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; // 模板类 @WebServlet({"/dept/list", "/dept/save", "/dept/edit", "/dept/detail", "/dept/delete", "/dept/modify"}) // 模糊匹配 // 只要请求路径是以"/dept"开始的,都走这个Servlet。 //@WebServlet("/dept/*") public class DeptServlet extends HttpServlet { // 模板方法 // 重写service方法(并没有重写doGet或者doPost) @Override protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 获取servlet path String servletPath = request.getServletPath(); if("/dept/list".equals(servletPath)){ doList(request, response); } else if("/dept/save".equals(servletPath)){ doSave(request, response); } else if("/dept/edit".equals(servletPath)){ doEdit(request, response); } else if("/dept/detail".equals(servletPath)){ doDetail(request, response); } else if("/dept/delete".equals(servletPath)){ doDel(request, response); } else if("/dept/modify".equals(servletPath)){ doModify(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(); 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 as a,dname,loc from dept"; ps = conn.prepareStatement(sql); // 执行SQL语句 rs = ps.executeQuery(); // 处理结果集 int i = 0; while(rs.next()){ String deptno = rs.getString("a"); String dname = rs.getString("dname"); String loc = rs.getString("loc"); out.print(" <tr>"); out.print(" <td>"+(++i)+"</td>"); out.print(" <td>"+deptno+"</td>"); out.print(" <td>"+dname+"</td>"); out.print(" <td>"); out.print(" <a href='javascript:void(0)' οnclick='del("+deptno+")'>删除</a>"); out.print(" <a href='"+contextPath+"/dept/edit?deptno="+deptno+"'>修改</a>"); out.print(" <a href='"+contextPath+"/dept/detail?fdsafdsas="+deptno+"'>详情</a>"); out.print(" </td>"); out.print(" </tr>"); } } catch (SQLException e) { e.printStackTrace(); } finally { // 释放资源 DBUtil.close(conn, ps, rs); } /*下面一部分是死的*/ out.print(" </table>"); out.print(" <hr >"); out.print(" <a href='"+contextPath+"/add.html'>新增部门</a>"); out.print(" </body>"); out.print("</html>"); } private void doSave(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 获取部门的信息 // 注意乱码问题(Tomcat10不会出现这个问题) request.setCharacterEncoding("UTF-8"); String deptno = request.getParameter("deptno"); String dname = request.getParameter("dname"); String loc = request.getParameter("loc"); // 连接数据库执行insert语句 Connection conn = null; PreparedStatement ps = null; int count = 0; try { conn = DBUtil.getConnection(); String sql = "insert into dept(deptno, dname, loc) values(?,?,?)"; ps = conn.prepareStatement(sql); ps.setString(1, deptno); ps.setString(2, dname); ps.setString(3, loc); count = ps.executeUpdate(); } catch (SQLException e) { e.printStackTrace(); } finally { DBUtil.close(conn, ps, null); } if (count == 1) { // 保存成功跳转到列表页面 // 转发是一次请求。 //request.getRequestDispatcher("/dept/list").forward(request, response); // 这里最好使用重定向(浏览器会发一次全新的请求。) // 浏览器在地址栏上发送请求,这个请求是get请求。 response.sendRedirect(request.getContextPath() + "/dept/list"); }else{ // 保存失败跳转到错误页面 //request.getRequestDispatcher("/error.html").forward(request, response); // 这里也建议使用重定向。 response.sendRedirect(request.getContextPath() + "/error.html"); } } private void doEdit(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/modify' method='post'>"); // 获取部门编号 String deptno = request.getParameter("deptno"); // 连接数据库,根据部门编号查询部门的信息。 Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; try { conn = DBUtil.getConnection(); String sql = "select dname, loc as location from dept where deptno = ?"; ps = conn.prepareStatement(sql); ps.setString(1, deptno); rs = ps.executeQuery(); // 这个结果集中只有一条记录。 if(rs.next()){ String dname = rs.getString("dname"); String location = rs.getString("location"); // 参数"location"是sql语句查询结果列的列名。 // 输出动态网页。 out.print(" 部门编号<input type='text' name='deptno' value='"+deptno+"' readonly /><br>"); out.print(" 部门名称<input type='text' name='dname' value='"+dname+"'/><br>"); out.print(" 部门位置<input type='text' name='loc' value='"+location+"'/><br>"); } } catch (SQLException e) { e.printStackTrace(); } finally { DBUtil.close(conn, ps, rs); } out.print(" <input type='submit' value='修改'/><br>"); out.print(" </form>"); 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 >"); // 获取部门编号 // /oa/dept/detail?fdsafdsas=30 // 虽然是提交的30,但是服务器获取的是"30"这个字符串。 String deptno = request.getParameter("fdsafdsas"); // 连接数据库,根据部门编号查询部门信息。 Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; try { conn = DBUtil.getConnection(); String sql = "select dname,loc from dept where deptno = ?"; ps = conn.prepareStatement(sql); ps.setString(1, deptno); 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) { e.printStackTrace(); } finally { DBUtil.close(conn, ps, rs); } out.print(" <input type='button' value='后退' οnclick='window.history.back()'/>"); out.print(" </body>"); out.print("</html>"); } private void doDel(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 根据部门编号,删除部门。 // 获取部门编号 String deptno = request.getParameter("deptno"); // 连接数据库删除数据 Connection conn = null; PreparedStatement ps = null; int count = 0; try { conn = DBUtil.getConnection(); // 开启事务(自动提交机制关闭) conn.setAutoCommit(false); String sql = "delete from dept where deptno = ?"; ps = conn.prepareStatement(sql); ps.setString(1, deptno); // 返回值是:影响了数据库表当中多少条记录。 count = ps.executeUpdate(); // 事务提交 conn.commit(); } catch (SQLException e) { // 遇到异常要回滚 if (conn != null) { try { conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } } e.printStackTrace(); } finally { DBUtil.close(conn, ps, null); } // 判断删除成功了还是失败了。 if (count == 1) { //删除成功 //仍然跳转到部门列表页面 //部门列表页面的显示需要执行另一个Servlet。怎么办?转发。 //request.getRequestDispatcher("/dept/list").forward(request, response); response.sendRedirect(request.getContextPath() + "/dept/list"); }else{ // 删除失败 //request.getRequestDispatcher("/error.html").forward(request, response); response.sendRedirect(request.getContextPath() + "/error.html"); } } private void doModify(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 解决请求体的中文乱码问题。 request.setCharacterEncoding("UTF-8"); // 获取表单中的数据 String deptno = request.getParameter("deptno"); String dname = request.getParameter("dname"); String loc = request.getParameter("loc"); // 连接数据库执行更新语句 Connection conn = null; PreparedStatement ps = null; int count = 0; try { conn = DBUtil.getConnection(); String sql = "update dept set dname = ?, loc = ? where deptno = ?"; ps = conn.prepareStatement(sql); ps.setString(1, dname); ps.setString(2, loc); ps.setString(3, deptno); count = ps.executeUpdate(); } catch (SQLException e) { e.printStackTrace(); } finally { DBUtil.close(conn, ps, null); } if (count == 1) { // 更新成功 // 跳转到部门列表页面(部门列表页面是通过Java程序动态生成的,所以还需要再次执行另一个Servlet) //request.getRequestDispatcher("/dept/list").forward(request, response); response.sendRedirect(request.getContextPath() + "/dept/list"); }else{ // 更新失败 //request.getRequestDispatcher("/error.html").forward(request, response); response.sendRedirect(request.getContextPath() + "/error.html"); } } }
🥽 分析使用纯粹Servlet开发web应用的缺陷
- 在Servlet当中编写HTML/CSS/JavaScript等前端代码。存在什么问题?
- java程序中编写前端代码,编写难度大。麻烦。
- java程序中编写前端代码,显然程序的耦合度非常高。
- java程序中编写前端代码,代码非常不美观。
- java程序中编写前端代码,维护成本太高。(非常难于维护)
- 修改小小的一个前端代码,只要有改动,就需要重新编译java代码,生成新的class文件,打一个新的war包,重新发布。
- 思考一下,如果是你的话,你准备怎么解决这个问题?
- 思路很重要。使用什么样的思路去做、去解决这个问题
- 上面的那个Servlet(Java程序)能不能不写了,让机器自动生成。我们程序员只需要写这个Servlet程序中的“前端的那段代码”,然后让机器将我们写的“前端代码”自动翻译生成“Servlet这种java程序”。然后机器再自动将“java”程序编译生成"class"文件。然后再使用JVM调用这个class中的方法。
🥽 JSP
🌊 我的第一个JSP程序
- 在WEB-INF目录之外创建一个index.jsp文件,然后这个文件中没有任何内容。
- 将上面的项目部署之后,启动服务器,打开浏览器,访问以下地址:
- http://localhost:8080/jsp/index.jsp 展现在大家面前的是一个空白。
- 实际上访问以上的这个:index.jsp,底层执行的是:index_jsp.class 这个java程序。
- 这个index.jsp会被tomcat翻译生成index_jsp.java文件,然后tomcat服务器又会将index_jsp.java编译生成index_jsp.class文件
- 访问index.jsp,实际上执行的是index_jsp.class中的方法。
🌊 JSP概述
- JSP实际上就是一个Servlet。
- index.jsp访问的时候,会自动翻译生成index_jsp.java,会自动编译生成index_jsp.class,那么index_jsp 这就是一个类。
- index_jsp 类继承 HttpJspBase,而HttpJspBase类继承的是HttpServlet。所以index_jsp类就是一个Servlet类。
- jsp的生命周期和Servlet的生命周期完全相同。完全就是一个东西。没有任何区别。
- jsp和servlet一样,都是单例的。(假单例。)
- jsp文件第一次访问的时候是比较慢的,为什么?
- 为什么大部分的运维人员在给客户演示项目的时候,为什么提前先把所有的jsp文件先访问一遍。
- 第一次比较麻烦:
- 要把jsp文件翻译生成java源文件
- java源文件要编译生成class字节码文件
- 然后通过class去创建servlet对象
- 然后调用servlet对象的init方法
- 最后调用servlet对象的service方法。
- 第二次就比较快了,为什么?
- 因为第二次直接调用单例servlet对象的service方法即可。
- JSP是什么?
- JSP是java程序。(JSP本质还是一个Servlet)
- JSP是:JavaServer Pages的缩写。(基于Java语言实现的服务器端的页面。)
- Servlet是JavaEE的13个子规范之一,那么JSP也是JavaEE的13个子规范之一。
- JSP是一套规范。所有的web容器/web服务器都是遵循这套规范的,都是按照这套规范进行的“翻译”
- 每一个web容器/web服务器都会内置一个JSP翻译引擎。
- 对JSP进行错误调试的时候,还是要直接打开JSP文件对应的java文件,检查java代码。
- 开发JSP的最高境界:
- 眼前是JSP代码,但是脑袋中呈现的是java代码。
- JSP既然本质上是一个Servlet,那么JSP和Servlet到底有什么区别呢?
- 职责不同:
- Servlet的职责是什么:收集数据。(Servlet的强项是逻辑处理,业务处理,然后链接数据库,获取/收集数据。)
- JSP的职责是什么:展示数据。(JSP的强项是做数据的展示)
🌊 JSP的基础语法
💦 JSP中直接编写文字
- 在jsp文件中直接编写文字,都会自动被翻译到哪里?
- 翻译到servlet类的service方法的out.write(“翻译到这里”),直接翻译到双引号里,被java程序当做普通字符串打印输出到浏览器。
- 在JSP中编写的HTML CSS JS代码,这些代码对于JSP来说只是一个普通的字符串。但是JSP把这个普通的字符串一旦输出到浏览器,浏览器就会对HTML CSS JS进行解释执行。展现一个效果。
💦 JSP的page指令
- JSP的page指令(这个指令后面再详细说,这里先解决一下中文乱码问题,写在文件内容开始位置),解决响应时的中文乱码问题:
- 通过page指令来设置响应的内容类型,在内容类型的最后面添加:charset=UTF-8
- <%@page contentType=“text/html;charset=UTF-8”%>,表示响应的内容类型是text/html,采用的字符集UTF-8
- <%@page import=“java.util.List,java.util.ArrayList”%>
💦 JSP中编写java程序
💧 <% java语句; %>
<% System.out.println("hello JSP"); %>
- 脚本块
- 在这个符号当中编写的被视为java程序,被翻译到Servlet类的service方法内部。
- 这里你要细心点,你要思考,在<% %>这个符号里面写java代码的时候,你要时时刻刻的记住你正在“方法体”当中写代码,方法体中可以写什么,不可以写什么,你心里是否明白呢?
- 在service方法当中编写的代码是有顺序的,方法体当中的代码要遵循自上而下的顺序依次逐行执行。
- service方法当中不能写静态代码块,不能写方法,不能定义成员变量。。。。。。
- 在同一个JSP当中 <%%> 这个符号可以出现多个。
💧 <%! %>
- 可以进行成员变量、静态代码块的声明
- 在这个符号当中编写的java程序会自动翻译到service方法之外。
- 这个语法很少用,为什么?不建议使用,因为在service方法外面写静态变量和实例变量,都会存在线程安全问题,因为JSP就是servlet,servlet是单例的,多线程并发的环境下,这个静态变量和实例变量一旦有修改操作,必然会存在线程安全问题。
💧 JSP的输出语句 <%= %>
- 怎么向浏览器上输出一个java变量。
- <% String name = “jack”; out.write("name = " + name); %>
- 注意:以上代码中的out是JSP的九大内置对象之一。可以直接拿来用。当然,必须只能在service方法内部使用。
- 九大内置对象只能在service方法中使用
- 如果向浏览器上输出的内容中没有“java代码”,例如输出的字符串是一个固定的字符串,可以直接在jsp中编写,不需要写到<%%> 这里。
- 如果输出的内容中含有“java代码”,这个时候可以使用以下语法格式:
- <%= %> 注意:在=的后面编写要输出的内容。
- <%= %> 这个符号会被翻译到哪里?最终翻译成什么?
- 翻译成了这个java代码: out.print();
- 翻译到service方法当中了。
<%="hello world"%>
- 什么时候使用<%=%> 输出呢?输出的内容中含有java的变量,输出的内容是一个动态的内容,不是一个死的字符串。如果输出的是一个固定的字符串,直接在JSP文件中编写即可。
💦 JSP中的注释
- 在JSP中如何编写JSP的专业注释
<%--JSP的专业注释,不会被翻译到java源代码当中。--%>
<!--这种注释属于HTML的注释,这个注释信息仍然会被翻译到java源代码当中,不建议。-->
💦 JSP基础语法总结
- JSP基础语法总结:
- JSP中直接编写普通字符串
- 翻译到service方法的out.write(“这里”)
- <%%>
- 翻译到service方法体内部,里面是一条一条的java语句。
- <%! %>
- 翻译到service方法之外。
- <%= %>
- 翻译到service方法体内部,翻译为:out.print();
- <%@page contentType=“text/html;charset=UTF-8”%>
- page指令,通过contentType属性用来设置响应的内容类型。
🥽 使用Servlet + JSP完成oa项目的改造
- 使用Servlet处理业务,收集数据。 使用JSP展示数据。
- 将之前原型中的html文件,全部修改为jsp,然后在jsp文件头部添加page指令(指定contentType防止中文乱码),将所有的JSP直接拷贝到web目录下。
- 完成所有页面的正常流转。(页面仍然能够正常的跳转。修改超链接的请求路径。)
- <%=request.getContextPath() %> 在JSP中动态的获取应用的根路径。
- Servlet中连接数据库,查询所有的部门,遍历结果集。
- 遍历结果集的过程中,取出部门编号、部门名、位置等信息,封装成java对象。
- 将java对象存放到List集合中。
- 将List集合存储到request域当中。
- 转发forward到jsp。
- 在JSP中:
- 从request域当中取出List集合。
- 遍历List集合,取出每个部门对象。动态生成tr。
- 思考一个问题:如果我只用JSP这一个技术,能不能开发web应用?
- 当然可以使用JSP来完成所有的功能。因为JSP就是Servlet,在JSP的<%%>里面写的代码就是在service方法当中的,所以在<%%>当中完全可以编写JDBC代码,连接数据库,查询数据,也可以在这个方法当中编写业务逻辑代码,处理业务,都是可以的,所以使用单独的JSP开发web应用完全没问题。
- 虽然JSP一个技术就可以完成web应用,但是不建议,还是建议采用servlet + jsp的方式进行开发。这样都能将各自的优点发挥出来。JSP就是做数据展示。Servlet就是做数据的收集。(JSP中编写的Java代码越少越好。)一定要职责分明。
- JSP文件的扩展名必须是xxx.jsp吗?
- jsp文件的扩展名是可以配置的。不是固定的。
- 在CATALINA_HOME/conf/web.xml,在这个文件当中配置jsp文件的扩展名。
<servlet-mapping> <servlet-name>jsp</servlet-name> <url-pattern>*.jsp</url-pattern> <url-pattern>*.jspx</url-pattern> </servlet-mapping>
- xxx.jsp文件对于小猫咪来说,只是一个普通的文本文件,web容器会将xxx.jsp文件最终生成java程序,最终调用的是java对象相关的方法,真正执行的时候,和jsp文件就没有关系了。
- 小窍门:JSP如果看不懂,建议把jsp翻译成java代码,就能看懂了。
- 同学问:包名bean是什么意思?
- javabean(java的logo是一杯冒着热气的咖啡。javabean被翻译为:咖啡豆)
- java是一杯咖啡,咖啡又是由一粒一粒的咖啡豆研磨而成。
- 整个java程序中有很多bean的存在。由很多bean组成。
- 什么是javabean?实际上javabean你可以理解为符合某种规范的java类,比如:
- 有无参数构造方法
- 属性私有化
- 对外提供公开的set和get方法
- 实现java.io.Serializable接口
- 重写toString
- 重写hashCode+equals
- …
- javabean其实就是java中的实体类。负责数据的封装。
- 由于javabean符合javabean规范,具有更强的通用性。
- 完成剩下所有功能的改造。
index.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>欢迎使用OA系统</title> </head> <body> <a href="<%= request.getContextPath() %>/dept/list">查看部门列表</a> </body> </html>
list.jsp
<%@ page import="java.util.ArrayList" %> <%@ page import="cw.javaweb.bean.Dept" %> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <% Object deptList = request.getAttribute("deptList"); ArrayList<Dept> depts = (ArrayList<Dept>) deptList; %> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>部门列表页面</title> </head> <body> <h1 align="center">部门列表</h1> <hr> <table align="center" border="1px solid black" width="50%"> <thead> <tr> <th>序号</th> <th>部门编号</th> <th>部门名称</th> <th>操作</th> </tr> </thead> <tbody> <% for (int i=0; i<depts.size(); i++) { Dept dept = depts.get(i); %> <tr> <td><%= (i+1) %></td> <td><%= dept.getDeptno()%></td> <td><%= dept.getDname()%></td> <td> <a href="javascript:void(0)" onclick="del(<%= dept.getDeptno() %>)" >删除</a> <%-- 由于修改和详情页面只是页面显示不同,数据的查询逻辑类似,所以数据的查询使用相同的逻辑 --%> <a href="<%= request.getContextPath() %>/dept/detail?f=m&deptno=<%= dept.getDeptno() %>">修改</a> <a href="<%= request.getContextPath() %>/dept/detail?f=d&deptno=<%= dept.getDeptno() %>">详情</a> </td> </tr> <% } %> </tbody> </table> <hr> <a href="<%= request.getContextPath() %>/add.jsp">新增部门</a> </body> <script type="text/javascript"> function del(dno){ if(window.confirm("亲,删了不可恢复哦!")){ document.location.href = "/oa/dept/delete?deptno=" + dno; } } </script> </html>
add.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>新增部门</title> </head> <body> <h1>新增部门</h1> <hr> <form action="<%= request.getContextPath() %>/dept/add" method="post"> 部门编号:<input type="text" name="deptno"><br> 部门名称:<input type="text" name="dname"><br> 部门位置:<input type="text" name="loc"><br> <input type="submit" value="新增"> </form> </body> </html>
detail.jsp
<%@ page import="cw.javaweb.bean.Dept" %> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <% Dept dept = (Dept) request.getAttribute("dept"); %> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>部门详情</title> </head> <body> <h1>部门详情</h1> <hr> <p>部门编号:<%= dept.getDeptno() %></p> <p>部门名称:<%= dept.getDname() %></p> <p>部门位置:<%= dept.getLoc() %></p> <input type="button" value="后退" onclick="window.history.back()"> </body> </html>
edit.jsp
<%@ page import="cw.javaweb.bean.Dept" %> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <% Dept dept = (Dept) request.getAttribute("dept"); %> <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>修改部门</title> </head> <body> <h1>修改部门</h1> <hr > <form action="<%= request.getContextPath() %>/dept/modify" method="get"> <!-- 部门编号不允许修改 --> 部门编号<input type="text" name="deptno" value="<%= dept.getDeptno() %>" readonly /><br> 部门名称<input type="text" name="dname" value="<%= dept.getDname() %>"/><br> 部门位置<input type="text" name="loc" value="<%= dept.getLoc() %>"/><br> <input type="submit" value="修改"/><br> </form> </body> </html>
Dept.java
package cw.javaweb.bean; public class Dept { private String deptno; private String dname; private String loc; public Dept() { } public Dept(String deptno, String dname, String loc) { this.deptno = deptno; this.dname = dname; this.loc = loc; } public String getDeptno() { return deptno; } public void setDeptno(String deptno) { this.deptno = deptno; } public String getDname() { return dname; } public void setDname(String dname) { this.dname = dname; } public String getLoc() { return loc; } public void setLoc(String loc) { this.loc = loc; } @Override public String toString() { return "Dept{" + "deptno='" + deptno + '\'' + ", dname='" + dname + '\'' + ", loc='" + loc + '\'' + '}'; } }
DeptServlet.java
package cw.javaweb.oa.action; import cw.javaweb.bean.Dept; import cw.javaweb.utils.DBUtil; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.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; import java.util.ArrayList; @WebServlet({"/dept/list", "/dept/detail", "/dept/modify", "/dept/delete", "/dept/add"}) public class DeptServlet extends HttpServlet { @Override protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 获取servlet路径 String servletPath = request.getServletPath(); if ("/dept/list".equals(servletPath)) { doList(request, response); } else if ("/dept/detail".equals(servletPath)) { doDetail(request, response); } else if ("/dept/modify".equals(servletPath)) { doModify(request, response); } else if ("/dept/delete".equals(servletPath)) { doDel(request, response); } else if ("/dept/add".equals(servletPath)) { doAdd(request, response); } } private void doAdd(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 获取部门的信息 // 注意乱码问题(Tomcat10不会出现这个问题) request.setCharacterEncoding("UTF-8"); String deptno = request.getParameter("deptno"); String dname = request.getParameter("dname"); String loc = request.getParameter("loc"); // 连接数据库执行insert语句 Connection conn = null; PreparedStatement ps = null; int count = 0; try { conn = DBUtil.getConnection(); String sql = "insert into dept(deptno, dname, loc) values(?,?,?)"; ps = conn.prepareStatement(sql); ps.setString(1, deptno); ps.setString(2, dname); ps.setString(3, loc); count = ps.executeUpdate(); } catch (SQLException e) { e.printStackTrace(); } finally { DBUtil.close(conn, ps, null); } if (count == 1) { // 保存成功跳转到列表页面 // 转发是一次请求。 //request.getRequestDispatcher("/dept/list").forward(request, response); // 这里最好使用重定向(浏览器会发一次全新的请求。) // 浏览器在地址栏上发送请求,这个请求是get请求。 response.sendRedirect(request.getContextPath() + "/dept/list"); }else{ // 保存失败跳转到错误页面 //request.getRequestDispatcher("/error.html").forward(request, response); // 这里也建议使用重定向。 response.sendRedirect(request.getContextPath() + "/error.html"); } } private void doDel(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 根据部门编号,删除部门。 // 获取部门编号 String deptno = request.getParameter("deptno"); // 连接数据库删除数据 Connection conn = null; PreparedStatement ps = null; int count = 0; try { conn = DBUtil.getConnection(); // 开启事务(自动提交机制关闭) conn.setAutoCommit(false); String sql = "delete from dept where deptno = ?"; ps = conn.prepareStatement(sql); ps.setString(1, deptno); // 返回值是:影响了数据库表当中多少条记录。 count = ps.executeUpdate(); // 事务提交 conn.commit(); } catch (SQLException e) { // 遇到异常要回滚 if (conn != null) { try { conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } } e.printStackTrace(); } finally { DBUtil.close(conn, ps, null); } // 判断删除成功了还是失败了。 if (count == 1) { //删除成功 //仍然跳转到部门列表页面 //部门列表页面的显示需要执行另一个Servlet。怎么办?转发。 //request.getRequestDispatcher("/dept/list").forward(request, response); response.sendRedirect(request.getContextPath() + "/dept/list"); }else{ // 删除失败 //request.getRequestDispatcher("/error.html").forward(request, response); response.sendRedirect(request.getContextPath() + "/error.html"); } } private void doModify(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 解决请求体的中文乱码问题。 request.setCharacterEncoding("UTF-8"); // 获取表单中的数据 String deptno = request.getParameter("deptno"); String dname = request.getParameter("dname"); String loc = request.getParameter("loc"); // 连接数据库执行更新语句 Connection conn = null; PreparedStatement ps = null; int count = 0; try { conn = DBUtil.getConnection(); String sql = "update dept set dname = ?, loc = ? where deptno = ?"; ps = conn.prepareStatement(sql); ps.setString(1, dname); ps.setString(2, loc); ps.setString(3, deptno); count = ps.executeUpdate(); } catch (SQLException e) { e.printStackTrace(); } finally { DBUtil.close(conn, ps, null); } if (count == 1) { // 更新成功 // 跳转到部门列表页面(部门列表页面是通过Java程序动态生成的,所以还需要再次执行另一个Servlet) //request.getRequestDispatcher("/dept/list").forward(request, response); response.sendRedirect(request.getContextPath() + "/dept/list"); }else{ // 更新失败 //request.getRequestDispatcher("/error.html").forward(request, response); response.sendRedirect(request.getContextPath() + "/error.html"); } } private void doDetail(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{ // 获取部门编号 String deptno = request.getParameter("deptno"); // 连接数据库,根据部门编号查询部门信息。 Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; try { conn = DBUtil.getConnection(); String sql = "select dname,loc from dept where deptno = ?"; ps = conn.prepareStatement(sql); ps.setString(1, deptno); rs = ps.executeQuery(); // 这个结果集一定只有一条记录。 if(rs.next()){ String dname = rs.getString("dname"); String loc = rs.getString("loc"); Dept dept = new Dept(deptno, dname, loc); request.setAttribute("dept", dept); } } catch (SQLException e) { e.printStackTrace(); } finally { DBUtil.close(conn, ps, rs); } // 获取是修改还是查看详情 String f = request.getParameter("f"); if ("m".equals(f)) { request.getRequestDispatcher("/edit.jsp").forward(request, response); } else { request.getRequestDispatcher("/detail.jsp").forward(request, response); } } private void doList(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 连接数据库,查询所有的部门 Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; ArrayList<Dept> depts = new ArrayList<Dept>(); try { // 获取连接 conn = DBUtil.getConnection(); // 获取预编译的数据库操作对象 String sql = "select deptno as a,dname,loc from dept"; ps = conn.prepareStatement(sql); // 执行SQL语句 rs = ps.executeQuery(); // 处理结果集 int i = 0; while(rs.next()){ String deptno = rs.getString("a"); String dname = rs.getString("dname"); String loc = rs.getString("loc"); Dept dept = new Dept(deptno, dname, loc); depts.add(dept); } } catch (SQLException e) { e.printStackTrace(); } finally { // 释放资源 DBUtil.close(conn, ps, rs); } request.setAttribute("deptList", depts); request.getRequestDispatcher("/list.jsp").forward(request, response); } }