JSP 的本质原理解析:"编写的时候是JSP,心里想解读的是 java 源码"
2023/4/30 18:22:13
本文主要是介绍JSP 的本质原理解析:"编写的时候是JSP,心里想解读的是 java 源码",对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
JSP 的本质原理解析:"编写的时候是JSP,心里想解读的是 java 源码"
@
-
JSP 的本质原理解析:"编写的时候是JSP,心里想解读的是 java 源码"
- 每博一文案
- 1. JSP 概述
- 2. 第一个 JSP 程序
- 3. JSP 的本质就是 Servlet
-
4. JSP 的基础语法
- 4.1 在 JSP 文件中直接编写文字
-
4.2 在JSP中编写Java程序 <% %> 与 <%! %>
- 4.2.1 <% %>
- 4.2.2 <%! %>
- 4.3 通过JSP当中的 <%= %>向浏览器前端输入 Java变量
- 4.4 在JSP 中的专业注释
- 4.5 JSP基础语法总结:
- 5. JSP的指令
- 6. JSP的九大内置对象
- 7. 使用Servlet + JSP完成oa项目的改造
- 8. 补充:
- 9. 总结:
- 10. 最后:
每博一文案
活明白的人,一生只做好了这两件事: 每个瞬间都充满了选择和承担,就算面前是一座独木桥,也必须选择是前进后退,亦或是留在原地此时此刻你所经历的一切。 这是过往无数个选择后的结果,哪些小的选择汇聚在了一起,最终成了我们今天的时光。 其实,活明白的人一生只做好了两件事看,一是选择,二是承担。常听人争论选择和努力哪个更重要。 其实,努力并不是选择的对比面,而是拥有选择的基本条件。不努力的人往往连选择的资格都没有,努力是为了, 更好的选择。正如马伯庸说:所谓的选择只是努力所赋予人的一种资格。 所有的一夜成名,刹那的焰火,实际是过往今年默默努力埋下的伏笔,因此这里说的选择不是投机取巧的小聪明。 而是积淀后的深思熟虑。常言道:选择不对,努力白费。 李兰娟院士在为了汕头大学的毕业学生送上给予时,表示:不管是在生活还是事业上,有很多外部变化和环境因素是我们 所无法选择的。我们可以选择的是在每一个人生路口要做出怎样的选择,以及如何勇敢的前行。 我们所有的人都在共享一段岁月,却在不同的选择中分道扬镳,因为人生最重要的除了努力还有选择。 当你被生活的疲惫裹挟着,被环境的艰难牵制着,这时你要么被生活牵着鼻子走,要么接收痛苦与打击,抗住一场场暴风雪,主动迎击一地鸡毛的琐碎,你要做的不是抱怨生活的不公,而是迈出步子,夺下生活的主导权,做出选择了。 一种选择就是一种代价,不同的选择造就了不同的人生,人打从生下来就面临着很多种未知的可能。 未来都是一张白纸,任由自己去上色作画。有些人完成地很好,也有人画得一团糟,人生的一万种可能好的,不好的 都是有认真选择好。 每一支画笔,在每次选择时都要坚持原则,便是对自己的人生负责。 —————— 《一禅心灵庙语》
1. JSP 概述
JSP(全称JavaServer Pages),sun公司主导的一种动态网页技术,JSP在服务端运行,可以响应客户端的请求,根据请求内容动态的生成HTML、XML或其他格式文档的Web网页然后返回请求者。在JSP页面可以嵌入Java代码,JSP文件在运行时会被其编译器转换成更原始的Servlet代码,然后再由Java编译器来编译成能快速执行的二进制机器码。
2.特点:
- 能以模板化的方式简单、高效地添加动态网页内容。
- 可利用JavaBean和标签库技术复用常用的功能代码。
- 有良好的工具支持。
- 继承了Java语言的相对易用性。
- 继承了Java的跨平台优势,实现“一次编写,处处运行”。
- 页面中的动(控制变动内容的部分)/静(内容不需变动的部分)区域以分散但又有序的形式组合在一起,方便开发维护。
- 可与其它企业级Java技术相互配合。JSP可以只专门负责页面中的数据呈现,实现分层开发。
3.JSP页面组成:
在 HTML 页面文件中加入 Java 程序段和 JSP 标签,即可构成一个 JSP 页文件,JSP 页面由 5 种元素组合而成。
普通的 HTML 标记符。
- JSP 标签,如指令标签、动作标签。
- 变量和方法的声明。
- Java 程序段。
- Java 表达式。
2. 第一个 JSP 程序
我的第一个JSP程序:
这里给一个小的建议,大家在阅读如下,文章时,可以带着一个这样的问题:JSP 是什么 ? 去阅读文章,有助于后面的内容上的阅读理解。
在WEB-INF目录之外创建一个 index.jsp
文件,然后这个文件中没有任何内容。注意 了:我们对于这个jsp 文件当中并没有编写任何的内容,一个字符,一个标点都可以,就是一个空白的。如下显示的:
将上面的项目部署之后,启动服务器,打开浏览器,访问以下地址 http://127.0.0.1:8080/servlet14/index.jsp
我们部署的项目的路径:具体显示如下。
重点: 实际上我们访问的以上的这个:index.jsp
,底层执行的是:index_jsp.class
这个java程序。我们可以在我们本地电脑上访问到该生成是 index_jsp.class,index_jsp.java
的文件,该生成的对应的.class,.java文件所在的路径是在我们启动 Tomcat 服务器当中提示的,一个 CATALINA_BASE
路径下的 C:\Users\huo\AppData\Local\JetBrains\IntelliJIdea2022.1\tomcat\4b6bbfbb-d520-498b-b8f2-090a7ad68f62
这个index.jsp会被tomcat翻译生成index_jsp.java文件,然后tomcat服务器又会将 index_jsp.java
编译生成index_jsp.class
文件。访问index.jsp
,实际上执行的是index_jsp.class
中的方法。
如下是我们访问 index.jsp
在我本地电脑上生成的 CATALINA_BASE的 路径下的,index_jsp.java,index_jsp.class 的文件。 C:\Users\huo\AppData\Local\JetBrains\IntelliJIdea2022.1\tomcat\4b6bbfbb-d520-498b-b8f2-090a7ad68f62\work\Catalina\localhost\servlet14\org\apache\jsp
如下是我们的一个 index.jsp(空内容)
被翻译为 index_jsp.java
文件的内容如下:
/* * Generated by the Jasper component of Apache Tomcat * Version: Apache Tomcat/10.0.12 * Generated at: 2023-04-21 03:20:48 UTC * Note: The last modified time of this file was set to * the last modified time of the source file after * generation to assist with modification tracking. */ package org.apache.jsp; import jakarta.servlet.*; import jakarta.servlet.http.*; import jakarta.servlet.jsp.*; public final class index_jsp extends org.apache.jasper.runtime.HttpJspBase implements org.apache.jasper.runtime.JspSourceDependent, org.apache.jasper.runtime.JspSourceImports { private static final jakarta.servlet.jsp.JspFactory _jspxFactory = jakarta.servlet.jsp.JspFactory.getDefaultFactory(); private static java.util.Map<java.lang.String,java.lang.Long> _jspx_dependants; private static final java.util.Set<java.lang.String> _jspx_imports_packages; private static final java.util.Set<java.lang.String> _jspx_imports_classes; static { _jspx_imports_packages = new java.util.HashSet<>(); _jspx_imports_packages.add("jakarta.servlet"); _jspx_imports_packages.add("jakarta.servlet.http"); _jspx_imports_packages.add("jakarta.servlet.jsp"); _jspx_imports_classes = null; } private volatile jakarta.el.ExpressionFactory _el_expressionfactory; private volatile org.apache.tomcat.InstanceManager _jsp_instancemanager; public java.util.Map<java.lang.String,java.lang.Long> getDependants() { return _jspx_dependants; } public java.util.Set<java.lang.String> getPackageImports() { return _jspx_imports_packages; } public java.util.Set<java.lang.String> getClassImports() { return _jspx_imports_classes; } public jakarta.el.ExpressionFactory _jsp_getExpressionFactory() { if (_el_expressionfactory == null) { synchronized (this) { if (_el_expressionfactory == null) { _el_expressionfactory = _jspxFactory.getJspApplicationContext(getServletConfig().getServletContext()).getExpressionFactory(); } } } return _el_expressionfactory; } public org.apache.tomcat.InstanceManager _jsp_getInstanceManager() { if (_jsp_instancemanager == null) { synchronized (this) { if (_jsp_instancemanager == null) { _jsp_instancemanager = org.apache.jasper.runtime.InstanceManagerFactory.getInstanceManager(getServletConfig()); } } } return _jsp_instancemanager; } public void _jspInit() { } public void _jspDestroy() { } public void _jspService(final jakarta.servlet.http.HttpServletRequest request, final jakarta.servlet.http.HttpServletResponse response) throws java.io.IOException, jakarta.servlet.ServletException { if (!jakarta.servlet.DispatcherType.ERROR.equals(request.getDispatcherType())) { final java.lang.String _jspx_method = request.getMethod(); if ("OPTIONS".equals(_jspx_method)) { response.setHeader("Allow","GET, HEAD, POST, OPTIONS"); return; } if (!"GET".equals(_jspx_method) && !"POST".equals(_jspx_method) && !"HEAD".equals(_jspx_method)) { response.setHeader("Allow","GET, HEAD, POST, OPTIONS"); response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, "JSP 只允许 GET、POST 或 HEAD。Jasper 还允许 OPTIONS"); return; } } final jakarta.servlet.jsp.PageContext pageContext; jakarta.servlet.http.HttpSession session = null; final jakarta.servlet.ServletContext application; final jakarta.servlet.ServletConfig config; jakarta.servlet.jsp.JspWriter out = null; final java.lang.Object page = this; jakarta.servlet.jsp.JspWriter _jspx_out = null; jakarta.servlet.jsp.PageContext _jspx_page_context = null; try { response.setContentType("text/html"); pageContext = _jspxFactory.getPageContext(this, request, response, null, true, 8192, true); _jspx_page_context = pageContext; application = pageContext.getServletContext(); config = pageContext.getServletConfig(); session = pageContext.getSession(); out = pageContext.getOut(); _jspx_out = out; out.write(' '); out.write(' '); } catch (java.lang.Throwable t) { if (!(t instanceof jakarta.servlet.jsp.SkipPageException)){ out = _jspx_out; if (out != null && out.getBufferSize() != 0) try { if (response.isCommitted()) { out.flush(); } else { out.clearBuffer(); } } catch (java.io.IOException e) {} if (_jspx_page_context != null) _jspx_page_context.handlePageException(t); else throw new ServletException(t); } } finally { _jspxFactory.releasePageContext(_jspx_page_context); } } }
3. JSP 的本质就是 Servlet
从上述我们编写的第一jsp 程序,index.jsp 空内容的,index.jsp访问的时候,会自动翻译生成index_jsp.java,会自动编译生成index_jsp.class,那么index_jsp 这就是一个类。
从图中我们可以看到,该index.jsp 翻译的 index_jsp.java 类是继承了 extends org.apache.jasper.runtime.HttpJspBase
类的,我们查阅其 Tomcat 10 的 HttpJspBase 源码如下:
如下是 HttpJspBase 的源码内容
/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.jasper.runtime; import java.io.IOException; import jakarta.servlet.ServletConfig; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.jsp.HttpJspPage; import org.apache.jasper.Constants; import org.apache.jasper.compiler.Localizer; /** * This is the super class of all JSP-generated servlets. * * @author Anil K. Vijendran */ public abstract class HttpJspBase extends HttpServlet implements HttpJspPage { private static final long serialVersionUID = 1L; protected HttpJspBase() { } @Override public final void init(ServletConfig config) throws ServletException { super.init(config); jspInit(); _jspInit(); } @Override public String getServletInfo() { return Localizer.getMessage("jsp.engine.info", Constants.SPEC_VERSION); } @Override public final void destroy() { jspDestroy(); _jspDestroy(); } /** * Entry point into service. */ @Override public final void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { _jspService(request, response); } @Override public void jspInit() { } public void _jspInit() { } @Override public void jspDestroy() { } protected void _jspDestroy() { } @Override public abstract void _jspService(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException; }
而 HttpServlet 是 extends 了 GenericServlet 抽象类,而 GenericServlet 抽象类是 实现了 Servlet 接口的。
所以:一个index_jsp类就是一个Servlet类。总结就是:一个 index.jsp 文件会被翻译为一个 index_jsp.java 类,而 该 翻译的 index_jsp.java类是 继承
了 org.apache.jasper.runtime.HttpJspBase 类的,而 org.apache.jasper.runtime.HttpJspBase 类 是继承了 HttpServlet 类的。这下逻辑就清晰了,JSP 就是 Servlet
。
- JSP 的生命周期和Servlet的生命周期完全相同。完全就是一个东西。没有任何区别。
- JSP和servlet一样,都是单例的。(假单例,真单例是:其类的构造器是 private 私有化的,而Servlte 的构造器不是 private 私有化的,是公开的。所以为假单例),想要了解更多的,大家可以移步至: 🔜🔜🔜 javaEE Web(Tomcat)深度理解 和 Servlet的本质_ChinaRainbowSea的博客-CSDN博客
现在我们可以比较清晰的回答 JSP是什么了 ?
- JSP是java程序。(JSP本质还是一个Servlet)
- JSP是:JavaServer Pages的缩写。(基于Java语言实现的服务器端的页面。)
- Servlet是JavaEE的13个子规范之一,那么JSP也是JavaEE的13个子规范之一。
- JSP是一套规范。所有的web容器/web服务器都是遵循这套规范的,都是按照这套规范进行的“翻译”
- 每一个web容器/web服务器都会内置一个JSP翻译引擎。这里所谓的翻译指的是: 将我们访问的 jsp 文件翻译成 对应的以
.java
后缀结尾的Java文件,再将 该 java 文件编译生成.class
文件。
问题:JSP第一次访问的时候是比较慢的,为什么?
同问: 为什么大部分的运维人员在给客户演示项目的时候,为什么提前先把所有的jsp文件先访问一遍。
因为我们第一次比较麻烦,需要进行一个项目的资源的加载。
- 第一次比较麻烦的地方:
- 要把jsp文件翻译生成java源文件
- java源文件要编译生成class字节码文件
- 然后通过class去创建servlet对象
- 然后调用servlet对象的init方法
- 最后调用servlet对象的service方法。
- 第二次就比较快了,为什么?
- 因为第二次直接调用单例servlet对象的service方法即可。
注意点:
实际上我们对于 JSP 进行错误调试的时候,还是要直接打开JSP文件对应翻译生成的 java文件,检查其中 java代码。
开发JSP的最高境界: 眼前编写的是JSP代码,但是我们心里,脑袋里面当中呈现的是 java代码。
JSP既然本质上是一个Servlet,那么JSP和Servlet到底有什么区别呢?
职责不同:
- Servlet的职责是什么:收集数据。(Servlet的强项是后端的逻辑处理,业务处理,然后链接数据库,获取/收集数据。)
- JSP的职责是什么:展示数据。(JSP的强项是做 前端数据的展示)
4. JSP 的基础语法
上面我们学习,了解到了 JSP 的本质原理,下面我们就来学习一下有关 JSP的基础语法吧
4.1 在 JSP 文件中直接编写文字
在 jsp 文件中直接编写文字,都会自动被翻译到哪里?
如下我们实验一下,我们在上一个 index.jsp 文件当中,直接编写一个 “Hello Wrold” 。当我们浏览器端访问该 index.jsp 文件时,浏览器端有是显示一个则样的效果,而其本质中 “Hello Wrold” 又是被翻译到了,成了什么,翻译到了哪里。
如下我们查看对应 index.jsp 翻译的 index_jsp.java 文件的内容如下:
/* * Generated by the Jasper component of Apache Tomcat * Version: Apache Tomcat/10.0.12 * Generated at: 2023-04-21 06:10:10 UTC * Note: The last modified time of this file was set to * the last modified time of the source file after * generation to assist with modification tracking. */ package org.apache.jsp; import jakarta.servlet.*; import jakarta.servlet.http.*; import jakarta.servlet.jsp.*; public final class index_jsp extends org.apache.jasper.runtime.HttpJspBase implements org.apache.jasper.runtime.JspSourceDependent, org.apache.jasper.runtime.JspSourceImports { private static final jakarta.servlet.jsp.JspFactory _jspxFactory = jakarta.servlet.jsp.JspFactory.getDefaultFactory(); private static java.util.Map<java.lang.String,java.lang.Long> _jspx_dependants; private static final java.util.Set<java.lang.String> _jspx_imports_packages; private static final java.util.Set<java.lang.String> _jspx_imports_classes; static { _jspx_imports_packages = new java.util.HashSet<>(); _jspx_imports_packages.add("jakarta.servlet"); _jspx_imports_packages.add("jakarta.servlet.http"); _jspx_imports_packages.add("jakarta.servlet.jsp"); _jspx_imports_classes = null; } private volatile jakarta.el.ExpressionFactory _el_expressionfactory; private volatile org.apache.tomcat.InstanceManager _jsp_instancemanager; public java.util.Map<java.lang.String,java.lang.Long> getDependants() { return _jspx_dependants; } public java.util.Set<java.lang.String> getPackageImports() { return _jspx_imports_packages; } public java.util.Set<java.lang.String> getClassImports() { return _jspx_imports_classes; } public jakarta.el.ExpressionFactory _jsp_getExpressionFactory() { if (_el_expressionfactory == null) { synchronized (this) { if (_el_expressionfactory == null) { _el_expressionfactory = _jspxFactory.getJspApplicationContext(getServletConfig().getServletContext()).getExpressionFactory(); } } } return _el_expressionfactory; } public org.apache.tomcat.InstanceManager _jsp_getInstanceManager() { if (_jsp_instancemanager == null) { synchronized (this) { if (_jsp_instancemanager == null) { _jsp_instancemanager = org.apache.jasper.runtime.InstanceManagerFactory.getInstanceManager(getServletConfig()); } } } return _jsp_instancemanager; } public void _jspInit() { } public void _jspDestroy() { } public void _jspService(final jakarta.servlet.http.HttpServletRequest request, final jakarta.servlet.http.HttpServletResponse response) throws java.io.IOException, jakarta.servlet.ServletException { if (!jakarta.servlet.DispatcherType.ERROR.equals(request.getDispatcherType())) { final java.lang.String _jspx_method = request.getMethod(); if ("OPTIONS".equals(_jspx_method)) { response.setHeader("Allow","GET, HEAD, POST, OPTIONS"); return; } if (!"GET".equals(_jspx_method) && !"POST".equals(_jspx_method) && !"HEAD".equals(_jspx_method)) { response.setHeader("Allow","GET, HEAD, POST, OPTIONS"); response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, "JSP 只允许 GET、POST 或 HEAD。Jasper 还允许 OPTIONS"); return; } } final jakarta.servlet.jsp.PageContext pageContext; jakarta.servlet.http.HttpSession session = null; final jakarta.servlet.ServletContext application; final jakarta.servlet.ServletConfig config; jakarta.servlet.jsp.JspWriter out = null; final java.lang.Object page = this; jakarta.servlet.jsp.JspWriter _jspx_out = null; jakarta.servlet.jsp.PageContext _jspx_page_context = null; try { response.setContentType("text/html"); pageContext = _jspxFactory.getPageContext(this, request, response, null, true, 8192, true); _jspx_page_context = pageContext; application = pageContext.getServletContext(); config = pageContext.getServletConfig(); session = pageContext.getSession(); out = pageContext.getOut(); _jspx_out = out; out.write("Hello World"); } catch (java.lang.Throwable t) { if (!(t instanceof jakarta.servlet.jsp.SkipPageException)){ out = _jspx_out; if (out != null && out.getBufferSize() != 0) try { if (response.isCommitted()) { out.flush(); } else { out.clearBuffer(); } } catch (java.io.IOException e) {} if (_jspx_page_context != null) _jspx_page_context.handlePageException(t); else throw new ServletException(t); } } finally { _jspxFactory.releasePageContext(_jspx_page_context); } } }
翻译到 servlet类的 service() 方法的 out.write("翻译到这里") ,直接翻译到双引号里,被java程序当做普通字符串打印输出到浏览器。
补充一点: 解决浏览器端显示的乱码问题 ?
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中编写的HTML CSS JS代码,这些代码对于JSP来说只是一个普通的字符串。但是JSP把这个普通的字符串一旦输出到浏览器,浏览器就会对HTML CSS JS进行解释执行。展现一个效果。举例如下:
<%@page contentType="text/html; chatSet=UTF-8" %> <html> <head> <title>my first JSP page</title> <script type="text/javascript"> function sayHello() { window.alert("你好 JSP") } </script> </head> <bady> <h1>My first JSP page</h1> <input type="button" value="hello jsp" onclick="sayHello()" /> fajsi ffasdfda fasd afjsfi9 Sytstem.out.println("hello JSP"); </bady> </html>
访问 index.jsp 显示的效果如下:
对应翻译的java 的内容如下:注意:都是被翻译到了 service()方法体当中去了
public void _jspService(final jakarta.servlet.http.HttpServletRequest request, final jakarta.servlet.http.HttpServletResponse response) throws java.io.IOException, jakarta.servlet.ServletException { if (!jakarta.servlet.DispatcherType.ERROR.equals(request.getDispatcherType())) { final java.lang.String _jspx_method = request.getMethod(); if ("OPTIONS".equals(_jspx_method)) { response.setHeader("Allow","GET, HEAD, POST, OPTIONS"); return; } if (!"GET".equals(_jspx_method) && !"POST".equals(_jspx_method) && !"HEAD".equals(_jspx_method)) { response.setHeader("Allow","GET, HEAD, POST, OPTIONS"); response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, "JSP 只允许 GET、POST 或 HEAD。Jasper 还允许 OPTIONS"); return; } } final jakarta.servlet.jsp.PageContext pageContext; jakarta.servlet.http.HttpSession session = null; final jakarta.servlet.ServletContext application; final jakarta.servlet.ServletConfig config; jakarta.servlet.jsp.JspWriter out = null; final java.lang.Object page = this; jakarta.servlet.jsp.JspWriter _jspx_out = null; jakarta.servlet.jsp.PageContext _jspx_page_context = null; try { response.setContentType("text/html; charset=UTF-8"); pageContext = _jspxFactory.getPageContext(this, request, response, null, true, 8192, true); _jspx_page_context = pageContext; application = pageContext.getServletContext(); config = pageContext.getServletConfig(); session = pageContext.getSession(); out = pageContext.getOut(); _jspx_out = out; out.write("\r\n"); out.write("\r\n"); out.write("<html>\r\n"); out.write("\r\n"); out.write("<head>\r\n"); out.write(" <title>my first JSP page</title>\r\n"); out.write("\r\n"); out.write(" <script type=\"text/javascript\">\r\n"); out.write(" function sayHello() {\r\n"); out.write(" window.alert(\"你好 JSP\")\r\n"); out.write(" }\r\n"); out.write(" </script>\r\n"); out.write("</head>\r\n"); out.write("<bady>\r\n"); out.write(" <h1>My first JSP page</h1>\r\n"); out.write(" <input type=\"button\" value=\"hello jsp\" onclick=\"sayHello()\" />\r\n"); out.write("\r\n"); out.write(" fajsi ffasdfda\r\n"); out.write(" fasd\r\n"); out.write(" afjsfi9\r\n"); out.write("\r\n"); out.write(" Sytstem.out.println(\"hello JSP\");\r\n"); out.write("</bady>\r\n"); out.write("</html>"); } catch (java.lang.Throwable t) { if (!(t instanceof jakarta.servlet.jsp.SkipPageException)){ out = _jspx_out; if (out != null && out.getBufferSize() != 0) try { if (response.isCommitted()) { out.flush(); } else { out.clearBuffer(); } } catch (java.io.IOException e) {} if (_jspx_page_context != null) _jspx_page_context.handlePageException(t); else throw new ServletException(t); } } finally { _jspxFactory.releasePageContext(_jspx_page_context); } } }
4.2 在JSP中编写Java程序 <% %> 与 <%! %>
4.2.1 <% %>
<% // java语句; // 在这里编写Java程序,编写 Java语句。 // 既然是编写 Java语句,自然要遵循Java的语法规范。 // 在这里编写的java语句,会被翻译到 service() 方法体内 %>
在这个<% java语句; %>
符号当中编写的被视为java程序,被翻译到Servlet类的 service方法体
内部。举例如下:
<% System.out.println("Hello World"); %> <% System.out.println("Hello JSP"); %> <% System.out.println("你好世界"); %>
访问 浏览器前端访问:demo01.jsp 效果
因为我们只在 demo01.jsp 文件当中编写了,Java代码,并没有编写其他的,而且我们的Java代码编写的仅仅只是一个简单的输出到控制台上的一个输出语句而已了。所以前端浏览器是没有任何的显示内容的。
如下是 对应我们访问 demo01.jsp 文件翻译为 demo01_jsp.java 文件的源码信息如下:
/* * Generated by the Jasper component of Apache Tomcat * Version: Apache Tomcat/10.0.12 * Generated at: 2023-04-21 06:43:13 UTC * Note: The last modified time of this file was set to * the last modified time of the source file after * generation to assist with modification tracking. */ package org.apache.jsp; import jakarta.servlet.*; import jakarta.servlet.http.*; import jakarta.servlet.jsp.*; public final class demo01_jsp extends org.apache.jasper.runtime.HttpJspBase implements org.apache.jasper.runtime.JspSourceDependent, org.apache.jasper.runtime.JspSourceImports { private static final jakarta.servlet.jsp.JspFactory _jspxFactory = jakarta.servlet.jsp.JspFactory.getDefaultFactory(); private static java.util.Map<java.lang.String,java.lang.Long> _jspx_dependants; private static final java.util.Set<java.lang.String> _jspx_imports_packages; private static final java.util.Set<java.lang.String> _jspx_imports_classes; static { _jspx_imports_packages = new java.util.HashSet<>(); _jspx_imports_packages.add("jakarta.servlet"); _jspx_imports_packages.add("jakarta.servlet.http"); _jspx_imports_packages.add("jakarta.servlet.jsp"); _jspx_imports_classes = null; } private volatile jakarta.el.ExpressionFactory _el_expressionfactory; private volatile org.apache.tomcat.InstanceManager _jsp_instancemanager; public java.util.Map<java.lang.String,java.lang.Long> getDependants() { return _jspx_dependants; } public java.util.Set<java.lang.String> getPackageImports() { return _jspx_imports_packages; } public java.util.Set<java.lang.String> getClassImports() { return _jspx_imports_classes; } public jakarta.el.ExpressionFactory _jsp_getExpressionFactory() { if (_el_expressionfactory == null) { synchronized (this) { if (_el_expressionfactory == null) { _el_expressionfactory = _jspxFactory.getJspApplicationContext(getServletConfig().getServletContext()).getExpressionFactory(); } } } return _el_expressionfactory; } public org.apache.tomcat.InstanceManager _jsp_getInstanceManager() { if (_jsp_instancemanager == null) { synchronized (this) { if (_jsp_instancemanager == null) { _jsp_instancemanager = org.apache.jasper.runtime.InstanceManagerFactory.getInstanceManager(getServletConfig()); } } } return _jsp_instancemanager; } public void _jspInit() { } public void _jspDestroy() { } public void _jspService(final jakarta.servlet.http.HttpServletRequest request, final jakarta.servlet.http.HttpServletResponse response) throws java.io.IOException, jakarta.servlet.ServletException { if (!jakarta.servlet.DispatcherType.ERROR.equals(request.getDispatcherType())) { final java.lang.String _jspx_method = request.getMethod(); if ("OPTIONS".equals(_jspx_method)) { response.setHeader("Allow","GET, HEAD, POST, OPTIONS"); return; } if (!"GET".equals(_jspx_method) && !"POST".equals(_jspx_method) && !"HEAD".equals(_jspx_method)) { response.setHeader("Allow","GET, HEAD, POST, OPTIONS"); response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, "JSP 只允许 GET、POST 或 HEAD。Jasper 还允许 OPTIONS"); return; } } final jakarta.servlet.jsp.PageContext pageContext; jakarta.servlet.http.HttpSession session = null; final jakarta.servlet.ServletContext application; final jakarta.servlet.ServletConfig config; jakarta.servlet.jsp.JspWriter out = null; final java.lang.Object page = this; jakarta.servlet.jsp.JspWriter _jspx_out = null; jakarta.servlet.jsp.PageContext _jspx_page_context = null; try { response.setContentType("text/html; charset=UTF-8"); pageContext = _jspxFactory.getPageContext(this, request, response, null, true, 8192, true); _jspx_page_context = pageContext; application = pageContext.getServletContext(); config = pageContext.getServletConfig(); session = pageContext.getSession(); out = pageContext.getOut(); _jspx_out = out; out.write(' '); out.write(' '); out.write(' '); out.write('\r'); out.write('\n'); System.out.println("Hello World"); out.write("\r\n"); out.write("\r\n"); out.write("\r\n"); System.out.println("Hello JSP"); out.write("\r\n"); out.write("\r\n"); System.out.println("你好世界"); out.write('\r'); out.write('\n'); } catch (java.lang.Throwable t) { if (!(t instanceof jakarta.servlet.jsp.SkipPageException)){ out = _jspx_out; if (out != null && out.getBufferSize() != 0) try { if (response.isCommitted()) { out.flush(); } else { out.clearBuffer(); } } catch (java.io.IOException e) {} if (_jspx_page_context != null) _jspx_page_context.handlePageException(t); else throw new ServletException(t); } } finally { _jspxFactory.releasePageContext(_jspx_page_context); } } }
<% %>的使用的注意事项:
- 这里你要细心点,你要思考,在<% %>这个符号里面写java代码的时候,你要时时刻刻的记住你正在“
sevice() 方法体
”当中写代码,方法体中可以写什么,不可以写什么,你心里是否明白呢?
方法体当中不可以,定义静态代码块,以及代码块,不可以再定义方法了,不可以被定义成员变量 (被权限修饰符修饰的变量。)
如果在 jsp 当中所编写的 Java程序存在错误,浏览器端是会抱 500 错误的。如下所示:以 5 开头的一般都是服务器端的错误,这时候,我们一般是通过查看其中翻译的 java 程序当中翻译的错误了。
- 在service方法当中编写的代码是有顺序的,方法体当中的代码要遵循自上而下的顺序依次逐行执行。
- service方法当中不能写静态代码块,不能写方法,不能定义成员变量。。。。。。
- 在同一个JSP当中
<% %>
这个符号可以出现多个。
4.2.2 <%! %>
<%! // 在这个符号当中编写的java程序会自动翻译到service方法之外。 // service() 方法体之外是什么,自然就是在 Servlet 类里面了。 %>
-
<%! %>
在这个符号当中编写的java程序会自动翻译到service方法之外。简单的说就是: Servlet 类当中,service() 方法外,那不就是 成员变量,方法,代码块了。
举例如下:
启动部署,访问运行如下
- 这个语法很少用,为什么?并且也不建议使用,因为在service方法外面写静态变量和实例变量,都会存在线程安全问题,因为JSP就是servlet,servlet是单例的,多线程并发的环境下的,这个静态变量和实例变量一旦有修改操作,必然会存在线程安全问题。
4.3 通过JSP当中的 <%= %>向浏览器前端输入 Java变量
怎么向浏览器上输出一个java变量。
错误方式: 直接在 jsp 当中编写对应变量名,因为我们的在 JSP文件当中直接编写的文字的内容是被翻译到了 service()
方法体当中去了被 out.write("</html>")
包裹的。所以翻译的不是对应变量的值,而是对应变量的名(以字符串的形式翻译到了浏览器端了)。举例如下:
正确的方式一:不推荐
<% String name = “jack”; out.write("name = " + name); %>
注意: 以上代码中的 out
是JSP的九大内置对象之一。可以直接拿来用。当然,必须只能在 service()
方法内部使用(因为 JSP的内置对象是定义在 service( ) 方法体内的,所以其作用域也仅仅只是在 service() 方法体内部,才有效的)。
举例:测试
-
如果向浏览器上输出的内容中没有“java代码”,例如:输出本身就是一个固定的字符串,可以直接在jsp中编写,不需要写到<% %> 这里。因为仅仅只是一个输出字符串的话,其在JSP直接编写,输出的就是字符串 的。
-
如果输出的内容中含有“java代码当中的变量的值”,这个时候可以使用以下语法格式:
<%=变量名%> // 在=的后面编写要输出的内容。
<%= %> 注意:在=的后面编写要输出的内容。
举例:
<%= %>
这个符号会被翻译到哪里?最终翻译成什么?
如下是:对应 demo02.jsp 文件翻译成的对应的 demo02_jsp.java 文件的源码如下:
/* * Generated by the Jasper component of Apache Tomcat * Version: Apache Tomcat/10.0.12 * Generated at: 2023-04-21 11:55:08 UTC * Note: The last modified time of this file was set to * the last modified time of the source file after * generation to assist with modification tracking. */ package org.apache.jsp; import jakarta.servlet.*; import jakarta.servlet.http.*; import jakarta.servlet.jsp.*; public final class demo02_jsp extends org.apache.jasper.runtime.HttpJspBase implements org.apache.jasper.runtime.JspSourceDependent, org.apache.jasper.runtime.JspSourceImports { private static final jakarta.servlet.jsp.JspFactory _jspxFactory = jakarta.servlet.jsp.JspFactory.getDefaultFactory(); private static java.util.Map<java.lang.String,java.lang.Long> _jspx_dependants; private static final java.util.Set<java.lang.String> _jspx_imports_packages; private static final java.util.Set<java.lang.String> _jspx_imports_classes; static { _jspx_imports_packages = new java.util.HashSet<>(); _jspx_imports_packages.add("jakarta.servlet"); _jspx_imports_packages.add("jakarta.servlet.http"); _jspx_imports_packages.add("jakarta.servlet.jsp"); _jspx_imports_classes = null; } private volatile jakarta.el.ExpressionFactory _el_expressionfactory; private volatile org.apache.tomcat.InstanceManager _jsp_instancemanager; public java.util.Map<java.lang.String,java.lang.Long> getDependants() { return _jspx_dependants; } public java.util.Set<java.lang.String> getPackageImports() { return _jspx_imports_packages; } public java.util.Set<java.lang.String> getClassImports() { return _jspx_imports_classes; } public jakarta.el.ExpressionFactory _jsp_getExpressionFactory() { if (_el_expressionfactory == null) { synchronized (this) { if (_el_expressionfactory == null) { _el_expressionfactory = _jspxFactory.getJspApplicationContext(getServletConfig().getServletContext()).getExpressionFactory(); } } } return _el_expressionfactory; } public org.apache.tomcat.InstanceManager _jsp_getInstanceManager() { if (_jsp_instancemanager == null) { synchronized (this) { if (_jsp_instancemanager == null) { _jsp_instancemanager = org.apache.jasper.runtime.InstanceManagerFactory.getInstanceManager(getServletConfig()); } } } return _jsp_instancemanager; } public void _jspInit() { } public void _jspDestroy() { } public void _jspService(final jakarta.servlet.http.HttpServletRequest request, final jakarta.servlet.http.HttpServletResponse response) throws java.io.IOException, jakarta.servlet.ServletException { if (!jakarta.servlet.DispatcherType.ERROR.equals(request.getDispatcherType())) { final java.lang.String _jspx_method = request.getMethod(); if ("OPTIONS".equals(_jspx_method)) { response.setHeader("Allow","GET, HEAD, POST, OPTIONS"); return; } if (!"GET".equals(_jspx_method) && !"POST".equals(_jspx_method) && !"HEAD".equals(_jspx_method)) { response.setHeader("Allow","GET, HEAD, POST, OPTIONS"); response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, "JSP 只允许 GET、POST 或 HEAD。Jasper 还允许 OPTIONS"); return; } } final jakarta.servlet.jsp.PageContext pageContext; jakarta.servlet.http.HttpSession session = null; final jakarta.servlet.ServletContext application; final jakarta.servlet.ServletConfig config; jakarta.servlet.jsp.JspWriter out = null; final java.lang.Object page = this; jakarta.servlet.jsp.JspWriter _jspx_out = null; jakarta.servlet.jsp.PageContext _jspx_page_context = null; try { response.setContentType("text/html;charset=UTF-8"); pageContext = _jspxFactory.getPageContext(this, request, response, null, true, 8192, true); _jspx_page_context = pageContext; application = pageContext.getServletContext(); config = pageContext.getServletConfig(); session = pageContext.getSession(); out = pageContext.getOut(); _jspx_out = out; out.write(' '); out.write("\r\n"); out.write("\r\n"); String name = "张三"; out.write("\r\n"); out.write("\r\n"); out.print(name); out.write("\r\n"); out.write("<br>\r\n"); out.print( 1 + 2); out.write("\r\n"); out.write("\r\n"); } catch (java.lang.Throwable t) { if (!(t instanceof jakarta.servlet.jsp.SkipPageException)){ out = _jspx_out; if (out != null && out.getBufferSize() != 0) try { if (response.isCommitted()) { out.flush(); } else { out.clearBuffer(); } } catch (java.io.IOException e) {} if (_jspx_page_context != null) _jspx_page_context.handlePageException(t); else throw new ServletException(t); } } finally { _jspxFactory.releasePageContext(_jspx_page_context); } } }
翻译成了这个java代码: out.print();
翻译到service方法当中了。
什么时候使用<%=%> 输出呢?输出的内容中含有java的变量,输出的内容是一个动态的内容,不是一个死的字符串。如果输出的是一个固定的字符串,直接在JSP文件中编写即可。
4.4 在JSP 中的专业注释
- 在JSP中如何编写JSP的专业注释
<%--JSP的专业注释,不会被翻译到java源代码当中。--%> 不会翻译对应的信息。该注解当中的内容会被忽略而不翻译的。 <!--这种注释属于HTML的注释,这个注释信息仍然会被翻译到java源代码当中,不建议。--> 翻译到HTML当中作为 html 的 注解存在。
4.5 JSP基础语法总结:
- JSP中直接编写普通字符串
翻译到service方法的out.write("这里")
- <%%>
翻译到service方法体内部,里面是一条一条的java语句。
- <%! %>
翻译到service方法之外。
- <%= %>
翻译到service方法体内部,翻译为:out.print();
- <%@page contentType="text/html;charset=UTF-8"%>
page指令,通过contentType属性用来设置响应的内容类型以及字符集编码的设置。
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文件对于 Tomcat 小猫咪来说,只是一个普通的文本文件,web容器会将xxx.jsp文件最终生成java程序,最终调用的是java对象相关的方法,真正执行的时候,和jsp文件就没有关系了。
5. JSP的指令
指令的作用: 指导JSP的翻译引擎如何工作(指导当前的JSP翻译引擎如何翻译JSP文件。)
指令包括哪些呢?
- include指令: 包含指令,在JSP中完成静态包含,很少用了。(这里不讲)
- taglib指令: 引入标签库的指令。这个到JJSTL标签库的时候再学习。现在先不管。
- page指令: 目前重点学习一个page指令。
指令的使用语法是什么?
<%@指令名 属性名=属性值 属性名=属性值 属性名=属性值....%> // 这里我们只学习 page 指令所以是: <%@page 属性名=属性值 属性名=属性值 属性名=属性值....%>
如下是:page指令当中一些 常用的属性
<%@page session="true|false" %> true表示启用JSP的内置对象session,表示一定启动session对象。没有session对象会创建。 如果没有设置,默认值就是session="true" session="false" 表示不启动内置对象session。当前JSP页面中无法使用内置对象session。 session 表示的是会话机制。
<%@page contentType="text/json" %> contentType属性用来设置响应的内容类型 但同时也可以设置字符集。 <%@page contentType="text/json;charset=UTF-8" %> <%-- 编码也可以单独分开设置如下--%> <%@page pageEncoding="UTF-8" %> pageEncoding="UTF-8" 表示设置响应时采用的字符集。
<%@page import="java.util.List, java.util.Date, java.util.ArrayList" %> 可以多个包一起导入 <%@page import="java.util.*" %> 也可以单个导包 import语句,导包。
<%@page errorPage="/error.jsp" %> 当前页面出现异常之后,跳转到error.jsp页面。 errorPage属性用来指定出错之后的跳转位置。 <%@page isErrorPage="true" %> 表示启用JSP九大内置对象之一:exception 默认值是false。 errorPage,和 isErrorPage 一般是连着一起使用的,因为 errorPage 表示错误跳转到指定的页面,但是不会有错误提示,而 isErrorPage 就是为了解决这个没有错误提示的问题。
举例:
测试:访问 demo03.jsp 该文件当中存在一个 java 程序的一个 null 指针访问的异常。当发生错误时,错误不处理,也不打印错误信息,而是直接跳转到 error.jsp 文件
可以使用 JSP当中的九大内置对象之一的 : **java.lang.Throwable exception ** 。
注意:想要使用该 java.lang.Throwable exception 内置对象必须,配置相关的指令:
<%-- 在错误页面可以启用JSP九大内置对象,exception exeption内置对象就是刚刚发生的异常对象 --%> <%@page isErrorPage="true" %> <%--isErrorPage 有两个参数一个是为:false,true :true 表示开启 isErrorPage ,false 表示禁用,默认不设置也是禁用状态的--%>
error.jsp 的内容上的编写如下
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%-- 在错误页面可以启用JSP九大内置对象,exception exeption内置对象就是刚刚发生的异常对象 --%> <%@page isErrorPage="true" %> <%--isErrorPage 有两个参数一个是为:false,true :true 表示开启 isErrorPage ,false 表示禁用,默认不设置也是禁用状态的--%> <html> <head> <title>程序错误</title> </head> <body> <h2>网络繁忙,稍后再试</h2> <% // 配置了 isErrorPage 内置的异常对象则可以 // 打印异常堆栈信息,输出到后台控制台上 // exception 是JSP 九大内置对象之一。 exception.printStackTrace(); %> </body> </html>
6. JSP的九大内置对象
注意:所谓的JSP的九大内置对象指的是:在 service()
方法体当中的局部的对象变量,service() 方法体外是无法使用的。
-
jakarta.servlet.jsp.PageContext pageContext 页面作用域
-
jakarta.servlet.http.HttpServletRequest request 请求作用域
-
jakarta.servlet.http.HttpSession session 会话作用域
-
jakarta.servlet.ServletContext application 应用作用域
- pageContext < request < session < application
- 以上四个作用域都有:setAttribute、getAttribute、removeAttribute方法。
- 以上作用域的使用原则:尽可能使用小的域。
-
java.lang.Throwable exception
-
jakarta.servlet.ServletConfig config
-
java.lang.Object page (其实是this,当前的servlet对象)
-
jakarta.servlet.jsp.JspWriter out (负责输出)
-
jakarta.servlet.http.HttpServletResponse response (负责响应)
思考一个问题:如果我只用JSP这一个技术,能不能开发web应用?
- 当然可以使用JSP来完成所有的功能。因为JSP就是Servlet,在JSP的<%%>里面写的代码就是在service方法当中的,所以在<%%>当中完全可以编写JDBC代码,连接数据库,查询数据,也可以在这个方法当中编写业务逻辑代码,处理业务,都是可以的,所以使用单独的JSP开发web应用完全没问题。
- 虽然JSP一个技术就可以完成web应用,但是不建议,还是建议采用servlet + jsp的方式进行开发。这样都能将各自的优点发挥出来。JSP就是做数据展示。Servlet就是做数据的收集。(JSP中编写的Java代码越少越好。)一定要职责分明。
7. 使用Servlet + JSP完成oa项目的改造
关于 上次的 oa 项目的详细内容,大家可以先移步至:🔜🔜🔜 Servlet注解的使用,简化配置 以及,使用模板方法设计模式优化oa项目_ChinaRainbowSea的博客-CSDN博客 ,以及: 使用“纯”Servlet做一个单表的CRUD操作_ChinaRainbowSea的博客-CSDN博客建议大家先,跳过去,看一看,有助于后面该文章内容上的阅读理解。
-
使用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。
模块对应的包目录如下:
DeptServlet
package com.RainbowSea.servlet; import com.RainbowSea.DBUtil.DBUtil; import com.RainbowSea.bean.Dept; 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.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; // 可以使用模糊查询 @WebServlet("/dept/*") @WebServlet({"/dept/list", "/dept/detail", "/dept/delete", "/dept/save", "/dept/modify"}) public class DeptServlet extends HttpServlet { @Override protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String servletPath = request.getServletPath(); // 获取到浏览器当中的uri // 双重的判断,一个是 session 会话域要存在,其次是 会话域当中存储了名为 "username" 的信息 if ("/dept/list".equals(servletPath)) { doList(request, response); } else if ("/dept/detail".equals(servletPath)) { doDetail(request, response); } else if ("/dept/delete".equals(servletPath)) { doElete(request, response); } else if ("/dept/save".equals(servletPath)) { doSave(request, response); } else if ("/dept/modify".equals(servletPath)) { doModify(request, response); } } /** * 修改部门信息 * * @param request * @param response */ private void doModify(HttpServletRequest request, HttpServletResponse response) throws IOException { request.setCharacterEncoding("UTF-8"); // 设置获取的的信息的编码集 Connection connection = null; PreparedStatement preparedStatement = null; // 影响数据库的行数 int count = 0; String deptno = request.getParameter("deptno"); String dname = request.getParameter("dname"); String loc = request.getParameter("loc"); try { // 1. 注册驱动,连接数据库 connection = DBUtil.getConnection(); // 2. 获取到操作数据库的对象,预编译sql语句,sql测试 String sql = "update dept set dname = ?,loc = ? where depton = ?"; preparedStatement = connection.prepareStatement(sql); // 3. 填充占位符,真正执行sql语句 // 从下标 1开始 preparedStatement.setString(1, dname); preparedStatement.setString(2, loc); preparedStatement.setString(3, deptno); count = preparedStatement.executeUpdate(); } catch (SQLException e) { throw new RuntimeException(e); } finally { // 4. 释放资源,最后使用的优先被释放 DBUtil.close(connection, preparedStatement, null); } if (count == 1) { // 更新成功 // 跳转到部门列表页面(部门列表表面是通过java程序动态生成的,所以还需要再次执行另一个Servlet) // 转发是服务器内部的操作,“/” 不要加项目名 // request.getRequestDispatcher("/dept/list/").forward(request,response); // 优化使用重定向,自发前端(需要指明项目名) response.sendRedirect(request.getContextPath() + "/dept/list"); } } /** * 保存部门信息 * * @param request * @param response */ private void doSave(HttpServletRequest request, HttpServletResponse response) throws IOException { request.setCharacterEncoding("UTF-8"); // 获取到前端的数据,建议 name 使用复制 String deptno = request.getParameter("deptno"); String dname = request.getParameter("dname"); String loc = request.getParameter("loc"); // 连接数据库,添加数据 Connection connection = null; PreparedStatement preparedStatement = null; // 影响数据库的行数 int count = 0; try { // 1. 注册驱动,连接数据库 connection = DBUtil.getConnection(); // 2. 获取操作数据库对象,预编译sql语句,Sql测试 String sql = "insert into dept(depton,dname,loc) values(?,?,?)"; preparedStatement = connection.prepareStatement(sql); // 3. 填充占位符, 真正执行sql语句, // 注意: 占位符的填充是从 1 开始的,基本上数据库相关的起始下标索引都是从 1下标开始的 preparedStatement.setString(1, deptno); preparedStatement.setString(2, dname); preparedStatement.setString(3, loc); // 返回影响数据库的行数 count = preparedStatement.executeUpdate(); // 5.释放资源 } catch (SQLException e) { throw new RuntimeException(e); } finally { DBUtil.close(connection, preparedStatement, null); } // 保存成功,返回部门列表页面 if (count == 1) { // 这里应该使用,重定向 // 这里用的转发,是服务器内部的,不要加项目名 //request.getRequestDispatcher("/dept/list/").forward(request, response); // 重定向 response.sendRedirect(request.getContextPath() + "/dept/list"); } } /** * 通过部门删除部门 * * @param request * @param response */ private void doElete(HttpServletRequest request, HttpServletResponse response) throws IOException { request.setCharacterEncoding("UTF-8"); // 设置获取的的信息的编码集 // 获取到发送数据 String deptno = request.getParameter("deptno"); /* 根据部门编号删除信息, 删除成功,跳转回原来的部门列表页面 删除失败,跳转删除失败的页面 */ Connection connection = null; PreparedStatement preparedStatement = null; // 记录删除数据库的行数 int count = 0; // 连接数据库进行删除操作 try { // 1.注册驱动,连接数据库 connection = DBUtil.getConnection(); // 开启事务(取消自动提交机制),实现可回滚 connection.setAutoCommit(false); // 2. 预编译sql语句,sql测试 String sql = "delete from dept where depton = ?"; // ? 占位符 preparedStatement = connection.prepareStatement(sql); // 3. 填充占位符,真正的执行sql语句 preparedStatement.setString(1, deptno); // 返回影响数据库的行数 count = preparedStatement.executeUpdate(); connection.commit(); // 手动提交数据 } catch (SQLException e) { // 遇到异常回滚 if (connection != null) { try { // 事务的回滚 connection.rollback(); } catch (SQLException ex) { throw new RuntimeException(ex); } } throw new RuntimeException(e); } finally { // 4. 释放资源 // 因为这里是删除数据,没有查询操作,所以 没有 ResultSet 可以传null DBUtil.close(connection, preparedStatement, null); } if (count == 1) { // 删除成功 // 仍然跳转到部门列表页面 // 部门列表页面的显示需要执行另外一个Servlet,怎么办,可以使用跳转,不过这里最后是使用重定向 // 注意:转发是在服务器间的,所以不要加“项目名” 而是 / + web.xml 映射的路径即可 //request.getRequestDispatcher("/dept/list/").forward(request,response); // 优化:使用重定向机制 注意: 重定向是自发到前端的地址栏上的,前端所以需要指明项目名 // 注意: request.getContextPath() 返回的根路径是,包含了 "/" 的 response.sendRedirect(request.getContextPath() + "/dept/list"); } } /** * 通过部门编号,查询部门的详情 * * @param request * @param response */ private void doDetail(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("UTF-8"); // 设置获取的的信息的编码集 Connection connection = null; PreparedStatement preparedStatement = null; ResultSet resultSet = null; // 获取到部门编号 String dno = request.getParameter("dno"); Dept dept = new Dept(); // 获取到部门编号,获取部门信息,将部门信息收集好,然后跳转到JSP做页面展示 try { // 2. 连接数据库,根据部门编号查询数据库 // 1.注册驱动,连接数据库 connection = DBUtil.getConnection(); // 2. 预编译SQL语句,sql要测试 String sql = "select dname,loc from dept where depton = ?"; // ? 占位符 preparedStatement = connection.prepareStatement(sql); // 3. 填充占位符,真正执行sql语句 preparedStatement.setString(1, dno); resultSet = preparedStatement.executeQuery(); // 4. 处理查询结果集 while (resultSet.next()) { String dname = resultSet.getString("dname"); String loc = resultSet.getString("loc"); // 封装对象(建议使用咖啡豆,因为只有一个对象) dept.setDeptno(dno); dept.setDname(dname); dept.setLoc(loc); } } catch (SQLException e) { throw new RuntimeException(e); } finally { // 5. 释放资源 DBUtil.close(connection, preparedStatement, resultSet); } // 这个咖啡豆只有一个,所以不需要袋子,只需要将这个咖啡豆放到request请求域当中, // 用于对应的 jsp显示 request.setAttribute("dept", dept); //String sign = request.getParameter("f"); /*if("m".equals(sign)) { // 转发:多个请求为一个请求(地址栏不会发生改变) // 注意: 该路径默认是从 web 开始找的 / 表示 web // 转发到修改页面 request.getRequestDispatcher("/edit.jsp").forward(request,response); } else if("d".equals(sign)) { // 跳转到详情页面 request.getRequestDispatcher("/detail.jsp").forward(request,response); }*/ // 或者优化 // 注意 无论是转发还是重定向都是从 “/” 开始的 // request.getParameter()拿到的是 f=edit,还是f=detail 就是跳转到的哪个页面 //<a href="<%=request.getContextPath()%>/dept/detail?f=edit&dno=<%=dept.getDeptno()%>">修改</a> //<a href="<%=request.getContextPath()%>/dept/detail?f=detail&dno=<%=dept.getDeptno()%>">详情</a> String forward = "/" + request.getParameter("f") + ".jsp"; request.getRequestDispatcher(forward).forward(request, response); } /** * 连接数据库,查询所有的部门信息,将部门信息收集好,然后跳转到JSP页面展示 * * @param request * @param response */ private void doList(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("UTF-8"); // 设置获取的的信息的编码集 Connection connection = null; PreparedStatement preparedStatement = null; ResultSet resultSet = null; // 创建一个集合List 存储查询到的信息 List<Dept> depts = new ArrayList<Dept>(); try { // 连接数据库,查询所有部门: // 1. 注册驱动,获取连接 connection = DBUtil.getConnection(); // 2. 获取操作数据库对象,预编译sql语句 String sql = "select depton as det,dname,loc from dept"; // 在mysql中测试一下是否正确 preparedStatement = connection.prepareStatement(sql); // 3. 执行sql语句 resultSet = preparedStatement.executeQuery(); // 4. 处理查询结果集 while (resultSet.next()) { String det = resultSet.getString("det"); // 有别名要使用别名 String dname = resultSet.getString("dname"); String loc = resultSet.getString("loc"); Dept dept = new Dept(det, dname, loc); // 将部门对象放到List集合当中 depts.add(dept); } } catch (SQLException e) { throw new RuntimeException(e); } finally { // 5. 关闭资源 DBUtil.close(connection, preparedStatement, resultSet); } // 查询到数据,将数据提交给 list.jsp 显示数据 // 将集合存储的数据放到请求域当中,用于其他Servlet 使用 jsp 也是Servelt request.setAttribute("depList", depts); // 转发(注意不要重定向),重定向无法共用 request 请求域当中的数据 // 转发路径,/ 默认是从 web 目录开始找的 request.getRequestDispatcher("/list.jsp").forward(request, response); } }
UserServlet
package com.RainbowSea.servlet; import com.RainbowSea.DBUtil.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.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; @WebServlet({"/user/login", "/user/exit"}) public class UserServlet extends HttpServlet { @Override protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 获取到浏览器地址栏上的URL路径 String servletPath = request.getServletPath(); if ("/user/login".equals(servletPath)) { doLogin(request, response); } else if ("/user/exit".equals(servletPath)) { doExit(request, response); } } private void doExit(HttpServletRequest request, HttpServletResponse response) throws IOException { } protected void doLogin(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 一个用户登录验证的方式:验证用户名和密码是否正确 // 获取用户名和密码 // 前端提交是数据是:username=111&password=fads // 注意:post 提交的数据是在请求体当中,而get提交的数据是在请求行当中 boolean success = false; // 标识登录成功 String username = request.getParameter("username"); String password = request.getParameter("password"); String exempt = request.getParameter("exempt"); // 连接数据库验证用户名和密码 Connection connection = null; PreparedStatement preparedStatement = null; ResultSet resultSet = null; try { // 1. 获取连接,注册驱动 connection = DBUtil.getConnection(); // 2. 获取操作数据对象,预编译sql语句, ? 占位符不要加,“”,'' 单双引号,成了字符串了,无法识别成占位符了。 String sql = "select username,password from t_user where username = ? and password = ?"; preparedStatement = connection.prepareStatement(sql); // 3. 填充占位符,真正执行sql语句 preparedStatement.setString(1, username); preparedStatement.setString(2, password); resultSet = preparedStatement.executeQuery(); // 4. 处理查询结果集 // 只有一条结果集 if (resultSet.next()) { // 登录成功 success = true; } } catch (SQLException e) { throw new RuntimeException(e); } finally { // 5. 关闭资源,最后使用的最先关闭, DBUtil.close(connection, preparedStatement, resultSet); } // 登录成功与否 if (success) { response.sendRedirect(request.getContextPath() + "/dept/list"); } else { // 失败,跳转到失败页面 response.sendRedirect(request.getContextPath() + "/error.jsp"); } } }
所以的 html 页面替换为了对应的 jsp
文件
add.html --> add.jsp
<%@page contentType="text/html; charset=utf-8" %> <!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> <form action="<%=request.getContextPath()%>/dept/save" 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.html --> detail.jsp
<%@ page import="com.RainbowSea.bean.Dept" %> <%@page contentType="text/html; charset=UTF-8" %> <% // 获取到请求域当中的数据, 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> 部门编号: <%=dept.getDeptno()%> <br> 部门名称: <%=dept.getDname()%><br> 部门位置: <%=dept.getLoc()%><br> <input type="button" value="后退" onclick="window.history.back()" /> </body> </html>
edit.html --> edit.jsp
<%@ page import="com.RainbowSea.bean.Dept" %> <%@page contentType="text/html; charset=UTF-8" %> <!DOCTYPE html> <html lang="en"> <% // 获取到存储到请求域当的 部门信息 Dept dept = (Dept) request.getAttribute("dept"); %> <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> <form action="<%=request.getContextPath()%>/dept/modify" method="post"> <!-- readonly 表示只读,不可修改的作用 --> 部门编号: <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="修改" /> </form> </body> </html>
error.html --> error.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ page session="false" %> <html> <head> <title>登录失败</title> </head> <body> 登录失败,密码或账号错误: <a href="<%=request.getContextPath()%>/index.jsp">请重新登录</a> </body> </html>
index.html --> index.jsp
<%@page contentType="text/html; charset=UTF-8" %> <%--表示访问 jsp 的时候不生成 session 对象--%> <%@page session="false" %> <%--该指令不是警用jsp内置对象当中的 session--%> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>欢迎使用OA系统</title> </head> <body> <%--<!--注意:对应前端的资源获取基本上都是要加项目名的,并且要"/"开始-->--%> <%--这种方式不好,将 项目名写死了,--%> <%--<a href="/servlet09/dept/list/">查看部门列表</a>--%> <%--使用request.getContextPath() 动态获取项目名的根路径带 / 的, 注意哪里可以加 空格,哪里不能加空格--%> <%--<a href="<%=request.getContextPath() %>/dept/list">查看部门列表</a>--%> <%--<%=request.getContextPath()%> <%–out.print(request.getContextPath()) 获取到该项目的根路径带有/的–%>--%> <h1>Login in</h1> <hr> <form action="<%=request.getContextPath()%>/user/login" method="post"> username: <input type="text" name="username" /><br> password: <input type="password" name="password" /> <br> 免十天登录 <input type="checkbox" name="exempt" value="true" /> <br> <input type="submit" value="login" /> </form> </body> </html>
list.html ---> list.jsp
<%@ page import="com.RainbowSea.bean.Dept" %> <%@ page import="java.util.List" %> <%--这是导包在jSP 当中,并翻译为import导入对于的报--%> <%@page contentType="text/html; charset=utf-8" %> <!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> <script type="text/javascript"> function del(dno) { // 弹出确认框,用户点击确定,返回true,点击取消返回false var ok = window.confirm("亲,删了不可恢复哦!"); if (ok) { // 发送请求进行删除数据的操作 // 在js代码当中如何发送请求给服务器 // // document.location.href="请求路径" // document.location = "请求路径" // window.location.href = "请求路径" // window.location = "请求路径" // 注意是根据所传的部门编号删除数据的 document.location.href = "<%=request.getContextPath()%>/dept/delete?deptno=" + dno; } } </script> <body> <h1 align="center">部门列表</h1> <%--显示登录的用户名信息--%> <h3><%=session.getAttribute("username")%></h3> <%--注意这里使用的是 jsp 内置的 session 对象所以不可以,不要把 session 禁用了。--%> <a href="<%=request.getContextPath()%>/user/exit">安全退出系统(手动清除 session 会话信息)</a> <table border="1px" align="center" width="50%"> <tr> <th>序号</th> <th>部门编号</th> <th>部门名称</th> </tr> <% // 获取到 request 域当中的数据,并取出来,我们知道是什么类型的数据,直接强制转换 List<Dept> depList = (List<Dept>) request.getAttribute("depList"); int i = 0; // 循环遍历List,取出数据 for (Dept dept : depList) { // 在后台输出测试 //System.out.println(dept.getDname()); // 在浏览器显示 测试 //out.write(dept.getDname() + "<br>"); %> <%-- 浏览器显示测试--%> <%-- <%=dept.getDname()%>--%> <tr> <td><%=++i%> </td> <td><%=dept.getDeptno()%> </td> <td><%=dept.getLoc()%> </td> <td> <!-- javascript:void(0)保持<a>超链接的格式,同时不会发生跳转 我只是希望用户点击该超链接的时候执行一段js代码,不进行页面的跳转 --> <a href="javascript:void(0)" onclick="del(<%=dept.getDeptno()%>)">删除</a> <%-- <a href="<%=request.getContextPath()%>/dept/detail?f=m&dno=<%=dept.getDeptno()%>">修改</a>--%> <%-- <a href="<%=request.getContextPath()%>/dept/detail?f=d&dno=<%=dept.getDeptno()%>">详情</a>--%> <%-- 这里的修改,和详情都是需要 部门编号,我们可以进行一个统一处理,通过所传的不同的 f= 值作为 标记,用于服务器端的 Servlet 判断转发处理到的--%> <%-- 或者是--%> <a href="<%=request.getContextPath()%>/dept/detail?f=edit&dno=<%=dept.getDeptno()%>">修改</a> <a href="<%=request.getContextPath()%>/dept/detail?f=detail&dno=<%=dept.getDeptno()%>">详情</a> </td> </tr> <% } %> </table> <hr> <a href="<%=request.getContextPath()%>/add.jsp">新增部门</a> </body> </html>
当前的oa应用存在的问题:
-
任何一个用户都可以访问这个系统,都可以对这个系统当中的数据进行增删改这些危险的操作。我只想让合法的用户去使用这个系统,不合法的用户不能访问这个系统,怎么办?
- 加一个登录功能。登录成功的可以访问该系统,登录失败不能访问。
-
实现登录功能:
- 步骤1:数据库当中添加一个用户表:t_user
- t_user表当中存储的是用户的登录信息,最基本的也包括:登录的用户名和登录的密码。
- 密码一般在数据库表当中存储的是密文。一般不以明文的形式存储。(这里先使用明文方式。)
- 向t_user表中插入数据。
- 步骤2:再实现一个登录页面。
- 登录页面上应该有一个登录的表单。有用户名和密码输入的框。
- 用户点击登录,提交表单,提交用户名和密码。form是post方式提交。
- 步骤3:后台要有一个对应的Servlet来处理登录的请求。
- 登录成功:跳转到部门列表页面。
- 登录失败:跳转到失败的页面。
- 步骤4:再提供一个登录失败的页面。
- 步骤1:数据库当中添加一个用户表:t_user
-
登录功能实现了,目前存在的最大的问题:
- 这个登录功能目前只是一个摆设,没有任何作用。只要用户知道后端的请求路径,照样可以在不登录的情况下访问。
- 这个登录没有真正起到拦截的作用。怎么解决?
解决方式:通过 session 会话机制,判断是否为同一个用户,并且登录过。因为涉及到篇幅内容的过多,所以详细内容大家可以移步至:🔜🔜🔜 B/S结构系统的会话机制(session)_ChinaRainbowSea的博客-CSDN博客
8. 补充:
包名 bean 是什么意思?
- javabean(java的logo是一杯冒着热气的咖啡。javabean被翻译为:咖啡豆)
- java是一杯咖啡,咖啡又是由一粒一粒的咖啡豆研磨而成。
- 整个java程序中有很多bean的存在。由很多bean组成。
- 什么是javabean?实际上javabean你可以理解为符合某种规范的java类,比如:
- 有无参数构造方法
- 属性私有化
- 对外提供公开的set和get方法
- 实现java.io.Serializable接口
- 重写toString
- 重写hashCode+equals
- ....
- javabean其实就是java中的实体类。负责数据的封装。
- 由于javabean符合javabean规范,具有更强的通用性。
举例:如下是一个简单的 bean 类关于 dept 类对应数据库表当中的 dept 数据表的字段信息。
package com.RainbowSea.bean; import java.io.Serializable; import java.util.Objects; public class Dept implements Serializable { private static final long serialVersionUID = 1L; 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 boolean equals(Object o) { if (this == o) return true; if (!(o instanceof Dept)) return false; Dept dept = (Dept) o; return Objects.equals(getDeptno(), dept.getDeptno()) && Objects.equals(getDname(), dept.getDname()) && Objects.equals(getLoc(), dept.getLoc()); } @Override public int hashCode() { return Objects.hash(getDeptno(), getDname(), getLoc()); } @Override public String toString() { return "Dept{" + "deptno='" + deptno + '\'' + ", dname='" + dname + '\'' + ", loc='" + loc + '\'' + '}'; } }
9. 总结:
-
JSP 的本质:一个index_jsp类就是一个Servlet类。总结就是:一个 index.jsp 文件会被翻译为一个 index_jsp.java 类,而 该 翻译的 index_jsp.java类是
继承
了 org.apache.jasper.runtime.HttpJspBase 类的,而 org.apache.jasper.runtime.HttpJspBase 类 是继承了 HttpServlet 类的。这下逻辑就清晰了,JSP 就是 Servlet
。 -
JSP的基础语法:
- 在JSP文件中直接编写文字,翻译的是在
service()
方法体内的,翻译到 servlet类的 service()方法的 out.write("翻译到这里") ,直接翻译到双引号里,被java程序当做普通字符串打印输出到浏览器。 - JSP的注释:
<%--JSP的专业注释,不会被翻译到java源代码当中。--%> 不会翻译对应的信息。该注解当中的内容会被忽略而不翻译的。 <!--这种注释属于HTML的注释,这个注释信息仍然会被翻译到java源代码当中,不建议。--> 翻译到HTML当中作为 html 的 注解存在。
- <% %> 在这个
<% java语句; %>
符号当中编写的被视为java程序,被翻译到Servlet类的service方法体
内部。注意方法体当中不可以编写什么内容。同时因为编写的是 Java代码需要遵从 Java的语法。
<% // java语句; // 在这里编写Java程序,编写 Java语句。 // 既然是编写 Java语句,自然要遵循Java的语法规范。 // 在这里编写的java语句,会被翻译到 service() 方法体内 %>
-
<%! %>
在这个符号当中编写的java程序会自动翻译到service方法之外。简单的说就是: Servlet 类当中,service() 方法外,那不就是 成员变量,方法,代码块了。
<%! // 在这个符号当中编写的java程序会自动翻译到service方法之外。 // service() 方法体之外是什么,自然就是在 Servlet 类里面了。 %>
- <%= %>向浏览器前端输入 Java变量值,而不是变量名。
<% String name = “jack”; out.write("name = " + name); %>
- 在JSP文件中直接编写文字,翻译的是在
-
JSP指令的作用:指导JSP的翻译引擎如何工作(指导当前的JSP翻译引擎如何翻译JSP文件。)主要说明的是 page 指令。指令的格式:
<%@指令名 属性名=属性值 属性名=属性值 属性名=属性值....%> // 这里我们只学习 page 指令所以是: <%@page 属性名=属性值 属性名=属性值 属性名=属性值....%>
- JSP 的九大内置对象:所谓的JSP的九大内置对象指的是:在
service()
方法体当中的局部的对象变量,service() 方法体外是无法使用的。 - JSP 第一次访问速度慢的原因:翻译为 java,并编译java,生成 class 文件
-
JSP文件的扩展名必须不是
xxx.jsp后缀吗,不是的,.jsp文`件的扩展名是可以配置的。
<servlet-mapping> <servlet-name>jsp</servlet-name> <url-pattern>*.jsp</url-pattern> <url-pattern>*.jspx</url-pattern> </servlet-mapping>
- 开发JSP的最高境界: 眼前编写的是JSP代码,但是我们心里,脑袋里面当中呈现的是 java代码。
10. 最后:
限于自身水平,其中存在的错误,希望大家给予指教,韩信点兵——多多益善,谢谢大家,江湖再见,后悔有期
这篇关于JSP 的本质原理解析:"编写的时候是JSP,心里想解读的是 java 源码"的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-15JavaMailSender是什么,怎么使用?-icode9专业技术文章分享
- 2024-11-15JWT 用户校验学习:从入门到实践
- 2024-11-15Nest学习:新手入门全面指南
- 2024-11-15RestfulAPI学习:新手入门指南
- 2024-11-15Server Component学习:入门教程与实践指南
- 2024-11-15动态路由入门:新手必读指南
- 2024-11-15JWT 用户校验入门:轻松掌握JWT认证基础
- 2024-11-15Nest后端开发入门指南
- 2024-11-15Nest后端开发入门教程
- 2024-11-15RestfulAPI入门:新手快速上手指南