JavaWeb 三大组件之 Servlet

  |   0 评论   |   0 浏览

Servlet

1.官方文档

https://tomcat.apache.org/tomcat-8.0-doc/servletapi/index.html

2.Java Web 技术体系的流程图

tomcat支持映射加载静态文件、解析Servlet

image-20220227171600321

3.什么是Servlet

Servlet 在开发动态 WEB 工程中,得到广泛的应用,掌握好 Servlet 非常重要了, Servlet(基石)是 SpringMVC 的基础

Servlet(java 服务器小程序),它的特点:
1.他是由服务器端调用和执行的(一句话:是Tomcat解析和执行)
2.他是用java语言编写的, 本质就是Java类
3.他是按照Servlet规范开发的(除了tomcat->Servlet weblogic->Servlet)
4.功能强大,可以完成几乎所有的网站功能 技术栈要求高

4.Servlet 在 JavaWeb 项目位置

image-20220227172010523

5.Servlet基本使用

1.Servlet开发方式说明

1.servlet3.0 前使用 web.xml , servlet3.0 版本以后(包括 3.0)支持注解, 同时支持 web.xml
配置

2.如何查看servlet 版本[如图]

image-20220227172104704

2.快速入门-手动开发Servlet

需求说明
1 、 开 发 一 个 HelloServlet

2、当浏览器 访问 http://localhost:8080/web 应用名/helloServlet 时,后台输出 "hi HelloServelt"

1.创建JavaWeb工程,并配置好Tomcat

2.添加servlet-api.jar(在tomcat/lib下) 到工程, 因为servlet.jar 不是jdk自带的, 要引入

3.在src 下 包HelloServlet.java ,并实现Servlet接口

HelloServlet

/**
 * 1. 开发一个Servlet 需要 实现Servlet接口
 * 2. 实现Servlet接口的方法5个
 */
public class HelloServlet implements Servlet {


    private int count = 0; //属性

    /**
     * 1.初始化 servlet
     * 2.当创建HelloServlet 实例时,会调用init方法
     * 3. 该方法只会被调用一次
     * @param servletConfig
     * @throws ServletException
     */
    @Override
    public void init(ServletConfig servletConfig) throws ServletException {
        System.out.println("init() 被调用");
    }

    /**
     * 返回ServletConfig 也就是返回Servlet的配置
     * @return
     */
    @Override
    public ServletConfig getServletConfig() {
        return null;
    }

    /**
     * 1. service方法处理浏览器的请求(包括get/post)
     * 2. 当浏览器每次请求Servlet时,就会调用一次service
     * 3. 当tomcat调用该方法时,会把http请求的数据封装成实现ServletRequest接口的request对象
     * 4. 通过servletRequest 对象,可以得到用户提交的数据
     * 5. servletResponse 对象可以用于返回数据给tomcat->浏览器
     * @param servletRequest
     * @param servletResponse
     * @throws ServletException
     * @throws IOException
     */
    @Override
    public void service(ServletRequest servletRequest,
                        ServletResponse servletResponse) throws ServletException, IOException {

        count++;
        //如果count的值,在不停的累计,说明HelloServlet是单例的
        System.out.println("hi HelloServlet~ count= " + count);
        //Tomcat每处理一次http请求,就生成一个新的线程
        System.out.println("当前线程id= " + Thread.currentThread().getId());

        //思考->从servletRequest对象来获取请求方式->
        //1. ServletRequest 没有得到提交方式的方法
        //2. ServletRequest 看看ServletRequest子接口有没有相关方法
        //3. 老师小技巧:ctrl+alt+b => 可以看到接口的子接口和实现子类
        //4. 把servletReqeust转成 HttpServletRequest引用
        //5. 仍然是Java基础的OOP
        HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
        String method = httpServletRequest.getMethod();
        if("GET".equals(method)) {
            doGet(); //用doGet() 处理GET请求
        } else if("POST".equals(method)) {
            doPost(); //用doPost() 处理POST请求
        }

    }

    /**
     * 用于响应get请求的
     */
    public void doGet() {
        System.out.println("doGet() 被调用..");
    }

    /**
     * 用于响应post请求的
     */
    public void doPost() {
        System.out.println("doPost() 被调用..");
    }

    /**
     * 返回servlet信息,使用较少
     * @return
     */
    @Override
    public String getServletInfo() {
        return null;
    }

    /**
     * 1. 该方法是在servlet销毁时,被调用
     * 2. 只会调用一次
     */
    @Override
    public void destroy() {
        System.out.println("destroy() 被调用...");
    }
}

web.xml配置

<!--小技巧: web.xml主要用来配置该web应用使用到的Servlet
        1. servlet-name: 给Servlet取名(程序员决定), 该名字唯一
        2. servlet-class: Servlet的类的全路径: Tomcat在反射生成该Servlet需要使用
        3. url-pattern: 这个就是该servlet访问的url的配置(路径)
        4. 这时我们应该这样访问servlet http://localhost:8080/servlet/helloServlet
        5. url-pattern 取名是程序员决定的
        6. load-on-startup 表示在tomcat 启动时,会自动的加载servlet实例
    -->
    <!-- 配置HelloServlet -->
    <servlet>
        <servlet-name>HelloServlet</servlet-name>
        <servlet-class>com.hspedu.servlet.HelloServlet</servlet-class>
        <!--<load-on-startup>1</load-on-startup>-->
    </servlet>
    <servlet-mapping>
        <servlet-name>HelloServlet</servlet-name>
        <url-pattern>/helloServlet</url-pattern>
    </servlet-mapping>

image-20220227223120451

3. 浏览器调用Servlet流程分析

第一次请求helloServlet根据url-pattern拿到servlet-name

在tomcat维护的HashMap<id,Servlet>中没有这个实例

底层根据反射技术将servlet实例化 init(),并放入到Map中

后面的请求就可以通过Map直接查找到对应的Servlet

image-20220227223146640

image-20220227223157112

4. Servlet生命周期

●主要有三个方法
1.init()初始化阶段
2.service()处理浏览器请求阶段
3.destroy()终止阶段

示意图(比较重要)

image-20220227230048310

1.初始化阶段

Servlet 容器(比如: Tomcat)加载 Servlet,加载完成后,Servlet 容器会创建一个 Servlet 实例并调用init()方法,init()方法只会调用一次, Servlet 容器在下面的情况装载 Servlet:

image-20220227230341916

1.Servlet 容器(Tomcat)启动时自动装载某些 servlet,实现这个需要在 web.xml 文件中添加

1 表示装载的顺序
<load-on-startup>1</load-on-startup>

2.在Servlet 容器启动后,浏览器首次向Servlet 发送请求
3.Servlet 重新装载时(比如 tomcat 进行 redeploy、Restart server、关闭server【会销毁所有的 Servlet 实例(Tomcat维护的HashMap<id,Servlet>也跟着销毁)】),再次请求或者重启时会重新初始化

2.处理浏览器请求阶段(service 方法)

Tomcat将浏览器发起http请求数据封装到了ServletRequest 对象中,简化了开发流程,我们只需要关注ServletRequest对象从中获取请求数据即可。

ServletResponse封装响应数据交给Tomcat,tomcat再将响应数据返回给浏览器

1.每收到一个 http 请求,服务器就会产生一个新的线程去处理[线程]
2.创建一个用于封装 HTTP 请求消息的 ServletRequest 对象和一个代表 HTTP 响应消息的ServletResponse 对象
3.然后调用Servlet 的service()方法并将请求和响应对象作为参数传递进去

tomcat自己维护了一个线程池,线程池的线程会进行复用

image-20220228224010354

3.终止阶段 destory 方法(体现 Servlet 完整的生命周期)

当web 应用被终止,或者 Servlet 容器终止运行,或者Servlet 类重新装载时,会调用destroy()
方法
, 比如重启tomcat ,或者 redeploy web 应用

5.GET和POST请求的分发处理

开发 Servlet, 通常编写 doGet、doPost 方法。来对表单的 get 和 post 请求进行分发处理

●代码演示 HelloServlet.java register.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>注册</title>

</head>
<body>
<h1>注册用户</h1>
<form action="http://localhost:8080/helloServlet"
      method="post">
    u: <input type="text" name="username"/><br><br>
    <input type="submit" value="注册用户"/>
</form>
</body>
</html>
@Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        System.out.println("浏览器访问HelloServlet。。。。");
        String name = Thread.currentThread().getName();
        long id = Thread.currentThread().getId();
        int hashCode = Thread.currentThread().hashCode();
        System.out.println("线程名称-" + name + ",线程id-" + id + ",线程hashCode-" + hashCode);
        HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
        String method = httpServletRequest.getMethod();
        System.out.println(method);
        String username = servletRequest.getParameter("username");
        System.out.println("username:"+username);
        if("GET".equals(method)){
           doGet();
        }else if("POST".equals(method)){
            doPost();
        }

    }

image-20220228225107684

6.通过继承HttpServlet开发Servlet

●HttpServlet 介绍

在实际项目中,都是使用继承 HttpServlet 类开发 Servlet 程序,更加方便

image-20220228225153469

public class HiServlet extends HttpServlet {

    //重写HttpServlet的doGet 和 doPost
    //alt +insert

    /**
     * 处理doGet请求
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("HiServlet doGet()...");
    }

    /**
     * 处理doPost
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("HiServlet doPost()...");
    }
}

子类调用父类的service方法(类型传递)

image-20220301215458399

7.IDEA开发Servlet程序

●说明

编手动开发 Servlet 需要程序员自己配置 Servlet ,比较麻烦,在工作中,直接使用 IDEA 开发 Servlet 会更加方便

image-20220301215746163

image-20220301215754281

8.Servlet 注意事项和细节

1.Servlet 是一个供其他 Java 程序(Servlet 引擎)调用的 Java 类,不能独立运行
2.针对浏览器的多次 Servlet 请求,通常情况下,服务器只会创建一个 Servlet 实例对象, 也就是说 Servlet 实例对象一旦创建,它就会驻留在内存中,为后续的其它请求服务,直至web 容器退出/或者 redeploy 该 web 应用,servlet 实例对象才会销毁 【示意图】

image-20220301232102935

3.在 Servlet 的整个生命周期内,init 方法只被调用一次。而对每次请求都导致 Servlet 引擎调用一次 servlet 的 service 方法。
4.对于每次访问请求,Servlet 引擎都会创建一个新的 HttpServletRequest 请求对象和一个新的 HttpServletResponse 响应对象,然后将这两个对象作为参数传递给它调用的 Servlet 的 service()方法,service 方法再根据请求方式分别调用 doXXX 方法
5.如果在元素中配置了一个元素,那么 WEB 应用程序在启动时, 就会装载并创建Servlet 的实例对象、以及调用 Servlet 实例对象的 init()方法, (定时发送邮件的服务/自动启动->完成任务)

<!--load-on-startup配置表示web程序启动时就创建Servlet实例对象,更具值的从小到大的先后顺序依次创建-->
	<servlet>
        <servlet-name>helloServlet</servlet-name>
        <servlet-class>com.llp.HelloServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
 	<servlet>
        <servlet-name>helloServlet</servlet-name>
        <servlet-class>com.llp.HiServlet</servlet-class>
        <load-on-startup>2</load-on-startup>
    </servlet>
	<servlet>
        <servlet-name>helloServlet</servlet-name>
        <servlet-class>com.llp.HiServlet</servlet-class>
        <load-on-startup>3</load-on-startup>
    </servlet>

6.Servlet-注解方式

1.快速入门

1.编写类OkServlet去继承HttpServlet
2.注解方式配置OkServlet, 一个Servlet支持配置多个urlPattern

/**
 * 1. @WebServlet 是一个注解 => java基础->注解
 * 2. @WebServlet 源码
 * @Target({ElementType.TYPE})
 * @Retention(RetentionPolicy.RUNTIME)
 * @Documented => 在javadoc工具生成文档有记录
 * public @interface WebServlet {
   <servlet-name>helloServlet</servlet-name>
 *     String name() default "";  
 *
 *     String[] value() default {};
 
 *	  <url-pattern>/helloServlet</url-pattern>
 *     String[] urlPatterns() default {};
 *	
    <load-on-startup>1</load-on-startup>
 *     int loadOnStartup() default -1;
 *
           <init-param>
 *             <param-name></param-name>
 *             <param-value></param-value>
 *         </init-param>
 *     WebInitParam[] initParams() default {};
 *
 *     boolean asyncSupported() default false;
 *
 *     String smallIcon() default "";
 *  }
 *  3. urlPatterns 对应 web.xml 的 <url-pattern></url-pattern>
 *  4. {"/ok1", "/ok2"} 可以给OkServlet配置多个 url-pattern
 *  5. 相当于这个@WebServlet(urlPatterns = {"/ok1", "/ok2"}) 代替了 web.xml的配置
 *     底层使用了 反射+注解+IO+集合 来完成一个支撑
 *  6. 浏览器可以这样访问OkServlet时,可以 http://localhost:8080/servlet/ok1 或者
 *     http://localhost:8080/servlet/ok2
 *  我们可以根据 @interface WebServlet 源码知道可以配置哪些
 *  web.xml init-param 在注解中,如何指定呢? 
 *          <init-param>
 *             <param-name></param-name>
 *             <param-value></param-value>
 *         </init-param>
 *  9. 注解方式开发Servlet和 web.xml配置servlet 流程机制是一样
 *  10. /ok1/aa /ok1/bb /ok1/cc /ok1/aa/bb/cc /ok2
 *  11. *.action 这时 zs.action ls.action
 *  12. http://localhost:8080/servlet/register.html
 *  13. 精确路径 > 目录路径 > 扩展名路径 > /* > /
 */
@WebServlet(urlPatterns = {"/ok1","/ok2","*.ok3","ok4/*"})
public class OkServlet extends HttpServlet {

    @Override
    public void init(ServletConfig config) throws ServletException {
        System.out.println("注解方式 OkServlet init()被调用");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("注解方式 OkServlet doPost()");

    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        System.out.println(10/0);//报错
        System.out.println("注解方式 OkServlet doGet()");
        System.out.println(req.getRemoteAddr());
    }

}

2.简单模拟Servlet注解方式实现

与之直接读取web.xml不同之处在于,Servlet注解方式是通过包扫码获取@WebServlet注解及注解的参数来创建Servlet实例及配置参数的。

image-20220227223146640

/*
@WebServlet注解,包扫描会逐级扫描
在这之前还会通过io流去获取目录从而得到包路径
*/   
public class TestAnnotationServlet {

    private static final HashMap<String, Object> hm = new HashMap<>();

    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {

        //1. 首先要得到扫描的包 路径 io, 进而得到类的全路径
        String classAllPath = "com.hspedu.servlet.annotation.OkServlet";
        //2. 得到 OkServlet的Class对象
        Class<?> aClass = Class.forName(classAllPath);
                //通过反射获取注解
//        Annotation[] annotations = aClass.getAnnotations();
//        for (Annotation annotation : annotations) {
//            if(annotation instanceof WebServlet){
//            }
//        }
        //3. 通过class对象,得到Annotation
        WebServlet annotation = aClass.getAnnotation(WebServlet.class);
        System.out.println(annotation);
        String[] strings = annotation.urlPatterns();
        for (String url : strings) {
            System.out.println("url= " + url);
        }

        //如果匹配url,如果是第一次,tomcat就会创建一个OkServlet实例,放入到hashmap
        Object instance = aClass.newInstance();
        System.out.println("instance= " + instance);//OkServlet

        //简单的模拟,没有深入.
        hm.put("OkServlet", instance);

        System.out.println(hm);

    }
}

3.Servlet urlPattern配置

1.精确匹配

配置路径 : @WebServlet("/ok/zs")

访问 servlet: localhost:8080/servlet/ok/zs

2.目录匹配

配置路径 : @WebServlet("/ok/*")

访问文件: localhost:8080/servlet/ok/aaa localhost:8080/servlet/ok/bbb

3.扩展名匹配

配置路径 : @WebServlet("*.action")

访问文件: localhost:8080/hsp/zs.action localhost:8080/hsp/ls.action

提示: @WebServlet("/*.action") , 不能带 / , 否则 tomcat 报错

4.任意匹配

配置路径 : @WebServlet("/") @WebServlet("/*") *

访问文件: localhost:8080/hsp/aaa localhost:8080/hsp/bbb localhost:8080/hsp/ccc

注意:/ 和 /*的配置,会匹配所有的请求,这个比较麻烦,要避免

4.注意事项和使用细节

1、当 Servlet 配置了 "/", 会覆盖 tomcat 的 DefaultServlet, 当其他的 utl-pattern 都匹配 不 上 时 , 都 会 走 这 个 Servlet, 这 样 会 拦 截 到 其 它 静 态 资 源 ,导致无法访问静态资源,如:

http://localhost:8080/hello.html 原本可以直接访问,这样配置之后就会映射到配置了“/”的Servlet中

<servlet>
        <servlet-name>default</servlet-name>
        <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
        <init-param>
            <param-name>debug</param-name>
            <param-value>0</param-value>
        </init-param>
        <init-param>
            <param-name>listings</param-name>
            <param-value>false</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
   <!-- The mapping for the default servlet -->
    <servlet-mapping>
        <servlet-name>default</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

2、当 Servelt 配置了 "/*", 表示可以匹配任意访问路径

3、提示: 建议不要使用 / 和 /*, 建议尽量使用精确匹配

4、优先级遵守: 精确路径 > 目录路径 > 扩展名路径 > /* > /

7.ServletConfig

1.ServletConfig 基本介绍

  1. ServletConfig 类是为 Servlet 程序的配置信息的类
  2. Servlet 程序和 ServletConfig 对象都是由 Tomcat 负责创建
  3. Servlet 程序默认是第 1 次访问的时候创建,ServletConfig 在 Servlet 程序创建时,就创 建一个对应的 ServletConfig 对 象

2.ServletConfig 类能干什么

  1. 获取 Servlet 程序的 servlet-name 的值
  2. 获取初始化参数 init-param
  3. 获取 ServletContext 对象

3.ServletConfig 应用实例

● 需求: 编写 DBServlet.java 完成如下功能

  1. 在 web.xml 配置连接 mysql 的用户名和密码

  2. 在 DBServlet 执行 doGet()/doPost() 时,可以获取到 web.xml 配置的用户名和密码

  3. 思路分析图

    image-20220305131123963

web.xml

<servlet>
        <servlet-name>DBServlet</servlet-name>
        <servlet-class>com.llp.http.DBServlet</servlet-class>
        <init-param>
            <param-name>username</param-name>
            <param-value>llp</param-value>
        </init-param>
        <init-param>
            <param-name>password</param-name>
            <param-value>llp123</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>DBServlet</servlet-name>
        <url-pattern>/db</url-pattern>
    </servlet-mapping>

DBServlet

public class DBServlet extends HttpServlet {


    /**
     * ServletConfig config 使用流程
     * 1. 当DBServlet对象初始化时, tomcat会同时创建一个 ServletConfig对象
     * 2. 这时如果DBServlet init() 方法中你调用 super.init(config);
     * 3. 调用 父类 GenericServlet
     * public void init(ServletConfig config) throws ServletException {
     * this.config = config;
     * this.init();
     * }
     * 这时就会把 Tomcat创建的 ServletConfig对象赋给 GenericServlet的属性 config
     * 4. 因此如果你重写init()方法,记住如果你想在其它方法通过 getServletConfig() 方法获取ServletConfig
     * , 则一定要记住 调用  super.init(config);
     * @param config
     * @throws ServletException
     */

    @Override
    public void init(ServletConfig config) throws ServletException {
        //ConcurrentHashMap, 是一个线程安全的容器.
        System.out.println("init" + config);
        super.init(config);
    }

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        //在DBServlet 执行 doGet()/doPost() 时,可以获取到web.xml配置的用户名和密码
        //你是一个OOP程序员->现有的方法或对象来搞定
        //DBServlet的父类GenericServlet有getServletConfig()
        /**
         * 1. getServletConfig() 方法是 GenericServlet
         * 2. 返回的 servletConfig对象是 GenericServlet private transient ServletConfig config;
         * 3. 当一个属性被 transient 修饰,表示该属性不会被串行化(有些重要信息,不希望保存到文件)
         */
        ServletConfig servletConfig = getServletConfig();
        System.out.println("doPost=" + servletConfig);
        String username = servletConfig.getInitParameter("username");
        String pwd = servletConfig.getInitParameter("pwd");
        System.out.println("初始化参数username= " + username);
        System.out.println("初始化参数pwd= " + pwd);

    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doPost(request, response);
    }
}

image-20220305131727314

image-20220305131515006

8.ServletContext

1.先看一个需求: 如果我们希望统计某个 web 应用的所有 Servlet 被访问的次数,怎么办?

2.方案 1-DB

image-20220305132811215

3.方案 2-ServletContext

image-20220305132842852

1.ServletContext 基本介绍

1.ServletContext 是一个接口,它表示 Servlet 上下文对象
2.一个 web 工程,只有一个 ServletContext 对象实例
3.ServletContext 对象 是在 web 工程启动的时候创建,在 web 工程停止的时销毁
4.ServletContext 对象可以通过 ServletConfig.getServletContext 方法获得对 ServletContext对象的引用,也可以通过this.getServletContext()来获得其对象的引用。
5.由于一个WEB 应用中的所有 Servlet 共享同一个 ServletContext 对象,因此 Servlet 对象之间可以通过 ServletContext 对象来实现多个Servlet 间通讯。ServletContext 对象通常也被称之为域对象。【示意图】

image-20220305133100021

2.ServletContext 可以做什么

1.获取 web.xml 中配置的上下文参数 context-param [信息和整个web 应用相关,而不是属于某个 Servlet]
2.获取当前的工程路径,格式: /工程路径 = 》 比 如 /servlet
3.获取工程部署后在服务器硬盘上的绝对路径—class文件目录 (比如: D:\javaweb\servlet\out\artifacts\servlet_war_exploded)
4.像 Map 一样存取数据, 多个 Servlet 共享数据

image-20220305133331386

3.应用实例 1-获取工程相关信息

●需求如下:

1.获取 web.xml 中配置的上下文参数 context-param

2.获取当前的工程路径,格式: /工程路径

3.获取工程部署后在服务器硬盘上的绝对路径

●代码实现 - 具体看 项目中的代码.

public class ServletContext_ extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {


        //1.获取到ServletContext对象
        ServletContext servletContext = getServletContext();
        //获取web.xml的context-parameter
        Object nickname = servletContext.getInitParameter("nickname");
        Object name = servletContext.getInitParameter("name");
        //获取项目发布会,正在的工作路径
        //  /表示我们的项目(发布后)的 根路径 C:\ide\IdeaProjects\llp-javase\out\artifacts\servlet_war_exploded\
        String contextPath = servletContext.getContextPath();
        String realPath = servletContext.getRealPath("/");

        System.out.println(nickname);
        System.out.println(name);
        System.out.println("工程路径:"+contextPath);
        System.out.println("工程绝对路径: "+realPath);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doPost(request,response);
    }
}

image-20220305143748633

4.应用实例 2-简单的网站访问次数计数器

1.需求分析/图解

image-20220305144348091

2.使用 Chrome 访问 Servlet01, 每访问一次,就增加 1 访问次数,在后台输出,并将结果返回给浏览器显示
3.使用火狐访问 Servlet02,每访问一次,就增加 1 访问次数,在后台输出,并将结果返回给浏览器显示

image-20220305144406062

2.代码实现

PayServlet

@WebServlet(name = "PayServlet",urlPatterns = {"/pay"})
public class PayServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        ServletContext servletContext = getServletContext();
        System.out.println("PayServlet servletContext = "+servletContext);
        response.setContentType("text/html;charset=utf-8");
        PrintWriter writer = response.getWriter();
        writer.print("<h1>网站访问次数:"+ WebVisitCountUtil.getVisitCount(servletContext)+"</h1>");
        writer.flush();
        writer.close();
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doPost(request, response);
    }
}

OrderServlet

@WebServlet(name = "OrderServlet", urlPatterns = {"/order"})
public class OrderServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        ServletContext servletContext = getServletContext();
        System.out.println("OrderServlet servletContext = "+servletContext);

        response.setContentType("text/html;charset=utf-8");
        PrintWriter writer = response.getWriter();
        writer.print("<h1>网站访问次数:"+ WebVisitCountUtil.getVisitCount(servletContext)+"</h1>");
        writer.flush();
        writer.close();
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doPost(request, response);
    }
}

工具类

public static Integer getVisitCount(ServletContext servletContext){
        Object visitCount = servletContext.getAttribute("visitCount");
        if (visitCount == null) {
            visitCount = 1;
            servletContext.setAttribute("visitCount", visitCount);
        } else {
            servletContext.setAttribute("visitCount", Integer.parseInt(visitCount + "") + 1);
        }
        return Integer.parseInt(visitCount+"");
    }
}

ServletContext 对象 是在 web 工程启动的时候创建,在 web 工程停止的时销毁

一个Web工程只有一个ServletContext对象

PayServlet servletContext = org.apache.catalina.core.ApplicationContextFacade@60e34aad
OrderServlet servletContext = org.apache.catalina.core.ApplicationContextFacade@60e34aad

image-20220305145431695

9.HttpServletRequest

1.HttpServletRequest 介绍

1.HttpServletRequest 对象代表客户端的请求
2.当客户端/浏览器通过 HTTP 协议访问服务器时,HTTP 请求头中的所有信息都封装在这个对象中通过这个对象的方法,可以获得客户端这些信息。

2.HttpServletRequest 类图

image-20220305145909401

image-20220305145916237

image-20220305145935545

3.HttpServletRequest 常用方法

1.getRequestURI() 获取请求的资源路径 http://localhost:8080/servlet/loginServlet
2.getRequestURL() 获取请求的统一资源定位符(绝对路径)
http://localhost:8080/servlet/loginServlet
3.getRemoteHost() 获取客户端的 主机, getRemoteAddr()
4.getHeader() 获取请求头

5.getParameter() 获取请求的参数
6.getParameterValues() 获取请求的参数(多个值的时候使用) , 比如 checkbox, 返回的数组
7.getMethod() 获取请求的方式 GET 或 POST
8.setAttribute(key, value); 设置域数据
9.getAttribute(key); 获取域数据
10.getRequestDispatcher() 获取请求转发对象, 请求转发的核心对象

4.HttpServletRequest 应用实例

1.需求: 说明: 在一个表单提交数据给 Servlet , 然后在 Servlet 通过 HttpServletRequest对象获取相关数据

2.创建表单页面register.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>注册</title>
</head>
<body>
<h1>注册用户</h1>
<form action="http://localhost:8080/servlet/requestMethods"
      method="post">
    u: <input type="text" name="username"/><br><br>
    p: <input type="password" name="pwd"/><br><br>
    选择你喜欢的人物:
    <input type="checkbox" name="hobby" value="hsp">孙悟空
    <input type="checkbox" name="hobby" value="lh">贝吉塔
    <input type="checkbox" name="hobby" value="spls">吉连<br/><br/>
    <input type="submit" value="注册用户"/>
</form>
</body>
</html>

image-20220305155055707

代码实现

@WebServlet(name = "HttpServletRequest_", urlPatterns = {"/request"})
public class HttpServletRequest_ extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //这里我们使用request对象,获取表单提交的各种数据
        System.out.println("HttpServletRequestMethods doPost() 被调用..");

        /***********************************
         *  获取和http请求头相关信息
         ***********************************/

        System.out.println("请求的资源路径URI= " + request.getRequestURI());// /request
        //http://主机/uri
        System.out.println("请求的统一资源定位符(绝对路径)URL= " + request.getRequestURL());// http://localhost:8080/request
        System.out.println("请求的客户端ip 地址= " + request.getRemoteAddr());//本地就是127.0.01
        //思考题:如发现某个ip 在10s中,访问的次数超过 100次,就封ip
        //实现思路: 1用一个集合concurrentHashmap[ip:访问次数] 2[线程/定时扫描] 3 做成处理
        // 获取http请求头的信息,可以指定其他,比如 User-Agent , Host等待
        System.out.println("http请求头HOST= " + request.getHeader("Host"));// localhost:8080
        // 说明,如果我们希望得到请求的头的相关信息,可以使用request.getHeader("请求头字段")
        System.out.println("该请求的发起地址是= " + request.getHeader("Referer"));// http://localhost:8080/register.html
        // 请获取访问网站的浏览器是什么?
        String userAgent = request.getHeader("User-Agent");
        //Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36
        System.out.println("User-Agent= " + userAgent);
        // 取出Chrome, 取出最后
        String[] s = userAgent.split(" ");
        System.out.println("浏览器=" + s[s.length - 2].split("\\/")[0]);
        // 取出操作系统
        String replace = s[1].replace("(", "");
        System.out.println(replace);
        //获取 Cookie
        // 	JSESSIONID=8CBBD23BDE01BAE6705E03C5C8916BD1
        String cookie = request.getHeader("Cookie");
        String JSESSIONID = cookie.split("=")[1];
        System.out.println("取出JSESSIONID= " + JSESSIONID);

        // 主要是Get / Post
        System.out.println("http请求方式~= " + request.getMethod());
        /***********************************
         *  获取和请求参数相关信息, 注意要求在返回数据前,获取参数
         ***********************************/

        //解决接收参数的中文乱码问题,写在 getParameter前.
        request.setCharacterEncoding("utf-8");
        //1. 获取表单的数据[单个数据]
        //username=tom&pwd=&hobby=hsp&hobby=spls
        String username = request.getParameter("username");
        String pwd = request.getParameter("pwd");


        //2. 获取表单一组数据
        String[] hobbies = request.getParameterValues("hobby");
        System.out.println("username= " + username);
        System.out.println("pwd= " + pwd);
        //增强for循环的快捷键 iter->回车即可 , 能使用快捷键,就使用快捷键
        for (String hobby : hobbies) {
            System.out.println("hobby=" + hobby);
        }

        //返回接收到的信息, 给浏览器回显
        //本质就是在http响应头,加上 Content-Type: text/html;charset=utf-8
        //说 text/html 表示返回的数据类型,浏览器会根据这个类型来解析数据
        // text/plain 表示返回的数据,请浏览器使用文本方式解析
        // application/x-tar 表示返回的是文件,浏览器就会以下载文件的方式处理
        response.setContentType("text/html;charset=utf-8");
        PrintWriter writer = response.getWriter();
        writer.print("<h1>提交的用户名= " + username + "</h1>");
        writer.flush();
        writer.close();

    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doPost(request, response);
    }
}

ContentType 设置为 application/x-tar 表示返回的是文件,浏览器就会以下载文件的方式处理

image-20220305161428796

5.HttpServletRequest 注意事项和细节

1.获取 doPost 参数中文乱码解决方案, 注意 setCharacterEncoding("utf-8") 要写在request.getParameter()前。

image-20220305161614470

2.注意:如果通过 PrintWriter writer, 有返回数据给浏览器,建议将获取参数代码写在writer.print() 之前,否则可能获取不到参数值

3.处理 http 响应数据中文乱码问题

image-20220305161653001

4.理解Http 协议响应 Content-Type 的含义, 比如 text/plain application/x-tar text/html

10.请求转发

1.为什么需要请求转发

一次请求,对应一个Servlet, 如图

image-20220305162008268

在实际开发中,往往业务比较复杂,需要在一次请求中,使用到多个 Servlet 完成一个任务(Servlet 链, 流水作业) 如图:

image-20220305162044437

2.请求转发说明

1.实现请求转发:请求转发指一个 web 资源收到客户端请求后,通知服务器去调用另外一个 web 资源进行处理
2.HttpServletRequest 对象(也叫Request 对象)提供了一个 getRequestDispatcher 方法,该方法返回一个RequestDispatcher 对象,调用这个对象的 forward 方法可以实现请求转发
3.request 对象同时也是一个域对象,开发人员通过 request 对象在实现转发时,把数据通过 request 对象带给其它web 资源处理
setAttribute方法
getAttribute方法
removeAttribute方法
getAttributeNames方法

3.实现请求转发

请求转发原理示意图

Servlet01处理完之后,tomcat根据web.xml文件调用Servlet02,

Servlet02处理完数据之后返回给Tomcat,tomcat将数据返回给浏览器

请求转发并不会创建一个新的request对象

image-20220305162325850

4.请求转发应用实例

●需求说明/图解, 如果是 tom,提示为管理员,其它是普通用户.

image-20220305162501614

image-20220305162510641

代码实现

login.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登录</title>
</head>

<body>
<form action="http://localhost:8080/check" method="post">
    u: <input type="text" name="username"><br>
    <input type="submit" value="登录">
</form>
</body>
</html>

CheckServlet

@WebServlet(name = "CheckServlet",urlPatterns = {"/check"})
public class CheckServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        request.setCharacterEncoding("utf-8");
        String username = request.getParameter("username");
        //注意:如果是同一个request对象(请求转发),那么可以在不同的servlet中,是getParameter
        if("tom".equals(username)){
            request.setAttribute("role","超级管理员");
        }else {
            request.setAttribute("role","平台管理员");
        }
        //获取分发器
        // 1. /manageServlet写的是 要转发的servlet的url
        // 2. / 会被解析成 /servlet
        // 3. forward(request, response) 表示把当前servlet的request对象和response对象,传递给下一个servlet使用
        RequestDispatcher requestDispatcher = request.getRequestDispatcher("/manager");
        requestDispatcher.forward(request,response);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doPost(request,response);
    }
}

ManagerServlet

@WebServlet(name = "ManagerServlet",urlPatterns = {"/manager"})
public class ManagerServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String username = request.getParameter("username");
        String role = (String)request.getAttribute("role");
        response.setContentType("text/html;charset=utf-8");
        PrintWriter writer = response.getWriter();
        writer.write("用户名: "+username+"</br>");
        writer.write("角色: "+role);
        writer.flush();
        writer.close();
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request,response);
    }
}

5.请求转发注意事项和细节

1.浏览器地址不会变化(地址会保留在第 1 个 servlet 的 url)
2.在同一次HTTP 请求中,进行多次转发,仍然是一次 HTTP 请求
3.在同一次HTTP 请求中,进行多次转发,多个 Servlet 可以共享request 域/对象的数据(因为始终是同一个 request 对象)
4.可以转发到 WEB-INF 目录下(后面做项目使用)
5.不能访问当前WEB 工程外的资源

image-20220305172129825

6.因为浏览器地址栏会停止在第一个 servlet ,如果你刷新页面,会再次发出请求(并且会带数据), 所以在支付页面情况下,不要使用请求转发,否则会造成重复支付[演示]

6.获取到浏览器所在电脑的操作系统版本和位数(练习)

请编写一个 Servlet, 可以获取到浏览器所在电脑的操作系统版本和位数(32 还是 64), 显示在页面即可

@WebServlet(name = "ComputerServlet",urlPatterns = {"/computerServlet"})
public class ComputerServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //System.out.println("ComputerServlet ..");

        //可以获取到浏览器所在电脑的操作系统版本和位数(32还是64), 显示在页面即可
        //分析一把 http请求头
        //User-Agent
        // 	Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:97.0) Gecko/20100101 Firefox/97.0
        //  我们要 Windows NT 10.0 和 Win64
        // (1) Windows NT 10.0; Win64; x64; rv:97.0
        // (2) Windows NT 10.0 和 Win64
        // (3) 使用正则表达式
        String userAgent = request.getHeader("User-Agent");
        //java基础 正则表达式
        String regStr = "\\((.*)\\)";
        Pattern compile = Pattern.compile(regStr);
        Matcher matcher = compile.matcher(userAgent);
        matcher.find();// 因为我们的userAgent只有一组 ()
        String group = matcher.group(0);// (Windows NT 10.0; Win64; x64; rv:97.0)
        String group1 = matcher.group(1);// Windows NT 10.0; Win64; x64; rv:97.0
        String[] operInfos = group1.split(";");
        System.out.println("操作系统=" + operInfos[0]);//Windows NT 10.0
        System.out.println("操作系统位数=" + operInfos[1].trim());// Win64
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doPost(request, response);
    }
}

11.HttpServletResponse

1.HttpServletResponse 介绍

1.每次 HTTP 请求,Tomcat 会创建一个 HttpServletResponse 对象传递给 Servlet 程序去使用。

2.HttpServletRequest 表示请求过来的信息,HttpServletResponse 表示所有响应的信息, 如果需要设置返回给客户端的信息,通过HttpServletResponse 对象来进行设置即可

image-20220305173215218

2.HttpServletResponse 类图

image-20220305173249297

3.向客户端返回数据方法

image-20220305173312449

1.字节流 getOutputStream(); 常用于下载(处理二进制数据)
2.字符流 getWriter(); 常用于回传字符串
3.(细节:)两个流同时只能使用一个。 使用了字节流,就不能再使用字符流,反之亦然, 否则就会报错

4.向客户端返回数据注意事项和细节

1.处理中文乱码问题-方案 1

image-20220305231241389

2.处理中文乱码问题-方案 2

image-20220305231257404

12.请求重定向

1.请求重定向介绍

1.请求重定向指:一个 web 资源收到客户端请求后,通知客户端去访问另外一个 web资源,这称之为请求重定向

2.请求重定向原理示意图

image-20220305231604756

3.重定向和转发的区别

1.重定向会跳转到重定向指定的页面,而请求转发不会跳转
根本原因:重定向浏览器(客户端)会发起两次http请求,而请求转发是在服务端进行请求,第二次请求浏览器不会参与因此不会跳转页面

2.重定向不能共享request域中的数据本质是两次http请求会生成两个HttpServletRequest对象,而请求转发可以共享指定的是同一个HttpServletRequest对象

3.重定向不能定向到/WEB-INF下的资源,tomcat有保护的机制;
请求转发是服务端发起请求,因此可以访问/WEB-INF下的资源

4.重定向可以重定向到Web工程以外的资源比如www.baidu.com
请求转发只能在服务端发起调用,不能访问web工程以外的资源

4.请求重定向应用实例

1.需求: 演示请求重定向的使用 当访问 DownServlet 下载文件, 重定向到
DownServletNew 下载文件

image-20220306085054371

download.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1>下载资源</h1>
    <a href="/servlet/downServlet">下载天龙八部</a>
</body>
</html>

DownServlet

@WebServlet(name = "DownServlet",urlPatterns = {"/downServlet"})
public class DownServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.sendRedirect("/servlet/downServletNew");
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doPost(request,response);
    }
}

DownServletNew

@WebServlet(name = "DownServlet",urlPatterns = {"/downServletNew"})
public class DownServletNew extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("application/x-tar");
        PrintWriter writer = response.getWriter();
        writer.write("降龙十八掌");
        writer.flush();
        writer.close();
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doPost(request,response);
    }
}

image-20220306092951335

5.请求重定向注意事项和细节

1.最佳应用场景:网站迁移,比如原域名是 www.hsp.com 迁移到 www.hsp.cn ,但是百度抓取的还是原来网址.
2.浏览器地址会发生变化,本质是两次http 请求.
3.不能共享Request 域中的数据,本质是两次 http 请求,会生成两个 HttpServletRequest
对象
4.不能重定向到 /WEB-INF 下的资源
5.可以重定向到Web 工程以外的资源, 比如 到 www.baidu.com 【在前面的案例演示】
6.重定向有两种方式, 推荐使用第 1 种.

image-20220306093056289

image-20220306093105548

7.动态获取到 application context

String contextPath = getServletContext().getContextPath(); System.out.println("contextPath= " + contextPath); response.sendRedirect(contextPath + "/downServletNew");

标题:JavaWeb 三大组件之 Servlet
作者:llp
地址:https://llinp.cn/articles/2022/03/02/1646150632327.html