一、Servlet简介
Servlet是sun公司提供的一门用于开发动态web资源的技术。
Sun公司在其API中提供了一个servlet接口,用户若想开发一个动态web资源(即开发一个Java程序向浏览器输出数据),需要完成以下2个步骤:
编写一个Java类,实现servlet接口。
把开发好的Java类部署到web服务器中。
二、Servlet 编写
第一个Servlet的编写
动手练习,完成目标:利用Servlet向客户端输出”HelloSerlvet”,不借助IDEA,手动编写。
过程:
1.编写java文件
阅读Servlet API,解决两个问题:
输出hello servlet的java代码应该写在servlet的哪个方法内?
如何向IE浏览器输出数据?
import javax.servlet.*;
public class FirstServlet extends GenericServlet{
public void service(ServletRequest req, ServletResponse res)
throws ServletException, java.io.IOException{
System.out.println("hello first servlet");
}
}
2.编译,生成.class文件
报错原因:Javax.servlet相关的类不在jdk中。
如何能够编译通过呢?
通过 javac -classpath 指明一个jar包,来解决找不到相关包的问题。
servlet相关jar包哪个地方会有呢?
Tomcat的lib目录下servlet-api.jar
执行命令javac -classpath jar包的路径 java文件名
,进行编译
3.如何运行
用java指令吗?
因为没有main方法,没法运行。再回想servlet定义。
A servlet is a small Java program that runs within a Web server
servlet它没有独立运行的能力,必须在服务器里面才能运行。
要想让实现servlet的相关代码运行起来,只能以发布应用的方式来实现,即让web服务器来调用servlet。首先要将编译后的class文件和tomcat服务器关联起来。
如何将class文件和tomcat关联?
标准JavaEE项目的目录结构。
这个结构是规定,记住。
接下来如何去访问该servlet呢?如何让servlet运行?
任何在WEB-INF目录下的文件都不可以直接被浏览器访问到,所以需要配置一个映射。
映射一个关系,比如访问/serv,那么让tomcat去运行与/serv相关联的一个servlet。告诉tomcat,你访问/serv,那么就是希望我去调用这个servlet的service方法。
在web.xml文件进行配置:
<?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_3_1.xsd"
version="3.1"
metadata-complete="true">
<!--重点关注部分: -->
<servlet>
<servlet-name>first</servlet-name>
<servlet-class>FirstServlet</servlet-class> //类名
</servlet>
<servlet-mapping>
<servlet-name>first</servlet-name>
<url-pattern>/serv</url-pattern> //url-pattern 将应用ROOT和servlet接口的实现类联系起来,访问/serv,就能找到FirstServlet类,进而调用类中的方法
</servlet-mapping>
</web-app>
就是配置一个映射关系,当访问/serv的时候,那么这个时候tomcat就去做一件事情,找到与/serv相对应的FirstServlet,反射实例化对象,调用该servlet的service方法。
url-pattern:servlet-class
访问servlet?
Localhost:8080/serv,这里应用名默认为ROOT,可以省略
如果你想让内容输出到浏览器正文,内容应当写入到响应报文的响应体中。
servletRequest和servletResponse其实就是代表了请求报文和响应报文。
那么response里面有哪个API是往响应体里面写入数据的呢?
Response.getWriter().println()
Localhost:8080/servlet/serv
在访问某个servlet的时候,一定要记得看一下它的应用名是谁。
执行过程总结:
- 在浏览器里面输入localhost:8080/servlet/serv,浏览器会帮助你构建一个http请求报文
- 请求报文到达主机之后,被connector获取到,会将请求报文封装成request对象,同时还给你一个response,用来填充数据
- 这两个对象被传给engine,挑选哪个host来处理
- host挑选哪个Context来处理,这两个对象交给指定的Context
- Context拿到/serv,根据/serv找到对应的类后,实例化对象,调用service方法,执行结束,返回
- 返回到connector,读取response里面的内容,按照一定的格式组装成响应报文,发送出去
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()方法。
三、其他开发servlet的方式
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方法。
- protected void doGet(HttpServletRequest req, HttpServletResponse
resp)
Called by the server (via the service method) to allow a servlet to handle a GET request.
由服务器调用(通过service方法),以允许servlet处理GET请求。 - protected void doPost(HttpServletRequest req, HttpServletResponse
resp)
Called by the server (via the service method) to allow a servlet to handle a POST request.
由服务器调用(通过service方法)以允许servlet处理POST请求。
1.继承HttpServlet
这里我们使用IDEA进行项目的开发,以下是关于IDEA的一些使用。
- 新建web项目
- 点击此处进行配置
如果下面的Application context是 / ,则表示应用名是ROOT,即默认的,当在浏览器访问时输入url时可以省略应用名的字段。
- 点击Debug(初学推荐)启动项目。
- 在浏览器输入url,就会访问到根目录下的index.jsp
- 再次点击Debug按钮,会出现如下图所示弹窗。
Update resources 是 更新静态资源文件
Update classes and resources 是 更新class文件及静态资源文件
Redeploy 重新部署当前应用
Restart server 重启Tomcat
如果只是更改了项目中的静态资源文件,选1;如果更改了java文件,选2;如果更改了web.xml,选3。第三种大部分情况下可以满足需求,第四种对电脑性能要求高。
A subclass of HttpServlet must override at least one method, usually one of these:
doGet, if the servlet supports HTTP GET requests
doPost, for HTTP POST requests
doPut, for HTTP PUT requests
doDelete, for HTTP DELETE requests
init and destroy, to manage resources that are held for the life of the servlet
getServletInfo, which the servlet uses to provide information about itself
对于get和post方法的区别,仅是语义上的区别。
Web.xml配置和上面手动实现相同。
如果支持get方法,那么就重写doGet
如果支持post方法,那么就重写doPost
public class ThirdServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//super.doGet(req, resp);
resp.getWriter().println("doget method");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//super.doPost(req, resp);
resp.getWriter().println("dopost method");
}
}
web.xml
<servlet>
<servlet-name>third</servlet-name>
<servlet-class>com.cskaoyan.servlet.ThirdServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>third</servlet-name>
<url-pattern>/third</url-pattern>
</servlet-mapping>
问题:为什么继承GenericServlet需要实现service方法,而继承HttpServlet不需要?
因为HttpServlet已经对这个抽象的service方法做了实现。
2.继承HttpServlet,使用注解进行映射
使用注解和使用web.xml方式完全等效。在实际开发中使用一种方式即可。
@WebServlet("/fourth")
public class FourthServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().println("fourth servlet");
}
}
3.简便方式
这种方式直接由IDEA帮助我们实现。
四、IDEA和Tomcat的关联方式
IDEA是如何让程序运行起来的呢?其实就是IDEA在Tomcat中部署了一个应用。
部署应用的方式?两种。直接部署和虚拟映射。
但是我们去conf/server.xml(虚拟映射)和webapps(直接部署)目录下看发现都没有。
怎么找呢?
去IDEA中找:
上面的两个目录会打印一些运行日志,查看错误日志需要这两个目录都看。
我们看一下CATALINA_BASE所指示的目录:
我们发现CATALINA_BASE目录的结构和Tomcat的安装目录结构很相似。为什么会这样呢?
IDEA复制了tomcat的一些核心配置文件,然后利用这些配置文件重新开启一个新的tomcat,利用这个tomcat来部署我们的应用。而不是直接在Tomcat中进行修改,这是IDEA的高明之处。
在其中的conf目录下有一个应用名.xml文件,docBase指向的就是你的部署根目录。
打开部署根目录去看一下:
总结:IDEA会复制tomcat的配置文件,然后到某一个目录下,在该目录下利用复制的配置文件重新开启一个新的tomcat来虚拟映射部署我们的应用。
但是如果你细心的话,你会发现,我们的开发目录和项目部署根目录不是同一个目录。
那么开发目录和部署目录之间有什么关联呢?
开发目录和部署目录不是一个目录。
开发目录里面存放的是java文件,这些文件编译之后,会按照某个规则,将class文件以及web相关联的文件(web.xml、index.jsp等)全部复制到部署根目录去。
规则在哪里体现出来的呢?
开发目录的web目录对应将来的部署根目录,src中的java文件会经过编译后放到classes文件下。
最常见的一个问题:在开发web目录下新建了一个1.html,然后访问显示404.
注意:凡是要以部署根目录里面的内容为准。
idea没有正常将开发web目录里面的文件同步到部署根目录,挺常见。
1.先重新部署一下试试
2.不行的话:
重新部署。
3.如果还是不行,可以直接把部署根目录里面的文件全部删了,然后再次执行2,重新部署
五、Servlet 生命周期
init:初次访问当前servlet时会被调用,再次访问不会被调用。
service:每次请求到来,都会被调用。
destroy:应用被卸载或者服务器关闭时被调用。
@WebServlet(value = "/Life")
public class LifeCycleServlet extends HttpServlet {
@Override
public void init() throws ServletException {
System.out.println("init");
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("service");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("service");
}
@Override
public void destroy() {
System.out.println("destroy");
}
}
其中有一个比较特殊的东西,init默认是在当前servlet第一次被访问的时候执行,但是可以通过设置一个load-on-startup为一个非负数,可以让其随着应用的加载而被执行。
或者通过web.xml
这样做的意义有哪些?
预先做一些初始化操作,比如查询数据库等。放在init方法里面。
六、url-patterns
1.url-patterns和servlet的对应关系
一个servlet可以配置多个url-pattern吗?
允许的
多个servlet可以配置同一个url-pattern吗?
不可以,会报错。报错信息一定要会找。
名为 [.url.UrlServlet2]和 [.url.UrlServlet] 的servlet不能映射为一个url模式(url-pattern) [/url1]。
总结:可以通过多个url-patterns找到同一个实现类,而不能通过一个url-patterns找到多个实现类。
url-pattern的写法有哪些?必须以/开头吗?
写法只有两种。一种/开头,另一种*.后缀。比如*.html。如果不是这两种写法,会报错
Caused by: java.lang.IllegalArgumentException: Invalid [Servlet1] in servlet mapping
2.url-patterns的优先级
Servlet1 映射到 /abc/*
Servlet2 映射到 /*
Servlet3 映射到 /abc
Servlet4 映射到 *.do
当请求URL为“/abc/a.html”,“/abc/”和“/ * ”都匹配,哪个servlet响应
Servlet引擎将调用Servlet1。
当请求URL为“/abc”时,“/abc” /都匹配,哪个servlet响应
Servlet引擎将调用Servlet3。
当请求URL为“/abc/a.do”时,“/abc/”和“.do”和/都匹配,哪个servlet响应
Servlet引擎将调用Servlet1。
当请求URL为“/a.do”时,“/”和“.do”都匹配,哪个servlet响应
Servlet引擎将调用Servlet2。
当请求URL为“/xxx/yyy/a.do”时,“/”和“*.do”都匹配,哪个servlet响应
Servlet引擎将调用Servlet2。
规律:
- /开头的优先级始终要高于*.后缀
- 匹配程度越高,越优先执行
3.两个特殊的url-patterns
一个/* 另外一个/
项目里设置了两个特殊的url-pattern,然后访问web根目录下面的index.jsp以及1.html发现此时均无法访问到,显示的均是/* servlet里面的内容。
接下来,将/*的servlet注释,只保留/
发现jsp页面可以正常显示,但是html页面仍然无法正常显示(显示的是/里面的内容)
从上面图片,可以得出什么结论?
平时访问的jsp页面,实际上是访问的是一个servlet。
比如localhost:8080/index.jsp,实际访问的是一个servlet,servlet做了一些事情,最终显示了页面。
问题1:为什么设置了/ *以后,jsp无法正常显示?
/ * 的优先级高于 * .jsp。因为 / * 没有做一些事情,所以最终无法显示出页面。
问题2:把 / * 注释,只保留/,jsp可以正常显示,但是html仍然无法正常显示?
/是一个比较特殊的url-pattern,DefaultServlet。
缺省servlet:看你有没有servlet可以处理你的这个请求,如果没有,缺省给你用。
任何一个请求到来,都应当有一个servlet来做出响应。但是如果你的应用中没有配置某一个请求的url-pattern,那么这个时候tomcat服务器会调用自身提供的缺省servlet来帮助你处理该请求。
比如:应用中没有配置任何的url-pattern映射,当访问/1.html时,发现当前应用内没有任何的servlet可以处理该请求,但是也不能让这个请求无人响应,tomcat会调用自身的缺省servlet来处理该请求,逻辑也非常简单:
到对应的目录中去找到对应的文件,如果找到,则以流的形式写出去;如果找不到,则404。(404也是一种回应)
当你自己设置了一个url-pattern,比如/1.html,再次访问/1.html,发现有servlet可以处理该请求。
总结:设置了/ * , /,jsp页面以及html页面均无法正常显示。/ * 的优先级是 比较高的,高于jsp的 * .jsp,所以显示的是 / * ,
接下来,/ * 注释,jsp可以交给*.jsp来处理,html到来,只能交给缺省servlet来处理。如果你的应用中,已经在注解中配置类url-patterns,tomcat会认为你有了一个更好的实现方式,tomcat自身提供的缺省servlet就不会给你使用了,而是使用你自身的/。
七、ServletConfig
可以获取某个servlet一些初始化的配置参数
步骤1:配置参数
步骤2:获取参数
八、ServletContext
1.配置全局初始化参数:直接在web-app节点下设置即可,不需要写在servlet节点下。
2.获取全局性参数:
String getInitParameter(String name)
Returns a String containing the value of the named context-wide initialization parameter, or null if the parameter does not exist.
返回一个字符串,其中包含命名的Context范围的初始化参数的值;如果该参数不存在,则返回null。
@WebServlet("/context1")
public class ContextServlet1 extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
ServletContext servletContext = getServletContext();
String name = servletContext.getInitParameter("name");
System.out.println(name);
}
}
@WebServlet("/context2")
public class ContextServlet2 extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
ServletContext servletContext = getServletContext();
String name = servletContext.getInitParameter("name");
System.out.println(name);
}
}
3.共享数据的场所(setAttribute/getAttribute)
当前应用下的两个servlet之间如果想要共享数据,怎么办?
Object getAttribute(String name)
Returns the servlet container attribute with the given name, or null if there is no attribute by that name.
返回具有给定名称的servlet容器属性;如果该名称没有属性,则返回null。
void setAttribute(String name, Object object)
Binds an object to a given attribute name in this servlet context.
在此Servlet的Context中将对象绑定到给定的属性名称。
- 先设置数据
@WebServlet("/domain1")
public class ContextDomainServlet1 extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
ServletContext servletContext = getServletContext();
//JDBC 商场的商品信息 List<Type> 电器 衣服 化妆品
servletContext.setAttribute("name", "iphone");
//servletContext.removeAttribute("name");
}
}
- 其他servlet去获取数据
@WebServlet("/domain2")
public class ContextDomainServlet2 extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
ServletContext servletContext = getServletContext();
String name = (String) servletContext.getAttribute("name");
System.out.println(name);
}
}
九、获取EE项目的绝对路径(getRealPath())
String getRealPath(String path)
Returns a String containing the real path for a given virtual path.
返回一个字符串,其中包含给定虚拟路径的真实路径(真实路径就是在部署根目录中的路径)。
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//获取web根目录下的1.html的file信息 流信息
File file = new File("1.html");
System.out.println(file.getAbsolutePath());
System.out.println(file.exists());
// $TOMCAT_HOME/bin/1.html????为啥会到这个目录下面
//EE项目:没有main方法,你写的代码只是供tomcat调用罢了
//file的相对路径相对的是谁?相对的是在哪个目录下调用jvm虚拟机
//获取文件的话,还是希望放在web根目录下:
//1.bin目录下不可以存放相同名称的文件,不同应用产生的相同文件会覆盖
//2.很不方便打包
//输入空字符串相当于已经定位到web根目录了
//接下来,如果想获取一个文件的file信息,只需要指明它和根目录的相对路径即可
String realPath = getServletContext().getRealPath("");
System.out.println(realPath);
String path1 = getServletContext().getRealPath("1.html");
boolean exists = new File(path1).exists();
System.out.println(exists);
//如果你想获取WEB-INF下面的1.txt,怎么获取
//能不能获取到?????可以。
//WEB-INF对于服务器来说,就是一个普通的文件夹
//该目录主要是用来拦截浏览器直接访问的
//WEB-INF目录用来拦截浏览器直接访问,但是防不住服务器有内鬼,将内容主动暴露出去
String realPath1 = getServletContext().getRealPath("WEB-INF/1.txt");
boolean exists1 = new File(realPath1).exists();
System.out.println(exists1);
}