servlet 介绍

1、什么是Servlet

  • Servlet(Server Applet)是Java Servlet的简称,称为小服务程序或服务连接器,用Java编写的服务器端程序,主要功能在于 交互式地浏览和修改数据,生成动态Web内容
  • 狭义的 Servlet 是指Java语言实现的一个接口,广义的 Servlet 是指任何实现了这个 Servlet 接口的类,一般情况下,人们将 Servlet 理解为后者;Servlet 运行于支持Java的应用服务器中;
  • 从原理上来讲,Servlet 可以响应任何类型的请求,但绝大多数情况下 Servlet 只用来扩展基于HTTP协议的Web服务器;

CGI与 Servlet 的区别和联系

  • Servlet的程序用来处理请求和发送响应,并且Servlet是为了解决实现动态页面而衍生的东西;浏览器通过网址来访问服务器,服务器接受请求交给Servlet进行处理,然后进行响应;
  • CGI是:Web服务器和运行其上的应用程序进行"交流"的一种约定。

在这里插入图片描述
tomcat 与 Servlet 的区别和联系

  • Tomcat 是 Web 应用服务器,是一个 Servlet/JSP 容器,Tomcat 作为Servlet容器,负责处理客户请求,把请求传送给Servlet,并将 Servlet 的响应传回给客户,而 Servlet 是一种运行在支持Java语言的服务器上的组件,Servlet 最常见的用途是扩展 Java Web 服务器功能,提供非常安全的、可移植的易于使用的CGI替代品;

在这里插入图片描述
①:Tomcat将http请求文本接收并解析,然后封装成ServletRequest(在HttpServlet类中强转为HttpServletRequest)类型的request对象,所有的HTTP头数据读可以通过request对象调用对应的方法查询到。

②:Tomcat同时会要响应的信息封装为ServletResponse(在HttpServlet类中强转为HttpServletRresponse)类型的response对象,通过设置response属性就可以控制要输出到浏览器的内容,然后将response交给tomcat,tomcat就会将其变成响应文本的格式发送给浏览器;

  • Java Servlet API 是Servlet容器(tomcat)和servlet之间的接口,它定义了serlvet的各种方法,还定义了Servlet容器传送给Servlet的对象类,其中最重要的就是ServletRequest和ServletResponse。所以说我们在编写Servlet时,需要实现Servlet接口,按照其规范进行操作。

2、Servlet入门代码

  • 首先,在创建好的Web工程下,定义一个Java类,实现Servlet接口,并重写里面的所有方法,在最核心的service()种写入一句话:“请求来了”;
public class MyServlet implements Servlet {
    @Override
    public void init(ServletConfig servletConfig) throws ServletException {

    }

    @Override
    public ServletConfig getServletConfig() {
        return null;
    }

    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
		System.out.println("请求来了");
    }

    @Override
    public String getServletInfo() {
        return null;
    }

    @Override
    public void destroy() {

    }
}
  • 然后,在web---WEB-INF---web.xml文件中进行配置:
<servlet>
    <servlet-name>MyServlet</servlet-name>
    <servlet-class>org.westos.demo1.MyServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>MyServlet</servlet-name>
    <url-pattern>/demo</url-pattern>
</servlet-mapping>

首先,你开启服务器之后,并输入你配置的虚拟路径名/demo,服务器会通过第二个servlet-name找到第一个servlet-name,(因此这里的servlet-name可以随意命名,一般是保持和Servlet类名一样)进而找到你的Servlet类的全限定名,通过反射加载Servlet类;

  • 最后,发布项目,访问该Servlet在浏览器中http://127.0.0.1:8080/项目名/web.xml中配置的虚拟路径名/demo

  • 这时候,后台明显收到了请求:在这里插入图片描述

  • Web工程下的其他资源可以被访问到吗?

除了WEB-INF目录下的文件,放在web目录下的其他资源都是可以被访问到的,并且web目录是作为整个项目开启的根路径;

在这里插入图片描述
在这里插入图片描述

3、Servlet的生命周期

  • servlet 生命周期如下:

servlet 加载——>实例化——>服务——>销毁;

  • init():

在Servlet的生命周期中,仅执行一次init()。它是在服务器装入Servlet时执行的,负责初始化Servlet对象。可以配置服务器,以在启动服务器或客户机首次访问Servlet时装入Servlet。无论有多少客户机访问Servlet,都不会重复执行init();

  • service():

它是Servlet的核心,负责响应客户的请求。每当一个客户请求一个HttpServlet对象,该对象的Service()就要调用,而且传递给这个方法一个“请求”(ServletRequest)对象和一个“响应”(ServletResponse)对象作为参数。

  • destroy():

仅执行一次,在服务器端停止且卸载Servlet时执行该方法。当Servlet对象退出生命周期时,负责释放占用的资源。一个Servlet在运行service()方法时可能会产生其他的线程,因此需要确认在调用destroy()方法时,这些线程已经终止或完成

  • 我们可以通过代码来查看这三个核心方法调用的次数以及时机:
import javax.servlet.*;
import java.io.IOException;

public class MyServlet2 implements Servlet {
    @Override
    public void init(ServletConfig servletConfig) throws ServletException {
        System.out.println("init()执行了,初始化工作将在这里完成");
    }

    @Override
    public ServletConfig getServletConfig() {
        return null;
    }

    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        System.out.println("service()执行了,servlet在这里接收请求,做出响应");
    }

    @Override
    public String getServletInfo() {
        return null;
    }

    @Override
    public void destroy() {
        System.out.println("destroy()执行了,服务器停止工作");
    }
}

在这里插入图片描述

init()只会执行一次,service()执行的次数取决于你访问虚拟路径的次数,destory()在服务器正常关闭时,销毁Servlet之前,调用一次;

现在有一个问题:init()是用于初始化的一个方法,他调用的时机是第一次服务器装入Servlet时,如果他执行没有完成,即使客户端发来请求,service()也会一直得不到调用,也就是服务端收不到请求,客户端也收不到响应,如果恰好这时候init()中的逻辑非常多,造成用户体验很不好,可不可以让Init()提前执行,在服务器一启动就开始做准备工作,这样不用等到用户第一次发来请求再执行;

可以;
1、Servlet容器启动时:读取web.xml配置文件中的信息,构造指定的Servlet对象,创建ServletConfig对象,同时将ServletConfig对象作为参数来调用Servlet对象的init方法
2、在Servlet容器启动后:客户首次向Servlet发出请求,Servlet容器会判断内存中是否存在指定的Servlet对象,如果没有则创建它,然后根据客户的请求创建HttpRequest、HttpResponse对象,从而调用Servlet对象的service方法

3、Servlet容器在启动时自动创建Servlet,这是由在web.xml文件中为Servlet设置的属性决定的。从中我们也能看到同一个类型的Servlet对象在Servlet容器中以单例的形式存在
4、也就是说,可以通过修改web.xml文件,添加配置信息,提前init()调用时机;

<servlet>
...
    <load-on-startup>
        number
    </load-on-startup>
</servlet>
  • number<0: 采用延迟装载,只有在第一次请求的容器才在该servlet调用初始化函数,默认就是-1;
  • number>=0: 采用预先装载,容器在应用启动时就加载并初始化这个servlet;
  • number越小越先被装载,number越大越晚被装载;

【注意:】

  • Servlet是单例的,可能会存在线程安全问题,尽量不要定义成员变量。

4、Servlet的重点对象——ServletConfig

  • ServletConfig是一个接口,它代表了Servlet的配置信息的对象,作用是可以获取配置的一些初始化参数;

  • Servlet容器启动时:读取web.xml配置文件中的信息,构造指定的Servlet对象,创建ServletConfig对象,同时将ServletConfig对象作为参数来调用Servlet对象init方法

@Override
public void init(ServletConfig servletConfig) throws ServletException {
    
}
  • 我们可以打印该对象,查看源码:
@Override
    public void init(ServletConfig servletConfig) throws ServletException {
        System.out.println("init()执行了,初始化工作将在这里完成");
        System.out.println(servletConfig);
    }
//org.apache.catalina.core.StandardWrapperFacade@da69797

在这里插入图片描述
作用1:动态获取初始化好的值

在配置文件中,我们可以添加这样的配置:

@Override
public void init(ServletConfig servletConfig) throws ServletException {
    String username = servletConfig.getInitParameter("username");
    System.out.println(username);
}

<servlet>
    <servlet-name>MyServlet3</servlet-name>
    <servlet-class>org.westos.demo1.MyServlet3</servlet-class>
    <init-param>
        <param-name>username</param-name>
        <param-value>zhangsan</param-value>
    </init-param>
</servlet>
<servlet-mapping>
    <servlet-name>MyServlet3</servlet-name>
    <url-pattern>/demo3</url-pattern>
</servlet-mapping>

在这里插入图片描述
作用2:获取ServletContext对象

  • 什么是ServletContext对象

WEB容器在启动时,它会为每个WEB应用程序都创建一个对应的ServletContext对象,它代表当前web应用(servlet上下文对象)

由于一个WEB应用中的所有Servlet共享同一个ServletContext对象,因此Servlet对象之间可以通过ServletContext对象来实现通讯;ServletContext对象通常也被称之为context域对象:1、是一个容器,2、作用范围是应用程序范围。

同样的,服务器一启动,该对象就被创建出来,我们只是通过配置对象去获取它;我们同样打印他的地址值,查看源码:

@Override
public void init(ServletConfig servletConfig) throws ServletException {
    ServletContext servletContext = servletConfig.getServletContext();
    System.out.println(servletContext);
}
    
//org.apache.catalina.core.ApplicationContextFacade@648c77d5

在这里插入图片描述
作用3:获取Servlet配置文件中的名字

@Override
public void init(ServletConfig servletConfig) throws ServletException {
    String servletName = servletConfig.getServletName();
    System.out.println(servletName);
    //MyServlet4
}
  • 观察重写的几个方法,发现ServletConfig对象只存在于init(),如果我们的核心方法service()需要用到它,怎么做?可以提升作用域,但是发现重写的这几个方法中有一个getServletConfig(),它的存在是为了什么?其实一般我们将ServletConfig对象提升作用域后,私有该变量,再通过getServletConfig()返回该变量,以便于 隐藏成员变量
public class MyServlet5 implements Servlet {
    ServletConfig servletConfig = null;

    @Override
    public void init(ServletConfig servletConfig) throws ServletException {
        this.servletConfig = servletConfig;
    }

    @Override
    public ServletConfig getServletConfig() {
        return this.servletConfig;
    }
    …………
}

5、创建Servlet对象的几种方式

  • 首先,servlet的继承体系如下:
    在这里插入图片描述

方式1:自定义类实现Servlet接口

import javax.servlet.*;
import java.io.IOException;
public class MyServlet implements Servlet {
    @Override
    public void init(ServletConfig servletConfig) throws ServletException {
        System.out.println("init()执行了,初始化工作将在这里完成");
        System.out.println(servletConfig);
    }

    @Override
    public ServletConfig getServletConfig() {
        return null;
    }

    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        System.out.println("service()执行了,servlet在这里接收请求,做出响应");
    }

    @Override
    public String getServletInfo() {
        return null;
    }

    @Override
    public void destroy() {
        System.out.println("destroy()执行了,服务器停止工作");
    }
}

这是最原始的一种方式,我们需要重写里面的所有抽象方法,但是与客户端进行交互的只有一个核心方法service(),如果简化代码量?

方式2:自定义类继承抽象类GenericServlet

抽象类GenericServlet源码:

package javax.servlet;
import java.io.IOException;
import java.io.Serializable;
import java.util.Enumeration;

public abstract class GenericServlet implements Servlet, ServletConfig, Serializable {
    private static final long serialVersionUID = 1L;
    private transient ServletConfig config;

    public GenericServlet() {
    }

    public void destroy() {
    }

    public String getInitParameter(String name) {
        return this.getServletConfig().getInitParameter(name);
    }

    public Enumeration<String> getInitParameterNames() {
        return this.getServletConfig().getInitParameterNames();
    }

    public ServletConfig getServletConfig() {
        return this.config;
    }

    public ServletContext getServletContext() {
        return this.getServletConfig().getServletContext();
    }

    public String getServletInfo() {
        return "";
    }

    public void init(ServletConfig config) throws ServletException {
        this.config = config;
        this.init();
    }

    public void init() throws ServletException {
    }

    public void log(String msg) {
        this.getServletContext().log(this.getServletName() + ": " + msg);
    }

    public void log(String message, Throwable t) {
        this.getServletContext().log(this.getServletName() + ": " + message, t);
    }

    public abstract void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;

    public String getServletName() {
        return this.config.getServletName();
    }
}

1、该抽象类重写了除service()以外的所有其他方法,我们只需要重写service()的逻辑就好了;
2、为什么该抽象类中存在两个init()

init(ServletConfig config)方法,因为只有init(ServletConfig config)中带有ServletConfig对象,为了方便能够在其他地方也能直接使用ServletConfig对象,而不仅仅局限在初始化方法中,所以创建一个私有的成员变量config,在初始化方法中就将其赋值给config,然后通过getServletConfig()方法就能够获取ServletConfig对象

还有一个init()方法,并且这个init()方法是空的,什么都没有,这是为什么呢?因为上面的init()是为了给成员变量ServletConfig赋值,但如果继承该抽象类的子类想要在init()中写一定的逻辑,就会破坏原来抽象类中的逻辑,导致ServletConfig一直为空,当然这里可以手动调用父类的super.init(ServletConfig config),但是这样很麻烦;因此,再提供一个init(),就不用覆盖之前的init();

3、小技巧:

由于GenericServlet不仅实现了 Servlet接口,还实现了配置接口ServletConfig,所以我们可以通过两种方式获取全局域对象:

import javax.servlet.*;
import java.io.IOException;

public class MyServlet extends GenericServlet {
    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        ServletContext servletContext1 = this.getServletContext();
        ServletContext servletContext2 = this.getServletConfig().getServletContext();
        System.out.println(servletContext1 == servletContext2);
        //true
    }
}

方式3:自定义类继承抽象类HttpServlet

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class MyServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("请求来了");
    }
}

1、为什么会出现ServletResponse、ServletRequest与HttpServletResponse、HttpServletRequest ?

从继承机构可以看到HttpServletResponse、HttpServletRequest是从HttpServlet中开始出现的,查看HttpServlet源码发现:

public void service(ServletRequest req, ServletResponse res)
    throws ServletException, IOException
{
    HttpServletRequest  request;
    HttpServletResponse response;

    try {
        request = (HttpServletRequest) req;
        response = (HttpServletResponse) res;
    } catch (ClassCastException e) {
        throw new ServletException("non-HTTP request or response");
    }
    service(request, response);
}

在HttpServlet中将ServletResponse、ServletRequest强转为:HttpServletResponse、HttpServletRequest,为什么如此做?

ServletResponse、ServletRequest、HttpServletRequest、HttpServletResponse都只是接口:

public interface HttpServletRequest extends ServletRequest

而request对象实际上的类型是org.apache.catalina.connector.RequestFacade,Tomcat中的源码为:

public class RequestFacade implements HttpServletRequest

2、servlet的生命周期中,可以看出,执行的是service方法,为什么我们就只需要写doGet和doPost方法呢 ?

service()是在javax.servlet.Servlet接口中定义的,在javax.servlet.GenericServlet中实现了这个方法, 而 doGet/doPost 则是在javax.servlet.http.HttpServlet 中实现的;

  • HttpServlet 里的三个方法:
service(HttpServletRequest req, HttpServletResponse resp)
doGet(HttpServletRequest req, HttpServletResponse resp)
doPost(HttpServletRequest req, HttpServletResponse res)
  • 区别和联系:

在servlet中默认情况下,无论你是get还是post提交过来,都会经过service()方法来处理,然后转向到doGet或是doPost方法,我们可以查看HttpServlet 类的service方法:

protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    String method = req.getMethod();
    long lastModified;
    if (method.equals("GET")) {
        lastModified = this.getLastModified(req);
        if (lastModified == -1L) {
            this.doGet(req, resp);
        } else {
            long ifModifiedSince;
            try {
                ifModifiedSince = req.getDateHeader("If-Modified-Since");
            } catch (IllegalArgumentException var9) {
                ifModifiedSince = -1L;
            }

            if (ifModifiedSince < lastModified / 1000L * 1000L) {
                this.maybeSetLastModified(resp, lastModified);
                this.doGet(req, resp);
            } else {
                resp.setStatus(304);
            }
        }
    } else if (method.equals("HEAD")) {
        lastModified = this.getLastModified(req);
        this.maybeSetLastModified(resp, lastModified);
        this.doHead(req, resp);
    } else if (method.equals("POST")) {
        this.doPost(req, resp);
    } else if (method.equals("PUT")) {
        this.doPut(req, resp);
    } else if (method.equals("DELETE")) {
        this.doDelete(req, resp);
    } else if (method.equals("OPTIONS")) {
        this.doOptions(req, resp);
    } else if (method.equals("TRACE")) {
        this.doTrace(req, resp);
    } else {
        String errMsg = lStrings.getString("http.method_not_implemented");
        Object[] errArgs = new Object[]{method};
        errMsg = MessageFormat.format(errMsg, errArgs);
        resp.sendError(501, errMsg);
    }
}

从上面可以看出,这里的service是用来 转向 的,但是如果你在自己的servlet类中覆盖了service方法,比如说你的service是这样的:

public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {    
	res.getOutputStream().print("this is my service");    
}          

那么这时service就不是用来转向的,而是用来处理业务的,现在不论你的客户端是用pos还是get来请求此servlet,都会执行service方法也只能执行servlet方法,不会去执行doPost或是doGet方法;

6、前后台交互——表单验证

servlet:

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class MyServlet8 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("这是get请求");
        this.doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //获取请求
        System.out.println("这是post请求");
        String username = (String) req.getParameter("username");
        String password = (String) req.getParameter("password");
        System.out.println(username + "------------" + password);
        //做出响应
        resp.getWriter().write("Hello World!");
    }
}

前台表单代码:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title></title>
</head>
<body>
    <form action="/myServlet_war_exploded/demo8" method="get">
        用户名:<input type="text" value="" name="username" placeholder="请输入用户名"/><br>
        密码:<input type="password" value="" name="password" placeholder="请输入密码"/><br>
        <input type="submit" value="get提交"/>
    </form>
    <br><br>
    <form action="/myServlet_war_exploded/demo8" method="post">
        用户名:<input type="text" value="" name="username" placeholder="请输入用户名"/><br>
        密码:<input type="password" value="" name="password" placeholder="请输入密码"/><br>
        <input type="submit" value="post提交"/>
    </form>
</body>
</html>

web.xml文件中的配置:

<servlet>
        <servlet-name>MyServlet8</servlet-name>
        <servlet-class>org.westos.demo1.MyServlet8</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>MyServlet8</servlet-name>
        <url-pattern>/demo8</url-pattern>
    </servlet-mapping>

在这里插入图片描述
在这里插入图片描述

表单验证中,路径写的是:web项目的上下文路径+servlet配置的虚拟路径

【注意:】

本质上,我们get请求与post请求中的代码逻辑都是一样的,因此,互相调用就好了,没必须要重复书写代码,但小心写成了死递归;

7、Servlet虚拟路径的配置问题

  • 理论上来说,一个Servlet的虚拟路径可以配置多级路径,也可以配置多个;可以通过不同的虚拟路径去访问这个Servlet;
<servlet>
    <servlet-name>MyServlet8</servlet-name>
    <servlet-class>org.westos.demo1.MyServlet8</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>MyServlet8</servlet-name>
    <url-pattern>/aaa/bbb/ccc</url-pattern>
    <url-pattern>/demo/ddd/eee</url-pattern>
</servlet-mapping>
  • 但是反过来,一个虚拟路径只能对应一个Servlet;
  • 快捷方式:定义好模板代码,在web工程下直接创建servlet类,并使用注解中的value属性或者urlPatterns属性进行配置虚拟路径,不用跑到web.xml下配置;

在这里插入图片描述
在这里插入图片描述

@WebServlet(name = "MyServlet1",value = "/demo1")
public class MyServlet1 extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    }

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

8、四大域对象

  • servlet中有四大域对象:ServletContext全局域、Session会话域、Request请求域、PageContext页面域;

  • 什么是ServletContext对象?

上面已经介绍过他的概念,这里不再赘述,简单来说,他就是一个web工程内共享的全局上下文对象;

  • 如何获取该对象?

ServletConfig对象中维护了ServletContext对象的引用,开发人员在编写servlet时,可以通过
1、ServletConfig.getServletContext方法获得ServletContext对象
2、this.getServletContext()方法;

  • ServletContext对象的方法:

1、属性操作方法

设置属性:setAttribute(String name, Object obj)在web项目范围内存放内容,以便让在web项目中所有的servlet读能访问到;
存储属性:getAttribute(String name)通过指定名称获得内容;
移除属性:removeAttribute(String name)通过指定名称移除内容;

2、参数操作方法

getInitPatameter(String name)  //通过指定名称获取初始化值
getInitParameterNames()  //获得枚举类型

3、资源和路径操作方法

getRealPath("/WEB-INF/web.xml") 获取web项目下指定资源的路径

context.getResource(java.lang.String path) 获取指定文件的统一资源定位符path必须是/开头,代表当前web应用程序的根目录。返回一个代表某个资源的URL对象。

getResourceAsStream(java.lang.String path) 获取web项目下指定资源的内容,返回的是字节输入流

getResourcePaths(java.lang.String path) 指定路径下的所有内容。

4、获取文件mime类型

getMimeType(文件名称)

什么是MIME类型?

  • 根据百度百科的解释:MIME:全称Multipurpose Internet Mail Extensions,多功能Internet邮件扩充服务,它是一种多用途网际邮件扩充协议,在1992年最早应用于电子邮件系统,但后来也应用到浏览器。MIME类型就是设定某种扩展名的文件用一种应用程序来打开的方式类型,当该扩展名文件被访问的时候,浏览器会自动使用指定应用程序来打开。多用于指定一些客户端自定义的文件名,以及一些媒体文件打开方式。
  • 说白了也就是文件的媒体类型。浏览器可以根据它来区分文件,然后决定什么内容用什么形式来显示。
  • 部分文件的MIME类型:
    在这里插入图片描述
  • 为什么要获取MIMEType

是因为在进行文件上传的时候,需要在POST请求体中传递相应的参数,来进行文件的上传操作;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.util.Iterator;
import java.util.Set;

@WebServlet(name = "Servlet1",value = "/home1")
public class Servlet1 extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //获取全局域对象
        ServletContext context = this.getServletContext();
        //设置属性
        context.setAttribute("username","zhangsan");

        //获取web项目下指定资源的路径
        String realPath = context.getRealPath("/WEB-INF/web.xml");
        System.out.println(realPath);

        //指定路径下的所有内容。
        Set set = context.getResourcePaths("/WEB-INF");
        Iterator iterator = set.iterator();
        System.out.println("WEB-INF文件下的所有文件:");
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }

        //获取web项目下指定资源的内容,返回的是字节输入流
        InputStream is = context.getResourceAsStream("/index.jsp");
        System.out.println(is);
		
		String mimeType = context.getMimeType("my.txt");
        System.out.println(mimeType);

        /*E:\myJavaProject\myServlet学习\out\artifacts\myServlet_war_exploded\WEB-INF\web.xml
        WEB-INF文件下的所有文件:
        /WEB-INF/classes/
        /WEB-INF/web.xml
        /WEB-INF/a.txt
        java.io.ByteArrayInputStream@5d4b7e46
        text/plain*/
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doPost(request, response);
    }
}
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet(name = "Servlet2",urlPatterns = "/home2")
public class Servlet2 extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //获取全局域对象
        ServletContext context = this.getServletContext();
        //获取属性值
        String name = (String) context.getAttribute("username");
        System.out.println(name);
        //移除属性值
        context.removeAttribute("username");
        
        //zhangsan
    }

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

因为服务器通常是不关闭的,也就是说,你每设置一个属性,他就会一直存在,这样下来数据越存越多,存在资源的消耗,因此域对象不是说范围越大越好,当然你可以手动清理这些数据;

9、访问Web工程下资源的路径问题

现在提出这样一个问题:以前我们使用Java代码访问文件的时候,文件路径有两种写法,一种是绝对路径,一种是相对路径,比如一个文件:a.txt存放在项目的根路径下,就可以使用a.txt直接访问;
现在有三个文件,分别放在项目的src下、web下、WEB-INF下,如何访问?

在这里插入图片描述

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.FileInputStream;
import java.io.IOException;

@WebServlet(name = "MyServlet1",value = "/demo1")
public class MyServlet1 extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        FileInputStream fis1 = new FileInputStream("/src/a.txt");
        System.out.println(fis1);

        FileInputStream fis2 = new FileInputStream("/web/b.txt");
        System.out.println(fis2);

        FileInputStream fis3 = new FileInputStream("web/WEB-INF/c.txt");
        System.out.println(fis3);
    }

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

在这里插入图片描述

  • 程序报错,找不到指定的路径,这是为什么?

这是因为你的web项目最终编译过后整个项目的目录发生了变化,可以在out目录下查看被编译过后的web项目,服务器只认编译过后的文件,他不认源码;

在这里插入图片描述

很明显,web项目编译后目录发生了很大的变化,这时候代码还按照原来的路径找资源,肯定是找不到的,怎么处理?我们首先想到的是填写绝对路径:

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.FileInputStream;
import java.io.IOException;

@WebServlet(name = "MyServlet1",value = "/demo1")
public class MyServlet1 extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        FileInputStream fis1 = new FileInputStream("myJavaProject\\myServlet学习2\\out\\artifacts\\myServlet2_war_exploded\\WEB-INF\\classes\\a.txt");
        System.out.println(fis1);

        FileInputStream fis2 = new FileInputStream("E:\\myJavaProject\\myServlet学习2\\out\\artifacts\\myServlet2_war_exploded\\b.txt");
        System.out.println(fis2);

        FileInputStream fis3 = new FileInputStream("E:\\myJavaProject\\myServlet学习2\\out\\artifacts\\myServlet2_war_exploded\\WEB-INF\\c.txt");
        System.out.println(fis3);
    }

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

在这里插入图片描述

这时候发现后台的访问完全没有问题,但是这样写代码肯定是不行的,因为我们写的项目最终要部署在公司的服务器上,但是这时候我们填写的路径是自己电脑上该web项目编译后其中资源的路径,所以应该动态的获取项目的路径,而不是写死;

  • 可以通过全局域对象动态的获取整个web项目的路径,最终达到访问里面资源的目的;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet(name = "MyServlet2",value = "/demo2")
public class MyServlet2 extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        ServletContext context = this.getServletContext();
        String rootpath = context.getRealPath("/");
        System.out.println(rootpath);
        //E:\myJavaProject\myServlet学习2\out\artifacts\myServlet2_war_exploded\
    }

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

打印全局域对象获取到的整个路径,可以发现他获取到的是整个web项目配置的上下文路径;
代码如下,顺利访问到所有的资源:

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.FileInputStream;
import java.io.IOException;

@WebServlet(name = "MyServlet2", value = "/demo2")
public class MyServlet2 extends HttpServlet {
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doPost(request, response);
    }

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        ServletContext context = this.getServletContext();
        String rootpath = context.getRealPath("/");
        System.out.println(rootpath);
        //E:\myJavaProject\myServlet学习2\out\artifacts\myServlet2_war_exploded\

        FileInputStream fis1 = new FileInputStream(rootpath + "WEB-INF\\classes\\a.txt");
        System.out.println(fis1);

        FileInputStream fis2 = new FileInputStream(rootpath + "b.txt");
        System.out.println(fis2);

        FileInputStream fis3 = new FileInputStream(rootpath + "WEB-INF\\c.txt");
        System.out.println(fis3);
    }
}

在这里插入图片描述

  • 如果我们现在有一个工具类,这个工具类需要读取资源的路径,怎么写代码?前面服务器的路径都是通过ServletContext全局域对象获取到的,但是Servlet对象是由服务器创建,普通的类中没办法通过创建它的对象获取全局域对象,ServletContext只能用于web环境。如果是非web环境,则使用ClassLoader来获取真实路径:

方式1:

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.FileInputStream;
import java.io.IOException;

@WebServlet(name = "MyServlet3",value = "/demo3")
public class MyServlet3 extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        FileInputStream stream = MyUtils.test();
        System.out.println(stream);
        //java.io.FileInputStream@3a259bf6
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doPost(request, response);
    }
}
public class MyUtils {
    public static FileInputStream test() throws FileNotFoundException {
        //获取该类对应的类加载器对象
        ClassLoader classLoader = MyUtils.class.getClassLoader();
        //获取文件运行时的真实路径
        URL url = classLoader.getResource("a.txt");
        //类加载器获取文件路径是有局限性的,只能获取src目录下的文件
        String path = url.getPath();
        FileInputStream fis = new FileInputStream(path);
        return fis;
    }
}

方式2:

public static InputStream show() throws FileNotFoundException {
    //获取该类对应的类加载器对象
    InputStream stream = MyUtils.class.getClassLoader().getResourceAsStream("/a.txt");
    return stream;
}

10、请求对象与响应对象

  • 请求对象和响应对象的原理:request 和response 对象都是由服务器创建,管理,销毁,我们拿来使用;
  • 继承体系结构:
ServletRequest(接口)<--------继承<-------HttpServletRequest(接口)<--------实现 org.apache.catalina.connector.RequestFacade@6049a827
ServletResponse(接口)<--------继承------HttpServletResponse(接口)<--------实现------org.apache.catalina.connector.ResponseFacade@3bec9d4
  • response:设置响应消息
设置响应行: HTTP/1.1   200   ok
设置响应状态码:setStatus(int code);重定向---------302

特点:
1、两次请求,两次响应
2、地址栏发生变化
3、不仅可以访问内部资源,也可访问外部资源

代码:

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //设置状态码302重定向
        response.setStatus(302);
        //设置响应头location
        response.setHeader("location","http://www.baidu.com");

        //以上两步可以合成一个简单的方法
        response.sendRedirect("http://www.baidu.com");
    }
  • 设置响应头:
例如:content-type:text/html;charset=utf-8
setHeader(String headName,String headValue); 设置响应头的名称和值
setDateHeader(String name,long date); 设置日期头
setIntHeader(String name,int count); 设置数字头
  • request 对象:获取请求消息

1、获取请求行: GET /MyServlet/index.jsp?name=zhangsan&age=23 HTTP/1.1

request.getMethod();//获取请求方式
request.getContextPath();//获取项目名称
request.getRequestURI();//获取URI
request.getRequestURL();//获取URL
request.getRemoteAddr();//获取IP地址
request.getQueryString();//获取请求参数
request.getProtocol();//获取协议版本

2.获取请求头:

request.getHeader("user-agent");//获取请求头的值
request.getDateHeader(name);//获取日期头
request.getIntHeader(name)//获取数字头

3.获取请求体: 请求体是专门用于封装Post请求的请求参数

获取字符数据: getReader();返回一个高效的字符流,我们通过一次读取一行的方法来获取请求参数数据,然后拆分字符串获取我们想要的数据;
获取字节数据:getInputStream();后期上传文件时讲解;

4.通用的方式来获取 请求参数

request.getParameter(name);  通过请求参数的名称来获取值
request.getParameterValues("hobby"); 通过请求参数的名称,来获取值的数组 一般用于复选框
request.getParameterMap();  获取所有参数的map集合
request.getParameterNames(); 获取所有参数的名称 枚举 不常用 了解
  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值