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目录
ge = servletContext.getAttribute(“age”);
System.out.println(age);
}
}
### 获取应用下的绝对路径 使用ServletContext.getRealpath()方法 如果直接输入一个空的字符串,那么获取到的是当前应用的根目录,接下来如果需要获取某个文件的绝对路径,只需要自己拼接相对于docbase的路径即可 ````java 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目录