1.[springMvc]Servlet的基础知识

24 篇文章 17 订阅
22 篇文章 11 订阅

servlet是啥

Java Servlet 是运行在 Web 服务器或应用服务器上的程序,它是作为来自 Web 浏览器或其他 HTTP 客户端的请求和 HTTP 服务器上的数据库或应用程序之间的中间层。

使用 Servlet,您可以收集来自网页表单的用户输入,呈现来自数据库或者其他源的记录,还可以动态创建网页。

Java Servlet 通常情况下与使用 CGI(Common Gateway Interface,公共网关接口)实现的程序可以达到异曲同工的效果。但是相比于 CGI,Servlet 有以下几点优势:

性能明显更好。
Servlet 在 Web 服务器的地址空间内执行。这样它就没有必要再创建一个单独的进程来处理每个客户端请求。
Servlet 是独立于平台的,因为它们是用 Java 编写的。
服务器上的 Java 安全管理器执行了一系列限制,以保护服务器计算机上的资源。因此,Servlet 是可信的。
Java 类库的全部功能对 Servlet 来说都是可用的。它可以通过 sockets 和 RMI 机制与 applets、数据库或其他软件进行交互。

在写spring mvc之前先来了解下servlet的基础知识,在早之前的java开发中,可能大家都使用过jsp+servlet的开发模式,相信servlet大家都很熟悉了,可以说java的后端就是最重要的就是servlet,servlet在java 后端开发过程中扮演这比较重要的角色,现在的开发人员如果说来就直接spring boot零配置开发,那么可能对servlet这个概念只停留这面试复习的时候吧,我自己最早做java 的时候,那个时候spring mvc还是一大堆配置文件,使用的也是视图解析器的模式来的,前后端没有进行分离,然而当时,struts还比较火,所以说那个时候spring mvc用的人不是很多,而当初我们自己的项目就是spring mvc+spring jdbc的模式进行的开发;我是在2010年开始做java开发的,当时没有使用任何框架,用的最多的还是jsp + servlet,视图页面使用了el的表达式,开发了好久的servlet,所以我们这些做的久一点的程序员对servlet还比较熟悉,我记得当初还自己简单封装了servlet的,类似于struts一样,但是当初只是为了项目而做,符合项目的需求和开发,而只要做后端的,servlet是比较重要的一个后端技术,servlet是一种规范,业界规定的规范,不管哪种框架要做后端,那么必定遵循servlet的定义规范来做,所以像大家现在使用比较多的spring webmvc、struts都是遵循了servlet的定义规范,所以说不要说你使用了高级框架,别个问你servlet是个啥,你至少得知道这是一个啥吧;简单来说servlet是一个基于http协议的容器,所有的开发框架都是基于这个规范进行开发的。
在这里插入图片描述
Servlet 执行以下主要任务:

读取客户端(浏览器)发送的显式的数据。这包括网页上的 HTML 表单,或者也可以是来自 applet 或自定义的 HTTP 客户端程序的表单。
读取客户端(浏览器)发送的隐式的 HTTP 请求数据。这包括 cookies、媒体类型和浏览器能理解的压缩格式等等。
处理数据并生成结果。这个过程可能需要访问数据库,执行 RMI 或 CORBA 调用,调用 Web 服务,或者直接计算得出对应的响应。
发送显式的数据(即文档)到客户端(浏览器)。该文档的格式可以是多种多样的,包括文本文件(HTML 或 XML)、二进制文件(GIF 图像)、Excel 等。
发送隐式的 HTTP 响应到客户端(浏览器)。这包括告诉浏览器或其他客户端被返回的文档类型(例如 HTML),设置 cookies 和缓存参数,以及其他类似的任务。

Servlet运行流程

我们来想象一个流程,当你通过web浏览器通过http的方式来访问网络中一个资源,比如图片资源,肯定是通过一个url的方式来访问的,那么它的初六流程肯定是:
1.接收请求,然后将请求解析成一个统一规范对象(ServletRequest);
2.处理请求,处理请求是交给开发者去实现的,servlet会调用它的service方法,然后根据请求类型(GET POST DELETE PUT)去调用相应的方法,开发者要不实现service方法要不实现相对应的方法。
3.相应请求信息,封装成统一的规范对象ServletResponse。

web服务器:将某个主机上的资源映射为一个URL供外界访问,完成接收和响应请求
servlet容器:存放着servlet对象(由程序员编程提供),处理请求

示例

tomcat中已经根据servlet的规范实现了对应的servlet容器,我们这里写个例子,就以tomcat为例,在项目中使用tomcat内嵌的一个插件运行

<plugin>
          <groupId>org.apache.tomcat.maven</groupId>
          <artifactId>tomcat7-maven-plugin</artifactId>
          <version>2.2</version>
          <configuration>
            <path>/</path>
            <port>8080</port>
          </configuration>
        </plugin>

maven中添加spring mvc的相关依赖

<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-webmvc</artifactId>
  <version>5.2.7.RELEASE</version>
</dependency>
<dependency>
  <groupId>javax.servlet</groupId>
  <artifactId>javax.servlet-api</artifactId>
  <version>3.1.0</version>
  <scope>provided</scope>
</dependency>
<dependency>
  <groupId>javax.servlet</groupId>
  <artifactId>jstl</artifactId>
  <version>1.2</version>
</dependency>

编写一个Controller


@RestController
public class IndexController {

    @RequestMapping("/test.do")
    public String test(){
        return "test";
    }

    @RequestMapping("/test1")
    public String test1(){
        return "test1";
    }
}

定义一个spring-servlet.xml文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
        http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">


    <context:component-scan base-package="com.dev1.web"/>
    <mvc:annotation-driven />



</beans>

web.xml内容如下:

<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
  <display-name>Archetype Created Web Application</display-name>
  <servlet>
    <servlet-name>dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:spring-servlet.xml</param-value>
    </init-param>

    <load-on-startup>1</load-on-startup>
  </servlet>

  <servlet-mapping>
    <servlet-name>dispatcher</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>
</web-app>

我们定义的这个sevlet就是DispatcherServlet,DispatcherServlet是spring webmvc中的一个sevlet
,然后这个servlet我们要去它的过滤路径是“/”,意思是拦截所有的请求项,项目的整体结构如下:

在这里插入图片描述
然后我们启动,在启动命令加入:
在这里插入图片描述
启动完成过后打开浏览器输入:
在这里插入图片描述
这样是能够到我们的IndexController中的,所以是生效的,比如我们的web中有两个资源文件,index.jsp和index.html文件,但是我们访问只有index.jsp能够访问,而index.html就不能访问,这是为什么呢?
我们来看这几个servlet:
DefaultServlet
DispatcherServlet
JspServlet(类似的名字)

我们在web.xml中配置的sevlet的过滤是“/”,代表的是这个路径下的所有被拦截到,而真正我们输入http://localhost:8080/test的时候是被拦截了,但是如果说输入http://localhost:8080/index.jsp这个就不是DispatcherServlet来处理的了,而是JspServlet来处理的,而html的时候就没有对应的sevlet去处理它,从而就走到了DefaultServlet,所以就报错了。

如果说我们把web.xml中的DispatcherServlet的过滤路径写成"/",那么这个时候只有DispatcherServlet的访问路径能够访问,其他都不能访问
在这里插入图片描述
在这里插入图片描述
这是为什么呢?我们再看来 “/”和“/
”的区别,这个两个方式都是找所有的路径,
但是如果说“/”它的优先级就最高了,也就是说加了"/"那么只能是找DispatcherServlet对应的请求
所以说在日常工作中要区分开来,否则有时候出现一些莫名其妙的错误,但是如果说你要区分看来,你可以写*.do这些类似的,每个servlet的mapping分开来写;
还有一个就是类似于我们在xml中配置配置文件的路径也有这样写:
classpath:springmvc.xml
classpath*:xxxxx.xml
那么这两种有什么区别呢?classpath:是找class路径下的配置文件,而classpath*:是除了class路径下的配置文件找之外还找jar中的路径。

Servlet

public interface Servlet {
    //tomcat反射创建servlet之后,调用init方法传入ServletConfig
    void init(ServletConfig var1) throws ServletException;
    ServletConfig getServletConfig();
    //tomcat解析http请求,封装成对象传入
    void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;
    String getServletInfo();
    void destroy();
}

ServletConfig:封装了servlet的参数信息,从web.xml中获取,init-param
ServletRequest:http请求到了tomcat后,tomcat通过字符串解析,把各个请求头(header),请求地址(URL),请求参数(queryString)都封装进Request。
ServletResponse:Response在tomcat传给servlet时还是空的对象,servlet逻辑处理后,最终通过response.write()方法,将结果写入response内部的缓冲区,tomcat会在servlet处理结束后拿到response,获取里面的信息,组装成http响应给客户端。

GenericServlet

改良版的servlet,抽象类

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

    public GenericServlet() {
    }

    //并不是销毁servlet的方法,而是销毁servlet前一定会调用的方法。默认空实现,可以借此关闭一些资源
    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() {
        //通过ServletConfig获取ServletContext
        return this.getServletConfig().getServletContext();
    }

    public String getServletInfo() {
        return "";
    }

    public void init(ServletConfig config) throws ServletException {
        this.config = config;//提升ServletConfig作用域,由局部变量变成全局变量
        this.init();//提供给子类覆盖
    }

    public void init() throws ServletException {
    }

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

    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();
    }
}

HttpServlet

GenericServlet的升级版,针对http请求所定制,在GenericServlet的基础上增加了service方法的实现,完成请求方法的判断
抽象类,用来被子类继承,得到匹配http请求的处理,子类必须重写以下方法中的一个
doGet,doPost,doPut,doDelete 未重写会报错(400,405)
service方法不可以重写
模板模式实现

public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
    HttpServletRequest request;
    HttpServletResponse response;
    try {
        request = (HttpServletRequest)req;//强转成http类型,功能更强大
        response = (HttpServletResponse)res;
    } catch (ClassCastException var6) {
        throw new ServletException(lStrings.getString("http.non_http"));
    }

    this.service(request, response);//每次都调
}

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) {
            //本来业务逻辑应该直接写在这里,但是父类无法知道子类具体的业务逻辑,所以抽成方法让子类重写,父类的默认实现输出405,没有意义
            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);
    }

}

一个类被声明为抽象的,一般有两个原因:
有抽象方法需要被实现
没有抽象方法,但是不希望被实例化

ServletContext

servlet上下文,代表web.xml文件,其实就是一个map,服务器会为每个应用创建一个servletContext对象:
创建是在服务器启动时完成
销毁是在服务器关闭时完成
javaWeb中的四个域对象:都可以看做是map,都有getAttribute()/setAttribute()方法。
ServletContext域(Servlet间共享数据)
Session域(一次会话间共享数据,也可以理解为多次请求间共享数据)
Request域(同一次请求共享数据)
Page域(JSP页面内共享数据)

servletConfig对象持有ServletContext的引用,Session域和Request域也可以得到ServletContext
五种方法获取:

  • ServletConfig#getServletContext();
  • GenericServlet#getServletContext();
  • HttpSession#getServletContext();
  • HttpServletRequest#getServletContext();
  • ServletContextEvent#getServletContext();//创建ioc容器时的监听

Filter

不仅仅是拦截Request
拦截方式有四种:
在这里插入图片描述
Redirect和REQUEST/FORWARD/INCLUDE/ERROR最大区别在于:
重定向会导致浏览器发送2次请求,FORWARD们是服务器内部的1次请求
因为FORWARD/INCLUDE等请求的分发是服务器内部的流程,不涉及浏览器,REQUEST/FORWARD/INCLUDE/ERROR和Request有关,Redirect通过Response发起
通过配置,Filter可以过滤服务器内部转发的请求。

servlet映射器

每一个url要交给哪个servlet处理,由映射器决定
在这里插入图片描述
映射器在tomcat中就是Mapper类:
internalMapWrapper方法定义了七种映射规则

private final void internalMapWrapper(ContextVersion contextVersion,
                                          CharChunk path,
                                          MappingData mappingData) throws IOException {

    int pathOffset = path.getOffset();
    int pathEnd = path.getEnd();
    boolean noServletPath = false;

    int length = contextVersion.path.length();
    if (length == (pathEnd - pathOffset)) {
        noServletPath = true;
    }
    int servletPath = pathOffset + length;
    path.setOffset(servletPath);

    // Rule 1 -- 精确匹配
    MappedWrapper[] exactWrappers = contextVersion.exactWrappers;
    internalMapExactWrapper(exactWrappers, path, mappingData);

    // Rule 2 -- 前缀匹配
    boolean checkJspWelcomeFiles = false;
    MappedWrapper[] wildcardWrappers = contextVersion.wildcardWrappers;
    if (mappingData.wrapper == null) {
        internalMapWildcardWrapper(wildcardWrappers, contextVersion.nesting,
                                   path, mappingData);
        if (mappingData.wrapper != null && mappingData.jspWildCard) {
            char[] buf = path.getBuffer();
            if (buf[pathEnd - 1] == '/') {
                mappingData.wrapper = null;
                checkJspWelcomeFiles = true;
            } else {
                // See Bugzilla 27704
                mappingData.wrapperPath.setChars(buf, path.getStart(),
                                                 path.getLength());
                mappingData.pathInfo.recycle();
            }
        }
    }

    if(mappingData.wrapper == null && noServletPath &&
       contextVersion.object.getMapperContextRootRedirectEnabled()) {
        // The path is empty, redirect to "/"
        path.append('/');
        pathEnd = path.getEnd();
        mappingData.redirectPath.setChars
            (path.getBuffer(), pathOffset, pathEnd - pathOffset);
        path.setEnd(pathEnd - 1);
        return;
    }

    // Rule 3 -- 扩展名匹配
    MappedWrapper[] extensionWrappers = contextVersion.extensionWrappers;
    if (mappingData.wrapper == null && !checkJspWelcomeFiles) {
        internalMapExtensionWrapper(extensionWrappers, path, mappingData,
                                    true);
    }

    ...

上面都不匹配,则交给DefaultServlet,就是简单地用IO流读取静态资源并响应给浏览器。如果资源找不到,报404错误
对于静态资源,Tomcat最后会交由一个叫做DefaultServlet的类来处理对于Servlet ,Tomcat最后会交由一个叫做 InvokerServlet的类来处理对于JSP,Tomcat最后会交由一个叫做JspServlet的类来处理
在这里插入图片描述
也就是说,servlet,/这种配置,相当于把DefaultServlet、JspServlet以及我们自己写的其他Servlet都“短路”了,它们都失效了。
这会导致两个问题:
JSP无法被编译成Servlet输出HTML片段(JspServlet短路)
HTML/CSS/JS/PNG等资源无法获取(DefaultServlet短路)
DispatcherServlet配置/,会和DefaultServlet产生路径冲突,从而覆盖DefaultServlet。此时,所有对静态资源的请求,映射器都会分发给我们自己写的DispatcherServlet处理。遗憾的是,它只写了业务代码,并不能IO读取并返回静态资源。JspServlet的映射路径没有被覆盖,所以动态资源照常响应。
DispatcherServlet配置/
,虽然JspServlet和DefaultServlet拦截路径还是.jsp和/,没有被覆盖,但无奈的是在到达它们之前,请求已经被DispatcherServlet抢去,所以最终不仅无法处理JSP,也无法处理静态资源。
tomcat中conf/web.xml
相当于每个应用默认都配置了JSPServlet和DefaultServlet处理JSP和静态资源。

<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>

<servlet-mapping>
    <servlet-name>default</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>


<servlet>
    <servlet-name>jsp</servlet-name>
    <servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>
    <init-param>
        <param-name>fork</param-name>
        <param-value>false</param-value>
    </init-param>
    <init-param>
        <param-name>xpoweredBy</param-name>
        <param-value>false</param-value>
    </init-param>
    <load-on-startup>3</load-on-startup>
</servlet>

<!-- The mappings for the JSP servlet -->
<servlet-mapping>
    <servlet-name>jsp</servlet-name>
    <url-pattern>*.jsp</url-pattern>
    <url-pattern>*.jspx</url-pattern>
</servlet-mapping>

在这里插入图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值