老杜 JavaWeb 讲解(七) ——GenericServlet简单了解

发布时间 2023-07-07 19:12:12作者: 猪无名

(九)GenericServlet

对应视频:

11-适配器模式改造Servlet

12-改造GenericServlet

13-ServletConfig接口详解

14-ServletContext接口详解

15-ServletContext接口详解

16-ServletContext接口详解

9.1 改进Servlet

  • 我们编写一个Servlet类直接实现Servlet接口有什么缺点?

    • 我们只需要service方法,其他方法大部分情况下是不需要使用的。代码很丑陋。
  • 适配器设计模式Adapter

    • 手机直接插到220V的电压上,手机直接就报废了。怎么办?可以找一个充电器。这个充电器就是一个适配器。手机连接适配器。适配器连接220V的电压。这样问题就解决了。
  • 编写一个GenericServlet类,这个类是一个抽象类,其中有一个抽象方法service。

    • GenericServlet实现Servlet接口。
    • GenericServlet是一个适配器。
    • 以后编写的所有Servlet类继承GenericServlet,重写service方法即可。
  • 思考:GenericServlet类是否需要改造一下?怎么改造?更利于子类程序的编写?

    • 思考第一个问题:我提供了一个GenericServlet之后,init方法还会执行吗?

      • 还会执行。会执行GenericServlet类中的init方法。
    • 思考第二个问题:init方法是谁调用的?

      • Tomcat服务器调用的。
    • 思考第三个问题:init方法中的ServletConfig对象是谁创建的?是谁传过来的?

      • 都是Tomcat干的。
      • Tomcat服务器先创建了ServletConfig对象,然后调用init方法,将ServletConfig对象传给了init方法。
      • 思考:Tomcat服务器伪代码:
public class Tomcat {
    public static void main(String[] args){
         // .....
         // Tomcat服务器伪代码
         // 创建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方法
   		 // ....
    
  }
}

9.2 GenericServlet源码解析

相关知识点

①:public void init(ServletConfig config) 中的ServletConfig是什么?
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package jakarta.servlet;

import java.util.Enumeration;

public interface ServletConfig {
    String getServletName();

    ServletContext getServletContext();

    String getInitParameter(String var1);

    Enumeration<String> getInitParameterNames();
}

jakarta.servlet.ServletConfig是一个接口,jakarta.servlet.Servlet也是一个接口。

②:谁实现的ServletConfig接口?
org.apache.catalina.core.StandardWrapperFacade

Tomcat服务器实现了该接口。

思考:如果把Tomcat服务器换为jetty服务器,输出ServletConfig的时候,信息还一样吗?

不一定一样,包名类名可能和Tomcat不一样,但他们都实现了该接口。

结论:web服务器实现了ServletConfig接口。

一个Servlet对象中有一个ServletConfig对象(Serclet对象和ServletConfig对象是一一对应的)。

③:ServletConfig 对象是谁创建的,什么时候创建的?

是Tomcat创建的,和Servlet对象同时创建的。

④:ServletConfig接口是用来干什么的?

Configuration :配置 。

ServletConfig被翻译为Servlet对象的配置信息对象。

⑤:ServletConfig对象中到底包含什么信息?

ServletConfig中包含的信息是web.xml文件中servlet标签里面的信息。

Tomcat解析web.xml文件,将web.xml文件中的servlet标签中的配置信息自动包装到ServletConfig对象中。

⑥:ServletConfig都包含哪些特殊方法?
  • getInitParametergetInitParameterNames方法
    • getInitParameterNames获取web.xml文件中的初始化参数的name。
    • getInitParameter根据name获取,初始化参数的值。

web.xml部分配置文件:

<servlet>
    <servlet-name>configTest</servlet-name>
    <servlet-class>com.ljy.javaweb.servlet.ConfigTestServlet</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/zwm</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>

init-param中的信息会被Tomcat服务器包装到ServletConfig对象中。

⑦:获取web.xml中的初始化信息的方法
//用于获取web.xml文件里面的初始化信息。
public String getInitParameter(String name) {
    return this.getServletConfig().getInitParameter(name);
}

// 获取所有初始化参数的name
public Enumeration<String> getInitParameterNames() {
    return this.getServletConfig().getInitParameterNames();
}

演示代码:

Enumeration<String> initParameterNames = config.getInitParameterNames();
//遍历集合

while (initParameterNames.hasMoreElements()){       //是否有更多元素
    String name = initParameterNames.nextElement();
    String driver = config.getInitParameter(name);
    out.print(name+"---"+driver);
    out.print("<br>");
}

结果:
password---root
driver---com.mysql.cj.jdbc.Driver
user---root
url---jdbc:mysql://localhost:3306/zwm
  • getServletContext方法
public ServletContext getServletContext() {
    return this.getServletConfig().getServletContext();
}

测试:

//通过ServletConfig获取ServletContext对象
ServletContext application = config.getServletContext();
out.print("<br>"+application);
ServletContext application2 = this.getServletContext();
out.print("<br>"+application2);

运行结果:

org.apache.catalina.core.ApplicationContextFacade@749dd4c2
org.apache.catalina.core.ApplicationContextFacade@749dd4c2

GenericServlet包含的四个方法:

public String getInitParameter(String name)
public Enumeration<String> getInitParameterNames()
public ServletConfig getServletConfig()
public ServletContext getServletContext()

以上的四个方法在自己编写的Servlet中也可以使用this直接调用。

ServletConfig对象

1. ServletConfig是什么?

Servlet对象的配置信息对象。
ServletConfig对象中封装了标签中的配置信息。(web.xml文件中servlet的配置信息)。

一个Servlet对应一个ServletConfig对象。

2. ServletConfig对象是谁创建的?

Tomcat服务器创建,和Servlet对象一样也是Tomcat服务器创建。并且默认情况下,他们都是在用户发送第一次请求的时候创建。

Tomcat服务器调用Servlet对象的init方法的时候需要传一个ServletConfig对象的参数给init方法。

ServletConfig接口的实现类是Tomcat服务器给实现的。(Tomcat服务器说的就是WEB服务器。)

3. ServletConfig接口有哪些常用的方法?
// 通过初始化参数的name获取value
public String getInitParameter(String name); 

// 获取所有的初始化参数的name
public Enumeration<String> getInitParameterNames(); 

// 获取ServletContext对象
public ServletContext getServletContext(); 

// 获取Servlet的name
public String getServletName(); 

以上方法在Servlet类当中,都可以使用this去调用。因为GenericServlet实现了ServletConfig接口。

ServletContext对象

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 = this.getServletContext();
        out.print("A---"+servletContext);
    }
}


public class BServlet extends GenericServlet {
    @Override
    public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html");
        PrintWriter out = response.getWriter();

        ServletContext servletContext = this.getServletContext();
        out.print("B---"+servletContext);
    }
}

运行结果:

//A---org.apache.catalina.core.ApplicationContextFacade@7db94b27
//B---org.apache.catalina.core.ApplicationContextFacade@7db94b27
1. ServletContext是什么?

ServletContext是一个接口,是Servlet规范中的一员。

2. ServletContext的谁实现的?

Tomcat服务器(web服务器)实现了ServletContext接口。

3. ServletContext对象是谁创建的?什么时候创建的?

org.apache.catalina.core.ApplicationContextFacade

在web服务器启动的时候创建,在服务器关闭的时候销毁。

对于一个webapp来说,ServletContext对象只有一个。

4. ServletContext

被称为Servlet上下文对象/环境对象,或者对应的就是整个web.xml对象。

5. ServletContext对应显示生活中的什么例子呢?

一个教室里有多个学生,那么每一个学生就是一个Servlet,这些学生都在同一个教室当中,那么我们可以把这个教室叫做ServletContext对象。那么也就是说放在这个ServletContext对象(环境)当中的数据,在同一个教室当中,物品都是共享的。比如:教室中有一个空调,所有的学生都可以操作。可见,空调是共享的。因为空调放在教室当中。教室就是ServletContext对象。

Tomcat是一个容器,一个容器当中可以放多个webapp,一个webapp对应一个ServletContext。

一个Servlet对象对应一个ServletConfig对象,但在同一个webapp里面的Servlet对象对应一个ServletContext对象。

Tomcat服务器中有一个webapps文件夹,webapps下可以存放多个webapp,加入有100个webapp,那么就有100个ServletContext对象。

ServletContext被称为Servlet上下文对象或者Servlet的四周环境对象。

一个ServletContext对应一个web.xml文件。

6. ServletContext接口中有哪些常用的方法?
① getInitParameterNames() 和 getInitParameter(String name)
public String getInitParameter(String name); // 通过初始化参数的name获取value
public Enumeration<String> getInitParameterNames(); // 获取所有的初始化参数的name

web.xml

<!-- 上下文的初始化参数-->
<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对象来获取。-->
Enumeration<String> initParameterNames = servletContext.getInitParameterNames();
while(initParameterNames.hasMoreElements()){
    String s = initParameterNames.nextElement();
    out.print(s+"= "+servletContext.getInitParameter(s)+"<br>");
}
结果:
startIndex= 0
pageSize= 10
② getContextPath()
ServletContext servletContext = this.getServletContext();
String contextPath = servletContext.getContextPath();
out.print(contextPath);
结果:
/s4
  • 动态获取应用的根。

  • 获取应用的根路径(非常重要),因为在java源代码当中有一些地方可能会需要应用的根路径,这个方法可以动态获取应用的根路径

  • 在java源码当中,不要将应用的根路径写死,因为你永远都不知道这个应用在最终部署的时候,起一个什么名字。

③ getRealPath(String path)
// 动态获取文件的绝对路径(真实路径)
public String getRealPath(String path);

举例:

String realPath = servletContext.getRealPath("/index.jsp");
//String realPath = servletContext.getRealPath("index.jsp"); 也行(默认起点,web文件夹)
out.print("<br>"+realPath);
结果:
D:\Test-For_Money\javaweb\out\artifacts\servlet04_war_exploded\index.jsp
④ log(String message) 和 log(String message, Throwable t);

通过ServletContext对象也是可以记录日志的

public void log(String message);
public void log(String message, Throwable t);
// 这些日志信息记录到哪里了?
// localhost.2023-07-07.log

// Tomcat服务器的logs目录下都有哪些日志文件?
//catalina.2023-07-07.log      服务器端的java程序运行的控制台信息。
//localhost.2023-07-07.log     ServletContext对象的log方法记录的日志信息存储到这个文件中。
//localhost_access_log.2023-07-07.txt  访问日志
⑤ setAttribute getAttribute removeAttribute
  • ServletContext对象还有另一个名字:应用域(后面还有其他域,例如:请求域、会话域)

  • 如果所有的用户共享一份数据,并且这个数据很少的被修改,并且这个数据量很少,可以将这些数据放到ServletContext这个应用域中

  • 为什么是所有用户共享的数据?为什么数据量要小?为什么这些共享数据很少的修改,或者说几乎不修改?

    • 不是共享的没有意义。因为ServletContext这个对象只有一个。只有共享的数据放进去才有意义。
    • 因为数据量比较大的话,太占用堆内存,并且这个对象的生命周期比较长,服务器关闭的时候,这个对象才会被销毁。大数据量会影响服务器的性能。占用内存较小的数据量可以考虑放进去。
    • 所有用户共享的数据,如果涉及到修改操作,必然会存在线程并发所带来的安全问题。所以放在ServletContext对象中的数据一般都是只读的。
  • 数据量小、所有用户共享、又不修改,这样的数据放到ServletContext这个应用域当中,会大大提升效率。因为应用域相当于一个缓存,放到缓存中的数据,下次在用的时候,不需要从数据库中再次获取,大大提升执行效率。

// 存(怎么向ServletContext应用域中存数据)
public void setAttribute(String name, Object value); // map.put(k, v)
// 取(怎么从ServletContext应用域中取数据)
public Object getAttribute(String name); // Object v = map.get(k)
// 删(怎么删除ServletContext应用域中的数据)
public void removeAttribute(String name); // map.remove(k)

举例:

public class User {
    private String name;
    private int age;
    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
public class CServlet extends GenericServlet {

    @Override
    public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html");
        PrintWriter out = response.getWriter();

        ServletContext servletContext = this.getServletContext();
        //准备数据
        User user = new User("tom", 12);
        //存数据
        servletContext.setAttribute("userObj",user);
        //取数据
        Object userObj = servletContext.getAttribute("userObj");
        out.print(userObj);
    }
}
结果:
User{name='tom', age=12}

在其他的Servlet的文件中,使用

Object userObj = servletContext.getAttribute("userObj");
out.print(userObj);

也可以获取User{name='tom', age=12},但要在访问CServlet之后才会有值,不然的话对应的为null。

注意

  • 以后我们编写Servlet类的时候,实际上是不会去直接继承GenericServlet类的,因为我们是B/S结构的系统,这种系统是基于HTTP超文本传输协议的,在Servlet规范当中,提供了一个类叫做HttpServlet,它是专门为HTTP协议准备的一个Servlet类。我们编写的Servlet类要继承HttpServlet。(HttpServlet是HTTP协议专用的。)使用HttpServlet处理HTTP协议更便捷。

  • HttpServlet的继承结构:

    jakarta.servlet.Servlet(接口)【爷爷】
    jakarta.servlet.GenericServlet implements Servlet(抽象类)【儿子】
    jakarta.servlet.http.HttpServlet extends GenericServlet(抽象类)【孙子】
    
    我们以后编写的Servlet要继承HttpServlet类。