Servlet

不管现实多么惨不忍睹,都要持之以恒地相信,这只是黎明前短暂的黑暗而已。不要惶恐眼前的难关迈不过去,不要担心此刻的付出没有回报,别再花时间等待天降好运。真诚做人,努力做事!你想要的,岁月都会给你。Servlet,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

Servlet

动态web资源:数据是时刻变化的,不同的用户、不同的时间看到的数据都有可能完全不相同。

Servlet合成词。

Servlet = Server + applet。运行在服务器上面的一个小程序。

A servlet is a small Java program that runs within a Web server.

servlet接口里面定义了三个生命周期函数。init、service、destroy

这三个函数分别会在servlet的生命周期各个阶段被执行到。

servlet在生命周期的创建阶段会调用init方法

servlet在整个生命周期服务阶段会调用service方法

如果servlet死亡,那么会调用destroy方法。

可以在这些方法里面做一些特定的事情。

如何开发一个动态web资源

如何去编写一个servlet。
在这里插入图片描述

因为JDK里面没有这个包。SE、EE

javax.servlet相关的类在JDK中是不存在的,所以无法编译通过。

类加载器。负责将本地硬盘上面的jar、class加载到内存中。

BootStrap:加载jre目录下面的类库

Extension:加载jre/ext目录下面的类库

System:-classpath的方式将一个本地硬盘上面的jar包手动加载到内存中

javac -classpath xxxx.jar FirstServlet.java
在这里插入图片描述

使用java指令发现无法运行servlet。

使用java指令实际上是调用jvm,而jvm的执行入口时main方法,但是在servlet中并没有一个main方法。

编译过后的servlet需要运行在服务器里面,也就是tomcat。

class文件不应该被浏览器直接访问到,原因有二

1.即便直接访问到,那么也是下载该class文件,而不是执行

2.如果下载了class文件,那么服务器的源代码就暴露在了客户端面前

服务器为了保护源代码的安全,设置了一个目录专门用来存放源代码文件

WEB-INF目录。任何文件只要在WEB-INF目录下,那么浏览器绝对无法直接访问到它。

JavaWeb项目的一个目录结构:

规定在应用的根目录中必须要有一个WEB-INF目录用来存放源代码文件(当然如果没有class文件,只有静态资源文件,那么该目录可以没有)

源代码文件已经放置到了WEB-INF目录下,直接访问肯定时无法访问到了,如何能够让该servlet运行呢?

做了这样的一个映射。曲线救国。

服务器给你提供了一个映射机制,比如 /servlet1对应你编写的servlet,那么今后只要你输入/servlet1那么就知道了应该去调用当前编写的servlet。

你陪着领导去接待客人,领导给了你一个暗示,他一摸耳朵,你就喝酒。

今后只要浏览器输入了/servlet1,那么服务器就懂了,需要去调用该servlet,也就是执行该servlet。

还有一个说明:

就是源代码文件,按照EE规范,必须要放置在WEB-INF/classes目录下,如果有运行时依赖的jar包,那么该jar包需要放置在WEB-INF/lib目录下(如果没有,那么就不写该目录),以及一个配置潜规则的文件,该文件名也必须要求时web.xml。

目录结构:

应用的根目录

​ —————-静态资源文件

​ —————-WEB-INF目录

​ ———————-classes(里面存放的是全类名的类)

​ ———————-lib(存放的是运行时依赖的jar包)

​ ———————-web.xml(里面配置了servlet的映射关系)

最终访问的时候,一定要看是在哪个应用下面的

比如我们设置的应用叫做/application,servlet的映射叫做/servlet1,那么访问的时候

http://localhost/application/servlet1.

servlet究竟是怎么执行的呢?

反射。可以利用反射在程序运行的时候动态的生成一个对象。

Class.forName(com.cskaoyan.servlet.FirstServlet).newinstance();

/servlet1———————-FirstServlet

FirstServlet servlet = Class.forName(FirstServlet).newInstance();

servlet.service(request,response)

Servlet的执行流程

以访问http://localhost/application/servlet1为例,简述整个请求的访问过程

1.浏览器帮助生成一个HTTP请求报文,传输到服务器

2.被监听着80端口号的Connector接收到,将请求报文解析成为request对象,同时生成一个response对象

3.将这两个对象传给engine,engine进一步挑选合适的host,将对象进行进一步下发

4.host的职责就是来挑选一个合适的Context,将这两个对象进行进一步下发

5.Context内部根据/servlet1寻找对应的servlet-class,如果找到,则反射实例化一个servlet对象出来,同时调用service方法,并且此时,把request、response对象作为参数传递进去执行

6.Connector读取response里面的数据,生成HTTP响应报文

使用IDEA开发Servlet

IDE。集成开发环境。

编写Servlet的方式二

继承HttpServlet。

在这里插入图片描述

如果java文件的代码变更,一般选择第二个即可,但是有时候会失效,这个时候你可以再次选择第三个就ok了,或者说如果你的电脑性能比较好, 那么你可以每次都选择第三个。

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 SecondServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //super.doGet(req, resp);
        System.out.println("doget");
    }

    //如何让doPost方法执行到?
    //doGet, if the servlet supports HTTP GET requests
    //doPost, for HTTP POST requests
    //你需要使用post请求去访问当前servlet,就会执行该方法
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //super.doPost(req, resp);
        System.out.println("dopost");
    }
}

根据刚刚的实验:

1.如果使用的是get请求去访问当前servlet,那么会进入到doGet方法中

使用的是post请求,那么会进入到doPost方法中。

问题1:既然每个servlet都有service方法,那么为什么继承httpServlet没有service?

因为父类里面有service方法,所以可以继承得到父类的该方法。

HttpServlet已经对service方法做出了实现。

问题2:为什么程序会执行到doGet或者doPost里面呢?

servlet程序的执行入口是什么?

一定要记住,是service方法。

public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
    HttpServletRequest request;
    HttpServletResponse response;
    try {
        request = (HttpServletRequest)req;
        response = (HttpServletResponse)res;
    } catch (ClassCastException var6) {
        throw new ServletException("non-HTTP request or response");
    }

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

}

总结:

继承HttpServlet,他会根据请求方法的不同,调用不同的doXXX方法,但是不管怎么说,最终在分析的时候抓住一个点,

程序的执行入口始终是service方法。

问题3:为什么要有doGet和doPost之分呢?

可以做一些更精细的划分。比如当前请求只允许post请求方式进行提交,那么我就可以把逻辑写在doPost里面,doGet不写对应的逻辑。

编写Servlet的方式三

关于映射关系的配置,不仅可以通过web.xml方式,还可以通过注解的方式。

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 = "thirdServlet", urlPatterns = "/servlet3")
//我们还可以进一步简化,因为我们发现其实name属性好像没啥用
//@WebServlet(urlPatterns = "/servlet3")
//到这一步之后,还可以精简到底,如果括号里面只有一个值,那么默认表示的就是urlPattern
@WebServlet("/servlet3")
public class ThirdServlet extends HttpServlet {

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

    }

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

    }
}

IDEA和tomcat关联的方式

在tomcat的三个可能有部署痕迹的地方都没有找到部署的迹象。

CATALINA_BASE:

你可以理解为idea会复制tomcat的配置文件,然后再该目录下重新开启一个新的tomcat实例(记住结论即可,过程不需要掌握)。

在这里插入图片描述

开发目录和部署目录不是同一个目录,但是也不是毫无关联,因为开发目录里面web目录下的文件会再部署根目录里面出现

开发目录里面的java源代码文件,经过编译以后,也会出现再部署根目录的WEB-INF/classes目录下。

开发目录里面的文件会按照某种复制规则将文件复制到部署根目录里面去,我们需要去探究这个复制规则。

其中web(带有蓝色小点的目录为一个功能性目录),它做了如下的一个映射,其实是为了方便再部署根目录里面放置静态资源文件。

规则:

凡是再改web目录下出现的文件,最终再部署之前,都会原封不动地复制到部署根目录里面去。也就是说,如果今后你希望再部署根目录里面新建一个1.html,那么你只需要再这个web目录里面设置就可以了,因为再项目部署地时候,它会把1.html给复制到部署根目录里面去。

今后如果问你,如何去查找某个应用地部署根目录,应该如何去查找?artifacts

java文件报红,意味着接下来编译地时候,无法编译通过。

本质就是开发目录里面地文件会东拼西凑凑在一起,凑到out/artifacts/xxxx目录下,然后将这个目录部署再tomcat里面。

这块会经常出现地一个问题是这样地:

比如再开发环境的web目录下有一个1.html,然后通过tomcat部署,访问该1.html发现无法访问

1.确保开发目录里面的web是一个功能性目录。

2.也会出现有时候开发目录里面的文件无法复制到部署根目录的情况。

此时可以先尝试这么做:

1.先重新部署,点击Redeploy

如果不行,那么执行

2.rebuild project,然后再次执行1

如果再实际过程中,此时还是不行,那么可以

3.把部署根目录全部删了,然后再次执行2,再次执行1

Servlet的生命周期

顾名思义就是servlet从出生到死亡的整个过程。

servlet接口中制定了三个方法

init:当servlet被创建的时候,会调用它

service:当servlet被访问到的时候,会调用它

destroy:当servlet被销毁的时候,会调用它

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(value = "/life",loadOnStartup = 1)
public class LifeCycleServlet extends HttpServlet {

    //init方法只会再当前servlet第一次访问的时候调用,后面再次访问当前servlet它不会被调用了
    //我们之前说,init是再servlet被创建的时候会调用,既然只调用了一次,那么说明该servlet只被创建一个对象
    //实际上servlet它是单例(每一个servlet再内存中只有一个对象存在)
    //实际上,如果多用户访问,servlet里面不应当设置成员变量
    //init仅会再当前servlet第一次访问的时候执行一次,后面再次访问,不会被执行
    //其中关于init有一个额外的补充,默认情况下init会再第一次访问当前servlet之前被调用
    //但是呢,我们可以设置一个参数,让其随着应用的启动而直接执行
    //load-on-startup设置为一个非负数 web.xml  注解
    @Override
    public void init() throws ServletException {
        System.out.println("init");
    }

    // service方法会再每次访问当前servlet时都会执行
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("service get");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("service post");

    }

    //应用被卸载、服务器被关闭
    @Override
    public void destroy() {
        System.out.println("service destroy");
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    <servlet>
        <servlet-name>firstServlet</servlet-name>
        <servlet-class>servlet.FirstServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>firstServlet</servlet-name>
        <url-pattern>/servlet1</url-pattern>
    </servlet-mapping>

    <servlet>
        <servlet-name>secondServlet</servlet-name>
        <servlet-class>servlet.SecondServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>secondServlet</servlet-name>
        <url-pattern>/servlet2</url-pattern>
    </servlet-mapping>

    <servlet>
        <servlet-name>life</servlet-name>
        <servlet-class>servlet.LifeCycleServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>life</servlet-name>
        <url-pattern>/life</url-pattern>
    </servlet-mapping>
</web-app>

有什么样的的意义呢?

首先,生命周期函数本身的意义,对于init、destroy,如果你希望再servlet创建以及servlet被销毁的时候去做一些额外的事情,那么可以再init、destoy方法里面写对应的逻辑。

设置init load-on-startup非负数有什么样的意义呢?

比如说,当前servlet需要预先做一些运算,如果在第一次访问之前去执行,那么可能时间有点来不及,这个时候我就可以设置init方法随着应用的启动而执行,那么就有了更多的时间来运算。

比如对于一个商城来说,如果需要在页面加载当前商城里面商品的分类,并且将这个分类和其他的servlet所共享

init—load-on-startup非负数

init方法里面写入获取商品分类的逻辑,紧接着将这些数据和其他servlet共享。即便当前servlet没有被访问到,如果用户直接去访问另外一个servlet,是不是也可以取出里面的数据啊

Servlet执行流程补充

以访问http://localhost/application/servlet1为例,简述整个请求的访问过程

1.浏览器帮助生成一个HTTP请求报文,传输到服务器

2.被监听着80端口号的Connector接收到,将请求报文解析成为request对象,同时生成一个response对象

3.将这两个对象传给engine,engine进一步挑选合适的host,将对象进行进一步下发

4.host的职责就是来挑选一个合适的Context(形成 应用名——应用映射关系),将这两个对象进行进一步下发

5.Context内部根据/servlet1寻找对应的servlet-class,此时先判断当前servlet是否已经有了一个对象(判断当前请求是否是第一次访问某个servlet),如果没有,则通过反射实例化一个对象出来;如果有,则直接执行后面的操作

6.紧接着执行该servlet的service方法,同时将这两个对象作为参数传递进去,service方法执行(service方法中可以读取request里面的数据,以及对response做出相应的响应)

6.Connector读取response里面的数据,生成HTTP响应报文

在这里插入图片描述

项目一

url-pattern优先级

如果servlet的url-pattern存在着相互覆盖的情况,那么最终tomcat会调用哪个servlet来处理该请求呢?

规则:

1. **/开头的url-pattern的优先级高于*.后缀**
2. **对于都是/开头的url-pattern,匹配程度越高(匹配的越多),优先级也越高**

url-pattern细节

1.一个servlet可以设置多个url-pattern吗?可以

2.多个servlet可以映射同一个url-pattern吗?不可以,它会报一个错,希望大家今晚可以自己主动去复现这个错误,然后去查找真正的报错信息,看能否找到。

debug:

1.看报错信息。英文。

2.去找和你的代码相关的部分。(tomcat不会无缘无故的报错,只可能因为你的代码编写的问题导致了它启动报错)

The servlets named [detail] and [detail2] are both mapped to the url-pattern [/detail2] which is not permitted

3.servlet的url-pattern有哪些写法呢? */xxxx .后缀 ,其他全部都是非法的。

反面例子。常见的报错信息。 xxx比如写了一个 servlet2

Invalid [detail2] in servlet mapping 正确的应该写成/detail2

两个特殊的url-pattern

当访问静态资源文件时,有没有servlet参与进来呢?

可不可以设置一个url-pattern叫做*.html

实际上是有的。

介绍两个特殊的url-pattern,一个是/* 一个是/

设置完这两个url-pattern之后,接下来访问html以及jsp

此时发现无论访问jsp页面还是访问html页面,均无法访问到

实际上访问jsp时,访问的是一个servlet。

tomcat的conf/web.xml文件

这个xml文件和我们每个应用的xml文件之间的关系,你可以通俗地理解为他们之间可以有继承关系,我们应用可以继承得到tomcat地web.xml里面地设置。

jsp org.apache.jasper.servlet.JspServlet fork false xpoweredBy false 3

<servlet-mapping>
    <servlet-name>jsp</servlet-name>
    <url-pattern>* .jsp</url-pattern>
    <url-pattern>* .jspx</url-pattern>
</servlet-mapping>

http://localhost/index.jsp时

当我们设置了/ *时,访问jsp页面以及访问静态资源页面均无法访问到,/ *的优先级是比较高的

如果把/ *注释,

访问jsp页面以及html页面,发现jsp页面可以正常显示,html页面无法正常显示,显示的是/的内容

处理静态资源的servlet叫做缺省servlet,专门用来接收无家可归的人(专门用来收留哪些被白酒、诺安绿过的人)

专门用来接收那些没有任何servlet可以处理的请求。就会交给缺省servlet来处理。

廉租房。对于家庭全部成员下面全部都没有房产的,这个时候可以申请廉租房

在这里插入图片描述

在这里插入图片描述

ServletConfig

获取某个servlet的初始化参数

做一个简单介绍。只需要了解即可。

fork false

有的servlet里面会有这么一个节点。key-value键值对。如何在servlet中拿到呢?

properties

init-param里面的数据如何获取到呢?

其实就可以使用servletConfig对象来获取。

getInitParameter(String name)—–String value

启动的时候,扫描这个servlet的时候,是不是可以读取到该键值对呢,能不能把键值对封装到一个map中。

initParameters = new HashMap();

initParameters .put(param-name,param-value);

getInitParameter(String name){

​ return initParameters .get(name);

}

import javax.servlet.ServletConfig;
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 ConfigServlet1 extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //只需要在当前servlet中先拿到config对象
        //这个方法哪来的?getServletConfig();
        //祖传的宝贝
        ServletConfig servletConfig = getServletConfig();
        //紧接着调用config.getInitParameter(key)
        String name = servletConfig.getInitParameter("name");
        System.out.println(name);
    }

}

这部分了解即可。init-param节点是用来干啥的

ServletContext

获取全局性的初始化参数(了解)

使用场景:

web.xml中有如下标签

<context-param>
    <param-name>charset</param-name>
    <param-value>gbk</param-value>
</context-param>

可不可以将这个键值对也封装到一个map中

ServletContext对象中有一个map

map.put(param-name,param-value);

getInitParameter(String name){

​ return map.get(name);

}

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("/context1")
public class ContextServlet1 extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //1.先拿到ServletContext对象
        ServletContext servletContext = getServletContext();
        //2.调用getInitParameter
        String charset = servletContext.getInitParameter("charset");
        System.out.println(charset);
    }
}

context域(非常重要)

上面我们介绍的是在程序执行之前先创建了一个键值对,但是如果在程序运行时,动态生成的数据需要多个servlet之间进行共享的话,那么这个时候怎么办呢?


ServletContext{

​    attributes = new HashMap();

 public void setAttribute(String name, Object value){

​		attributes.put(name, value);

   }

public Object getAttribute(String name){return attributes.get(name);

}

}

获取绝对路径

为什么要有这个API?

获取应用根目录的绝对路径。

每个应用其实有两个属性,path叫做应用名,一个叫做docBase是当前应用的绝对路径。docBase就是我们应用的部署根目录。

接下来只需要提供一个相对部署根目录的相对路径,那么拼上前面docBase是不是就可以拿到绝对路径了。

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.File;
import java.io.IOException;

@WebServlet("/path")
public class PathServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //希望可以拿到部署根目录下面的1.txt的absolutePath----file----inputStream
        File file = new File("1.txt");
        System.out.println(file.exists());
        // 为什么再EE项目里调用相对路径,发现相对的是tomcat的bin目录?
        // 工作目录其实是tomcat的bin目录,再哪个目录下调用了jvm
        //实际上是再bin目录下调用了jvm
        //从本质上去看EE项目:tomcat是一个java程序,调用我们写的代码片段
        System.out.println(file.getAbsolutePath());

        //获取部署根目录下面1.txt文件的绝对路径 -----file
        ServletContext servletContext = getServletContext();
        //可以输入一个空字符串,那么它回给你返回当前部署根目录的绝对路径
        //也可以输入一个相对路径,那么它回给你返回部署根目录的绝对路径+提供的相对路径
        //比如你提供了一个1.html    docBase + /1.html
        //如果你希望获取应用下面的任何一个文件的绝对路径,那么只需要给它传入一个相对部署根目录的一个相对路径即可
        String realPath = servletContext.getRealPath("1.html");
        System.out.println(realPath);
        //WEB-INF用来屏蔽浏览器的直接访问
        String realPath1 = servletContext.getRealPath("WEB-INF/2.txt");
        boolean exists = new File(realPath1).exists();
        System.out.println(exists);

    }
}

在这里插入图片描述

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/181119.html

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!