前置
系统架构有哪些?
- B/S架构 (浏览器/服务器)
- 如baidu.com,升级方便,只需要升级服务端即可,不需要安装特定的客户端软件,但是速度慢,体验差,不安全(所有数据都在服务器上)
- C/S架构 (客户端/服务器)
- 如QQ电脑端,需要安装客户端软件,速度快,安全,体验好,服务器压力小,但是升级维护比较麻烦
没有哪个好哪个不好之说,不同的系统架构在不同的业务场景下有不同的使用场景
web服务器软件有哪些?
- Tomcat (WEB服务器) java语言写的,运行必须有jre
- jetty (WEB服务器)
- JBOSS (应用服务器)
- WebLogic (应用服务器)
- WebSphere (应用服务器)
应用服务器和WEB服务器的关系?
- 应用服务器实现了JavaEE的所有规范(13种)
- WEB服务器只实现了JavaEE的Servlet + JSP两个核心规范
- 可以看出应用服务器是包含WEB服务器的
Tomcat
-
Tomcat的目录
- bin 存放Tomcat服务器的命令文件,如启动,关闭Tomcat
- conf 配置文件存放目录,如server.xml可以配置端口号,默认8080
- lib Tomcat服务器的核心程序,都是jar包,里面都是class文件
- logs 日志目录
- temp Tomcat服务器的临时目录,放临时文件
- webapps 该目录存放大量的webapp (web application: web应用)
- work 用来存放jsp文件翻译之后的java文件及编译之后的class文件
-
启动Tomcat
bin目录下有个 startup.bat ,通过命令行输入startup.bat可以启动Tomcat服务器
-
这里的bat文件是Windows专用的,bat是批处理文件,可以编写大量的windows的dos命令,然后执行bat就相当于执行dos命令
-
startup.sh 在Windows中无法执行,实在Linux系统中使用的,里面有大量的shell命令,然后执行sh文件就相当于执行shell命令
命令行启动的前提是环境变量里有
-
JAVA_HOME 值是jdk的根目录
-
CATALINA_HOME 值是tomcat的根目录
-
有根目录后要在Path里写 %某某_HOME%\bin
-
-
关闭Tomcat
通过命令行输入shutdown.bat可以关闭Tomcat服务器,不要输入shutdown,防止Windows关机
-
如何测试是否成功启动Tomcat
打开浏览器,输入
http://127.0.0.1:8080或者http://localhost:8080,8080是tomcat的端口号
实现一个基本的web应用(没有用到java小程序)
-
找到CATALINA_HOME(tomcat的根目录)的webapps目录
所有的webapp都要放到webapps目录下,否则tomcat服务器找不到
-
在webapps目录下建一个子目录,起个名字就是web应用的名字
-
在建好的目录下放入资源文件,如index.html
-
启动Tomcat服务器
-
这种网页是静态网页
浏览器的访问路径也可以是一个web应用里的java小程序,让它返回一组数据等等,一个servlet小程序对应一个浏览器上输入的路径
在B/S架构中,我们开发的java小程序连接了浏览器和数据库,写的webapp遵循servlet规范,使得其在Tomcat,jetty,JBOSS,WebLogic,WebSphere上都能用,由此引出下面的servlet规范,规范的作用: 使得WEB Server和webapp解耦合(sun公司制定)
浏览器和WEB Server之间的协议是HTTP超文本传输协议(w3c制定)
webapp和DB Server之间有一套规范: JDBC规范(sun公司指定)
作为java程序员,只需要编写一个类,实现servlet接口,将编写的类配置到配置文件中,在配置文件中指定请求路径和类名的关系,tomcat会根据反射机制,从浏览器发过来的路径中找到对应的类名,去创建类
Servlet
开发一个B/S结构的系统,其实就是开发网站,Java做WEB开发就是JavaWEB开发,JavaWEB开发最核心的规范:Servlet(Server Applet:服务器端的java小程序)
JavaEE包括很多种规范(13种),Servlet就是JavaEE规范之一,学Servlet实际上在学java
根据servlet规范,文件放的位置,文件的命名,目录结构,java小程序的位置等等都得符合规范,这样的webapp才能在各种服务器软件中部署,规范包括
- 规范了哪些接口
- 规范了哪些类
- 规范了一个web应用中应该有哪些配置文件
- 规范了一个web应用中配置文件的名字
- 规范了一个web应用中配置文件存放的路径
- 规范了一个web应用中配置文件的内容
- 规范了一个合法有效的web应用它的目录结构应该是怎样的
- ......
手动开发一个带有Servlet(java小程序)的webapp
步骤
-
第一步 Tomcat根目录里在webapps目录下新建一个目录,起名(webapp的名字),比如银行项目,可以创建一个目录bak,办公系统可以创建一个目录oa,注意:该目录就是这个webapp的根
-
第二步 在webapp的根下,新建一个目录
WEB-INF,这个名字是Servlet规定好的,必须的 -
第三步 在WEB-INF目录下,新建一个目录
classes,这个名字是Servlet规定好的,必须的,全部小写,这个目录下一定存放的是Java程序编译之后的class文件(字节码文件) -
第四步 在WEB-INF目录下,新建一个目录
lib,注意,这个目录不是必须的,但如果一个webapp需要第三方的jar包的话,这个jar包要放到这个lib目录下,这个目录的名字也不能随意编写,必须是全部小写的lib,例如Java语言连接数据库需要数据库的驱动jar包,那么这个jar包就一定要放到lib目录下,这是Servlet规范中规定的。 -
第五步 在WEB-INF目录下,新建一个文件
web.xml,注意,这个文件是必须的,这个文件名必须叫做web.xml,这个文件必须放在这里,一个合法的webapp里web.xml文件是必须的,这个web.xml文件就是一个配置文件,在这个配置文件中描述了请求路径和Servlet类之间的对照关系,这个文件最好从其他的webapp中拷贝,最好别手写,没必要,复制粘贴,内容大概是这样<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <servlet> <servlet-name>studentServlet</servlet-name> <servlet-class>com.javastudy.javaweb.servlet.StudentServlet</servlet-class> 3.该servlet的包名 </servlet> <servlet-mapping> <servlet-name>studentServlet</servlet-name> 2.浏览器请求的路径对应的servlet <url-pattern>/servlet/student</url-pattern> 1.浏览器请求的路径 </servlet-mapping> </web-app> -
第六步 编写一个Java小程序,这个小Java程序也不能随意开发,这个小Java程序必须实现Servlet接口(注意引入的包版本)
- 这个Servlet接口不在JDK当中,因为Servlet不是JavaSE了,Servlet属于JavaEE,是另外的一套类库
- Servlet接口(Servlet.class文件)是Oracle提供的,(最原始的是sun公司提供的)
- Servlet接口是JavaEE的规范中的一员
- Tomcat服务器实现了Servlet规范,所以Tomcat服务器也需要使用Servlet接口,Tomcat服务器中应该有这个接口,Tomcat服务器的CATALINA_HOME\lib目录下有一个servlet-api.jar,解压这个servlet-api.jar之后,你会看到里面有一个Servlet.class文件
- 注意,编写这个Java小程序的时候,Java源代码你愿意在哪里就在哪里,位置无所谓,你只需要将Java源代码编译之后的class文件放到classes目录下即可
public class HelloServlet implements Servlet { public void init(ServletConfig config) throws ServletException { } // 不用自己写main方法了,创建HelloServlet对象,调用service方法都是tomcat在做,我们只需要完成这个方法就行了 public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException { System.out.println("My first servlet!"); } public void destroy() { } public String getServletInfo() { return ""; } public ServletConfig getServletConfig() { return null; } } -
第七步 编译我们写的java小程序,想javac编译通过,就注意将tomcat/lib下的servlet-api.jar添加到CLASSPATH中,编译得到字节码文件
-
第八步 将得到的xxx.class字节码文件拷贝到WEB-INF\classes目录下
-
第九步 在web.xml文件中编写配置信息,让"请求路径"和"servlet类名"关联在一起,这一步的专业术语叫:在web.xml文件中注册servlet类
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd" version="5.0" metadata-complete="true"> <!-- servlet描述信息 --> <!-- 任何一个servlet都对应一个servlet-mapping --> <servlet> <servlet-name>xxx</servlet-name> <!-- 这个位置必须是带有包名的全限定类名 --> <servlet-class></servlet-class> </servlet> <!-- servlet映射信息 --> <servlet-mapping> <!-- 名字和上面的一样,可以随便写 --> <servlet-name>xxx</servlet-name> <!-- 这里需要一个路径,唯一要求是必须以 / 开始,可以随便写 --> <url-pattern>/adf/adf/adf/sdfs/aaa</url-pattern> </servlet-mapping> </web-app> -
第十步 启动Tomcat服务器,打开浏览器在地址栏输入url
http://localhost:8080/项目名/adf/adf/adf/sdfs/aaa,此时就可以在tomcat控制台看到输出的My first servlet!
怎样将一个信息输出到浏览器上?
需要使用ServletResponse接口:response
PrintWriter out = response.getWriter();
out.print("Hello servlet~");
这是一个输出流,负责将字符串输出到浏览器
这个流不需要我们刷新,也不需要我们关闭,都由tomcat来负责
也可以向浏览器响应一段html代码
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.print("<h1>hello</h1>");
一个合法的webapp目录结构如下
注意html文件只能放到WEB-INF目录外面
webapproot
|-------WEB-INF
|-------classes (放字节码文件)
|-------lib (第三方jar包)
|-------web.xml (注册servlet)
|-------html
|-------css
|-------js
|-------image
...
在Servlet中连接数据库
直接在Servlet的service方法中编写Java的JDBC代码即可,注意将驱动jar包放到lib目录
总结
浏览器发动请求,到最终的服务器调用servlet中的方法,大致是怎样的过程?
- 用户输入url,或点击查链接
http://localhost:8080/项目名/adf/adf/adf/sdfs/aaa - 然后tomcat收到请求,截取到路径
/项目名/adf/adf/adf/sdfs/aaa - tomcat找到项目
- tomcat服务器在web.xml文件中查找
/adf/adf/adf/sdfs/aaa对应的servlet - tomcat通过反射机制,创建对应servlet的对象
- tomcat服务器调用该对象的service方法
JavaEE8版本升级之后的"JavaEE9",不再是"JavaEE9"这个名字了,叫做JakartaEE9,JavaEE8的时候对应的Servlet类名是 javax.servlet.Servlet JakartaEE9的时候对应的Servlet类名是 jakarta.servlet.Servlet(包名都换了) 如果你之前的项目还是在使用javax.servlet.Servlet,那么你的项目无法直接部署到Tomcat10+版本上,你只能部署到Tomcat9-版本上,在Tomcat9以及Tomcat9之前的版本中还是能够识别javax.servlet这个包。
用IDEA开发Servlet程序
-
新建一个空工程,名为javaweb
-
新建模块(文件-->新建-->新模块)
- 这里新建的一个普通的javase类型的模块,名为servlet01,会自动放在javaweb工程下
- 这时servlet01模块就是一个普通的javase类型的模块,需要将它变成javaee模块(让它符合servlet规范,变成一个webapp模块),在模块上鼠标右键选择添加框架支持,选择JavaEE里的Web应用程序,并勾选上创建web.xml选项,点击确认
- 选择webapp支持后,IDEA会自动生成一个符合Servlet规范的webapp目录结构,此时除了src文件夹还有个web文件夹,这个web文件夹就是之前手动写的例子的webapps里面的项目根目录的映射,即web文件夹就是webapp的根
-
此时就可以写一个servlet了,在src文件夹里写各种servlet类去实现Servlet接口
-
但是,此时会发现没有Servlet.class接口,因为此时是一个普通的JavaSE的模块,下方的外部库中只有jdk,而jdk里是没有Servlet接口的,Servlet属于JavaEE,这时就需要去tomcat安装目录下找lib文件夹,再找到里面的jsp-api.jar和servlet-api.jar,将他们加入到IDEA中去,(文件-->项目结构-->模块-->模块名-->依赖-->加号-->jar或目录-->添加tomcat安装目录下的lib文件夹里面的jsp-api.jar和servlet-api.jar-->确定),这时候就可以导入jakarta.servlet.Servlet包,成功地实现Servlet类了
-
实现Servlet的五个方法,将业务代码写在service方法中(包含连接数据库的操作)
package com.javastudy.javaweb.servlet; import jakarta.servlet.*; import java.io.IOException; import java.io.PrintWriter; import java.sql.*; public class StudentServlet implements Servlet { @Override public void init(ServletConfig servletConfig) throws ServletException { } @Override public ServletConfig getServletConfig() { return null; } @Override public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException { servletResponse.setContentType("text/html"); PrintWriter out = servletResponse.getWriter(); Connection connection = null; ResultSet resultSet = null; PreparedStatement preparedStatement = null; try { Class.forName("com.mysql.cj.jdbc.Driver"); String user = "root"; String password = "root"; String url = "jdbc:mysql://localhost:3306/servletstudy"; connection = DriverManager.getConnection(url, user, password); String sql = "select * from student"; preparedStatement = connection.prepareStatement(sql); resultSet = preparedStatement.executeQuery(); while (resultSet.next()) { String id = resultSet.getString("id"); String name = resultSet.getString("name"); out.print(id + name); } } catch (Exception e) { e.printStackTrace(); } finally { if (resultSet != null) { try { resultSet.close(); } catch (SQLException e) { throw new RuntimeException(e); } } if (preparedStatement != null) { try { preparedStatement.close(); } catch (SQLException e) { throw new RuntimeException(e); } } if (connection != null) { try { connection.close(); } catch (SQLException e) { throw new RuntimeException(e); } } } } @Override public String getServletInfo() { return null; } @Override public void destroy() { } } -
在WEB-INF目录下创建一个lib文件夹,将连接数据库的驱动jar包加入到lib目录下
-
在web.xml文件里完成该servlet类的注册(浏览器不同的请求路径会和不同的servlet对应起来)
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <servlet> <servlet-name>studentServlet</servlet-name> <servlet-class>com.javastudy.javaweb.servlet.StudentServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>studentServlet</servlet-name> <url-pattern>/servlet/student</url-pattern> </servlet-mapping> </web-app> -
给index.html(放在WEB-INF外面)页面里写一个超链接,只要用户点了超链接就会跳到对应路径,也就会执行相应的servlet
<!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>Document</title> </head> <body> <!--这里的路径是 项目名/xml配置的路径--> <a href="/stu/servlet/student">点我试试</a> </body> </html> -
让IDEA工具关联Tomcat服务器,关联的过程中将webapp部署到Tomcat服务器中,点击运行按钮旁边的添加配置,点击加号,选择Tomcat服务器-本地,服务器-->点击应用程序服务器的配置,选择本地的tomcat_home,再选择合适的jre,再点击部署-->加号-->模块名,再看下面有个应用程序上下文,自定义项目名,点击应用
-
启动tomcat服务器,点击右上角的启动或debug启动tomcat,开发过程中建议使用debug模式启动
关于WEB-INF目录
在WEB-INF目录下新建了一个文件:welcome.html打开浏览器访问http://localhost:8080/servlet07/WEB-INF/welcome.html出现了404错误,注意,放在WEB-INF目录下的资源是受保护的,在浏览器上不能够通过路径直接访问,所以像HTML,CSS,JS,image等静态资源一定要放到WEB-INF目录之外
Servlet对象的生命周期
-
生命周期就是servlet对象的创建到销毁的过程
-
servlet对象的创建销毁,对象上方法的调用,javaweb程序员是无权干预的,由tomcat服务器(web server)全权负责
-
Tomcat服务器(web server)又被称为WEB容器,WEB容器来管理servlet对象的死活
-
我们自己new出来的Servlet对象是不受web容器管理的,web容器创建的servlet对象是放在集合当中的,只有该集合中的对象才能被web容器管理,所以自己创建的servlet对象是不受管理的
-
默认情况下服务器在启动时servlet对象并不会被实例化,用户在请求时才会创建实例对象,特殊情况下如何让服务器启动时创建servlet对象呢?可以在web.xml的
<servlet>里加一个<load-on-startup>2<load-on-startup>标签,中间的数字表示多个servlet启动时的优先级
- 用户在发送第一次请求时Servlet对象被实例化,马上执行init方法,且只会被调用1次,init方法被执行后又马上执行了service方法
- 发送第二次及后面的请求,只会执行service方法,此时的对象还是第一次创建的对象,可见servlet对象是单实例的,但是Servlet类并不符合单例模式,我们称之为假单例
- init方法只在用户发送第一次请求时被执行,只执行一次
- 只要用户发送请求,service方法必然会被Tomcat调用
- 关闭服务器时,destroy方法只会被调用一次,执行destroy方法时,servlet对象还在,可以在destroy方法里写一些关闭流,关闭连接等操作
servlet 对象就像人的一生
- 无参构造方法执行时,标志着出生了
- init方法执行时,标志着正在接受教育
- service方法执行时,开始工作了
- destroy方法执行时,标志着临终说遗言了
关于Servlet类中的方法执行了几次?
- 构造方法执行了一次
- init方法只执行了一次
- service方法用户发送几次请求就执行几次
- destroy方法只执行一次
注意:不建议在servlet类中写构造器,写无参构造器没事,只要一写有参构造器直接报错500,500一般是因为服务器的java程序出现了异常,人家给的规范中的init方法就是让你用来代替构造器写初始化代码的,把想写在构造器中的代码写道init方法中就好了,瞎写构造方法容易导致Servlet对象无法被实例化出来
Servlet类中,只有Service方法时最常用的,别的很少用,但是只要实现了Servlet接口就必须实现5个方法,全写上代码很丑,利用适配器模式设计一个抽象的适配器类,让该类去实现Servlet接口,它里面实现了5中方法,但是,只给service方法设置成了抽象方法,这时Servlet类就不要实现Servlet接口了,变为去继承这个适配器类且只用重写Service方法就行,这样一来Servlet方法就简洁很多,只有service方法了
好消息是,这个抽象的适配器类在Jakarta.servlet包里有,名字就叫GenericServlet,截至目前,以后写的servlet就去继承它就行了,不要再直接实现Servlet接口了
ServletConfig
ServletConfig是什么?是Servlet规范中的一员,是一个接口
现在写一个servlet,继承GenericServlet后,可以调用
ServletConfig servletConfig = this.getServletConfig();
得到一个servletConfig对象,ServletConfig是一个接口,谁去实现了这个接口呢?
是org.apache.catalina.core.StandardWrapperFacade,可见,Tomcat服务器实现了ServletConfig接口
这里把Tomcat服务器换成jetty服务器,包名就不一样了,但是不同服务器都实现了ServletConfig这个规范
一个servlet对象中有一个ServletConfig对象,一对一
Tomcat服务器(WEB服务器)在创建Servlet对象时同时也创建了ServletConfig对象,也就是用户发送第一次请求时
在web.xml文件中,每个servlet对象的<servlet>标签中的配置信息,也就是下面的这一堆,会被包装到一个ServletConfig对象(配置信息对象)中
<servlet>
<servlet-name>studentServlet</servlet-name>
<servlet-class>com.javastudy.javaweb.servlet.StudentServlet</servlet-class>
</servlet>
ServletConfig对象有啥用呢?首先看看xml文件的<servlet>标签里还可以写啥
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"
version="5.0">
<servlet>
<servlet-name>s1</servlet-name>
<servlet-class>com.javastudy.javaweb.servlet.Servlet01</servlet-class>
<init-param>
<param-name>driver</param-name>
<param-value>com.mysql.cj.jdbc.Driver</param-value>
</init-param>
<init-param>
<param-name>url</param-name>
<param-value>jdbc:mysql://localhost:3306/servletstudy</param-value>
</init-param>
<init-param>
<param-name>user</param-name>
<param-value>root</param-value>
</init-param>
<init-param>
<param-name>password</param-name>
<param-value>root</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>s1</servlet-name>
<url-pattern>/test</url-pattern>
</servlet-mapping>
</web-app>
可以看到,<servlet>标签里还可以写<init-param>标签,可以定义一些初始化的参数,而这些参数将会被Tomcat封装到ServletConfig对象中,这时就可以通过ServletConfig对象身上的两个方法获取到这些初始化参数信息:
getInitParameterNames() 获取所有初始化参数的name,返回一个集合,遍历集合就行了
getInitParameter(初始化参数的名字) 传入初始化参数的名字,返回初始化参数的值
除此之外还有
getServletContext() 获取servletContext对象
getServletName() 获取servlet的name
想调用以上方法,可以不用获取ServletConfig对象,直接this调用也行,因为GenericServlet实现了ServletConfig接口
ServletContext对象
ServletContext对象又被称为应用域对象
ServletContext是什么?
context是上下文的意思,ServletContext对象其实对应的其实就是整个web.xml文件
举个例子,50个学生每一个都是一个Servlet,这50个学生在同一个教室,则ServletContext对象就是教室,ServletContext对象中的数据,所有servlet一定是共享的
是个接口,是Servlet规范中的一员,Tomcat服务器实现了ServletContext接口
ServletContext对象在web服务器启动的时候有Tomcat创建,在服务器关闭的时候销毁
Tomcat是一个容器,里面可以放多个webapp,对于一个webapp来说ServletContext对象只有一个
通过调用ServletConfig对象上的getServletContext方法获取ServletContext对象,直接this调用也行
在web.xml文件中,可以加<context-param>标签,将全局参数写在里面
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"
version="5.0">
<!-- 以下<context-param>中的是全局初始化信息,所有的servlet共享,里面的配置信息可以在servletContext对象中获取 -->
<context-param>
<param-name>msg1</param-name>
<param-value>666</param-value>
</context-param>
<context-param>
<param-name>msg2</param-name>
<param-value>888</param-value>
</context-param>
<context-param>
<param-name>msg3</param-name>
<param-value>999</param-value>
</context-param>
<!-- 分界线 -->
<!-- 以下的是每个servlet单独的配置 -->
<servlet>
<servlet-name>servlet01</servlet-name>
<servlet-class>com.javastudy.javaweb.servlet.Servlet01</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>servlet01</servlet-name>
<url-pattern>/s1</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>servlet02</servlet-name>
<servlet-class>com.javastudy.javaweb.servlet.Servlet02</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>servlet02</servlet-name>
<url-pattern>/s2</url-pattern>
</servlet-mapping>
</web-app>
这时ServletContext对象就派上用场了,它可以获取<context-param>中的全局参数,并且有方法可以读出其中的值ServletContext对象有哪些方法?
getInitParameterNames() 获取配置的name
getInitParameter(name) 根据name获取配置的值
getContextPath() 获取该项目的根路径,因为在java源代码当中有一些地方可能会需要应用的根路径,这个方法可以动态获取应用的根路径
getRealPath(String path) 里面传入一个文件路径,返回文件的绝对路径
log(Sting msg) 可以记录日志,记到tomcat根目录的log目录下,参数也可以再加一个异常类型
ServletContext对象还有一个名字:应用域(后面还会有请求域,会话域等等),如果一份数据是所有用户共享的,且数据量很小且很少修改,可以将其放入到servletContext对象中,为什么是共享的?因为是该对象一个webapp只有一个,为什么数据量要求要小?因为这个对象的生命周期比较长,从服务器开启到关闭,太占堆内存了影响服务器性能,为什么是很少修改的?因为共享的数据涉及到线程安全问题,所以放在servletContext对象中的数据一般都是只读的
数据量小,所有用户共享,又不修改,这样的数据放到ServletContext这个应用域当中,会大大提升效率,因为应用域相当于一个大缓存,放到缓存中的数据,下次再用的时候,不需要从数据库中再次获取,大大提升执行效率
说到缓存,有哪些缓存技术呢?
- 字符串常量池
- 整数型常量池-128~128,但凡是在这个范围当中的Integer对象不再创建新对象,直接从这个整数型常量池中获取,大大提升系统性能
- 数据库连接池(提前创建好N个连接对象,将连接对象放到集合当中,使用连接对象的时候,直接从缓存中拿,省去了连接对象的创建过程,效率提升
- 线程池(Tomcat服务器就是支持多线程的,所谓的线程池就是提前先创建好N个线程对象,将线程对象存储到集合中,然后用户请求过来之后,直接从线程池中获取线程对象,直接拿来用,提升系统性能
- 后期还会学习更多的缓存技术,例如:redis,mongoDB...
那么怎么增删取呢?
setAttribute(String name,Object value) 存
getAttribute(String name) 取
removeAttribute(String name) 删
但是,以后写的servlet不会继承GenericServlet,因为是B/S架构,这种系统属于HTTP超文本传输协议的,在Servlet规范当中,提供了一个类叫做HttpServlet,它是专门为HTTP协议准备的一个Servlet类,我们编写的Servlet类要继承HttpServlet(HttpServlet是HTTP协议专用的)使用HttpServlet处理HTTP协议更便捷,HttpServlet继承了GenericServlet
Servlet --> GenericServlet --> HttpServlet 爷孙三代
Tomcat服务器是支持多线程的,Tomcat服务器是在用户发送一次请求,就新建一个Threads线程对象吗?当然不是,实际上是在Tomcat服务器启动的时候,会先创建好N多个线程Thread对象,然后将线程对象放到集合当中,称为线程池,用户发送请求过来之后,需要有一个对应的线程来处理这个请求,这个时候线程对象就会直接从线程池中拿,效率比较高,所有的WEB服务器,或者应用服务器,都是支持多线程的,都有线程池机制
HttpServlet
HTTP协议
w3c制定的超文本传输协议,不但可以传普通文本,还有流媒体如声音,视频,图片等
B/S互相通讯都需要遵守HTTP协议,HTTP协议包括请求协议(B-->S)和响应协议(S-->B)
HTTP请求协议(4部分)
- 请求行
- 请求方式(7种)
- GET(常用)
- POST(常用)
- DELETE
- PUT
- HEAD
- OPTIONS
- TRACE
- URI
- URI:统一资源标识符,代表网络中某个资源的名字,无法定位资源
- 它和URL有什么区别?
- URL是统一资源定位符,URL包括URI,如www.xx.com/servlet/ind… 是URL, /servlet/index.html是URI
- 协议版本号
- 请求方式(7种)
- 请求头
- 请求的主机
- 主机的端口
- 浏览器的信息
- 平台信息
- cookie等信息
- ...
- 空白行
- 用来分隔请求头和请求体
- 请求体
- 向浏览器发送的具体数据
HTTP响应协议(4部分)
- 状态行
- 协议版本号
- 状态码(HTTP协议规定的响应状态号,不同响应结果对应不同号码)
- 200表示请求响应成功,正常结束
- 404表示访问的资源不存在,通常是因为要么是你路径写错了,要么是路径写对了,但是服务器中对应的资源并没有启动成功,总之404是前端错误
- 405表示前端发送的请求方式和后端请求的处理方式不一致时发生,比如前端是POST请求,后端的处理方式按照GET方式进行处理时,发生405
- 500表示服务器端的程序出现了异常,一般认为是服务器端的错误导致的
- 以4开始的,一般是浏览器端的错误导致的
- 以5开始的,一般是服务器端的错误导致的
- 状态的描述信息
- ok 表示正常成功结束
- not found 表示资源找不到
- 响应头
- 响应的内容类型
- 响应的内容长度
- 响应的时间
- ...
- 空白行
- 用来分隔响应头和响应体
- 响应体
- 响应体就是响应的正文,这些内容是一个长的字符串,这个字符串被浏览器渲染,解释并执行,最终展示出效果
怎样向服务器发送POST请求?
目前为止,只有一种情况可以发送POST请求:form表单中的method属性值为method="post",其他一律是get请求:即在浏览器地址栏输入url,敲回车和点击超链接标签都是get,或者form表单没有method或method="get"默认都为get
GET和POST请求有什么区别?
-
get请求在发送数据时,数据会挂在URI的后面,并且在URI后添加了一个?,?后面的就是数据,如www.bilibili.com/video/BV1Z3…
-
post请求在发送数据时,数据会在请求体中发送,不会显示到地址栏上
-
不管发的是get或是post,发送的请求数据格式都是相同的,只是位置不同,get在请求行里,post在请求体里,且值都是name=value&name=value&name=value&name=value&name=value的格式
name和value是什么?name就是h5标签name属性中的值,value就是h5标签value属性中的值
-
get请求只能发送普通的字符串,并且有长度限制,具体限制长度没有明确规范
-
post可以发任何类型的数据,包括普通字符串,流媒体如视频,声音,图片,没有长度限制
-
get请求在W3C中是这样说的:get请求比较适合从服务器端获取数据,post请求比较适合向服务器端传送数据
-
get请求是绝对安全的,因为get只是为了从服务器获取,不会对服务器造成威胁,而post是相对危险的,因为post是向服务器提交数据,如果这些数据通过后门的方式进入到服务器,服务器是很危险的,另外post是为了提交数据,所以一般拦截请求时,都会拦截post
-
get本身是安全的,不要用错了地方说get不好
-
get请求支持缓存,任何一个get请求最终的"响应结果"都会被浏览器缓存起来,在浏览器缓存当中 实际上,你只要发送get请求,览器做的第一件事都是先从本地浏览器缓存中找,找不到的时候才会去服务器上获取,这种缓存机制目的是为了提高用户的体验,有没有这样一个需求:我们不希望get请求走缓存,怎么办?怎么避免走缓存?我希望每一次这个get请求都去服务器上找资源,不想从本地浏览器的缓存中取,只要每一次get请求的请求路径不同即可,可以在路径的后面添加一个每时每刻都在变化的"时间戳",这样每一次的请求路径都不一样,浏览器就不走缓存了
-
post请求不支持缓存,post请求之后,服务器"响应的结果"不会被浏览器缓存起来,因为这个缓存没有意义
-
GET请求和POST请求如何选择,什么时候使用GET请求,什么时候使用POST请求? 怎么选择GET请求和POST请求呢?衡量标准是什么呢?看你这个请求是想获取服务器端的数据,还是想向服务器发送数据,如果你是想从服务器上获取资源,建议使用GET请求,如果你这个请求是为了向服务器提交数据,建议使用POST请求,大部分的form表单提交,都是post方式,因为form表单中要填写大量的数据,这些数据是收集用户的信息,一般是需要传给服务器,服务器将这些数据保存/修改等,如果表单中有敏感信息,还是建议适用post请求,因为get请求会回显敏感信息到浏览器地址栏上(如密码信息),做文件上传或要传的数据不是普通文本,一定是post请求,其他情况都可以使用get请求
在说HttpServlet之前先看看设计模式
设计模式就是某个问题的某个固定的解决方案,可以被重复使用
有哪些设计模式?
- GoF设计模式:通常我们说的23种设计模式
- 单例模式
- 工厂模式
- 代理模式
- 门面模式
- 责任链设计模式
- 观察者模式
- 模块方法设计模式
- ...
- JavaEE设计模式
- DAO
- DTO
- VO
- PO
- pojo
- ...
- ...
其中,什么是模板方法设计模式?
在模板类的模板方法当中定义核心算法骨架,具体的实现步骤可以延迟到子类当中完成,模板类通常是一个抽象类,模板类当中的模板方法定义核心算法,这个方法通常是final的(但也可以不是final的),模板类当中的抽象方法就是不确定实现的方法,这个不确定怎么实现的事交给子类去做,HttpServlet类就是这样的
HttpServlet类中有doGet和doPost方法,我们就不用重写service方法了,直接重写doGet或者doPost,在里面写业务代码
我们写的servlet继承HttpServlet类之后,要是重写了service方法,再里面写业务代码,也行,但是会失去提示405错误的服务,因为正确的写法是不重写service方法,而是根据需求转而重写doGet或者doPost方法,只要前端发的请求比如get收到后一旦发现你所写的servlet类中没有doGet方法,就会去父类HttpServlet中执行doGet方法,而HttpServlet的doGet方法是用来提醒405的,405表示是前端的错误,发送的请求方式不对,和服务器不一致,不是服务器需要的请求方式,总之,只要HttpServlet类中的doGet或者doPost方法执行,必然405,但是不要为了避免报405而将doGet和doPost都写,这样虽然看着就不报405了,但是失去了405机制的意义
到现在,终于定下来了一个最终版的Servlet类的开发步骤:
- 编写一个servlet类,直接继承HttpServlet
- 重写doGet方法或者重写doPost方法,到底写谁,根据业务需求由javaweb程序员说了算
- 在doGet或doPost里写业务代码
- 将所写的servlet类配置到web.xml文件中去
- 部署
ServletTest.class
package com.javastudy.javaweb.servlet;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
public class ServletTest extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html");
PrintWriter writer = resp.getWriter();
writer.print("<h1>测试代码</h1>");
}
// 或者写doPost,和doGet二选一
}
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"
version="5.0">
<servlet>
<servlet-name>Servlet1</servlet-name>
<servlet-class>com.javastudy.javaweb.servlet.ServletTest</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Servlet1</servlet-name>
<url-pattern>/test</url-pattern>
</servlet-mapping>
</web-app>
关于web站点的欢迎页面
当访问webapp时没有指定任何资源路径,这时候会默认访问你设置好的欢迎页面
即http://localhost:8080/webappname会跳到设置好的欢迎页面
在web.xml文件的web-app标签里写,注意该html在webapp的根下的话前面不用写/
要是该index.html在某个目录下,前面不用写/,直接写路径,它会自动从webapp的根下找
<welcome-file-list>
<welcome-file>index.html</welcome-file>
</welcome-file-list>
注意,一个webapp是可以设置多个欢迎页的,约考上优先级越高,找不到继续向下找,如下所示
<welcome-file-list>
<welcome-file>index1.html</welcome-file>
<welcome-file>index2.html</welcome-file>
</welcome-file-list>
当我的文件名设置为index.html的时候,不需要在web.xml文件中进行配置欢迎页面,这是为什么? 这是因为小猫咪Tomcat服务器已经提前配置好了,实际上配置欢迎页面有两个地方可以配置,一个是在webapp内部的web.xml文件中(在这个地方配置的属于局部配置),另一个是在CATALINA_HOME/conf/web.xml文件中进行配置(在这个地方配置的属于全局配置)
欢迎页可以是个servlet吗?当然可以,将上面的index.html换成<url-pattern>里的自定义路径名就行
关于HttpServletRequest接口详解
public class ServletTest extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html");
PrintWriter writer = resp.getWriter();
writer.print("<h1>测试代码</h1>");
}
}
从上面代码看出,继承HttpServlet类之后,HttpServletRequest req, HttpServletResponse resp这两个参数显得尤为重要,有必要详细研究一下
- HttpServletRequest 接口时Servlet规范中的一员
- HttpServletRequest 接口的父接口是ServletRequest
- Tomcat服务器(WEB服务器,WEB容器)实现了HttpServletRequest接口,还是说明了Tomcat服务器实现了Servlet规范,而对于我们Javaweb程序员来说,实际上不需要关心这个,我们只需要面向接口编程即可,我们关心的是HttpServletRequest接口中有哪些方法,这些方法可以完成什么功能!!!!
- 实际上是用户发送请求的时候,遵循了HTTP协议,发送的是HTTP的请求协议,Tomcat服务器将HTTP协议中的信息以及数据全部解析出来,然后Tomcat服务器把这些信息封装到HttpServletRequest对象当中,传给了我们Javaweb程序员,Javaweb程序员面向HttpServletRequest接口编程,调用方法就可以从req获取到请求的信息了
- req和resp对象的生命周期?request对象和response对象,一个是请求对象,一个是响应对象,这两个对象只在当前请求中有效,一次请求对应一个request,两次请求则对应两个request...
- 前端一旦提交表单,传过来的数据是username=asdfasdf&age=22&aihao=chouyan这种形式的,思考一下收到后存在什么数据结构里比较好?当然是Map<String, String[]>里,因为传过来的键值对可能是一个key对应好几个不同的value,所以要用string[]数组来存值
key value
===================================
username {"adsf"}
password {"xcvxcvx"}
aihao {"抽烟","喝酒"}
注意:前端表单提交数据的时候,假设提交了120这样的数字,其实是以字符串"120"的方式提交的,所以服务器端获取到的一定是一个字符串的"120",而不是一个数字,前端永远提交的是字符串,后端获取的也永远是字符串
request对象有哪些常用的方法呢?
- getParameterMap();
- getParameterNames();
- getParameterValues();
- getParameter();
Map<String, String[]> parameterMap = req.getParameterMap();// 获取Map,用的不多
Enumeration<String> parameterNames = req.getParameterNames();// 获取Map中所有的key,用的不多
String[] parameterValues = req.getParameterValues(name);//根据key获取Map中的value(一维数组),用的不多
String parameter = req.getParameter(name);//根据key获取Map中的value的一维数组的第一个元素,这个最常用!!因为一维数组一般只有一个元素
request对象由被称为请求域对象
-
和之前的应用域对象相比,请求域对象要比应用域对象范围小很多,生命周期短很多
-
请求域对象也有三个方法
setAttribute(String name,Object value) 存 getAttribute(String name) 取 removeAttribute(String name) 删请求域对象和应用域对象怎样选?
个请求对象request对应一个请求域对象,一次请求结束之后,这个请求域就销毁了,所以尽量使用小的域对象,因为小的域对象占用的资源较少
题外话,如何一次请求让服务器执行两个servlet?
使用转发机制,即在第一个servlet里写如下代码
RequestDispatcher requestDispatcher = request.getRequestDispatcher("/test02");// xml里配置的第二个servlet的路径,注意,也可以是个资源,如html
requestDispatcher.forward(request, response);// 将request, response对象传给第二个servlet,第二个servlet接收到request, response
// 因为是同一个request, response对象,所以两个servlet算同一个请求
两个servlet怎样共享数据?
- 将数据放到ServletContext应用域当中当然是可以的,但是应用域范围太大,占用资源太多,不建议使用
- 可以将数据放到request域当中,然后AServlet转发到BServlet,保证AServlet和BServlet在同一次请求当中,这样就可以做到两个Servlet,或者多个Servlet共享同一份数据
关于request对象中两个非常容易混淆的方法
getAttribute(name") 获取请求域中绑定的数据,之前一定执行过request.setAttribute("name",new object(O)
getParameter("name") 获取用户在浏览器上提交的数据
request对象还有哪些常用的方法呢?
getRemoteAddr() 获取客户端的IP地址
setCharacterEncoding("UTF-8") 设置请求体的字符集,显然这个方法是处理POST请求的乱码问题,这种方式并不能解决get请求的乱码问题,Tomcat10之后,request请求体当中的字符集默认就是UTF-8,不需要设置字符集,不会出现乱码问题,Tomcat9前(包括9在内),如果前端请求体提交的是中文,后端获取之后出现乱码,怎么解决这个乱码?执行该方法就行,除了请求,在Tomcat9之前(包括9),响应response的中文也是有乱码的,怎么解决这个响应的乱码?
response.setContentType("text/html;charset=UTF-8");在Tomcat10之后,包括10在内,响应中文的时候就不在出现乱码问题了,以上代码就不需要设置UTF-8了,get请求乱码问题怎么解决?get请求发送的时候,数据是在请求行上提交的,不是在请求体当中提交的,方案:修改CATALINA_HOME/conf/server.xml配置文件<Connector URIEncoding="UTF-8"/>注意,从Tomcat8之后,URIEncoding的默认值就是UTF-8,所以GET请求也没有乱码问题了
getContextPath() 动态获取webapp的根路径
getMethod() 获取请求方式
getRequestURI() 获取请求的URI
getServletPath() 获取servlet的路径
使用纯Servlet做一个单表的增删改查
-
在javaweb数据库中先创建一张表
create table dept( deptno int primary key, dname varchar(255), loc varchar(255) ); insert into dept (deptno,dname,loc) values (10,'sell','beijing'); insert into dept (deptno,dname,loc) values (20,'media','shanghai'); insert into dept (deptno,dname,loc) values (30,'research','guangzhou'); insert into dept (deptno,dname,loc) values (40,'technology','shenzhen'); -
准备一套html页面,确保页面间跳转是能成功的
- 欢迎页面 index.html
- 列表页面 list.html (以该页面为核心,展开其他操作)
- 新增页面 add.html
- 修改页面 edit.html
- 详情页面 detail.html
分析这个系统有哪些功能?
- 查看部门列表
- 新增部门
- 删除部门
- 查看部门信息
- 修改部门
-
在IDEA中搭建开发环境
-
创建一个webapp(依赖了jsp-api.jar和servlet-api.jar)
-
向webapp中添加连接数据库的jar包(MySQL驱动)
- 必须在WEB-INF目录下新建lib目录,然后将MySQL的驱动jar包拷贝到这个lib目录下,这个目录名必须叫做lib,全部小写
-
JDBC工具类,和对应的jdbc.properties配置文件,放在了src-resources目录下
DBUtils
package com.javastudy.oa.utils; import java.sql.*; import java.util.ResourceBundle; // JDBC的工具类 public class DBUtils { // 静态变量,类加载时执行 // 自上而下执行 // 属性资源文件绑定 private static ResourceBundle bundle = ResourceBundle.getBundle("resources.jdbc"); // 根据属性配置文件key获取value private static String driver = bundle.getString("driver"); private static String url = bundle.getString("url"); private static String user = bundle.getString("user"); private static String password = bundle.getString("password"); static { // 注册驱动,只需要一次,放在静态代码块中,DBUtils类加载时调用 try { Class.forName(driver); } catch (ClassNotFoundException e) { throw new RuntimeException(e); } } // 获取连接对象connection public static Connection getConnection() throws SQLException { // 获取连接 return DriverManager.getConnection(url, user, password); } // 关闭资源 public static void close(Connection conn, Statement stmt, ResultSet rs) throws SQLException { if (rs != null) { rs.close(); } if (stmt != null) { stmt.close(); } if (conn != null) { conn.close(); } } }jdbc.properties
driver=com.mysql.jdbc.Driver url=jdbc:mysql://localhost:3306/javaweb user=root password=root -
将所有html页面拷贝到web目录下
-
-
实现功能
-
我们应该怎么去实现一个功能呢?建议:你可以从后端往前端一步一步写,也可以从前端一步一步往后端写,都可以,但是千万要记住不要想起来什么写什么,你写代码的过程最好是程序的执行过程,也就是说,程序执行到哪里,你就写哪里,这样一个顺序流下来之后,基本上不会出现什么错误和意外
-
第一步可以将前端的首页页面里查看部门列表按钮的a标签的跳转连接写成
<a href="/oa/dept/list">展示所有部门</a>,因为用户最先点击的就这个超链接<!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 style="text-align: center">欢迎使用部门管理系统</h1> <hr /> <a href="/oa/dept/list" style="display: block; text-align: center">展示所有部门</a> </body> </html> -
第二步,编写web.xml文件
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="https://jakarta.ee/xml/ns/jakartaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd" version="5.0"> <servlet> <servlet-name>list</servlet-name> <servlet-class>com.javastudy.oa.web.action.DeptListServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>list</servlet-name> <!--这里的路径不需要加项目名,前端页面里的需要--> <url-pattern>/dept/list</url-pattern> </servlet-mapping> </web-app> -
第三步,编写
DeptListServlet这个类,继承HttpServlet类,然后重写doGet方法,因为前端超链接属于get请求package com.javastudy.oa.web.action; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; public class DeptListServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { } } -
第四步,在
DeptListServlet类中的doGet方法中连接数据库,查询所有部门,动态展示部门列表分析list.html页面中那部分是固定死的,那部分是需要动态展示的,list.html页面中的内容所有的双引号要替换成单引号,因为writer.print("")这里有一个双引号,容易冲突,现在写完这个功能之后,你会有一种感觉,感觉开发很繁琐,只使用servlet写代码太繁琐了
package com.javastudy.oa.web.action; import com.javastudy.oa.utils.DBUtils; import jakarta.servlet.ServletException; 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; public class DeptListServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 获取应用的根路径 String contextPath = req.getContextPath(); // 设置响应的内容类型以及字符集,防止中文乱码问题 resp.setContentType("text/html;charset=UTF-8"); PrintWriter writer = resp.getWriter(); writer.print("<!DOCTYPE html>"); writer.print("<html lang='en'>"); writer.print(" <head>"); writer.print(" <meta charset='UTF-8' />"); writer.print(" <meta http-equiv='X-UA-Compatible' content='IE=edge' />"); writer.print(" <meta name='viewport' content='width=device-width, initial-scale=1.0' />"); writer.print(" <title>部门列表</title>"); writer.print(" </head>"); writer.print(" <body>"); writer.print(" <h1 style='text-align: center'>部门列表</h1>"); writer.print(" <hr />"); writer.print(" <table border='1px' align='center'>"); writer.print(" <tr>"); writer.print(" <th>序号</th>"); writer.print(" <th>部门编号</th>"); writer.print(" <th>部门名称</th>"); writer.print(" <th>操作</th>"); writer.print(" </tr>"); // 以上内容是固定的 // 连接数据库,查询所有部门 Connection connection = null; PreparedStatement preparedStatement = null; ResultSet resultSet = null; try { // 获取连接 connection = DBUtils.getConnection(); // 编写sql语句,获取预编译的数据库操作对象 String sql = "select * from dept"; preparedStatement = connection.prepareStatement(sql); // 执行sql语句 resultSet = preparedStatement.executeQuery(); // 处理结果集 int i = 0; while (resultSet.next()) { String deptno = resultSet.getString("deptno"); String dname = resultSet.getString("dname"); String loc = resultSet.getString("loc"); // 以下是动态的 writer.print(" <tr>"); writer.print(" <td>" + (++i) + "</td>"); writer.print(" <td>" + deptno + "</td>"); writer.print(" <td>" + dname + "</td>"); writer.print(" <td>"); writer.print(" <a href=''>删除</a>"); writer.print(" <a href='edit.html'>修改</a>"); writer.print(" <a href='detail.html'>详情</a>"); writer.print(" </td>"); writer.print(" </tr>"); } } catch (SQLException e) { e.printStackTrace(); } finally { try { // 释放资源 DBUtils.close(connection, preparedStatement, resultSet); } catch (SQLException e) { e.printStackTrace(); } } // 以下内容是固定的 writer.print(" </table>"); writer.print(" <a href='add.html' style='text-align: center; display: block'>新增</a>"); writer.print(" </body>"); writer.print("</html>"); } } -
第五步, 实现部门详情页
建议:从前端往后端一步一步实现,首先要考虑的是,用户点击的是什么?用户点击的东西在哪里?
找了半天,在java后端的servlet程序中找到了
writer.print(" <a href='detail.html'>详情</a>");,明显要将里面的detail.html换成一个servlet路径,因为详情页的内容是要连接数据库的,所以肯定有java代码,即在一个servlet中,即将上边的连接写成:writer.print(" <a href='" + contextPath + "/dept/detail?deptno=" + deptno + "'>详情</a>");重点:向服务器提交数据的格式
url?name=value&name=value&name=value然后就可以再写一个servlet文件了,名为
DeptDetailServlet,并且在web.xml文件中配置一下package com.javastudy.oa.web.action; import com.javastudy.oa.utils.DBUtils; import jakarta.servlet.ServletException; 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; public class DeptDetailServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("text/html;charset=UTF-8"); PrintWriter writer = resp.getWriter(); // 固定的内容 writer.print("<!DOCTYPE html>"); writer.print("<html lang='en'>"); writer.print(" <head>"); writer.print(" <meta charset='UTF-8' />"); writer.print(" <meta http-equiv='X-UA-Compatible' content='IE=edge' />"); writer.print(" <meta name='viewport' content='width=device-width, initial-scale=1.0' />"); writer.print(" <title>部门详情</title>"); writer.print(" </head>"); writer.print(" <body>"); writer.print(" <h1 style='text-align: center'>部门详情</h1>"); writer.print(" <hr />"); writer.print(" <div style='text-align: center;'>"); // 获取部门编号 // /oa/dept/detail?deptno=10 // 前端提交的是10,但是接收到的是"10"字符串 String deptno = req.getParameter("deptno");// 将接收到的部门编号保存到字符串中 // 连接数据库,根据部门编号查询部门信息 Connection connection = null; PreparedStatement preparedStatement = null; ResultSet resultSet = null; try { connection = DBUtils.getConnection(); String sql = "select dname,loc from dept where deptno = ?"; preparedStatement = connection.prepareStatement(sql); preparedStatement.setString(1, deptno); resultSet = preparedStatement.executeQuery(); if (resultSet.next()) { String dname = resultSet.getString("dname"); String loc = resultSet.getString("loc"); System.out.println(deptno); System.out.println(dname); System.out.println(loc); // 动态内容 writer.print("<p>部门编号:" + deptno + "</p>"); writer.print("<p>部门名称:" + dname + "</p>"); writer.print("<p>部门位置:" + loc + "</p>"); } } catch (SQLException e) { e.printStackTrace(); } finally { try { DBUtils.close(connection, preparedStatement, resultSet); } catch (SQLException e) { e.printStackTrace(); } } // 固定的内容 writer.print(" <input type='button' value='返回' onclick='window.history.back()'>"); writer.print(" </div>"); writer.print(" </body>"); writer.print("</html>"); } }上面的servlet对应的xml配置
<servlet> <servlet-name>detail</servlet-name> <servlet-class>com.javastudy.oa.web.action.DeptDetailServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>detail</servlet-name> <url-pattern>/dept/detail</url-pattern> </servlet-mapping> -
第六步,删除部门
package com.javastudy.oa.web.action; import com.javastudy.oa.utils.DBUtils; import jakarta.servlet.ServletException; 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; public class DeptDeleteServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 根据部门编号,删除部门 String deptno = req.getParameter("deptno"); // 连接数据库,根据部门编号删除部门 Connection connection = null; PreparedStatement preparedStatement = null; ResultSet resultSet = null; int count = 0; try { connection = DBUtils.getConnection(); // 开启事务 connection.setAutoCommit(false); String sql = "delete from dept where deptno = ?"; preparedStatement = connection.prepareStatement(sql); preparedStatement.setString(1, deptno); // 返回受影响的行数 count = preparedStatement.executeUpdate(); // 提交事务 connection.commit(); } catch (SQLException e) { try { // 遇到异常回滚事务 assert connection != null; connection.rollback(); } catch (SQLException ex) { throw new RuntimeException(ex); } e.printStackTrace(); } finally { try { DBUtils.close(connection, preparedStatement, resultSet); } catch (SQLException e) { e.printStackTrace(); } } // 判断是否删除成功 if (count == 1) { System.out.println("成功"); // 成功后跳回列表页面,这里用了转发,没有重定向 req.getRequestDispatcher("/dept/list").forward(req, resp); } else { System.out.println("失败"); } } } -
第七步,新增部门
package com.javastudy.oa.web.action; import com.javastudy.oa.utils.DBUtils; import jakarta.servlet.ServletException; 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.SQLException; public class DeptAddServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("UTF-8"); String deptno = req.getParameter("deptno"); String dname = req.getParameter("dname"); String loc = req.getParameter("loc"); // 连接数据库,根据部门编号查询部门信息 Connection connection = null; PreparedStatement preparedStatement = null; int count = 0; try { connection = DBUtils.getConnection(); String sql = "insert into dept values (?,?,?)"; preparedStatement = connection.prepareStatement(sql); preparedStatement.setString(1, deptno); preparedStatement.setString(2, dname); preparedStatement.setString(3, loc); count = preparedStatement.executeUpdate(); } catch (SQLException e) { e.printStackTrace(); } finally { try { DBUtils.close(connection, preparedStatement, null); } catch (SQLException e) { e.printStackTrace(); } } // 判断是否删除成功 if (count == 1) { System.out.println("成功"); // 成功后跳回列表页面,这里用了转发,没有重定向,注意,这里是doPost请求,给DeptListServlet转发,DeptListServlet是doGet请求,会出现405,解决方法是在DeptListServlet里再重写一个doPost,在doPost里调用doGet!!!!! req.getRequestDispatcher("/dept/list").forward(req, resp); } else { System.out.println("失败"); req.getRequestDispatcher("/dept/list").forward(req, resp); } } }add.html
<!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 style="text-align: center">新增部门</h1> <hr/> <form action="/oa/dept/add" align="center" 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>xml配置
<!--添加部门--> <servlet> <servlet-name>add</servlet-name> <servlet-class>com.javastudy.oa.web.action.DeptAddServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>add</servlet-name> <url-pattern>/dept/add</url-pattern> </servlet-mapping>注意,最后保存成功之后,转发到/dept/list的时候,会出现405,为什么?
- 保存用的是post请求,底层要执行doPost方法
- 转发是一次请求,之前是post,之后还是post,因为它是一次请求
- /dept/list Servlet当中只有一个doGet方法
- 怎么解决?两种方案 第一种 在/dept/list Servlet中添加doPost方法,然后在doPost方法中调用doGet 第二种 重定向,还没学
-
跳转到修改部门界面,修改部门
<!--跳转到编辑部门的页面--> <servlet> <servlet-name>edit</servlet-name> <servlet-class>com.javastudy.oa.web.action.DeptEditServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>edit</servlet-name> <url-pattern>/dept/edit</url-pattern> </servlet-mapping> <!--修改部门--> <servlet> <servlet-name>modify</servlet-name> <servlet-class>com.javastudy.oa.web.action.DeptModify</servlet-class> </servlet> <servlet-mapping> <servlet-name>modify</servlet-name> <url-pattern>/dept/modify</url-pattern> </servlet-mapping>展示修改页面
package com.javastudy.oa.web.action; import com.javastudy.oa.utils.DBUtils; import jakarta.servlet.ServletException; 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; public class DeptEditServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 获取应用的根路径 String contextPath = req.getContextPath(); resp.setContentType("text/html;charset=UTF-8"); PrintWriter writer = resp.getWriter(); writer.print("<!DOCTYPE html>"); writer.print("<html lang='en'>"); writer.print("<head>"); writer.print(" <meta charset='UTF-8'/>"); writer.print(" <meta http-equiv='X-UA-Compatible' content='IE=edge'/>"); writer.print(" <meta name='viewport' content='width=device-width, initial-scale=1.0'/>"); writer.print(" <title>修改部门</title>"); writer.print("</head>"); writer.print("<body>"); writer.print("<h1 style='text-align: center'>修改部门</h1>"); writer.print("<hr/>"); writer.print("<form action='" + contextPath + "/dept/modify' align='center' method='post'>"); // 获取部门编号 String deptno = req.getParameter("deptno"); // 连接数据库,根据部门编号查询部门信息 Connection connection = null; PreparedStatement preparedStatement = null; ResultSet resultSet = null; try { connection = DBUtils.getConnection(); String sql = "select dname,loc from dept where deptno = ?"; preparedStatement = connection.prepareStatement(sql); preparedStatement.setString(1, deptno); resultSet = preparedStatement.executeQuery(); if (resultSet.next()) { String dname = resultSet.getString("dname"); String loc = resultSet.getString("loc"); // 动态内容 writer.print("部门编号<input type='text' name='deptno' value='" + deptno + "' readonly/><br/>"); writer.print("部门名称<input type='text' name='dname' value='" + dname + "'/><br/>"); writer.print("部门位置<input type='text' name='loc' value='" + loc + "'/><br/>"); } } catch (SQLException e) { e.printStackTrace(); } finally { try { DBUtils.close(connection, preparedStatement, resultSet); } catch (SQLException e) { e.printStackTrace(); } } writer.print(" <input type='submit' value='修改'/>"); writer.print("</form>"); writer.print("</body>"); writer.print("</html>"); } }真正的修改
package com.javastudy.oa.web.action; import com.javastudy.oa.utils.DBUtils; import jakarta.servlet.ServletException; 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; public class DeptModify extends HttpServlet { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("UTF-8"); String deptno = req.getParameter("deptno"); String dname = req.getParameter("dname"); String loc = req.getParameter("loc"); // 连接数据库,根据部门编号查询部门信息 Connection connection = null; PreparedStatement preparedStatement = null; ResultSet resultSet = null; int count = 0; try { connection = DBUtils.getConnection(); String sql = "update dept set dname = ?,loc = ? where deptno = ?"; preparedStatement = connection.prepareStatement(sql); preparedStatement.setString(1, dname); preparedStatement.setString(2, loc); preparedStatement.setString(3, deptno); count = preparedStatement.executeUpdate(); } catch (SQLException e) { e.printStackTrace(); } finally { try { DBUtils.close(connection, preparedStatement, resultSet); } catch (SQLException e) { e.printStackTrace(); } } if (count == 1) { System.out.println("修改成功"); // 更新成功后跳转 req.getRequestDispatcher("/dept/list").forward(req, resp); } else { System.out.println("修改失败"); // 更新失败后也跳转 req.getRequestDispatcher("/dept/list").forward(req, resp); } } } -
大功告成,但其实上面写的项目的跳转都应该用重定向,而不是转发
-
在一个web应用中应该如何完成资源的跳转
web应用中有两种方式完成资源的跳转
- 转发
- 重定向
转发和重定向有什么区别?
-
代码上的区别
-
转发
//获取请求转发器对象 RequestDispatcher dispatcher = request.getRequestDispatcher("/dept/list"); //调用请求转发器对象的forward方法完成转发 dispatcher.forward(request, response); //可以合并成一句代码 request.getRequestDispatcher("/dept/list").forward(req, resp); //转发的时候是一次请求,不管你转发了多少次,都是一次请求 //AServlet转发到BServlet,再转发到CServlet,再转发到DServlet,不管转发了多少次,都在同一个request当中 //这是因为调用Forward方法的时候,会将当前的request和response对象传给下一个Servlet, -
重定向
response.sendRedirect(request.getContextPath() + "/B");// 项目名+xml里的地址 // 重定向(重新定方向) // 重定向时的路径当中需要以项目名开始,或者说需要添加项目名,因为这个路径到时候由浏览器发送,当然需要项目名 // response对象将这个路径:"/servlet06/B"响应给浏览器了 // 浏览器又自发的向服务器发送了一次全新的请求:http://localhost:8080/servlet06/B // 所以浏览器一共发送了两次请求 // 第一次请求:http://localhost:8080/servlet06/A // 第二次请求:http://localhost:8080/servlet06/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文件中,一般都是注解+配置文件的开发模式,一些不会经常变化修改的配置建议使用注解,一些可能会被修改的建议写到配置文件中
注解里有哪些属性?
name 用来指定Servlet的名字,等同于<servlet-name>
urlPatterns 用来指定Servlet的映射路径,可以指定多个字符串<url-pattern>
value 这个value属性和urlPatterns属性一致,都是用来指定Servlet的映射路径的
loadOnStartup 用来指定在服务器启动阶段是否加载该Servlet,等同于<load-on-startup>
需要什么写什么,不是必须的
name = "s1"
urlPatterns = {"/a", "/b", "/c"}
loadOnStartup = 1
initParams = {@WebInitParam(name = "username", value = "root"), @WebInitParam(name = "password", value = "abc")}
...
注意:当注解的属性是一个数组,并且数组中只有一个元素,大括号可以省略
如果注解的属性名是value的话,属性名也是可以省略的,即@WebServlet("/bbb")
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;
@WebServlet(name = "s1", urlPatterns = {"/a", "/b", "/c"})
public class AServlet extends HttpServlet {
@Override()
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
}
}
解决类爆炸
可以看到,上面的项目里一个单表的CRUD就有好多个servlet,如果一个复杂的业务系统,这种开发方式,显然会导致类爆炸(类的数量太多)
怎么解决上面问题?
可以使用模板方法设计模式,这样一个servlet就可以了
package com.javastudy.oa.web.action;
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;
// 模板类
@WebServlet({"/dept/list", "/dept/detail", "/dept/delete", "/dept/add", "/dept/edit", "/dept/modify"})
// @WebServlet("/dept/*")也行
public class DeptServlet extends HttpServlet {
// 模板方法,重写service方法(并没有重写doGet或者doPost)
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 获取servlet Path
String servletPath = req.getServletPath();
if ("/dept/list".equals(servletPath)) {
doListServlet(req, resp);
} else if ("/dept/detail".equals(servletPath)) {
doDetailServlet(req, resp);
} else if ("/dept/delete".equals(servletPath)) {
doDeleteServlet(req, resp);
} else if ("/dept/add".equals(servletPath)) {
doAddServlet(req, resp);
} else if ("/dept/edit".equals(servletPath)) {
doEditServlet(req, resp);
} else if ("/dept/modify".equals(servletPath)) {
doModifyServlet(req, resp);
}
}
private void doModifyServlet(HttpServletRequest req, HttpServletResponse resp) {
// 把之前的例子里的代码写到这里面就行了
}
private void doEditServlet(HttpServletRequest req, HttpServletResponse resp) {
}
private void doAddServlet(HttpServletRequest req, HttpServletResponse resp) {
}
private void doDeleteServlet(HttpServletRequest req, HttpServletResponse resp) {
}
private void doDetailServlet(HttpServletRequest req, HttpServletResponse resp) {
}
private void doListServlet(HttpServletRequest req, HttpServletResponse resp) {
}
}
JSP
前面的servlet里写前端代码,难度大,程序的耦合度非常高,代码非常不美观,维护成本也很高,调个样式啥的还得跑去改后端java代码,重新编译部署
如果是你,你会怎么解决?
可以搞一个程序,让它帮忙打印之前我们servlet中写的那些输出语句如writer.print("<h1></h1>"),而我们程序员只需要专注写出前端代码就行,打印的事交给程序去做,而JSP就是这种机制
我的第一个jsp
在web应用的根下创建一个index.jsp文件即可
浏览器地址栏输入访问http://localhost:8080/jsp01/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实际上就是一个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文件翻译生成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的基础语法 在JSP文件中直接编写文字,都会自动被翻译到哪里? 翻译到servlet类的service方法的out.write("翻译到这里"),直接翻译到双引号里,被java当作普通字符串,打印输出到浏览器,但是浏览器会解析成页面,展现成效果
-
在jsp文件最前面写
<%@ page contentType="text/html;charset=UTF-8" %>即可解决中文乱码问题 -
jsp注释
<%-- --%> -
在
<% Java语句 %>括号内编写java语句便可被识别,在这个符号当中编写的被视为java程序,被翻译到Servlet类的service方法内部,这里你要细心点,要思考在<% %>这个符号里面写java代码的时候,要时时刻刻的记住你正在"方法体"当中写代码,方法体中可以写什么,不可以写什么心里要明 -
<! >在这个符号当中编写的java程序会自动翻译到service方法之外,这个语法很少用,不建议使用,JSP就是servlet,servlet是单例的,多线程并发的环境下,这个静态变量和实例变量一旦有修改操作,必然会存在线程安全问题 -
怎么向浏览器输出一个变量?
<% int i = 10; out.write(i); out.write("asdfasdfasddf");//脱裤子放屁,不如直接在尖括号外面直接写 %>注意,以上的out是JSP的九大内置对象之一,可以直接拿来用
-
如果输出的内容中含有java代码,是个动态内容,这时可以用以下语法格式
<%= %>,在=后面编写想输出的内容,<%= %>被翻译成了out.print()如
<%=100+200 %>,被翻译成out.print(100+200);注意200后面不要加分号;
JSP和Servlet有什么本质区别呢?
职责不同
- servelt 收集数据,他的强项是逻辑处理,业务处理,连接数据库,获取/收集数据
- jsp 展示数据
上面做的oa项目,就可以改造成jsp
-
将之前所有的页面原型html文件后缀全部修改为.jsp,防止中文乱码,在第一行加上
<%@ page contentType="text/html;charset=UTF-8" %>,将所有的jsp文件拷贝到idea的web目录下,即应用的根 -
完成页面的正常跳转,修改超链接的请求路径,改成
<%=request.getContextPath() %>/list.jsp,动态获取应用根路径<a href="<%=request.getContextPath() %>/list.jsp"></a> -
在servlet中将数据库结果集中的数据按while循环收集数据封装到一个javabean对象中(创建Dept类,属性和表的字段一样),再将这些javabean对象塞进一个集合中,将这个集合用request.setAttribute("起个名",集合)放到request请求域中,再用request.getRequestDispatcher("/list.jsp").forward(request,response)转发给list.jsp
-
在jsp文件里,在本来需要循环展示的代码部分写这样的代码
<%@page import="java.util.List, com.javastudy.oa.bean.Dept"%> 注意得导包 <% // 从request域中取出集合 List<Dept> deptList = (List<Dept>)request.getAttribute("刚才给集合起的名字") // 遍历 for(Dept dept:deptList){ // 后台输出 // System.out.println(dept.getDname()); <% <tr> <td><%=dept.getDname()%></td> </tr> <% } %>
只用 JSP 可不可以开发一个web应用?
当然可以使用 JSP 来完成所有的功能,因为JSP就是Servlet,在JSP的<%%>里面写的代码就是在service方法当中的,所以在<%%>当中完全可以编写,可以但没必要,应该各司其职,jsp是用来展示数据的,servlet负责收集数据,jsp中编写的java代码越少越好
jsp文件的拓展名必须是xxx.jsp吗?
是可以配置的,不是固定的,xxx.jsp文件对于小猫咪来说,只是一个普通的文本文件,web容器会将xxx.jsp文件最终生成java程序,最终调用的是java对象相关的方法,真正执行的时候,和jsp文件就没有关系了
什么是javabean?
实际上javabean你可以理解为符合某种规范的java类,通常放在bean包下,用来封装零散的数据
- 有无参数构造方法
- 属性私有化
- 对外提供公开的set和get方法
- 实现java.io.Serializable接口
- 重写toString
- 重写hashCode+equals
- ...
符合上面的规范,有更强的通用性
引出问题,当前的项目任何用户都可以访问,只想让合法用户访问该怎么办?
可以加一个登录功能:
-
先写一个登录页面
登录页面上应该有一个登录的表单,有用户名和密码输入的框,用户点击登录,提交表单,提交用户名和密码,form是post方式提交
-
给数据库中添加一个用户表,里面存储用户名和密码,密码实际上不以明文存储
-
写一个servlet处理登录请求
成功跳转到部门列表页面
失败跳转到失败页面(得写一个)
-
密码正确能跳转到相应的页面,但是要是在地址栏直接输入正确的地址,也能访问登录成功后的页面,怎么解决呢?这时就可以引出session了
JSP指令
指令的作用:指导翻译引擎如何工作
<%@指令名 属性名=属性值 属性名=属性值 属性名=属性值...%>
指令包括那些?
-
include指令 包含指令,在jsp中完成静态包含,很少用了
-
taglib指令 引入标签库指令,这个到 JSTL 标签库时再学习,先不管
-
page指令
-
<%@page session="true或false" %>true表示启用JSP的内置对象session,表示一定启动session对象,没有session会创建,没设置默认为true -
<%@page contentType="text/json" pageEncoding="UTF-8" %>设置响应的内容类型 -
<%@page improt="java.util.List, java.util.Date" %>导包 -
<%@page errorPage="/error.jsp" %>服务器错误后跳转到指定页面,不会再显示错误代码了但是这样设置在发生错误后后台也不会报错了,可以启用九大内置对象之一的exception
<%@page isErrorPage="true" %>加<% exception.printStackTrace(); %> -
<%@page %>
-
JSP的九大内置对象
- pageContext 页面作用域
- request 请求作用域
- session 会话作用域
- application 应用作用域
pageContext<request<session<application
且以上四个作用域都有setAttribute,getAttribute,removeAttribute方法,尽量使用小的域
- exception
异常
- config
配置
- page
this,当前的servlet对象,没啥用
- out
- response
out负责输出
response负责响应
EL表达式
实际上是jsp的语法,表达式语言(Expression Language)
有啥用呢?
- EL表达可以代替JSP中的Java代码,让JSP文件里的程序看起来更加整洁美观
- JSP中夹杂着如<% java代码 %>等,导致特别乱
- EL表达式归属于JSP,是JSP语法的一部分
EL表达式出现在JSP中主要是
- 从某个作用域中取数据,然后将其转换成字符串,然后将其输出到浏览器,这就是EL表达式的功效,三大功效:
- 从某个域中取数据(只负责取数据!)
- 将取出的数据转换成字符串,如果取出一个对象,自动调用toString方法
- 将字符串输出到浏览器
EL表达式很好用,基本的语法格式:
-
${表达式} -
如
${userObj} // 等同于<%=request.getAttribute("userObj")%>,user是个对象 直接输出对象,则底层调用了toString方法 能写上面的${userObj},说明之前一定有这样的代码: 域.setAttribute("userObj",对象)
${ }内直接可以写对象 . 属性的方式,输出对象的属性值,实际上底层在调用getXXX方法,前提得有getXXX方法
注意,在EL表达式内不能加双引号,会当作普通字符串输出
没有指定取出范围的情况下,EL表达式优先从小范围的域中取数据
假如现在四个域中都有同名的属性,怎么取出指定的?
session.setAttrubute("data","session");
request.setAttrubute("data","request");
pageContext.setAttrubute("data","pageContext");
application.setAttrubute("data","application");
可以前面加个限定,就能按指定域范围取数据了
${pageScope.data}
${requestScope.data}
${sessionScope.data}
${applicationScope.data}
在实际开发中,因为在不同域中的存数据的时候,name是不同的,所以xxxScope一般不写
${obj.name}也可以这样写${obj["name"]}
假如EL表达式没取到数据,不会在页面上展示个null,比<%=request.getAttribute("xxx") %>更友好
使用EL表达式怎么从Map集合中取数据? ${map.key}
使用EL表达式怎么从数组和List集合中取数据? ${arr[1]}
page指令中,有一个属性,可以忽略EL表达式
<%@page isELIgnored="true" %> true表示忽略EL表达式,false表示不忽略
\${user} 表示忽略单个EL表达式(加反斜杠)
使用EL表达式获取应用根路径${pageContext.request.contextPath}
使用EL表达式获取用户传过来的参数${param.age}输出age对应的值
使用EL表达式获取用户传过来的同名参数的多个值${paramValues.aihao[1]}
使用EL表达式获取xml中配置的默认信息${initParam.xxx}
EL表达式中的运算符
+ - * /
== != > >= < <= eq
! && || not and or
? :
[] 和 .
empty
加号只能求和,不能拼接字符串,==和eq和!=都调用了equals方法
!和not都是取反,empty判断是否为空,是空为true
总结
EL就是用来从域中取数据然后输出
Session
可以看出,上面的登录功能就是个摆设,输入正确的地址照样能访问登录后才能看的内容,在不登陆的状态下也能访问
这时就需要一个会话机制Session,B/S架构都有session机制,这种会话机制实际上是种规范
什么是会话?
用户打开浏览器,进行一系列操作,最终将浏览器关闭,这个过程就叫一次会话,会话在服务器端有一个对应的java对象,这个java对象叫session,最主要的作用是保存会话状态(这是一种用户登录成功的状态)
什么是请求?
用户在浏览器点击一下,然后到页面停下来,可以粗略认为是一次请求,对应的也在服务器端有一个java对象request,一次session会话对应N次请求
为什么需要session对象保存会话状态呢?
-
因为HTTP协议是一种无状态协议
-
什么是无状态?请求的时候,B和S是连接的,但是请求结束之后,连接就断了,为什么要这么做,HTTP协议为什么要设计成这样?因为这样的无状态协议,可以降低服务器的压力,请求的瞬间是连接的,请求结束之后,连接断开,这样服务器压力小
-
只要B和S断开了,那么关闭览器这个动作,服务器知道吗,不知道,服务器是不知道浏览器关闭的
-
张三打开一个浏览器A,李四打开一个浏览器B,访问服务器之后,在服务器端会生成 张三专属的session对象 李四专属的session对象
-
有些网站的安全退出,其实就是主动销毁session对象,取消正在登录的状态
-
为什么不使用request对象保存会话状态?为什么不使用ServletContext对象保存会话状态?
request是一次请求一个对象,域太小,ServletContext对象是服务器启动的时候创建,服务器关闭的时候销毁,这个ServletContext对象只有一个,ServletContext对象的域太大,尽量使用小的域
-
request请求域(HttpServletRequest)请求级别的
session会话域(HttpSession)用户级别的
application应用域(ServletContext)项目级别的,所有用户共享
他们三个域都有setAttribute,getAttribute,removeAttribute方法
-
request < session < application
package com.study;
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 jakarta.servlet.http.HttpSession;
import java.io.IOException;
@WebServlet("/test/session")
public class TestSessionServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// request和session都是服务器中的java对象
// 先看request对象,一次请求代表一个request对象,两次代表两个
// session对象代表一次会话,
// 从服务器中获取当前session对象
// 如果没有获取到任何session对象,则新建
HttpSession session = request.getSession();
// 从服务器中获取session对象
// 如果没有获取到任何session对象,则不会新建,返回null
// HttpSession session1 = request.getSession(false);
// 向会话域中绑定数据
session.setAttribute();
// 从会话域中取数据
session.getAttribute();
}
}
HttpSession session = request.getSession();这句话很神奇,为什么张三在浏览器A访问,是一个Session对象,而李四在浏览器B访问却是另一个session对象呢?为什么张三不会把李四的session对象拿到呢?
-
实际上在Tomcat服务器上有一个session列表,有key和value,key里面存session的编号,value里存session的对象,用户发送第一次请求的时候,服务器会创建一个新的session对象,同时给session对象生成一个id,然后将id发送给浏览器,浏览器将session的id保存在浏览器的缓存中
-
用户发送第二次请求的时候,会自动将浏览器缓存中的session id发给服务器,服务器会将接收到的id在session列表中查找对应的session对象,只要关闭浏览器,缓存一清除再次打开就没有session id了,所以会新建一个会话(session对象)
-
到底什么是一次会话?其实就是session对象从创建到死掉,这就是一次会话
-
假如张三现在关闭了浏览器,服务器是不知道的,因为http协议是无状态协议,但是session有个超时机制,只要超过特定的时间未向服务器发送数据,那么服务器端的session对象就会被销毁
<!--设置session的超时时长为10分钟,默认是30--> <session-config> <session-timeout>10</session-timeout> </session-config> <!--具体设置多久看系统,比如网银系统3分钟就行--> -
session对象什么时候销毁?1.超时销毁2.手动销毁(安全退出,这时服务器立刻销毁session对象)'
-
浏览器将session id 以cookie的形式保存在浏览器当中的(JSESSIONID=XXXXXXXXXXXXX),注意是放在内存中的,不是在硬盘里,只要浏览器退出就没了
禁用cookie是什么意思?
不是说是服务器不发了,而是浏览器发过来浏览器不要了,每一次请求都会收到新的session对象,那禁用后session机制还能实现吗?可以,通过url重写机制解决,在地址栏url后面加一个分号;再跟上jsessionid=xxxxxxxxxxx,但是会提高开发者的成本,在编写任何请求路径时后面都要加一个sessionid,特别麻烦,所以大部分的网站都是这样设计的:你要是禁用cookie,你就别用了
我们掌握session后,如何改进上面案例的登录问题?
登陆成功后可以将用户信息存到session对象中,session中有信息则代表登陆成功,没有信息则跳转到登录页面
if (success){
HttpSession session = request.getSession();
session.setAttribute("username",username);
}
用户登录后才能执行对应的业务操作,否则回到登录页
// 只是获取当前session,获取不到返回null
HttpSession session = request.getSession(false);
if (session != null && session.getAttribute("username") != null) {
String servletPath = request.getServletPath();
if ("/dept/list".equals(servletPath)) {
doListServlet(request, response);
} else if ("/dept/detail".equals(servletPath)) {
doDetailServlet(request, response);
} else if ("/dept/delete".equals(servletPath)) {
doDeleteServlet(request, response);
}
} else {
response.sendRedirect("/oa/index.html");
}
注意,在jsp中写<%@page session="false" %>可以禁用jsp自带的session对象
安全退出,手动销毁session对象
// 获取session对象,销毁它
HttpSession session = request.getSession(false);
if (session != null) {
// 手动销毁session对象
session.invalidate();
}
cookie
session的实现原理中,每一个session对象都会关联一个sessionid,例如
JSESSIONID=adfadsfasdfsdfadf
以上这个键值对数据其实就是cookie对象
对于session管理的cookie来说,这个cookie是被保存在浏览器的"运行内存"中,只要浏览器不关闭,用户再次发送请求时,将自动将运行内存中的cookie发送给服务器
问题来了,cookie怎样生成?存在哪里?有啥用?浏览器什么时候,发送什么cookie给服务器?
- cookie最终保存在浏览器上
- 可以保存在运行内存中,只要关闭浏览器cookie就消失了
- 也可以保存在硬盘文件中,永久保存
- cookie和session机制其实都是为了保存会话的状态
- cookie是将会活的状态保存在浏览器客户端上(cookie数据存储在浏览器客户端上)
- session是将会话的状态保存在服务器端上(session对象是存储在服务器上)
- 为什么要有cookie和session机制呢?因为HTTP协议是无状态无连接协议
- 在HTTP协议中是这样规定的:当浏览器发送请求的时候,会自动携带该path下的cookie数据给服务器,访问不同的url带该url对应的cookie
cookie的经典案例:
- 比如京东商城在没登陆的状态下可以加购物车,关闭浏览器发现商品还在,是因为其将商品编号信息放到了cookie中,保存到了硬盘中,即使关闭浏览器,下次打开还在
- 比如十天免登录功能:用户输入正确的用户名和密码,并且同时选择十天内免登录,登录成功后,浏览器客户端会保存一个cookie,这个cookie中保存了用户名和密码等信息,这个cookie是保存在硬盘文件当中的,十天有效,在十天内用户再次访问126的时候,浏览器自动提交126的关联的cookie给服务器,服务器接收到cookie之后,获取用户名和密码,验证,通过之后,自动登录成功,怎样让cookie失效?改密码,或10填自动失效,或手动删除cookie
实际上cookie机制和session机制其实都不属于Java中的机制,实际上cookie机制和session机制都是HTTP协议的一部分,php开发中也有cookie和session机制,要是你是做web开发,不管是什么编程语言,cookie和session机制都是需要的
HTTP协议中规定:任何一个cookie都是name和value组成,都是字符串类型
在Java的servlet中,对cookie提供了那些支持呢?
提供了一个类专门表示cookie数据,但是服务器要造cookie对象干啥呢?是因为java响应类的addCookie(cookie)方法可以携带cookie发送给浏览器,让浏览器保存
// 服务器生成cookie,发给浏览器,浏览器接受到cookie后保存到客户端上
Cookie cookie = new Cookie("id","123");
response.addCookie(cookie);//发送
可以设置cookie的有效时间,以秒为单位cookie.setMaxAge(60 * 60);// 设置一小时后失效
没有设置有效时间默认是保存在内存中,只要关闭浏览器就会消失
只要设置cookie的有效时间>0,一定是存在硬盘中
设置cookie的有效期为0,表示该cookie被除,主要应用在,使用这种方式除浏览器上的同名cookie,有效期小于0表示不会存储该cookie,表示不会被存储到硬盘中,会放在浏览器运行内存中,和不调用setMaxAge是一个效果
关于cookie的path,cookie关联的路径
手动设置cookie的path
cookie.setPath("/oa");// 表示oa项目里所有的请求路径,都会将这个cookie发给服务器
现在浏览器将cookie发过来了,服务器该怎样接受cookie?
当然是在request里的方法
// 得到浏览器发过来的所有cookie,要是没有cookie的时候,返回的是null,并不是空数组
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
// 获取cookie的name
String name = cookie.getName();
// 获取cookie的value
String value = cookie.getValue();
}
}
JSTL标签库
什么是JSTL标签库?
- Java Standard Tag Lib (Java标准的标签库)
- JSTL标签库通常和EL表达式一起使用,目的是让JSP中的Java代码消失
- 标签是写在JSP当中的,但实际上还是要执行对应的java程序(java程序在jar包中)
使用步骤
-
引入JSTL标签库对应的jar包
tomcat10之后引入的是
jakarta.servlet.jsp.jstl-2.0.0.jar taglibs-standard-impl-1.2.5.jar taglibs-standard-spec-1.2.5.jar(自己找)
在IDEA中在WEB-INF下的lib目录中放入jar包,然后右键添加到项目,和mysql驱动一样
-
在jsp文件中引入JSTL标签库
JSTL提供了很多种标签,你要引入哪个标签?重点掌握核心标签库
<%@taglib prefix="xxxx" uri="http://java.sun.com.jsp.jstl.core" %> 这是核心标签库,prefix="xxxx"里通常写"c" -
在需要使用的地方使用即可,表面使用的是标签,底层实际上还是java程序
<%@taglib prefix="c" uri="http://java.sun.com.jsp.jstl.core" %> <c:forEach items="${stuList}" var="s"> id:${s.id},name:${s.name} </c:forEach> -
有哪些常用标签呢?
<c:if test=""> test内可以写EL表达式,Boolean类型,为真显示里面内容 类似vue中的v-if </c:if> <c:forEach var="i" begin="1" end="10" step="1"> ${i} 循环输出1-10,var指定循环中的变量 </c:forEach> <c:choose> if <c:when test="${param.age<18}"> 未成年 </c:when> <c:when test="${param.age>=18}"> 成年 </c:when> <c:otherwise> 输入有误 </c:otherwise> </c:choose>
过滤器
多个处理不同业务的servlet中,一些公共的代码,如设置请求字符集,响应字符集,cookie判断用户是否登录等
之前的代码中没有复用这些代码,怎么解决?
可以使用servlet规范中的Filter过滤器
-
可以在servlet执行之前过滤,也可以执行之后过滤
-
过滤器也是java程序,也是类
-
过滤器也是通过不同的请求路径通过对应的过滤器
-
一次请求可能通过的过滤器有多个,有解决乱码的过滤器,有安全控制的过滤器,有判断登录的过滤器
-
一般在过滤器中写公共代码
-
注意,Servlet对象在默认情况下,服务器启动时不会新建对象
Filter对象在默认情况下,在服务器启动时会新建对象
Servlet是单例的,Filter也是单例的(单实例,只创建一个)
如何写一个过滤器呢?
-
编写一个java类实现一个接口:jarkata.servlet.Filter,并实现这个接口的所有方法
- init 在Filter对象第一次被创建后执行,只调用一次
- doFilter 只要用户发送一次请求,执行一次,发送N次,执行N次
- destroy 在Filter对象被释放/销毁前执行,只调用一次
package com.study; import jakarta.servlet.*; import java.io.IOException; public class MyFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { Filter.super.init(filterConfig); } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { } @Override public void destroy() { Filter.super.destroy(); } } -
在web.xml文件中对Filter进行配置,和servlet配置很像
<filter> <filter-name>filter1</filter-name> <filter-class>com.study.MyFilter</filter-class> </filter> <filter-mapping> <filter-name>filter1</filter-name> <url-pattern>/abc</url-pattern> <url-pattern>/def</url-pattern> </filter-mapping> 既可以在xml文件里配置成这样 也可以在Filter类之前加注解 @WebFilter({"/abc", "/def"}) 也行 -
假如现在有个AServlet,他的路径是
/a,如下所示package com.study; 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; @WebServlet("/a") public class AServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("A Servlet 的doGet方法执行了"); } }有一个过滤器MyFilter,上面有注解@WebFilter("/a")
package com.study; import jakarta.servlet.*; import jakarta.servlet.annotation.WebFilter; import java.io.IOException; @WebFilter("/a") public class MyFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { Filter.super.init(filterConfig); } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { System.out.println("doFilter被执行了"); } @Override public void destroy() { Filter.super.destroy(); } }这时你访问
项目名/a,发现跳转不到AServlet了,此时MyFilter中的doFilter方法执行了,这就被拦截了想让向下走,执行AServlet,就得在doFilter方法里写
@Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { System.out.println("doFilter被执行了"); filterChain.doFilter(servletRequest,servletResponse); }注意,@WebFilter("/a")里可以写/xx/*或者*.xx或者/*表示匹配模糊或者匹配所有
目标Servlet是否执行,取决于两个条件
- 第一,在过滤器当中是否编写了chain.doFilter(request,response)代码
- 第二,用户发送的请求路径是否和Servlet的请求路径一致
chain.doFilter(request,response)这行代码的作用
- 执行下一个过滤器,如果下面没有过滤器了,执行最终的Servlet
- 注意Filter的优先级,天生的就比Servlet优先级高,假如
/a.do对应一个Filter,也对应一个Servlet,那么一定是先执行Filter,然后再执行Servlet
Filter的执行顺序?
- 在web.xml文件中多个filter依靠filter-mapping标签的顺序依次执行
- 在@WebFilter({"/b","/a"})配置的按照过滤器类的名字大小依次进行,比如FilterA和FilterB先执行FilterA,Filter1和Filter2先执行Filter1
Filter的生命周期
和Servlet的生命周期一致,唯一区别是Filter在服务器启动时会实例化,而Servlet不会
Filter的设计模式:责任链设计模式
Filter的最大的优点:在程序编译阶段不会确定调用顺序,因为Filter的调用顺序是配置到web.xml文件中的,只要修改web.xml配置文件中filter-mapping的顺序就可以调整Filter的执行顺序,显然Filter的执行顺序是在程序运行阶段动态组合的,那么这种设计模式被称为责任链设计模式,责任链设计模式最大的核心思想:在程序运行阶段,动态的组合程序的调用顺序
监听器
监听器时Servlet规范中的一员,就像Filter一样,也是servlet规范中的一员
在Servlet中,所有的监听器接口都是以"Listener"结尾
监听器有什么用?
- 监听器实际上是Servlet规范留给我们javaweb程序员的特殊时机
- 特殊的时刻如果想执行这段代码,你需要想到使用对应的监听器
Servlet规范中提供了哪些监听器?
-
jakarta.servlet包下
- ServletContextListener
- ServletContextAttributeListener
- ServletRequestListener
- ServletRequestAttributeListener
-
jakarta.servlet.http包下
-
HttpSessionListener
-
HttpSessionAttributeListener
- 该监听器需要使用@WebListener注解进行标注
- 该监听器监听的是session域中数据的变化,只要数据变化,就会执行相应的方法
- 主要监听点在session域对象上
-
HttpSessionBindingListener
-
该监听器不需要使用@WebListener进行标注
-
假设User类实现了该监听器,那么User类对象被放入session域中会触发bind事件
User类对象被从session域中删除的时候,触发unbind事件
-
假设Customer类没有实现该监听器,那么Customer类对象被放入session域或者删除时不会触发bind和unbind事件
-
-
HttpSessionldListener
- session的id发生改变的时候,监听器中的唯一个方法就会被调用
-
HttpSessionActivationListener
- 监听session对象的钝化和活化的 钝化:session对象从内存存储到硬盘文件 活化:从硬盘文件把session恢复到内存
-
实现监听器的步骤
-
编写一个类,实现ServletContextListener接口,并实现里面的方法
package com.study; import jakarta.servlet.ServletContextEvent; import jakarta.servlet.ServletContextListener; public class MyListener implements ServletContextListener { @Override public void contextInitialized(ServletContextEvent sce) { //想在这个特殊的时刻写代码,你写就是了,它会被服务器自动调用 } @Override public void contextDestroyed(ServletContextEvent sce) { //想在这个特殊的时刻写代码,你写就是了,它会被服务器自动调用 } } -
在web.xml文件中对MyListener进行配置
<listener> <listener-class>com.study.MyListener</listener-class> </listener>同过滤器一样,可以使用注解的方式配置@WebListener
@WebListener public class MyListener implements ServletContextListener { @Override public void contextInitialized(ServletContextEvent sce) { //这个方法是在ServletContext对象被创建的时候调用 //服务器启动时间点,想在这个时候执行一段代码,写就行了 } @Override public void contextDestroyed(ServletContextEvent sce) { //这个方法是在ServletContext对象被销毁的时候调用 //服务器关闭时间点,想在这个时候执行一段代码,写就行了 } } -
注意,所有监听器的方法都是服务器调用,不用javaweb程序员调用,什么时候调用呢?
是发生某个特殊事件后被服务器调用
如ServletContextListener监听器主要监听的是ServletContext对象的状态
思考一个业务场景:记录该网站实时的在线用户的个数
我们可以通过服务器端有没有分配session对象判断,因为一个session代表了一个用户,有一个session就代表有一个用户,如果你采用这种逻辑去实现的话session有多少个,在线用户就有多少个,这种方式的话HttpSessionListenert够用了,session对象只要新建,则count++,然后将count存储到ServletContext域当中,在页面展示在线人数即可
那要是想统计登录的在线人数的数量,该怎么办?
只要登录,说明肯定有session.setAttribute("user",userObj),只要退出,session.removeAttribute("user")肯定要执行或者session超时被销毁,这时候就要用HttpSessionBindingListener监听器了,会检测有没有往session里存过特定类型的对象,有添加过userObj才会触发监听器