Servlet

jakarta.servlet.Servlet 核心接口

一个Servlet对象从创建到销毁整个过程(生命周期)

Servlet对象的创建,对象上方法的调用,对象最终的销毁,JavaWeb程序员是无权干预的 , 是由Tomcat服务器(WEB Server)全权负责的

  • Tomcat服务器通常我们又称为:WEB容器【WEB Container】
  • WEB容器创建的Servlet对象,这些Servlet对象都会被放到一个集合当中(HashMap),只有放到这个HashMap集合中的Servlet对象才能够被WEB容器管理
  • 服务器在启动的时候会解析XML文件 , 把Servlet类的全类名和请求路径放入这个HashMap集合当中

web容器中的map集合

我们自己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(); 

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值