jakarta.servlet.Servlet 核心接口
一个Servlet对象从创建到销毁整个过程(生命周期)
Servlet对象的创建,对象上方法的调用,对象最终的销毁,JavaWeb程序员是无权干预的 , 是由Tomcat服务器(WEB Server)全权负责的
- Tomcat服务器通常我们又称为:WEB容器【WEB Container】
- WEB容器创建的Servlet对象,这些Servlet对象都会被放到一个集合当中(HashMap),只有放到这个HashMap集合中的Servlet对象才能够被WEB容器管理
- 服务器在启动的时候会解析XML文件 , 把Servlet类的全类名和请求路径放入这个HashMap集合当中
我们自己new的Servlet对象不受WEB容器的管理
- 自己new的Servlet对象不会被WEB容器管理。(自己new的Servlet对象不在容器当中)
Servlet对象的创建时机
-
默认情况下,服务器在启动的时候Servlet对象并不会被实例化(在Servlet中提供一个无参数的构造方法,启动服务器的时候构造方法并没有执行)
-
这个设计是合理的。用户没有发送请求之前,如果提前创建出来所有的Servlet对象,必然是耗费内存的,并且创建出来的Servlet如果一直没有用户访问,显然这个Servlet对象是一个废物,没必要先创建。
让服务器启动的时候创建Servlet对象: 在servlet标签中添加 load-on-startup子标签,在该子标签中填写整数,越小的整数优先级越高
<servlet>
<servlet-name>aservlet</servlet-name>
<servlet-class>com.bjpowernode.javaweb.servlet.AServlet</servlet-class>
<!--在servlet标签中添加<load-on-startup>子标签,在该子标签中填写整数,越小的整数优先级越高-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>aservlet</servlet-name>
<url-pattern>/a</url-pattern>
</servlet-mapping>
关于Servlet类中的一些方法
方法名 | 执行时机 | 方法作用 | 使用次数 |
---|---|---|---|
无参数构造方法 | 当服务器接收到用户的请求路径时执行 | 创建Servlet对象 , 标志着你出生了 | 只执行一次 |
init | 在Servlet对象第一次被创建之后执行 , | 是完成初始化操作的 , 例如:初始化数据库连接池,初始化线程池… , 标志着你正在接受教育 | init方法很少用 , 并且只需要执行一次。 |
service | 只要用户发送一次请求,service方法必然会执行一次 , | 是处理用户请求的核心方法 , 标志着你已经开始工作了,已经开始为人类提供服务了 | 使用最多 , service方法是一定要实现的 , 发送N次请求则执行N次 |
destroy | 在销毁Servlet对象之前会调用一次destroy方法 | 关闭服务器的时候,关闭程序中开启的流和数据库连接等资源的代码就可以写到destroy方法当中 , 标志着临终 | destroy方法也很少用。只执行一次 |
getServletInfo | 获取servlet的相关信息(作者,版权号) | ||
getServletConfig | 获取 ServletConfig 对象(封装Servelt对象的初始化参数信息) |
对Servlet中方法的测试
public class AServlet implements Servlet {
// 无参数构造方法
public AServlet() {
System.out.println("AServlet无参数构造方法执行了");
}
// init方法通常是完成初始化操作的。init方法在执行的时候AServlet对象已经被创建出来了。
@Override
public void init(ServletConfig servletConfig) throws ServletException {
System.out.println("AServlet's init method execute!");
}
// 只要用户发送一次请求,service方法必然会执行一次。发送100次请求,service方法则执行100次。
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("AServlet's service method execute!");
}
// destroy方法在执行的时候,AServlet对象的内存还没有被销毁。即将被销毁。destroy方法中可以编写销毁前的准备。
@Override
public void destroy() {
System.out.println("AServlet's destroy method execute!");
}
//获取Servlet的相关信息(作者,版权号)
@Override
public ServletConfig getServletConfig() {
return null;
}
//获取 ServletConfig 对象(封装Servelt对象的初始化参数信息)
@Override
public String getServletInfo() {
return null;
}
}
Servlet对象的的生命周期中方法的执行
用户发送第一次请求的时候
- Tomcat服务器通过反射机制,调用无参数构造方法。创建Servlet对象。( 我们在web.xml文件中配置的Servlet类对应的对象。)
- Tomcat服务器调用Servlet对象的init方法完成初始化。
- Tomcat服务器调用Servlet对象的service方法处理请求。
//控制台输出内容
AServlet无参数构造方法执行了
AServlet's init method execute!
AServlet's service method execute!
用户在发送第一次请求的时候的执行原理
- 首先通过这个请求路径找到对应的Servlet类并实例化(AServlet的构造方法被执行了 , 并且执行的是无参数构造方法)
- AServlet对象被创建出来之后,Tomcat服务器马上调用了AServlet对象的init方法(init方法在执行的时候,AServlet对象已经被创建出来了)
- init方法执行之后,Tomcat服务器马上调用AServlet对象的service方法。
**用户发送第二次请求的时候 **
- Tomcat服务器调用Servlet对象的service方法处理请求。
//控制台输出内容
AServlet's service method execute!
用户在发送第二次,或者第三次请求的时候,Servlet对象并没有新建,还是使用之前创建好的Servlet对象,直接调用该Servlet对象的service方法
第一:Servlet对象是单实例的(但是Servlet类并不符合单例模式。我们称之为假单例)
因为Servlet对象的创建我们javaweb程序员管不着,这个对象的创建只能是Tomcat来说了算,Tomcat只创建了一个,所以导致了单例,但是属于假单例。真单例模式,构造方法是私有化的
第二:无参数构造方法、init方法只在第一次用户发送请求的时候执行。即无参数构造方法只执行一次。init方法也只被Tomcat服务器调用一次
第三:只要用户发送一次请求:service方法必然会被Tomcat服务器调用一次。发送100次请求,service方法会被调用100次
关闭服务器的时候,控制台输出
- Tomcat服务器调用Servlet对象的destroy方法,做销毁之前的准备工作。
- Tomcat服务器销毁Servlet对象。
AServlet's destroy method execute!
Servlet的destroy方法在服务器关闭的时候只被Tomcat服务器调用一次
- 因为服务器关闭的时候要销毁AServlet对象的内存 , 所以在销毁AServlet对象内存之前,Tomcat服务器会自动调用AServlet对象的destroy方法
- 注意: destroy方法执行的时候AServlet对象还在,没有被销毁。destroy方法执行结束之后,AServlet对象的内存才会被Tomcat释放
两个问题
当我们Servlet类中编写一个有参数的构造方法,如果没有手动编写无参数构造方法出现的问题
- 如果没有无参数的构造方法,会导致出现500错误,无法实例化Servlet对象
- 500是一个HTTP协议的错误状态码 , 表示服务器内部发生了错误 , 即服务器端的Java程序出现了异常
- 所以在Servlet开发当中,不建议程序员来定义构造方法,因为定义不当,一不小心就会导致无法实例化Servlet对象
Servlet的无参数构造方法和init方法都是在对象第一次创建的时候执行,并且只执行一次。那么可以把init方法中的代码放到无参数构造方法中吗?即可以用无参数构造方法可以代替掉init方法
- 不能 , 因为Servlet规范中有要求,作为javaweb程序员,编写Servlet类的时候,不建议手动编写构造方法,因为编写构造方法,很容易让无参数构造方法消失,这个操作可能会导致Servlet对象无法实例化。所以将想要初始化的的操作单独放在一个init方法是很有存在的必要的
jakarta.servlet.GenericServlet 标准通用的Servlet抽象类
jakarta.servlet.GenericServlet 是官方提供的不需要我们手写 , 但是我们可以仿造手写一个
GenericServlet 实现 Servlet 的基本方法
方法名 | 功能 |
---|---|
getServletConfig() | 获取 ServletConfig 对象(封装Servelt对象的初始化参数信息) |
getServletContext() | 获取 ServletContext 对象 |
getServletInfo() | 获取servlet的相关信息(作者,版权号) |
log() | 记录日志信息 |
GenericServlet 实现 ServletConfig 的扩展方法
方法名 | 功能 |
---|---|
public String getInitParameter() | 利用ServletConfig 对象 通过初始化参数标签的name获取value |
public String getInitParameter() | 利用ServletConfig 对象 通过初始化参数标签的name获取value |
public Enumeration< String > getInitParameterNames() | 利用ServletConfig 对象获取所有的初始化参数标签的name |
public ServletContext getServletContext() | 利用ServletConfig 对象获取ServletContext对象 |
public String getServletName() | 利用ServletConfig 对象获取Servlet对象的name |
适配器设计模式
手机直接插到220V的电压上,手机直接就报废了。怎么办?
- 可以找一个充电器。这个充电器就是一个适配器。手机连接适配器。适配器连接220V的电压。这样问题就解决了
**对于UserService类来说,core方法是最主要的方法; 对于CustomerService类来说,最主要的方法是m2 , 其他方法大部分情况下是不用使用的 **
public interface MyInterface {
void m1();
void m2();
void m3();
void m4();
void m5();
void m6();
void m7();
void core();
}
UserService类的适配器
public abstract class UserAdapter implements MyInterface {
@Override
public void m1() {
}
@Override
public void m2() {
}
@Override
public void m3() {
}
@Override
public void m4() {
}
@Override
public void m5() {
}
@Override
public void m6() {
}
@Override
public void m7() {
}
//UserService类的主要方法适配器没有去实现
public abstract void core();
}
public class UserService extends UserAdapter {
@Override
public void core() {
}
}
CustomerService专用的适配器
public abstract class CustomerAdapter implements MyInterface {
@Override
public void m1() {
}
//CustomerService类的主要方法适配器没有去实现
public abstract void m2() ;
@Override
public void m3() {
}
@Override
public void m4() {
}
@Override
public void m5() {
}
@Override
public void m6() {
}
@Override
public void m7() {
}
@Override
public void core() {
}
}
public class CustomerService extends CustomerAdapter{
@Override
public void m2() {
}
}
使用适配器设计模式Adapter改造Servlet类手写 GenericServlet
我们编写一个Servlet类直接实现Servlet接口有什么缺点?
- 我们只需要service方法,其他方法大部分情况下是不需要使用的。如果我们都直接实现这个接口的方法 , 代码会很丑陋
编写一个标准通用的Servlet,起名:GenericServlet,这个类是一个抽象类,其中有一个抽象方法service
- GenericServlet实现Servlet接口 , GenericServlet是一个适配器。以后编写的所有Servlet类都不要直接实现Servlet接口了 , 直接继承GenericServlet,重写service方法即可
我提供了一个GenericServlet之后,Servlet对象 init方法还会执行吗?
- 还会执行父类GenericServlet类中的init方法
init方法中的ServletConfig对象是谁创建的?
- 是Tomcat服务器先创建了ServletConfig对象,然后调用init方法的时候将ServletConfig对象传给了init方法
- 这个ServletConfig对象目前在init方法的参数上,属于局部变量
Tomcat服务器伪代码
public class Tomcat {
public static void main(String[] args){
// .....
// 创建LoginServlet对象(通过反射机制,调用无参数构造方法来实例化LoginServlet对象)
Class clazz = Class.forName("com.bjpowernode.javaweb.servlet.LoginServlet");
Object obj = clazz.newInstance();
// 向下转型
Servlet servlet = (Servlet)obj;
// 创建ServletConfig对象
// Tomcat服务器负责将ServletConfig对象实例化出来。
// 多态(Tomcat服务器完全实现了Servlet规范)
ServletConfig servletConfig = new org.apache.catalina.core.StandardWrapperFacade();
// 调用Servlet的init方法
servlet.init(servletConfig);
// 调用Servlet的service方法
//....
}
}
ServletConfig对象肯定以后要在service方法中使用,如何保证ServletConfig对象在service方法中能够使用?
- 给GenericServlet类声明一个成员变量 , 把 init 方法中的局部变量赋值给成员变量 , 这样子类就可以通过父类提供的 getServletConfig 方法访问
public abstract class GenericServlet implements Servlet {
// 成员变量
private ServletConfig config;
@Override
//使用final修饰的init方法子类不能重写
public final void init(ServletConfig config) throws ServletException {
//System.out.println("servletConfig对象,小猫咪创建好的:" + config);
this.config = config;
// 调用init()方法 , 这个this就是Servlet对象 ,如果子类重写了init方法 ,那么一定是调用子类的init方法
this.init();
}
//这个init方法是供子类重写的
public void init(){
}
@Override
public ServletConfig getServletConfig() {
return config;
}
//抽象方法,这个方法最常用。所以要求子类必须实现service方法。
public abstract void service(ServletRequest servletRequest, ServletResponse servletResponse)
throws ServletException, IOException;
@Override
public String getServletInfo() {
return null;
}
@Override
public void destroy() {
}
}
如果子类 LoginServlet 必须要重写 GenericServlet 父类的 init 方法如何重写?
- 如果子类重写了父类的init方法 , 会导致父类的init方法不执行 , 那么GenericServlet类的ServletConfig对象就为null , 将来造成空指针异常
- GenericServlet类应该提供一个无参的init方法供子类重写 , 然后当服务器调用有参的init方法的时候调用无参的init方法
- 如果子类重写了init方法 ,那么一定是调用子类的init方法 , 否则调用本类的init方法
public class LoginServlet extends GenericServlet{
@Override
public void init(){
System.out.println("LoginServlet's init() method execute!");
}
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("正在处理用户登录请求,请稍后。。。。。");
// 想在LoginServlet子类中使用ServletConfig对象怎么办?
ServletConfig config = this.getServletConfig();
System.out.println("service方法中是否可以获取到ServletConfig对象?" + config);
}
}
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>loginServlet</servlet-name>
<servlet-class>com.bjpowernode.javaweb.servlet.LoginServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>loginServlet</servlet-name>
<url-pattern>/login</url-pattern>
</servlet-mapping>
</web-app>
jakarta.servlet.ServletConfig Servlet配置信息接口
ServletConfig的全类名jakarta.servlet.ServletConfig是Servlet规范中的一员
ServletConfig接口的实现类是Tomcat服务器给实现的 : public class org.apache.catalina.core.StandardWrapperFacade implements ServletConfig {}
- 如果把Tomcat服务器换成jetty服务器,输出ServletConfig对象的时候,不一定是这个结果,包名类名可能和Tomcat不一样。但是他们都实现了ServletConfig这个规范
ServletConfig对象的创建时机
- 默认情况下,Tomcat服务器创建Servlet对象的时候同时创建的ServletConfig对象, 它们都是在用户发送第一次请求的时候创建
ServletConfig对象的使用时机
- Tomcat服务器调用 Servlet对象的 init 方法的时候需要传给 init 方法的参数就是 ServletConfig 对象
ServletConfig被翻译为Servlet对象的配置信息对象
- Tomcat服务器解析web.xml文件,将 web.xml 文件中< servlet >< /servlet >标签中的配置信息自动包装到 ServletConfig 对象中
- 一个Servlet对象对应一个ServletConfig对象 , 100个Servlet对象则对应100个ServletConfig对象(Servlet和ServletConfig对象是一对一)
<!--Tomcat服务器解析web.xml文件,将web.xml文件中<servlet></servlet>标签中的配置信息自动包装到ServletConfig对象中-->
<servlet>
<servlet-name>configTest</servlet-name>
<servlet-class>com.bjpowernode.javaweb.servlet.ConfigTestServlet</servlet-class>
</servlet>
验证一个Servlet对象对应一个ServletConfig对象
配置两个Servlet对象
<!--配置ConfigTestServlet-->
<servlet>
<servlet-name>configTest</servlet-name>
<servlet-class>com.bjpowernode.javaweb.servlet.ConfigTestServlet</servlet-class>
<!--这里是可以配置一个Servlet对象的初始化信息的。-->
<init-param>
<param-name>driver</param-name>
<param-value>com.mysql.cj.jdbc.Driver</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>configTest</servlet-name>
<url-pattern>/test</url-pattern>
</servlet-mapping>
<!--配置ConfigTestServlet2-->
<servlet>
<servlet-name>configTest2</servlet-name>
<servlet-class>com.bjpowernode.javaweb.servlet.ConfigTestServlet2</servlet-class>
<init-param>
<param-name>key</param-name>
<param-value>value</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>configTest2</servlet-name>
<url-pattern>/test2</url-pattern>
</servlet-mapping>
ConfigTestServlet
public class ConfigTestServlet extends GenericServlet {
@Override
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
response.setContentType("text/html");
PrintWriter out = response.getWriter();
// 获取ServletConfig对象
ServletConfig config = this.getServletConfig();
// 输出ServletConfig对象: org.apache.catalina.core.StandardWrapperFacade@aea0d43
out.print("ServletConfig对象是:" + config.toString());
out.print("<br>");
}
}
**ConfigTestServlet2 **
public class ConfigTestServlet2 extends GenericServlet {
@Override
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
response.setContentType("text/html");
PrintWriter out = response.getWriter();
// 获取ServletConfig对象
ServletConfig config = this.getServletConfig();
// 输出ServletConfig对象: org.apache.catalina.core.StandardWrapperFacade@287af686
out.print("ServletConfig对象是:" + config);
//通过初始化参数的name获取value
String value = config.getInitParameter("key");
out.print("<br>" + value);//value
}
}
一个Servlet对象对应一个ServletConfig对象 , 100个Servlet对象则对应100个ServletConfig对象(Servlet和ServletConfig对象是一对一)
- ConfigTestServlet 对应的ServletConfig对象是org.apache.catalina.core.StandardWrapperFacade@287af686
- ConfigTestServlet2 对应的ServletConfig对象是org.apache.catalina.core.StandardWrapperFacade@287af686
ServletConfig接口中常用的方法
< servlet > < /servlet >标签的子标签< init-param > < /init-param >中的初始化参数信息会自动被Tomcat服务器解析并封装到ServletConfig对象当中 , 我们可以通过 ServletConfig 接口提供的的方法获取初始化参数信息
如果你的配置信息只是想给某一个servlet作为参考,那么你配置到servlet标签当中即可,使用ServletConfig对象来获取
<?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>configTest</servlet-name>
<servlet-class>com.bjpowernode.javaweb.servlet.ConfigTestServlet</servlet-class>
<!--这里是可以配置一个Servlet对象的初始化信息的。-->
<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/bjpowernode</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>root1234</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>configTest</servlet-name>
<url-pattern>/test</url-pattern>
</servlet-mapping>
</web-app>
只要自己写的 Servlet 继承了GenericServlet 也可以使用this去调用这四个方法
方法名 | 方法作用 |
---|---|
public String getInitParameter(String name) | 通过初始化参数标签的name获取value |
public Enumeration< String > getInitParameterNames() | 获取所有的初始化参数标签的name |
public ServletContext getServletContext() | 获取ServletContext对象 |
public String getServletName() | 获取Servlet对象的name |
通过父类的 getServletConfig 方法获取 ServletConfig 对象的方式获取一个Servlet对象的初始化参数
public class ConfigTestServlet extends GenericServlet {
@Override
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
response.setContentType("text/html");
PrintWriter out = response.getWriter();
// 通过父类的方法获取ServletConfig对象
ServletConfig config = this.getServletConfig();
// 获取<servlet-name></servlet-name>标签中的name
String servletName = config.getServletName();
out.print("<servlet-name>"+servletName+"</servlet-name>");//configTest
out.print("<br>");
// java.util.Enumeration<java.lang.String> getInitParameterNames() 获取所有的初始化参数的name
Enumeration<String> initParameterNames = config.getInitParameterNames();
// 遍历集合
while(initParameterNames.hasMoreElements()) { // 是否有更多 name 元素
String parameterName = initParameterNames.nextElement(); // 取 name 元素
String parameterVal = config.getInitParameter(parameterName); // 通过name获取value
out.print(parameterName + "=" + parameterVal);
out.print("<br>");
}
// java.lang.String getInitParameter(java.lang.String name) 通过初始化参数的name获取value
String driver = config.getInitParameter("driver");
out.print(driver);
}
}
直接通过 this 获取一个Servlet对象的初始化参数
因为我们编写的Servlet类继承了GenericServlet , 而GenericServlet又提供了获取初始化参数的方法 , 所以我们可以在Servlet类中直接使用 this 去调用父类的方法获取初初始化参数信息
父类帮我们去调用ServletConfig对象的方法 , 所以我们不用再获取ServletConfig对象也可以间接使用它的方法
public abstract class GenericServlet implements Servlet {
// 成员变量
private ServletConfig config;
@Override
public final void init(ServletConfig config) throws ServletException {
this.config = config;
this.init();
}
@Override
public ServletConfig getServletConfig() {
return config;
}
//获取初始化参数的信息
@Override
public ServletConfig getInitParameter(String name) {
return getServletConfig().getInitParameter(String name);
}
@Override
public ServletConfig getInitParameterNames() {
return getServletConfig().getInitParameterNames();
}
}
直接通过this获取一个Servlet对象的初始化参数
public class ConfigTestServlet extends GenericServlet {
@Override
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
response.setContentType("text/html");
PrintWriter out = response.getWriter();
// 实际上获取一个Servlet对象的初始化参数,可以不用获取ServletConfig对象。直接通过this也可以。
Enumeration<String> names = this.getInitParameterNames();
while(names.hasMoreElements()){
String name = names.nextElement();
String value = this.getInitParameter(name);
// 打印到后台
System.out.println(name + "=" + value);
}
}
}
jakarta.servlet.ServletContext Servlet上下文接口
概述
ServletContext对应显示生活中的什么例子呢?
- 一个教室里有多个学生,那么每一个学生就是一个Servlet,这些学生都在同一个教室当中,那么我们可以把这个教室叫做ServletContext对象。那么也就是说放在这个ServletContext对象(环境)当中的数据,在同一个教室当中,物品都是共享的。比如:教室中有一个空调,所有的学生都可以操作。可见,空调是共享的。因为空调放在教室当中。
ServletContext是接口,是Servlet规范中的一员。
- Tomcat服务器(WEB服务器)中的 org.apache.catalina.core.ApplicationContextFacade 这个类实现了ServletContext接口。
ServletContext对象是谁创建的?什么时候创建和销毁?
- ServletContext对象是WEB服务器在启动的时候创建。在web服务器关闭的时候销毁。这就是ServletContext对象的生命周期。
ServletContext被称为Servlet上下文对象(Servlet对象的四周环境对象) , ServletContext对象也是应用级对象。
-
对于一个webapp来说,ServletContext对象只有一个。一个ServletContext对象通常对应的是一个web.xml文件
-
只要在同一个webapp当中,只要在同一个应用当中,所有的Servlet对象都是共享同一个ServletContext对象的。
Tomcat是一个容器,一个容器当中可以放多个webapp,一个webapp只对应一个ServletContext对象。
- Tomcat服务器中有一个webapps目录,这个webapps目录下可以存放多个webapp,假设有100个webapp,那么就有100个ServletContext对象。
获取ServletContext对象的两种方式
结论: 在同一个webapp中的所有 Servelt程序中执行getServletContext获取的都是同一个ServletContext对象
第一种方式:通过 ServletConfig 对象获取 ServletContext 对象
public class ContextTestServlet1 extends GenericServlet {
@Override
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
response.setContentType("text/html");
PrintWriter out = response.getWriter();
// 因为JSP的内置对象的变量名就叫application , 所以我们也这样叫
ServletContext application = config.getServletContext();
// org.apache.catalina.core.ApplicationContextFacade@19187bbb
out.print("<br>" + application);
}
}
第二种方式:通过this也可以获取ServletContext对象
public class ContextTestServlet2 extends GenericServlet {
@Override
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
response.setContentType("text/html");
PrintWriter out = response.getWriter();
ServletContext application2 = this.getServletContext();
// org.apache.catalina.core.ApplicationContextFacade@19187bbb
out.print("<br>" + application2);
}
}
获取应用级的配置信息和记录日志的方法
以下的配置信息属于应用级的配置信息,一般一个项目中共享的配置信息会放到以下的标签当中
- 如果你的配置信息只是想给某一个servlet作为参考,那么你配置到servlet标签当中即可,使用ServletConfig对象来获取
<?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">
<!--上下文的初始化参数 , 可以通过 ServletContext 对象的方法获取的是以下的配置信息-->
<!--以下的配置信息对当前web.xml文件中的所有的 Servlet 都起作用-->
<context-param>
<param-name>pageSize</param-name>
<param-value>10</param-value>
</context-param>
<context-param>
<param-name>startIndex</param-name>
<param-value>0</param-value>
</context-param>
<!--注意:以上的配置信息属于应用级的配置信息,一般一个项目中共享的配置信息会放到以上的标签当中。-->
<!--如果你的配置信息只是想给某一个servlet作为参考,那么你配置到servlet标签当中即可,使用ServletConfig对象来获取。-->
<servlet>
<servlet-name>configTest</servlet-name>
<servlet-class>com.bjpowernode.javaweb.servlet.ConfigTestServlet</servlet-class>
<!--这里是可以配置一个Servlet对象的初始化信息的。只对当前的 Servet 起作用-->
<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/bjpowernode</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>root1234</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>configTest</servlet-name>
<url-pattern>/test</url-pattern>
</servlet-mapping>
</web-app>
方法名 | 功能 |
---|---|
String getInitParameter(String name) | 通过初始化参数的 name 获取 value |
Enumeration< String > getInitParameterNames() | 获取所有的初始化参数的 name |
String getContextPath() | 获取应用的根路径( / 项目名) , 在java源代码当中有一些地方可能会需要应用的根路径,这个方法可以动态获取应用的根路径 , 实际开发中不要将应用的根路径写死,因为你永远都不知道这个应用在最终部署的时候该起一个什么名字 |
String getRealPath(String path) | 获取文件的绝对路径(真实路径) |
void log(String message) | 纪录日志 ,如果没有使用 idea 工具 这个日志会自动记录到 CATALINA_HOME/logs 目录下 , 控制台上不会显示 |
void log(String message, Throwable t) | 控制台上异常不会发生 , 只是记录到了日志当中 |
IDEA 工具可以参照官方的Tomcat服务器安装目录中的资源, 创建多个Tomcat服务器副本 , 这些服务器都存储在idea的相关目录下( CATALINA_BASE指定 )
- 日志信息都记录到了IDEA创建的这些Tomcat服务器副本中的 logs 目录中
# 服务器端的java程序运行的控制台信息
catalina.2021-11-05.log
# ServletContext对象的log方法记录的日志信息存储到这个文件中
localhost.2021-11-05.log ServletContext
# 访问日志
localhost_access_log.2021-11-05.txt
对ServeltContext对象获取配置信息方法的测试
public class AServlet extends GenericServlet {
@Override
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
response.setContentType("text/html");
PrintWriter out = response.getWriter();
// 获取ServletContext对象
ServletContext application = this.getServletContext();
out.print("ServletContext对象是:" + application + "<br>");
// 获取上下文的初始化参数
Enumeration<String> initParameterNames = application.getInitParameterNames();
while(initParameterNames.hasMoreElements()){
String name = initParameterNames.nextElement();
String value = application.getInitParameter(name);
out.print(name + "=" + value + "<br>");
}
// 获取context path (获取应用上下文的根)
String contextPath = application.getContextPath();
out.print(contextPath + "<br>");
// 获取文件的绝对路径
// 后面的这个路径,加了一个“/”,这个“/”代表的是web的根
String realPath = application.getRealPath("/index.html");
// 不加“/”也可以,默认也是从web的根下开始找
String realPath2 = application.getRealPath("index.html");
out.print(realPath + "<br>");
// log 记录日志
application.log("大家好,我是动力节点杜老师,欢迎大家和我一起学习Servlet规范!");
int age = 17; // 17岁
// 当年龄小于18岁的时候,表示非法,记录日志
if(age < 18) {
application.log("对不起,您未成年,请绕行!", new RuntimeException("小屁孩,快走开,不适合你!"));
}
}
}
向应用域中存储数据的方法
ServletContext对象还有另一个名字:应用域(后面还有请求域、会话域)
- 应用域相当于一个缓存,放到缓存中的数据,下次在用的时候 直接从缓存中取,减少IO的操作,,不需要从数据库中(硬盘)再次获取,大大提升执行效率。
如果所有的用户共享一份数据,并且这个数据很少的被修改,并且这个数据量很少,可以将这些数据放到ServletContext这个应用域中 , 这样会大大提升效率。
- ServletContext这个对象只有一个。只有共享的数据放进去才有意义。
- 数据量比较大的数据太占用堆内存,并且 ServletContext 对象的生命周期比较长,服务器关闭的时候,这个对象才会被销毁。大数据量会影响服务器的性能。占用内存较小的数据量可以考虑放进去。
- 所有用户共享的数据,如果涉及到修改操作,必然会存在线程并发所带来的安全问题。所以放在ServletContext对象中的数据一般都是只读的
方法名 | 功能 |
---|---|
void setAttribute(String name, Object value) | 以 Object 的形式向 ServletContext 应用域中存数据 , 类似 map.put(k, v) |
Object getAttribute(String name) | 从 ServletContext 应用域中取数据 , 默认返回Object类型 ,类似 map.get(k) |
void removeAttribute(String name) | 删除ServletContext应用域中的数据 , 类似map.remove(k) |
User
public class User {
private String name;
private String password;
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", password='" + password + '\'' +
'}';
}
public User() {
}
public User(String name, String password) {
this.name = name;
this.password = password;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
对ServeltContext对象存储数据方法的测试
- 在 AServlet 中向应用域中存数据 , 在 BServelt 中从应用域取出存储的数据
public class AServlet extends GenericServlet {
@Override
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
response.setContentType("text/html");
PrintWriter out = response.getWriter();
// 获取ServletContext对象
ServletContext application = this.getServletContext();
out.print("ServletContext对象是:" + application + "<br>");
// 准备数据
User user = new User("jack", "123");
// 向ServletContext应用域当中存储数据 , 在BServelt中取出数据
application.setAttribute("userObj", user);
// 取出来
//Object userObj = application.getAttribute("userObj");
// 输出到浏览器
//out.print(userObj + "<br>");
}
}
在 BServlet 中向应用域中取数据
public class BServlet extends GenericServlet {
@Override
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
response.setContentType("text/html");
PrintWriter out = response.getWriter();
// AServelt 和 BServllet 获取的是同一个ServletContext对象
ServletContext application = this.getServletContext();
out.print("ServletContext对象是:" + application + "<br>");
// 取出来
Object userObj = application.getAttribute("userObj");
// 输出到浏览器
out.print(userObj + "<br>");
}
}
jakarta.servlet.http.HttpServlet
概述
以后我们编写Servlet类的时候,实际上是不会去直接继承 GenericServlet类的,而是要继承HttpServlet
- 因为我们是B/S结构的系统,这种系统是基于HTTP超文本传输协议的 , 所以在Servlet规范当中,为我们专门提供了一个为HTTP协议准备的Servlet类叫做HttpServlet
- 使用 HttpServlet 处理HTTP协议更便捷。但是你需要直到它的继承结构
jakarta.servlet.http 包下的类和接口
- jakarta.servlet.http.HttpServlet (HTTP协议专用的Servlet类,抽象类)
- jakarta.servlet.http.HttpServletRequest (HTTP协议专用的请求对象)
- jakarta.servlet.http.HttpServletResponse (HTTP协议专用的响应对象)
HttpServletRequest,简称request对象。我们只要面向HttpServletRequest,就可以获取请求协议中的数据。
- Tomcat服务器(WEB服务器)将“请求协议”中的数据全部解析出来,然后将这些数据全部封装到 request 对象当中。
HttpServletResponse , 简称response对象 , 是专门用来响应HTTP协议到浏览器的。
- Tomcat服务器(WEB服务器)将“响应协议”中的数据全部解析出来,然后将这些数据全部封装到 response 对象当中。
HttpServlet类的继承结构
#我们以后编写的Servlet要继承HttpServlet类
jakarta.servlet.Servlet(接口)【爷爷】
jakarta.servlet.GenericServlet implements Servlet(抽象类)【儿子】
jakarta.servlet.http.HttpServlet extends GenericServlet(抽象类)【孙子】
HttpServlet源码分析
Tomcat服务器调用Servlet对象的init方法完成初始。
- 本类没有提供 init 方法,那么必然执行父类HttpServlet的init方法。
public class HelloServlet extends HttpServlet {
// Tomcat服务器通过反射机制,调用无参数构造方法。创建Servlet对象。( 我们在web.xml文件中配置的Servlet类对应的对象。)
// 用户第一次请求,创建HelloServlet对象的时候,会执行这个无参数构造方法
public HelloServlet() {
}
//override 重写 doGet方法
//override 重写 doPost方法
}
HttpServlet类中也没有init方法,会继续执行GenericServlet类中的init方法。
public abstract class GenericServlet implements Servlet, ServletConfig,java.io.Serializable {
// 用户第一次请求的时候,HelloServlet对象第一次被创建之后,这个init方法会执行。
public void init(ServletConfig config) throws ServletException {
this.config = config;
this.init();
}
// 用户第一次请求的时候,带有参数的init(ServletConfig config)执行之后,会执行这个没有参数的init()
public void init() throws ServletException {
// NOOP by default
}
}
Tomcat服务器调用Servlet对象的service方法处理请求。
- 若本类没有提供service方法。那么必然执行父类HttpServlet类service方法。
- 用户只要发送一次请求,这个service方法就会执行一次。
public abstract class HttpServlet extends GenericServlet {
// 用户发送第一次请求的时候这个service会执行
// 用户发送第N次请求的时候,这个service方法还是会执行。
@Override
public void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException {
HttpServletRequest request;
HttpServletResponse response;
try {
// 将ServletRequest和ServletResponse向下转型为带有Http的HttpServletRequest和HttpServletResponse
request = (HttpServletRequest) req;
response = (HttpServletResponse) res;
} catch (ClassCastException e) {
throw new ServletException(lStrings.getString("http.non_http"));
}
// 调用重载的service方法。
service(request, response);
}
}
HttpServlet类重载的service方法是一个模板方法
- 在该方法中定义核心算法骨架,具体的实现步骤延迟到子类中去完成。
// 这个service方法的两个参数都是带有Http的。
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// 获取请求方式
// 这个请求方式最终可能是:""
// 注意:request.getMethod()方法获取的是请求方式,可能是七种之一:
// GET POST PUT DELETE HEAD OPTIONS TRACE
String method = req.getMethod();
// 如果请求方式是GET请求,则执行doGet方法。
if (method.equals(METHOD_GET)) {
long lastModified = getLastModified(req);
if (lastModified == -1) {
// servlet doesn't support if-modified-since, no reason
// to go through further expensive logic
doGet(req, resp);
} else {
long ifModifiedSince;
try {
ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
} catch (IllegalArgumentException iae) {
// Invalid date header - proceed as if none was set
ifModifiedSince = -1;
}
if (ifModifiedSince < (lastModified / 1000 * 1000)) {
// If the servlet mod time is later, call doGet()
// Round down to the nearest second for a proper compare
// A ifModifiedSince of -1 will always be less
maybeSetLastModified(resp, lastModified);
doGet(req, resp);
} else {
resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
}
}
} else if (method.equals(METHOD_HEAD)) {
long lastModified = getLastModified(req);
maybeSetLastModified(resp, lastModified);
doHead(req, resp);
} else if (method.equals(METHOD_POST)) {
// 如果请求方式是POST请求,则执行doPost方法。
doPost(req, resp);
} else if (method.equals(METHOD_PUT)) {
doPut(req, resp);
} else if (method.equals(METHOD_DELETE)) {
doDelete(req, resp);
} else if (method.equals(METHOD_OPTIONS)) {
doOptions(req,resp);
} else if (method.equals(METHOD_TRACE)) {
doTrace(req,resp);
} else {
// Note that this means NO servlet supports whatever
// method was requested, anywhere on this server.
String errMsg = lStrings.getString("http.method_not_implemented");
Object[] errArgs = new Object[1];
errArgs[0] = method;
errMsg = MessageFormat.format(errMsg, errArgs);
resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
}
}
我们编写的 Servlet 直接继承 HttpServlet 并重写HttpServlet类中的 service() 方法行吗
可以,只不过你享受不到 405 错误的提醒。享受不到HTTP协议专属的东西。
- 报 405 错误的原因是前端发起的请求方式 , 子类没有重写对应的处理该请求方式的方法 , 然后执行了 HttpServelt 的 doXxx方法
public class HelloServlet extends HttpServlet {
// 通过无参数构造方法创建对象。
public HelloServlet() {
}
// 没有提供init方法,那么必然执行父类HttpServlet的init方法。HttpServlet类中没有init方法,会继续执行GenericServlet类中的init方法。
// 没有提供service方法。那么必然执行父类HttpServlet类service方法。
// 重写HttpServlet类中的service()方法 ,HttpServlet类中的service()方法就不会在执行
// service方法没有执行 , 里面的doXxx方法也就无法执行 , 就不会报 405 错误
@Override
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.print("<h1>hello servlet</h1>");
}
}
HttpServelt 的 doGet方法 和 doPost方法
- 如果子类没有重写doGet或者doPost方法 , 则会执行 HttpServlet 的doGet或者doPost方法 , 这两个方法只要执行了,必然报 405 错误
//如果子类没有重写doGet方法 , 则会执行 HttpServlet 的doGet方法
//该方法的作用是报一个 405 错误
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException{
// 报405错误
String msg = lStrings.getString("http.method_get_not_supported");
sendMethodNotAllowed(req, resp, msg);
}
//如果子类没有重写doPost方法 , 则会执行 HttpServlet 的doPost方法
//该方法的作用是报一个 405 错误
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// 报405错误
String msg = lStrings.getString("http.method_post_not_supported");
sendMethodNotAllowed(req, resp, msg);
}
怎么避免405的错误
前端到底需要发什么样的请求,其实应该后端说了算。后端让发什么方式,前端就得发什么方式。
- 后端重写了doGet方法,前端一定要发get请求。
- 后端重写了doPost方法,前端一定要发post请求。
注意: 有的人为了避免 405 错误,在Servlet类当中,将doGet和doPost方法都进行了重写。这样,确实可以避免405的发生,但是不建议
- 405错误还是有用的。该报错的时候就应该让他报错。
- 如果你要是同时重写了 doGet 和 doPost,那还不如你直接重写service方法好了。这样代码还能少写一点。
最终的一个Servlet类的开发步骤
开发步骤
第一步:编写一个Servlet类,直接继承HttpServlet
第二步:重写doGet方法或者重写doPost方法,到底重写谁,javaweb程序员说了算。( 重写父类某个方法有快捷键 ctrl + o )
第三步:将Servlet类配置到web.xml文件当中。
第四步:准备前端的页面(form表单),form表单中指定请求路径即可。
public class HelloServlet extends HttpServlet {
// 通过无参数构造方法创建对象。
public HelloServlet() {
}
// 没有提供init方法,那么必然执行父类HttpServlet的init方法。HttpServlet类中没有init方法,会继续执行GenericServlet类中的init方法。
// 没有提供service方法。那么必然执行父类HttpServlet类service方法。
// 当前端发送的请求是get请求的时候,我这里重写doGet方法。
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException{
PrintWriter out = response.getWriter();
out.print("<h1>doGet</h1>");
}
// 当前端发送的请求是post请求的时候,我这里重写doPost方法。
/*protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException{
PrintWriter out = response.getWriter();
out.print("<h1>doPost</h1>");
}*/
}
jakarta.servlet.http.HttpServletRequest接口
HttpServletRequest接口是Servlet规范中的一员。它的父接口是 ServletRequest , org.apache.catalina.connector.RequestFacade 实现了 HttpServletRequest接口
public interface HttpServletRequest extends ServletRequest {}
public class RequestFacade implements HttpServletRequest {}
HttpServletRequest对象是 Tomcat 服务器创建的 , HttpServletRequest 对象中封装的是 Http 请求协议
- 因为 Tomcat服务器(WEB服务器、WEB容器)实现了Servlet规范 , 所以一定会实现 HttpServletRequest接口。
- 用户发送请求的时候,遵循了HTTP协议,发送的是HTTP的请求协议,Tomcat服务器将HTTP协议中的信息以及数据全部解析出来,然后把这些信息封装到 HttpServletRequest 对象当中,传给了我们javaweb程序员。
- javaweb 程序员面向 HttpServletRequest 接口编程,知道 HttpServletRequest接口中方法的功能 , 并调用方法获取到请求的信息了。
request和response对象的生命周期
request对象和response对象,一个是请求对象,一个是响应对象。这两个对象只在当前请求中有效。
- 一次请求对应一个request。
- 两次请求则对应两个request。
HttpServletRequest 获取前端用户提交的数据的方法
HttpServletRequest父接口是 ServletRequest , 所有父类的方法子类可以直接使用
前端的form表单提交了数据之后,你准备采用什么样的数据结构去存储这些数据
- 注意:前端表单提交数据的时候,假设提交了120这样的“数字”,其实是以字符串"120"的方式提交的,所以服务器端获取到的一定是一个字符串的"120",而不是一个数字。(前端永远提交的是字符串,后端获取的也永远是字符串。)
- 前端提交的数据格式:username=abc&userpwd=111&aihao=s&aihao=d&aihao=tt
- 我会采用Map集合来存储 , Map<String, String[]> ,采用 String[] 可以避免 Map集合 key重复的时候value覆盖问题
#Map<String,String> , key存储String , value存储String
#如果是该数据结构存储会发现 key 重复的时候value覆盖。
key value
---------------------
username abc
userpwd 111
aihao s
aihao d
aihao tt
#Map<String, String[]> , key存储String , value存储String[]
key value
-------------------------------
username {"abc"}
userpwd {"111"}
aihao {"s","d","tt"}
ServletRequest接口 的方法
方法名 | 功能 |
---|---|
Map<String,String[]> getParameterMap() | 这个是获取Map |
Enumeration< String > getParameterNames() | 这个是获取Map集合中所有的key |
String[] getParameterValues(String name) | 根据Map集合的 key 获取 Map 集合的value |
String getParameter(String name) | 根据Map集合的 key 获取 value 这个一维数组当中的第一个元素。这个方法最常用。因为大部分的一维数组中只有一个元素 |
测试HttpServletRequest接口中的相关方法。
- 前端提交的数据 username=zhangsan&userpwd=123&interest=s&interest=d 会被服务器封装到request对象中 , 采用 Map<String,String[]>结构存储
key value
---------------------------
"username" {"zhangsan"}
"userpwd" {"123"}
"interest" {"s", "d"}
一维数组的toString()的输出形式
public class ArrayTest{
public static void main(String[] args){
String[] values = {"abc", "def", "xyz"};
//[Ljava.lang.String;@2f92e0f4
System.out.println(values);
}
}
public class RequestTestServlet extends HttpServlet{
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws IOException,ServletException{
// 获取参数Map集合
Map<String,String[]> parameterMap = request.getParameterMap();
//获取Map集合中所有的key 遍历Map集合
Set<String> keys = parameterMap.keySet();
Iterator<String> it = keys.iterator();
while(it.hasNext()){
String key = it.next();
// 通过key获取value , value是个一维数组
/*
username=[Ljava.lang.String;@7cce40b4
userpwd=[Ljava.lang.String;@7453f0b9
interest=[Ljava.lang.String;@4063ebb5
*/g
String[] values = parameterMap.get(key);
System.out.println(key + "=" + values);
// 遍历一维数组
System.out.print(key + "=");
for(String value : values){
System.out.print(value + ",");
}
// 换行
System.out.println();
}
// 直接通过getParameterNames()这个方法,可以直接获取这个Map集合的所有key
Enumeration<String> names = request.getParameterNames();
while(names.hasMoreElements()){
String name = names.nextElement();
System.out.println(name);
}
// 直接通过name获取value这个一维数组。
String[] usernames = request.getParameterValues("username");
String[] userpwds = request.getParameterValues("userpwd");
String[] interests = request.getParameterValues("interest");
// 遍历一维数组
for(String username : usernames){
System.out.println(username);
}
for(String userpwd : userpwds){
System.out.println(userpwd);
}
for(String interest : interests){
System.out.println(interest);
}
// 通过name获取value这个一维数组的第一个元素 , 这个方法使用最多,因为这个一维数组中一般只有一个元素。
String username = request.getParameter("username");
String userpwd = request.getParameter("userpwd");
// 表单 interest 是个复选框有多个元素 , 需要获取这个数组
String[] interests = request.getParameterValues("interest");
// 获取的都是一维数组当中的第一个元素。
System.out.println(username);
System.out.println(userpwd);
// 遍历 interests 数组
for(String interest : interests2){
System.out.println(interest);
}
}
}
HttpRequest 操作数据和转发的方法
方法名 | 功能 |
---|---|
void setAttribute(String name, Object obj) | 向请求域当中绑定数据 |
Object getAttribute(String name) | 从请求域当中根据name获取数据 |
void removeAttribute(String name) | 将请求域当中绑定的数据移除 |
RequestDispatcher getRequestDispatcher() | 获取请求转发器对象 |
request 对象也叫“请求域”对象 , “请求域”对象要比“应用域”对象范围小很多。生命周期短很多。请求域只在一次请求内有效。
- 一个请求对象 request 对应一个请求域对象。
- 只有用户发送请求的时候 , 才会有请求对象 , 才会开启请求域 , 一次请求结束之后,这个请求域就销毁了。
请求域和应用域的选用原则
- 尽量使用小的域对象,因为小的域对象占用的资源较少。
在 AServelt 中向请求域中绑定数据 , 在 BSevlet 中拿不到 , 因为它们是两个不同的请求域
public class AServlet extends HttpServlet{
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws IOException,ServletException{
// 获取系统当前时间
Date nowTime = new Date();
// 向request域当中绑定数据。
request.setAttribute("sysTime", nowTime);
// 从request域当中取出绑定的数据。
//Object obj = request.getAttribute("sysTime");
// 输出到浏览器
/*
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.print("request域当中获取的系统当前时间 = " + obj);
*/
}
}
BServlet
public class BServlet extends HttpServlet{
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws IOException,ServletException{
// 从request域当中取出绑定的数据。
Object obj = request.getAttribute("sysTime");
// 输出到浏览器
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.print("request域当中获取的系统当前时间 = " + obj);
}
}
注意: 在 AServlet 当中 new 一个 BServlet 对象,然后调用 BServlet 对象的 doGet 方法,把 request 对象传过去。
- 这个代码虽然可以实现功能,但是Servlet对象不能自己由程序员来new。自己new的Servlet对象生命周期不受Tomcat服务器的管理。
跳转
由于在 BSevlet 中拿不到 AServelt 中向请求域中绑定数据 。可不可以把 AServlet 和 BServlet 放到一次请求当中。即执行了AServlet之后,跳转到BServlet。
- 可以 , 使用 Servlet 当中的请求转发机制(一次请求)
public class AServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 获取系统当前时间
Date nowTime = new Date();
// 将系统当前时间绑定到请求域当中
request.setAttribute("sysTime", nowTime);
// 第一步:获取请求转发器对象
// 相当于把"/b"这个路径包装到请求转发器当中,实际上是把下一个跳转的资源的路径告知给Tomcat服务器了。
//RequestDispatcher dispatcher = request.getRequestDispatcher("/b");
// 第二步:调用请求转发器RequestDispatcher的forward方法。进行转发。
// 转发的时候:这两个参数很重要。request和response都是要传递给下一个资源的。
//dispatcher.forward(request, response);
// 转发到一个Servlet,一行代码搞定
//request.getRequestDispatcher("/b").forward(request, response);
// 也可以转发到一个HTML,只要是WEB容器当中的合法资源即可。
request.getRequestDispatcher("/test.html").forward(request, response);
}
}
两个Servlet怎么共享数据
- 将数据放到ServletContext应用域当中,当然是可以的,但是应用域范围太大,占用资源太多。不建议使用。
- 可以将数据放到request域当中,然后AServlet转发到BServlet,保证AServlet和BServlet在同一次请求当中,这样就可以做到两个Servlet,或者多个Servlet共享同一份数据。
只要是Tomcat服务器当中的合法资源,都是可以转发的。例如:静态的 html 和 动态的Servlet
- 注意:转发的时候,路径的写法要注意,转发的路径以“/”开始,不加项目名。
HttpServletRequest接口的其他常用方法
Tomcat10之后,request请求体当中的字符集默认就是UTF-8,不需要设置字符集,不会出现乱码问题。
- 在Tomcat10之后,包括10在内,响应中文也不在出现乱码问题了。
Tomcat9前(包括9在内),如果前端 Post 请求体提交的是中文,后端获取之后出现乱码,需要设置请求体的字符集
- 在Tomcat9之前(包括9),响应中文也是有乱码的 , 需要设置 响应体的字符集 response.setContentType(“text/html;charset=UTF-8”)
方法名 | 功能 |
---|---|
String getRemoteAddr(); | 获取客户端的IP地址 |
void setCharacterEncoding(String) | 设置请求体的字符集 , 因为get请求在请求行上提交数据。所以这个方法是处理POST请求的乱码问题。这种方式并不能解决get请求的乱码问题。解决 get 请求乱码问题需要设置配置文件 |
String getContextPath() | 获取应用的根路径 ( 继承GenericServlet类的方法 ) |
String getMethod() | 获取请求方式 |
String getRequestURI() | 获取请求的URI (带项目名) |
String getServletPath() | 获取Servlet 的路径 (项目名后面的内容) |
测试常用方法
// 获取客户端的IP地址
String remoteAddr = request.getRemoteAddr();
// 设置请求体的字符集。( 处理POST请求的乱码问题 )
request.setCharacterEncoding("UTF-8");
// 在Tomcat9之前(包括9),响应中文也是有乱码的,怎么解决这个响应的乱码?
response.setContentType("text/html;charset=UTF-8");
// 获取应用的根路径
String contextPath = request.getContextPath();
// 获取请求方式
String method = request.getMethod();
// 获取请求的URI , /aaa/testRequest (aaa是项目名)
String uri = request.getRequestURI(); //
// 获取Servlet 路径 , /testRequest (不带项目名)
String servletPath = request.getServletPath();