Servlet(下)

44 阅读9分钟

一、ServletConfig

1. 基本介绍

ServletConfig类是 Servlet程序的配置信息的类

Servlet程序和ServletConfig对象都是由Tomcat负责创建

Servlet程序默认是第1次访问的时候创建,在Servlet程序创建时,就创建一个对应的ServletConfig对象

2. 能干什么

获取Servlet程序的servlet-name的值

获取初始化参数init-param

获取ServletContext对象

3. ServletConfig应用实例

完成以下功能:

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

思路分析:

image-20240122194135128

可以这样配置:

@WebServlet(urlPatterns = {"/db"}, initParams = {@WebInitParam(name="username",value = "root"),@WebInitParam(name="password", value="1234")})
public class DbServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        ServletConfig servletConfig = getServletConfig();
        String username = servletConfig.getInitParameter("username");
        String pwd = servletConfig.getInitParameter("password");
        System.out.println("username = " + username + "; password = " + pwd);
    }
}

ServletConfig如果注掉super.init(config),则会报错

image-20240122201245055

也就是说当DbServlet对象初始化时,会同时创建一个ServletConfig对象,如果DbServlet的init方法调用了super.init(config) ,也就是调用了父类GenericServlet没有初始化ServletConfig对象,则该对象没办法传给DbServlet,所以是空指针异常

public class DbServlet extends HttpServlet {
    @Override
    public void init(ServletConfig config) throws ServletException {
        System.out.println("config1 = " + config);
        super.init(config);
    }

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

    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        ServletConfig servletConfig = getServletConfig();
        System.out.println("config2 = " + servletConfig);
        String username = servletConfig.getInitParameter("username");
        String pwd = servletConfig.getInitParameter("password");
        System.out.println("username = " + username + "; password = " + pwd);
    }
}

如果真的要重写init方法,则必须初始化ServletConfig对象,也就是调用super.init(config)

二、ServletContext

1. 为什么需要ServletContext

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

方案1:

image-20240122201833625

方案2:

image-20240122202254742

2. ServletContext基本介绍

ServletContext是一个接口,它表示Servlet上下文对象

一个web工程,只有一个ServletContext对象实例

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

ServletContext对象可以通过ServletConfig.getServletContext方法获得对ServletContext对象的引用,也可以通过this.getServletContext()来获得其对象的引用

由于一个web应用中的所有Servlet共享同一个ServletContext对象,因此Servlet对象之间可以通过ServletContext对象来实现多个Servlet间通讯。ServletContext对象通常也被称之为域对象

image-20240122202451847

3. ServletContext可以做什么

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

代码实现:

web.xml配置:

<servlet>
    <servlet-name>ServletContext_</servlet-name>
    <servlet-class>com.olivier.servlet.ServletContext_</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>ServletContext_</servlet-name>
    <url-pattern>/servletContext_</url-pattern>
</servlet-mapping>
<!--配置整个网站的信息-->
<context-param>
    <param-name>website</param-name>
    <param-value>http://www.olivier.net</param-value>
</context-param>
<context-param>
    <param-name>company</param-name>
    <param-value>橄榄油</param-value>
</context-param>

通过如下代码可以获取参数:

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

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //获取web.xml的context-parameter

        //1. 获取ServletContext对象
        ServletContext servletContext = getServletContext();

        //2. 获取Website
        String website = servletContext.getInitParameter("website");
        System.out.println("website = " + website);
        String company = servletContext.getInitParameter("company");
        System.out.println("company = " + company);

        //3. 获取工程路径
        String contextPath = servletContext.getContextPath();
        System.out.println("contextPath = " + contextPath);

        //4. 获取真实路径,这个"/"就表示发布后的根路径
        //D:\Knowledge Files\code in here\oliver_servlet\out\artifacts\oliver_servlet_war_exploded
        String realPath = servletContext.getRealPath("/");
        System.out.println("realPath = " + realPath);
    }
}

结果如下:

image-20240122205931557

真实路径就是项目发布后的绝对路径,如下图所示:

image-20240122210026612

4. 实际案例

需求:完成一个简单的网站访问次数计数器,

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

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

如下图:

image-20240122210243381

写两个servlet由于code都一样,就写一个:

public class Servlet01 extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //获取到ServletContext对象
        ServletContext servletContext = getServletContext();
        //获取visit_count属性
        Object visit_count = servletContext.getAttribute("visit_count");
        //判断visit_count是否为null
        if (visit_count == null) {
            servletContext.setAttribute("visit_count", 1);
            visit_count = 1;
        } else {
            visit_count = Integer.parseInt(visit_count + "") + 1;
            servletContext.setAttribute("visit_count", visit_count);
        }

        //输出显示
        response.setContentType("text/html;charset=utf-8");
        PrintWriter writer = response.getWriter();
        writer.write("<h1>网站被访问了" + visit_count + "次</h1>");
        writer.flush();
        writer.close();
    }
}

这样确实实现了记录网站被访问次数的功能

三、HttpServletRequest

1. HttpServletRequest介绍

HttpServletRequest对象代表客户端的请求

当客户端/浏览器通过HTTP协议访问服务器时,HTTP请求头中的所有信息都封装在这个对象中

通过这个对象的方法,可以获得客户端这些信息

2. HttpServletRequest类图

继承关系

image-20240122213412234

ServletRequest

image-20240122213420107

HttpServletRequest

image-20240122213719339

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():获取请求转发对象,请求转发的核心对象

实例:

@WebServlet(name = "httpServletRequestMethods", urlPatterns = "/httpReq")
public class HttpServletRequestMethods extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //使用request获取表单提交的各种数据

        System.out.println("请求的资源路径URI: " + request.getRequestURI());
        System.out.println("请求的统一资源定位符(绝对路径) URL=\n: " + request.getRequestURL());
        System.out.println("请求的客户端ip地址:" + request.getRemoteAddr());
        System.out.println("http请求头HOST:" + request.getHeader("Host"));
        System.out.println("http请求头HOST:" + request.getHeader("Host"));
        System.out.println("该请求的发起地址是= " + request.getHeader("Referer"));
        System.out.println("User-Agent= " + request.getHeader("User-Agent"));
        //思考题:如发现某个ip在10s中,访问的次数超过100次,就封ip
        //实现思路:1. 用一个集合concurrentHashmap[ip:访问次数],线程/定时扫描,做成处理
        System.out.println("http 请求头 HOST= " + request.getHeader("Host"));
        //说明,如果我们希望得到请求的头的相关信息,可以使用
        request.getHeader("请求头字段");
        System.out.println("该请求的发起地址是= " + request.getHeader("Referer"));
        //请获取访问网站的浏览器是什么?
        String userAgent = request.getHeader("User-Agent");
        System.out.println("User-Agent= " + userAgent);
        //取出fireFox, 也就是取出最后一个参数
        String[] s = userAgent.split(" ");
        System.out.println("浏览器=" + s[s.length - 1].split("\\/")[0]);
        //课堂练习: 要求同学们取出Windows NT10.0和Win64
        System.out.println("http 请求方式~= " + request.getMethod());
        //获取和请求参数相关信息, 注意要求在返回数据前,获取参数
        //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);
        }


    }

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

4. HttpServletRequest的细节

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

image-20240122225629480

  • 注意:如果通过PrintWriter writer,有返回数据给浏览器,建议将获取参数代码写在writer.print()之前,否则可能获取不到参数值(doPost)
  • 处理http响应数据中文乱码问题

image-20240122225726260

  • 再次理解Http协议响应Content-Type含义,比如text/plain application/x-tar

5. 请求转发

为什么需要请求转发?目前我们学习的都是一次请求,对应一个Servlet

image-20240122225902418

但是在实际开发中,往往业务比较复杂,需要在一次请求中,使用到多个Servlet完成

一个任务(Servlet链,流水作业)如图:

image-20240122225950067

请求转发说明:

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

请求转发原理示意图

image-20240122231110377

四、HttpServletResponse

1. HttpServletResponse介绍

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

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

几个方法:

  • setStatus
  • setHeader
  • getWriter:常用于回传字符串
  • getOutputStream:常用于下载,处理二进制数据

向客户端返回数据一般使用getWriter和getOutputStream,两个流同时只能使用一个。 使用了字节流,就不能再使用字符流,反之亦然,否则就会报错

2. HttpServletResponse类图

image-20240123210343298

3. HttpServletRequest的使用

案例:浏览器请求 , 返回 hello world httpServletRequest

public class HttpServletResponse_ extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html; charset=utf-8");
        PrintWriter writer = response.getWriter();
        writer.write("<h1>hello world httpServletResponse</h1>");
        writer.write("<h1>你好世界</h1>");
        writer.flush();
        writer.close();
    }

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

注意:

  1. setContentType会设置服务器和客户端都用utf-8字符集,还设置了响应头

  2. setContentType要在获取流对象(getWriter)之前调用才有效

  3. 处理浏览器中文乱码问题:

    • 方法1:
    //设置服务器字符集为UTF-8
    resp.setCharacterEncoding("UTF-8");
    
    //通过响应头,设置浏览器也使用UTF-8字符集
    resp.setHeader("Content-Type", "text/html; charset=UTF-8");
    
    • 方法2:
    //setContentType会设置服务器和客户端都使用utf-8字符集,还设置了响应头
    resp.setContentType("text/html;charset=utf-8");
    PrintWriter writer = resp.getWriter();
    

4. 请求重定向

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

image-20240124230200393

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

DownlodServlet代码:

public class DownloadServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String contextPath = request.getContextPath();
        System.out.println("contextPath = " + contextPath);
        response.setContentType("text/html; charset=utf-8");
        response.setStatus(302);
        response.setHeader("Location",contextPath + "/downloadNew");
        PrintWriter writer = response.getWriter();
        writer.write("请求的资源要重定向到downloadNew");
    }

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

DownlodServletNew代码:

public class DownloadServletNew extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("DownServletNew 被调用...");
        response.setContentType("application/x-tar;charset=utf-8");
        PrintWriter writer = response.getWriter();
        writer.print("下载天龙八部..");
        writer.flush();
        writer.close();

    }

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

5. 请求重定向的细节

最佳应用场景:网站迁移,比如原域名是www.olivier.com 迁移到 www.oliviernew.co ,但是百度抓取的还是原来网址

浏览器地址会发生变化,本质是两次HTTP请求

不能共享Request域中的数据,本质是两次HTTP请求,会生成两个HttpServletRequest对象

不能重定向到/WEB-INF下的资源

可以重定向到Web工程以外的资源,比如到www.baidu.com

重定向有两种方式,推荐使用第1种:

  • 第一种:

image-20240124231802393

  • 第二种:

image-20240124231832016