3.3.开发servlet的第二种方式
IDEA:
1.创建一个project
我是这样做的:
问题:
访问http://localhost:8080,那么打开的是哪个页面?????
ROOT应用,里面的index三个页面:index.html、index.jsp、index.htm
如果Application context:/表示跟目录ROOT
2.然后点击debug(JavaEE阶段用debug比较好)
就会出现以下界面:
其实显示的就是index.jsp中的内容,我们更改index.jsp中的内容后,重新debug
如果你没有更改web.xml,选择第二个就行,如果更改了,选择第三个。
3.然后访问http://localhost:8080/app/second
IDEA中就会有输出:
SecondServlet:
Web.xml:
根据配置的应用名,加以访问即可。
3.4.开发servlet的第三种方式
可以继承GenericServlet,也可以继承HttpServlet。
看一下doGet源码:
//Protocol表示协议,在http中表示的就是http1.1、http1.0、https
//msg表示通过相应的key去取相应的value值。
Get和post主要的区别就是语义,如果你支持get,就重写doGet方法,如果支持post,就重写doPost方法。
ThirdServlet:
package com.cskaoyan.servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author shihao
* @create 2020-06-22 15:40
*/
public class ThirdServlet extends HttpServlet {
/**
* 如果当前servlet支持get请求方法,那么应当重写这个方法
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doGet(req, resp);
}
/**
* 如果当前servlet支持post方法,应当重写dopost
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doPost(req, resp);
}
}
3.5.开发servlet的第四种方式
之前我们声明servlet都是采用web.xml方式,那么也可以使用注解的方式。
看一下注解WebServlet的定义:
目前只需要了解前三个属性:
Name没有什么特殊含义
value和urlPatterns表示的含义是相同的,都表示urlPatterns,是一个别名的关系。
FourthServlet:
package com.cskaoyan.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;
//如果不想使用web.xml,那么使用注解即可
//web.xml和注解任选其一即可,不要都设置
//@WebServlet(name = "fourth",value = "/fourth")
//进一步简化
//可以不设置name
//WebServlet里面只写一个东西,默认表示的是value或者urlPatterns
//value和urlPatterns表示的含义是相同的,都表示urlPatterns,是一个别名的关系。
@WebServlet("/fourth")
public class FourthServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.printf("hello fourth");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
}
}
建议使用第四种方式,我们可以这样做:new—Servlet,通过这种方式来创建一个Servlet。
3.6.编写servlet需要实现service方法,那么为什么继承HttpServlet不需要
因为在HttpServlet中已经做了实现。为什么继承GenericServlet时,重写service,但是继承HttpServlet时,重写doGet或者doPost呢?执行逻辑是什么样的呢?
如果让你去分析,你打算怎么做?你的切入点在哪?
HttpServlet的service方法,它已经对GenericServlet中的service方法进行了实现。
Servlet接口实现类
Servlet接口SUN公司定义了两个默认实现类,分别为:GenericServlet、HttpServlet。
HttpServlet指能够处理HTTP请求的servlet,它在原有Servlet接口上添加了一些与HTTP协议处理方法,它比Servlet接口的功能更为强大。因此开发人员在编写Servlet时,通常应继承这个类,而避免直接去实现Servlet接口。
HttpServlet在实现Servlet接口时,覆写了service方法,该方法体内的代码会自动判断用户的请求方式,如为GET请求,则调用HttpServlet的doGet方法,如为Post请求,则调用doPost方法。因此,开发人员在编写Servlet时,通常只需要覆写doGet或doPost方法,而不要去覆写service方法。
阅读HttpServlet API文档,看一下servlet-api.jar
首先会调用service方法,service方法进行参数向下转型,然后调用HttpServlet自身的service方法,方法里面的逻辑就是根据请求方法的不同,分发到不同的doXXX方法中。
自身的service方法:主要逻辑就是获取请求方法,如果请求方法是get就调用doGet方法,如果请求时post就调用doPost方法。所以就需要我们重写doGet方法或doPost方法。
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);
}
}
3.7.为什么要区分get和post方法呢?
对于登录,仅允许使用post方法。做更精细的划分。
注意:用户不仅可以通过登录页面进行登录,也可以手动的去构建一个请求,如你在网址栏输入:http://localhost:8080/app/third?name=213&password=123
3.8.IDEA如何与tomcat关联
其实就是IDEA在Tomcat中部署了一个应用
部署应用的方式?两种。直接部署、虚拟映射。
但是我们去conf/server.xml(虚拟映射)和webapps(直接部署)目录下看发现都没有。
怎么找呢?
去IEDA中找:
在我的电脑中:
去看一下这个路径。
接下来,IDEA会使用复制的这些配置文件,重新开启一个新的tomcat。利用这个新的tomcat来部署应用。
其实就是在启动Tomcat的时候利用左边的配置文件替换右边的配置文件。
在左边这个目录下conf/Catalina/localhost下,找到了虚拟映射的配置文件app.xml。
那么docBase指向的位置就是当前app应用的根目录。打开该docBase
这就是部署目录
标准的java web项目目录结构。
但是到此还没有结束,下面这个是开发目录。
红线框出来的路径不是我们最终的部署路径????
对于开发目录和最终部署目录之间的关系?
对于src目录下的java文件来说,首先需要进行编译,编译产物复制到部署目录WEB-INF/classes目录里
对于web目录里面的文件来说,会直接复制到部署的根目录里去。
3.9.IDEA项目设置
上面已经提到转换的规则,那么规则是在哪里配置的呢?
右键选择servlet01—open module setting
作用是把servlet所需的两个jar包导入
具体转换规则:
执行过程:
1.客户端发出请求http://localhost:8080/servlet/first
2.服务器对应的应根据web.xml文件的配置,找到子元素的值“/first”的元素
3.读取元素的子元素的值,由此确定Servlet的名字为”first”
4.找到值为HelloServlet的元素
5.读取元素的子元素的值,由此确定Servlet的类名为cn.cskaoyan.HelloServlet。
6.到Tomcat安装目录/webapps/Demo1/WEB-INF/classes/cn/cskaoyan目录下查找到HelloServlet.class文件
7.初始化,并执行这个类的 service方法。
3.10.servlet的生命周期
servlet的init、service、destroy三个阶段。
servlet被创建的时候,会调用init方法
servlet发挥功能,会调用service方法
servlet将要被销毁,会调用destroy方法
LifeCycleServlet:
package com.cskaoyan.servlet.lifeCycle;
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
public void init() throws ServletException {
System.out.println("life cycle init");
}
@Override
public void destroy() {
System.out.println("life cycle destroy");
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("life cycle doget");
}
}
访问http://localhost:8080/app/life
Tip: Servlet的运行过程:
Servlet程序是由WEB服务器调用,web服务器收到客户端的Servlet访问请求后:
1.Web服务器首先检查是否已经装载并创建(利用反射创建)了该Servlet的实例对象。如果是,则直接执行第④步,否则,执行第②步。
2.装载并创建该Servlet的一个实例对象。
3.调用Servlet实例对象的init()方法。
4.tomcat创建一个用于封装HTTP请求消息的HttpServletRequest对象和一个代表HTTP响应消息的HttpServletResponse对象,然后调用Servlet的service()方法并将请求和响应对象作为参数传递进去。
5.WEB应用程序被停止或重新启动之前,Servlet引擎将卸载Servlet,并在卸载之前调用Servlet的destroy()方法。
Init:servlet在实例化的时候,会调用init来完成一些初始化,但是servlet只会实例化一个对象出来,所以init只会在当前servlet第一次被访问的时候执行。
Service:任何发往servlet的请求,都会执行service方法,也就是doGet或者doPost
Destroy:当前应用被卸载或者tomcat服务器被关闭
另外的一个补充
默认情况下,init会在第一次调用该servlet的时候执行,但是也可以通过添加相应的参数,
在应用被加载的时候,就执行。
设置load-on-startup为非负数即可。
Web.xml方式同理
那么这么做有什么意义呢?
假如某个servlet需要在初始化时需要做一些操作,比如访问数据库等。可以让应用加载的时候就去数据库中进行一些操作,保存一些数据等。后面就不做这步操作了,缓解服务器的压力。
3.11.Url-pattern细节
1.一个servlet可以设置多个url-pattern吗?
可以。
2.多个servlet可以设置同一个url-pattern吗?
不可以.
Caused by:
java.lang.IllegalArgumentException:名为 [com.cskaoyan.servlet.urlPattern.UrlPatternServlet2]和[com.cskaoyan.servlet.urlPattern.UrlPatternServlet1] 的servlet不能映射为一个url模式(url-pattern) [/url2]
3.url-pattern究竟有哪些写法呢?必须以/开头吗?
不一定,但是url-pattern只有两种写法
一种是/xxxx写法
另外一种*.后缀,比如*.html *.jsp等
最常见的犯错是直接写一个url-pattern叫做servlet1.
2和3的报错信息,大家一定要学会找。疫苗----产生抗体。
疑惑:
究竟访问一个html的时候,访问的是静态资源html还是动态资源servlet?
或者换一个问法?
当访问静态资源html时,有没有servlet参与进来?有的。default servlet
从现在开始,往后的学习中,整理一个Bug-list.
3.12.Url-pattern优先级
对于如下的一些映射关系:
Servlet1 映射到 /abc/*
Servlet2 映射到 /*
Servlet3 映射到 /abc
Servlet4 映射到 *.do
问题:
1.当请求URL为“/abc/a.html”,“/abc/”和“/”都匹配,哪个servlet响应
Servlet引擎将调用Servlet1。
2.当请求URL为“/abc”时,“/abc” /都匹配,哪个servlet响应
Servlet引擎将调用Servlet3。
3.当请求URL为“/abc/a.do”时,“/abc/”和“.do”和/都匹配,哪个servlet响应
Servlet引擎将调用Servlet1。
4.当请求URL为“/a.do”时,“/”和“.do”都匹配,哪个servlet响应
Servlet引擎将调用Servlet2。
5.当请求URL为“/xxx/yyy/a.do”时,“/”和“.do”都匹配,哪个servlet响应
Servlet引擎将调用Servlet2。
总结规律:
1./开头的url-pattern优先级要高于*.后缀的
2.如果多个/开头的url-pattern同时满足,那么匹配的程度越高,越优先执行谁
3.13.两个特殊的url-pattern
/* /。
在项目中,不要随意设置/*,因为该url-pattern优先级比较高,可能很多文件无法正常显示。
在项目中同时设置/*和/,静态资源文件以及jsp均无法正常显示,把/*去掉,留有/,发现index.jsp可以正常显示,但是静态资源文件仍然无法显示,为什么呢?
其实当你访问index.jsp时,此时仍然访问的是一个servlet。
这取自Tomcat中的web.xml中的一截代码:
当访问http://localhost:8080/index.jsp时,此时执行流程是什么样的?
正常情况下,访问jsp页面,此时是一个叫做jsp的servlet在执行,该servlet的url-pattern叫做*.jsp。
那么,当/存在时,会发生什么样的情况?
/开头的url-pattern优先级要高于.后缀的。所以,此时永远访问到的是/*
为什么当把/*去掉之后,静态资源仍然无法正常显示呢?
此时显示的是/的内容。为什么呢?
因为/本身就是用来显示静态资源文件的。如果你在项目中设置了一个/,那么tomcat会认为你有了一个更好的实现,此时,tomcat提供的/的servlet就不会再给你使用了,如果你想显示静态资源文件,需要自己去实现具体的业务逻辑。
如果某个Servlet的映射路径仅仅为一个正斜杠(/),那么这个Servlet就成为当前Web应用程序的缺省Servlet。
凡是在web.xml文件中找不到匹配的元素的URL,它们的访问请求都将交给缺省Servlet处理,也就是说,缺省Servlet用于处理所有其他Servlet都不处理的访问请求。
在
当访问Tomcat服务器中的某个静态HTML文件和图片时,实际上是在访问这个缺省Servlet。
所有web应用程序的默认servlet,用于提供静态资源。它处理所有没有映射到具有servlet映射的其他servlet的请求
DefaultServlet是用来处理静态资源文件的。如果一个请求来了以后,发现没有任何的servlet能够匹配到它,那么就会调用default servlet来处理该请求。廉租房的概念。
default servlet到当前应用中找指定目录下有没有指定的资源存在,如果有的话,以流的形式写出到response中,然后返回。如果没有的话,返回404.
浏览器输入:http://localhost:8080/servlet02_war_exploded2/,默认会去访问应用中的index.jsp文件。
浏览器输入:http://localhost:8080/servlet02_war_exploded2/1.txt,你的web.xml中并没有配置1.txt的Servlet,会去应用中找1.txt,如果找到了,就以流的形式写出,如果找不到,显示404。
为什么设置/和/之后,很多页面均无法访问了?
设置/,优先级比较高,jsp页面、1.txt均无法显示,显示的都是/*里面的内容
当把/*去掉之后,jsp页面可以正常显示(实际上访问的是jsp的servlet),为什么此时1.txt仍然无法正常显示呢?
当你输入一个/1.txt请求时,发现当前应用下所有的servlet均不能够匹配该请求,将该请求交给default servlet来处理,但是你在自己的应用下重新写了一个default servlet,tomcat默认提供的就不会再给你使用了,使用的是你自己的default servlet。
接下来,框架阶段,SpringMVC中的核心分发器DispatcherServlet的url-pattern就是 /。
3.14.ServletConfig
ServletConfig对象
在Servlet的配置文件中,可以使用一个或多个标签为 某个servlet配置一些初始化参数。
当servlet配置了初始化参数后,web容器在创建servlet实例对象时,会自动将这些初始化参数封装到ServletConfig对象中,并在调用servlet的init方法时,将ServletConfig对象传递给servlet。进而,程序员通过ServletConfig对象就可以得到当前servlet的初始化参数信息。
做一个简单的了解即可,重要性不是非常的重要。
可以获取某一个servlet的初始化参数。
配置web.xml:
ConfigServlet代码:
3.15.ServletContext
WEB容器在启动时,它会为每个WEB应用程序都创建一个对应的ServletContext对象,它代表当前web应用。
ServletConfig对象中维护了ServletContext对象的引用,开发人员在编写servlet时,可以通过ServletConfig.getServletContext方法获得ServletContext对象。
由于一个WEB应用中的所有Servlet共享同一个ServletContext对象,因此Servlet对象之间可以通过ServletContext对象来实现通讯。ServletContext对象通常也被称之为context域对象。
非常核心的一个对象。
3.15.1.获取全局性初始化参数(多个serlvet获取相同参数)
配置web.xml:
任何地方都能获取到:
3.15.2.可以作为一个全局性共享数据的场所
假如在某个servlet中处理了一个逻辑,保存了一些数据,接下来,在其他servlet中需要把该数据取出来,继续处理,应该怎么做?某网站的历史访问次数。(访问domain1和domain2都是访问此网站)
数据库。
Context.setAttribute(key,value)
getAttribute(key)
removeAttribute(key)
DomainServlet2:
一个应用可以有很多的servlet,但context对象只有一个。
当前所有的servlet都可以通过getServletContext()拿到context对象的引用。
可以同时对context对象进行操作。
某网站历史访问次数?
DomainServlet1:
DomainServlet2代码同上,只是@WebServlet(“/domain2”)
3.15.3.获取EE项目的绝对路径
关于file的相对路径,其实jdk描述的是相对jvm调用目录,哪个目录下调用jvm,那么相对路径相对的就是该目录。
但我们想获取项目部署根目录里面的一个文件,所以这种方法不可行。
PathServlet:
package com.cskaoyan.path;
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 {
//获取EE项目根目录1.txt的file信息或者流信息
File file = new File("1.txt");
System.out.println(file.exists());
// $TOMCAT_HOME/bin/1.txt
System.out.println(file.getAbsolutePath());
//此时就是定位到部署的根目录,那么接下来如果想获取某个文件的路径
//只需要指明该文件和部署根目录的相对路径关系即可
String realPath = getServletContext().getRealPath("");
System.out.println(realPath);
System.out.println("=========================");
//想获取应该部署根目录里面1.txt如何获取?
String path1 = getServletContext().getRealPath("1.txt");
System.out.println(new File(path1).exists());
//如果想获取WEB-INF目录下的2.txt应该怎么办?
//之前说过,WEB-INF是用来屏蔽浏览器的,浏览器是访问不到WEB-INF中的内容的,
//但是服务器想获取WEB-INF下的东西就和获取普通文件一样。
//就是说如果有内鬼,想把WEB-INF中的内容公布出去,是完全可以的
String path2 = getServletContext().getRealPath("WEB-INF\\2.txt");
System.out.println(path2);
System.out.println(new File(path2).exists());
}
}
作业:2:
1新建一个工程,里面有3个servlet再使用context配置一个默认全局初始化参数,要求在三个servlet里都获取该初始化参数。
module Servlet01中的LifeCycleServlet:
package com.cskaoyan.servlet.lifeCycle;
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
public void init() throws ServletException {
System.out.println("life cycle init");
}
@Override
public void destroy() {
System.out.println("life cycle destroy");
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("life cycle doget");
}
}
2.如果项目里的web/WEB-INF/pic/mm.jpg 有这个图片文件。
用户可以直接访问到吗?
尝试使用ServletContext获取该文件的绝对路径并打印出来。
(这是后面将Web/WEB-INF/里的文件通过手写代码返回给用户的第一步)。
module Servlet02中Homework包下的Servlet01:
package com.cskaoyan.Homework;
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;
/**
* @author shihao
* @create 2020-06-23 15:39
*/
@WebServlet("/Servlet01")
public class Servlet01 extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
ServletContext context = getServletContext();
String key = (String)context.getInitParameter("key");
System.out.println(key);
String realPath = context.getRealPath("WEB-INF\\2.txt");
System.out.println(realPath);
System.out.println(new File(realPath).exists());
}
}