【Java之 Servlet】

Servlet

http://tomcat.apache.org/tomcat-8.5-doc/servletapi/官方文档网址

服务器:处理静态资源

Servlet容器:能够提供servlet运行的环境

tomcat:服务器、servlet容器

使用Servlet主要有以下的两种方式:

To implement this interface, you can write a generic servlet that extends javax.servlet.GenericServlet or an HTTP servlet that extends javax.servlet.http.HttpServlet.

手动完成Servlet开发

编写java源代码

import javax.servlet.*;


public class Servlet1 extends GenericServlet {

	public void service(ServletRequest req,ServletResponse res)throws ServletException,java.io.IOException{
		System.out.println("servlet1");
	}
}

编译

第三方的jar包需要手动加载到内存之中

所以这时候如果手动编译,需要使用类加载器,类加载器的职责就是将硬盘上的jar包、类加载到内存中

类加载器分类:

1.BootStrap类加载器:负责去加载jdk中的核心类库

2.Extension类加载器:主要加载jdk中ext目录下的类库

3.System类加载器:可以加载第三方的类库,可以通过 -classpath xxxx.jar 通过这种方式将jar包加载到内存中

于是在编译的时候就需要使用-classpath,我们需要使用的javax.servlet在tomcat中提供了

编译的写法为:javac -classpath D:\tomcat的根目录\lib\servlet-api.jar Servlet1.java

运行

无法直接使用java指令运行,因为我们写的Servlet是没有main方法的,所以需要通过服务器调用

于是需要将文件部署到服务器上

  • 在webapps目录下创建一个应用名文件夹

  • 应用的根目录设置一个WEB-INF目录,该目录的特点是目录下的任何文件客户端都无法直接访问到

  • 运行class

    • 在WEB-INF下新建一个classes文件夹,所有的class文件必须放在这个文件夹下面

    • 新建一个web.xml文件来配置映射关系

      • <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_3_1.xsd"
          version="3.1"
          metadata-complete="true">
        
           
            <servlet>
              <servlet-name>first</servlet-name>
              <servlet-class>Servlet1</servlet-class>
            </servlet>
           
            <servlet-mapping>
                <servlet-name>first</servlet-name>
                <url-pattern>/servlet1</url-pattern>
            </servlet-mapping>
        </web-app>
        
        
    • 如果还需要其他的jar包,那么就需要将这些jar包放在WEB-INF/lib的目录下

原理

http://localhost/servlet/servlet1

1.地址栏输入相应的地址,首先进行域名解析(浏览器、操作系统、hosts、dns服务器)

2.三次握手建立TCP连接

3.浏览器帮我们生成HTTP/1.1 请求报文,经过tcp层拆包,打上标签,经过ip层打上标签目标主机的地址和端口号

4.经过链路层出去,在网络中中转传输,服务器接收到了后经过ip层去掉标签,经过tcp层拆掉标签完成对源文件的重组

5.HTTP请求报文被一直监听80端口的Connector接收到,将请求报文解析成request对象,同时提供一个response对象用来返回响应

6.Connector将这两个对象传给Engine,Engine会挑选出一个Host,进行对对象的进一步下发

7.Host的职责是挑选一个合适的Context,尝试去找servlet应用,主要有三个来源(webapps、conf/Catalina/localhost、server.xml),如果找到了就将这两个对象交给该应用来处理,如果这三个来源没找到,那就去缺省应用中去找,ROOT

8.到达应用后,有效的路径是/servlet1,根据应用配置的url-pattern和servlet-class的映射关系,找到对应的servlet-class字节码文件

9.如果找到了后,根据servlet-class的全类名,tomcat通过反射来进行实例化对象并执行该servlet对象中的service方法,需要传入两个参数,即request和response,里面需要执行具体的业务逻辑,写入response

10.Connector读取response中的数据,按照HTTP/1.1的格式要求来生成对应的响应报文

IDEA开发servlet

创建项目

创建一个JAVAEE项目,主要需要注意创建的时候要选择tomcat服务器以及勾选web Application,这样会默认生成web.xml

配置Deployment,如果只填一个/的话就表示是缺省项目,即ROOT项目

开发Servlet方式二

继承 HttpServlet

主要需要实现doGet和doPost方法,即通过不同的方法访问给出不同的结果

package com.fh.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 Servlet2 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        System.out.println("doGet");
    }

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

        System.out.println("doPost");
    }
}

对应的配置文件

 <servlet>
        <servlet-name>se2</servlet-name>
        <servlet-class>com.fh.servlet.Servlet2</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>se2</servlet-name>
        <url-pattern>/s2</url-pattern>
    </servlet-mapping>

如果修改了源码或者xml文件,在idea中不需要关闭服务器,直接redploy即可

为什么执行的是do方法而非service方法

主要是因为继承HttpServlet的Servlet中也有service方法,所以程序的入口依然是service方法,但这里的service方法根据不同的请求完成了对方法的分发

分发的意义:可以控制的更加精细;例如设置登录只允许post方法提交,如果用户手动携带请求参数来拼接地址,这样是无法访问的

开发Servlet方式三

使用注解来简化映射关系,推荐使用

使用@WebServlet注解

  • 简化的方式:name属性不是特别的重要,所以可以直接简化
    @WebServlet(urlPatterns = “/servlet3”)
    进一步简化,如果注解里面只有单一的值,那么表示的就是url-pattern
package com.fh.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("/s3")
public class Servlet3 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("doget");
    }
}

IDEA和tomcat的关联方式

通过观察服务器可以得到一条信息:

CATALINA_BASE: C:\Users\song.IntelliJIdea2019.3\system\tomcat\Tomcat_8_5_371_servlet

CATALINA_BASE:IDEA会复制tomcat的配置文件到该处,利用这个新的配置文件重新开启一个新的tomcat,利用这个新的tomcat来进行资源的部署。不会对原先的tomcat产生任何影响。

通过该目录下的配置文件来进行资源的部署,例如

<Context docBase="D:\ideaProjects\36th\servlet\out\artifacts\servlet_war_exploded" path="/servlet" />

此时我们在地址栏输入/servlet这个应用名就相当于是定位到了docBase下,应用名的本质就是帮我们定位到磁盘上的位置

那如果我们需要创建一个ROOT应用,也需要在该位置重新配置一个ROOT.xml

web目录并非idea的项目中的应用根目录

1.web目录下的资源文件会自动复制到部署的根目录下

即out\artifacts\servlet_war_exploded

2.src目录下的源代码经过编译之后也会复制到根目录的WEB-INF/classes目录下

这样在迁移项目的时候可以直接迁移,不受开发目录的影响

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这个地方指的是project的编译的输出路径

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在idea开发中的web蓝点目录并非我们最终运行应用的根目录,如果web目录下存在一个静态资源,但通过tomcat运行访问显示的是404,那么说明部署的根目录中没有找到该文件

解决的方法:

1.尝试redeploy

2.rebuild project然后执行redeploy

3.把out目录删除,执行操作2和操作1

Servlet生命周期

init、service、destroy

init:当servlet被创建的时候,会通过调用init方法来完成初始化工作;只有当servlet第一次被访问的时候调用并且只会调用一次,也就是说servlet只有一个对象,那么当不同的客户端来访问的时候,公用一个对象,这有可能会产生数据安全问题,慎用成员变量;

在使用init的时候可以在注解中设置一个参数Load-on-startup=非负数

随着应用的启动而执行,时机提前了一些

service:客户端的任何请求都会交给service方法来处理(每访问一次servlet,就会调用依次service方法)

destroy:servlet即将被销毁的时候,会调用destroy方法

package com.fh.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 {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("doGet");
    }

    @Override
    public void init() throws ServletException {
        System.out.println("init");
    }

    @Override
    public void destroy() {
        System.out.println("destroy");
    }
}

url-pattern细节

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

@WebServlet(value = {"/life","/life2"},loadOnStartup = 1)

url-pattern本质上可以看做是对servlet的一个引用,多个引用可以指向同一个对象

2.多个servlet不能设置同一个url-pattern

一个引用不能指向多个对象,否则会产生歧义

Caused by: java.lang.IllegalArgumentException: The servlets named [com.fh.servlet.Servlet4] and [com.fh.servlet.Servlet5] are both mapped to the url-pattern [/s4] which is not permitted

3.url-pattern的有效写法

  • /xxxx
  • *.xxx

**错误的写法:**s5

Caused by: java.lang.IllegalArgumentException: Invalid <url-pattern> [s5] in servlet mapping

url-pattern优先级

*.xxx的优先级是最低的,在有/xxx的情况下,永远都执行不到他

如果都是/开头,那么匹配的程度越精准,那么就越优先执行

package com.fh.priority;


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 ServletA extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().println("/abc/*");
    }
}
package com.fh.priority;

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 ServletB extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().println("/*");
    }
}
package com.fh.priority;

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 ServletC extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().println("/abc");
    }
}
package com.fh.priority;

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 ServletD extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().println("*.do");
    }
}
<?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>se1</servlet-name>
        <servlet-class>com.fh.servlet.Servlet1</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>se1</servlet-name>
        <url-pattern>/s1</url-pattern>
    </servlet-mapping>
    <servlet>
        <servlet-name>se2</servlet-name>
        <servlet-class>com.fh.servlet.Servlet2</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>se2</servlet-name>
        <url-pattern>/s2</url-pattern>
    </servlet-mapping>
    
    <servlet>
        <servlet-name>sa</servlet-name>
        <servlet-class>com.fh.priority.ServletA</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>sa</servlet-name>
        <url-pattern>/abc/*</url-pattern>
    </servlet-mapping>
    
    <servlet>
        <servlet-name>sb</servlet-name>
        <servlet-class>com.fh.priority.ServletB</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>sb</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>
    
    <servlet>
        <servlet-name>sc</servlet-name>
        <servlet-class>com.fh.priority.ServletC</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>sc</servlet-name>
        <url-pattern>/abc</url-pattern>
    </servlet-mapping>
    
    <servlet>
        <servlet-name>sd</servlet-name>
        <servlet-class>com.fh.priority.ServletD</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>sd</servlet-name>
        <url-pattern>*.do</url-pattern>
    </servlet-mapping>
</web-app>

缺省Servlet

新建两个特殊url-pattern的servlet,一个是/* 一个是/

在这两个servlet存在的情况下,依次去访问jsp页面以及html页面,观察现象

**两者均存在时,访问jsp 请求交给/来处理 访问html 请求依然交给 / 来处理

*把/注释, 访问jsp 正常了,可以显示出页面了 访问html 请求交给了 /

在tomcat的web.xml文件中有如下设置:

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

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

这一点有点类似于tomcat中的ROOT项目,是默认缺省的,虽然web应用中没有去配置该信息,但是所有部署在tomcat中的web应用都可以通过继承拿到该配置信息

当我们访问/index.jsp时,在没有其他的servlet干扰的时候,实际上执行的是JspServlet的配置

在出现了/* 之后,由于 /*的优先级比*.jsp高,所以会默认执行/*

缺省Servlet:

在tomcat中任何一个请求的到来都会交给一个servlet来处理,如果一个请求到来,发现没有任何合适的servlet可以处理该请求,那么就会交给缺省Servlet来处理,缺省Servlet的url-pattern就是/

默认情况下,tomcat是提供了一个缺省Servlet的,当一个请求交给缺省Servlet来处理的时候,tomcat提供的处理方式就是把该请求当做静态的资源文件来看待,通过磁盘IO来寻找该文件,写入response信息

如果你的应用中重新实现了一个新的servlet,即将url-pattern设置为/,那么tomcat默认的缺省servlet就会被覆盖掉,这时候就会执行我们默认的servlet

回到本例中

存在/*以及/的时候,访问jsp页面时,有servlet可以处理请求,并且有多个(/* *.jsp),那么此时会交给优先级最高的来处理,于是页面会打印出/*

访问html时,会使用/*来处理请求

当只存在/的时候,访问jsp页面,只有*.jsp可以处理该请求,于是就由该servlet来处理

访问html页面,没有额外的servlet来处理,那么会交给缺省的servlet,但此时由于我们自己实现了缺省servlet,所以只会打印/

这里注意缺省的servlet与缺省应用的区别:

Host在找应用的时候先从三个路径去找,如果找不到再去ROOT下面找,最终在找到应用之后,去访问应用中的资源,如果有servlet能处理,那就处理,没有servlet那就当做静态资源

在使用IDEA开发tomcat的时候,如果访问/应用名,后面不加任何参数,就会访问默认设置的欢迎页面,如果需要只输入域名就能访问,那么就需要提供ROOT应用

ServletConfig

可以获取servlet的初始化参数

在servlet的xml节点中、注解中设置一些键值对,这样当tomcat启动的时候可以通过servletConfig来获取这些参数

<servlet>
        <servlet-name>config</servlet-name>
        <servlet-class>com.cskaoyan.servlet.config.ConfigServlet</servlet-class>
        <init-param>
            <param-name>name</param-name>
            <param-value>jingtian</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>config</servlet-name>
        <url-pattern>/config</url-pattern>
    </servlet-mapping>

ServletContext

context域

运行时共享数据的一个场所

ServletContext对象在每个应用中有且只有一个

可以在Servlet1中的init方法中设置数据初始化,随着应用的加载而执行,即使用户没有访问servlet1,其他的servlet也能拿到数据

package com.fh.servlet;

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(value = "/s8",loadOnStartup = 1)
public class Servlet8 extends HttpServlet {
    @Override
    public void init() throws ServletException {
        ServletContext servletContext = getServletContext();
        servletContext.setAttribute("age",20);
    }

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

    }
}
package com.fh.servlet;

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("/s9")
public class Servlet9 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        ServletContext servletContext = getServletContext();
        Object age = servletContext.getAttribute("age");
        System.out.println(age);
    }
}

获取应用下的绝对路径

使用ServletContext.getRealpath()方法

如果直接输入一个空的字符串,那么获取到的是当前应用的根目录,接下来如果需要获取某个文件的绝对路径,只需要自己拼接相对于docbase的路径即可

 		String docBase = servletContext.getRealPath("");
        System.out.println(docBase);
        String path = docBase + "/1.html";

如果直接输入一个相对于应用根目录的相对路径,那么会直接返回该文件的绝对路径

        String realPath = servletContext.getRealPath("1.html");
        System.out.println(realPath);

如果直接拿相对路径,获取到的是tomcat的bin目录

相对路径是相对哪个目录下调用了JVM虚拟机,tomcat在bin目录下调用了虚拟机,所以相对的是tomcat的bin目录

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值