文章目录
1、什么是Servlet
Servlet(Server Applet)是Java Servlet
的简称,称为小服务程序或服务连接器
,用Java编写的服务器端程序,主要功能在于 交互式地浏览和修改数据,生成动态Web内容;- 狭义的 Servlet 是指Java语言实现的一个接口,广义的 Servlet 是指任何实现了这个 Servlet 接口的类,一般情况下,人们将 Servlet 理解为后者;Servlet 运行于支持Java的应用服务器中;
- 从原理上来讲,Servlet 可以响应任何类型的请求,但绝大多数情况下 Servlet 只用来扩展基于HTTP协议的Web服务器;
CGI与 Servlet 的区别和联系
- Servlet的程序用来处理请求和发送响应,并且Servlet是为了解决实现动态页面而衍生的东西;浏览器通过网址来访问服务器,服务器接受请求交给Servlet进行处理,然后进行响应;
- CGI是:Web服务器和运行其上的应用程序进行"交流"的一种约定。
tomcat 与 Servlet 的区别和联系
- Tomcat 是 Web 应用服务器,是一个 Servlet/JSP 容器,Tomcat 作为Servlet容器,负责处理客户请求,把请求传送给Servlet,并将 Servlet 的响应传回给客户,而 Servlet 是一种运行在支持Java语言的服务器上的组件,Servlet 最常见的用途是扩展 Java Web 服务器功能,提供非常安全的、可移植的易于使用的CGI替代品;
①:Tomcat将http请求文本接收并解析
,然后封装成ServletRequest(在HttpServlet类中强转为HttpServletRequest)类型的request对象
,所有的HTTP头数据读可以通过request对象调用对应的方法查询到。
②:Tomcat同时会要响应的信息封装为ServletResponse(在HttpServlet类中强转为HttpServletRresponse)类型的response对象
,通过设置response属性就可以控制要输出到浏览器的内容,然后将response交给tomcat,tomcat就会将其变成响应文本的格式发送给浏览器;
- Java Servlet API 是Servlet容器(tomcat)和servlet之间的接口,它定义了serlvet的各种方法,还定义了Servlet容器传送给Servlet的对象类,其中最重要的就是ServletRequest和ServletResponse。所以说我们在编写Servlet时,需要实现Servlet接口,按照其规范进行操作。
2、Servlet入门代码
- 首先,在创建好的Web工程下,定义一个Java类,实现
Servlet接口
,并重写里面的所有方法,在最核心的service()种写入一句话:“请求来了”;
public class MyServlet implements Servlet {
@Override
public void init(ServletConfig servletConfig) throws ServletException {
}
@Override
public ServletConfig getServletConfig() {
return null;
}
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("请求来了");
}
@Override
public String getServletInfo() {
return null;
}
@Override
public void destroy() {
}
}
- 然后,在
web---WEB-INF---web.xml文件
中进行配置:
<servlet>
<servlet-name>MyServlet</servlet-name>
<servlet-class>org.westos.demo1.MyServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>MyServlet</servlet-name>
<url-pattern>/demo</url-pattern>
</servlet-mapping>
首先,你开启服务器之后,并输入你配置的虚拟路径名
/demo
,服务器会通过第二个servlet-name
找到第一个servlet-name
,(因此这里的servlet-name
可以随意命名,一般是保持和Servlet类名一样)进而找到你的Servlet类的全限定名,通过反射加载Servlet类;
-
最后,发布项目,访问该Servlet在浏览器中
http://127.0.0.1:8080/项目名/web.xml
中配置的虚拟路径名/demo
; -
这时候,后台明显收到了请求:
-
Web工程下的其他资源可以被访问到吗?
除了WEB-INF目录下的文件,放在web目录下的其他资源都是可以被访问到的,并且web目录是作为整个项目开启的根路径;
3、Servlet的生命周期
- servlet 生命周期如下:
servlet 加载——>实例化——>服务——>销毁;
- init():
在Servlet的生命周期中,仅执行一次
init()
。它是在服务器装入Servlet时执行的,负责初始化Servlet对象。可以配置服务器,以在启动服务器或客户机首次访问Servlet时装入Servlet。无论有多少客户机访问Servlet,都不会重复执行init();
- service():
它是Servlet的核心,负责响应客户的请求。每当一个客户请求一个
HttpServlet对象
,该对象的Service()
就要调用,而且传递给这个方法一个“请求”(ServletRequest)对象
和一个“响应”(ServletResponse)对象
作为参数。
- destroy():
仅执行一次,在服务器端停止且卸载Servlet时执行该方法。当Servlet对象退出生命周期时,负责释放占用的资源。一个Servlet在运行service()方法时可能会产生其他的线程,因此
需要确认在调用destroy()方法时,这些线程已经终止或完成
。
- 我们可以通过代码来查看这三个核心方法调用的次数以及时机:
import javax.servlet.*;
import java.io.IOException;
public class MyServlet2 implements Servlet {
@Override
public void init(ServletConfig servletConfig) throws ServletException {
System.out.println("init()执行了,初始化工作将在这里完成");
}
@Override
public ServletConfig getServletConfig() {
return null;
}
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("service()执行了,servlet在这里接收请求,做出响应");
}
@Override
public String getServletInfo() {
return null;
}
@Override
public void destroy() {
System.out.println("destroy()执行了,服务器停止工作");
}
}
init()
只会执行一次,service()
执行的次数取决于你访问虚拟路径的次数,destory()
在服务器正常关闭时,销毁Servlet之前,调用一次;
现在有一个问题:init()是用于初始化的一个方法,他调用的时机是第一次服务器装入Servlet时,如果他执行没有完成,即使客户端发来请求,service()也会一直得不到调用,也就是服务端收不到请求,客户端也收不到响应,如果恰好这时候init()中的逻辑非常多,造成用户体验很不好,可不可以让Init()提前执行,在服务器一启动就开始做准备工作,这样不用等到用户第一次发来请求再执行;
可以;
1、Servlet容器启动时:读取web.xml
配置文件中的信息,构造指定的Servlet对象,创建ServletConfig对象
,同时将ServletConfig对象
作为参数来调用Servlet对象的init方法
。
2、在Servlet容器启动后:客户首次向Servlet发出请求,Servlet容器会判断内存中是否存在指定的Servlet对象
,如果没有则创建它,然后根据客户的请求创建HttpRequest、HttpResponse对象
,从而调用Servlet对象的service方法
;
3、Servlet容器在启动时自动创建Servlet,这是由在
web.xml
文件中为Servlet设置的属性决定的。从中我们也能看到同一个类型的Servlet对象在Servlet容器中以单例的形式存在。
4、也就是说,可以通过修改web.xml
文件,添加配置信息,提前init()调用时机;
<servlet>
...
<load-on-startup>
number
</load-on-startup>
</servlet>
- number<0: 采用延迟装载,只有在第一次请求的容器才在该servlet调用初始化函数,默认就是-1;
- number>=0: 采用预先装载,容器在应用启动时就加载并初始化这个servlet;
- number越小越先被装载,number越大越晚被装载;
【注意:】
- Servlet是单例的,可能会存在线程安全问题,尽量不要定义成员变量。
4、Servlet的重点对象——ServletConfig
-
ServletConfig是一个接口,它代表了Servlet的配置信息的对象,作用是可以获取配置的一些初始化参数;
-
Servlet容器启动时:读取
web.xml
配置文件中的信息,构造指定的Servlet对象
,创建ServletConfig对象
,同时将ServletConfig对象
作为参数来调用Servlet对象
的init方法
。
@Override
public void init(ServletConfig servletConfig) throws ServletException {
}
- 我们可以打印该对象,查看源码:
@Override
public void init(ServletConfig servletConfig) throws ServletException {
System.out.println("init()执行了,初始化工作将在这里完成");
System.out.println(servletConfig);
}
//org.apache.catalina.core.StandardWrapperFacade@da69797
作用1:动态获取初始化好的值
在配置文件中,我们可以添加这样的配置:
@Override
public void init(ServletConfig servletConfig) throws ServletException {
String username = servletConfig.getInitParameter("username");
System.out.println(username);
}
<servlet>
<servlet-name>MyServlet3</servlet-name>
<servlet-class>org.westos.demo1.MyServlet3</servlet-class>
<init-param>
<param-name>username</param-name>
<param-value>zhangsan</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>MyServlet3</servlet-name>
<url-pattern>/demo3</url-pattern>
</servlet-mapping>
作用2:获取ServletContext对象
- 什么是
ServletContext对象
?
WEB容器在启动时,它会为每个WEB应用程序都创建一个对应的
ServletContext对象
,它代表当前web应用(servlet上下文对象)
。
由于一个WEB应用中的所有Servlet共享同一个
ServletContext对象
,因此Servlet对象之间可以通过ServletContext对象来实现通讯;ServletContext对象通常也被称之为context域对象:1、是一个容器,2、作用范围是应用程序范围。
同样的,服务器一启动,该对象就被创建出来,我们只是通过配置对象去获取它;我们同样打印他的地址值,查看源码:
@Override
public void init(ServletConfig servletConfig) throws ServletException {
ServletContext servletContext = servletConfig.getServletContext();
System.out.println(servletContext);
}
//org.apache.catalina.core.ApplicationContextFacade@648c77d5
作用3:获取Servlet配置文件中的名字
@Override
public void init(ServletConfig servletConfig) throws ServletException {
String servletName = servletConfig.getServletName();
System.out.println(servletName);
//MyServlet4
}
- 观察重写的几个方法,发现ServletConfig对象只存在于
init()
,如果我们的核心方法service()需要用到它,怎么做?可以提升作用域,但是发现重写的这几个方法中有一个getServletConfig()
,它的存在是为了什么?其实一般我们将ServletConfig对象
提升作用域后,私有该变量,再通过getServletConfig()
返回该变量,以便于 隐藏成员变量;
public class MyServlet5 implements Servlet {
ServletConfig servletConfig = null;
@Override
public void init(ServletConfig servletConfig) throws ServletException {
this.servletConfig = servletConfig;
}
@Override
public ServletConfig getServletConfig() {
return this.servletConfig;
}
…………
}
5、创建Servlet对象的几种方式
- 首先,servlet的继承体系如下:
方式1:自定义类实现Servlet接口
import javax.servlet.*;
import java.io.IOException;
public class MyServlet implements Servlet {
@Override
public void init(ServletConfig servletConfig) throws ServletException {
System.out.println("init()执行了,初始化工作将在这里完成");
System.out.println(servletConfig);
}
@Override
public ServletConfig getServletConfig() {
return null;
}
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("service()执行了,servlet在这里接收请求,做出响应");
}
@Override
public String getServletInfo() {
return null;
}
@Override
public void destroy() {
System.out.println("destroy()执行了,服务器停止工作");
}
}
这是最原始的一种方式,我们需要重写里面的所有抽象方法,但是与客户端进行交互的只有一个核心方法
service()
,如果简化代码量?
方式2:自定义类继承抽象类GenericServlet
抽象类GenericServlet源码:
package javax.servlet;
import java.io.IOException;
import java.io.Serializable;
import java.util.Enumeration;
public abstract class GenericServlet implements Servlet, ServletConfig, Serializable {
private static final long serialVersionUID = 1L;
private transient ServletConfig config;
public GenericServlet() {
}
public void destroy() {
}
public String getInitParameter(String name) {
return this.getServletConfig().getInitParameter(name);
}
public Enumeration<String> getInitParameterNames() {
return this.getServletConfig().getInitParameterNames();
}
public ServletConfig getServletConfig() {
return this.config;
}
public ServletContext getServletContext() {
return this.getServletConfig().getServletContext();
}
public String getServletInfo() {
return "";
}
public void init(ServletConfig config) throws ServletException {
this.config = config;
this.init();
}
public void init() throws ServletException {
}
public void log(String msg) {
this.getServletContext().log(this.getServletName() + ": " + msg);
}
public void log(String message, Throwable t) {
this.getServletContext().log(this.getServletName() + ": " + message, t);
}
public abstract void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;
public String getServletName() {
return this.config.getServletName();
}
}
1、该抽象类重写了除service()
以外的所有其他方法,我们只需要重写service()
的逻辑就好了;
2、为什么该抽象类中存在两个init()
?
init(ServletConfig config)
方法,因为只有init(ServletConfig config)
中带有ServletConfig对象,为了方便能够在其他地方也能直接使用ServletConfig对象
,而不仅仅局限在初始化方法中,所以创建一个私有的成员变量config,在初始化方法中就将其赋值给config,然后通过getServletConfig()
方法就能够获取ServletConfig对象
了
还有一个
init()
方法,并且这个init()
方法是空的,什么都没有,这是为什么呢?因为上面的init()是为了给成员变量ServletConfig
赋值,但如果继承该抽象类的子类想要在init()中写一定的逻辑,就会破坏原来抽象类中的逻辑,导致ServletConfig一直为空,当然这里可以手动调用父类的super.init(ServletConfig config)
,但是这样很麻烦;因此,再提供一个init(),就不用覆盖之前的init();
3、小技巧:
由于GenericServlet不仅实现了 Servlet接口,还实现了配置接口ServletConfig,所以我们可以通过两种方式获取全局域对象:
import javax.servlet.*;
import java.io.IOException;
public class MyServlet extends GenericServlet {
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
ServletContext servletContext1 = this.getServletContext();
ServletContext servletContext2 = this.getServletConfig().getServletContext();
System.out.println(servletContext1 == servletContext2);
//true
}
}
方式3:自定义类继承抽象类HttpServlet
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 MyServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("请求来了");
}
}
1、为什么会出现ServletResponse、ServletRequest与HttpServletResponse、HttpServletRequest ?
从继承机构可以看到HttpServletResponse、HttpServletRequest是从HttpServlet中开始出现的,查看HttpServlet源码发现:
public void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException
{
HttpServletRequest request;
HttpServletResponse response;
try {
request = (HttpServletRequest) req;
response = (HttpServletResponse) res;
} catch (ClassCastException e) {
throw new ServletException("non-HTTP request or response");
}
service(request, response);
}
在HttpServlet中将ServletResponse、ServletRequest强转为:HttpServletResponse、HttpServletRequest,为什么如此做?
ServletResponse、ServletRequest、HttpServletRequest、HttpServletResponse都只是接口:
public interface HttpServletRequest extends ServletRequest
而request对象实际上的类型是org.apache.catalina.connector.RequestFacade,Tomcat中的源码为:
public class RequestFacade implements HttpServletRequest
2、servlet的生命周期中,可以看出,执行的是service方法,为什么我们就只需要写doGet和doPost方法呢 ?
service()是在javax.servlet.Servlet接口中定义的,在javax.servlet.GenericServlet中实现了这个方法, 而 doGet/doPost 则是在javax.servlet.http.HttpServlet 中实现的;
- HttpServlet 里的三个方法:
service(HttpServletRequest req, HttpServletResponse resp)
doGet(HttpServletRequest req, HttpServletResponse resp)
doPost(HttpServletRequest req, HttpServletResponse res)
- 区别和联系:
在servlet中默认情况下,无论你是get还是post提交过来,都会经过service()方法来处理,然后转向到doGet或是doPost方法,我们可以查看HttpServlet 类的service方法:
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);
}
}
从上面可以看出,这里的service是用来 转向 的,但是如果你在自己的servlet类中覆盖了service方法,比如说你的service是这样的:
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
res.getOutputStream().print("this is my service");
}
那么这时service就不是用来转向的,而是用来处理业务的,现在不论你的客户端是用pos还是get来请求此servlet,都会执行service方法也只能执行servlet方法,不会去执行doPost或是doGet方法;
6、前后台交互——表单验证
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 MyServlet8 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("这是get请求");
this.doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//获取请求
System.out.println("这是post请求");
String username = (String) req.getParameter("username");
String password = (String) req.getParameter("password");
System.out.println(username + "------------" + password);
//做出响应
resp.getWriter().write("Hello World!");
}
}
前台表单代码:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<form action="/myServlet_war_exploded/demo8" method="get">
用户名:<input type="text" value="" name="username" placeholder="请输入用户名"/><br>
密码:<input type="password" value="" name="password" placeholder="请输入密码"/><br>
<input type="submit" value="get提交"/>
</form>
<br><br>
<form action="/myServlet_war_exploded/demo8" method="post">
用户名:<input type="text" value="" name="username" placeholder="请输入用户名"/><br>
密码:<input type="password" value="" name="password" placeholder="请输入密码"/><br>
<input type="submit" value="post提交"/>
</form>
</body>
</html>
web.xml文件中的配置:
<servlet>
<servlet-name>MyServlet8</servlet-name>
<servlet-class>org.westos.demo1.MyServlet8</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>MyServlet8</servlet-name>
<url-pattern>/demo8</url-pattern>
</servlet-mapping>
表单验证中,路径写的是:
web项目的上下文路径+servlet配置的虚拟路径
【注意:】
本质上,我们get请求与post请求中的代码逻辑都是一样的,因此,互相调用就好了,没必须要重复书写代码,但小心写成了死递归;
7、Servlet虚拟路径的配置问题
- 理论上来说,一个Servlet的虚拟路径可以配置多级路径,也可以配置多个;可以通过不同的虚拟路径去访问这个Servlet;
<servlet>
<servlet-name>MyServlet8</servlet-name>
<servlet-class>org.westos.demo1.MyServlet8</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>MyServlet8</servlet-name>
<url-pattern>/aaa/bbb/ccc</url-pattern>
<url-pattern>/demo/ddd/eee</url-pattern>
</servlet-mapping>
- 但是反过来,一个虚拟路径只能对应一个Servlet;
- 快捷方式:定义好模板代码,在web工程下直接创建
servlet类
,并使用注解中的value属性
或者urlPatterns属性
进行配置虚拟路径,不用跑到web.xml
下配置;
@WebServlet(name = "MyServlet1",value = "/demo1")
public class MyServlet1 extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
}
8、四大域对象
-
servlet中有四大域对象:ServletContext全局域、Session会话域、Request请求域、PageContext页面域;
-
什么是ServletContext对象?
上面已经介绍过他的概念,这里不再赘述,简单来说,他就是一个web工程内共享的全局上下文对象;
- 如何获取该对象?
ServletConfig对象中维护了ServletContext对象的引用,开发人员在编写servlet时,可以通过
1、ServletConfig.getServletContext方法获得ServletContext对象
2、this.getServletContext()方法;
- ServletContext对象的方法:
1、属性操作方法
设置属性:
setAttribute(String name, Object obj)
在web项目范围内存放内容,以便让在web项目中所有的servlet读能访问到;
存储属性:getAttribute(String name)
通过指定名称获得内容;
移除属性:removeAttribute(String name)
通过指定名称移除内容;
2、参数操作方法
getInitPatameter(String name) //通过指定名称获取初始化值
getInitParameterNames() //获得枚举类型
3、资源和路径操作方法
getRealPath("/WEB-INF/web.xml") 获取web项目下指定资源的路径
context.getResource(java.lang.String path) 获取指定文件的统一资源定位符path必须是/开头,代表当前web应用程序的根目录。返回一个代表某个资源的URL对象。
getResourceAsStream(java.lang.String path) 获取web项目下指定资源的内容,返回的是字节输入流
getResourcePaths(java.lang.String path) 指定路径下的所有内容。
4、获取文件mime类型
getMimeType(文件名称)
什么是MIME类型?
- 根据百度百科的解释:MIME:全称
Multipurpose Internet Mail Extensions
,多功能Internet邮件扩充服务,它是一种多用途网际邮件扩充协议,在1992年最早应用于电子邮件系统,但后来也应用到浏览器。MIME类型就是设定某种扩展名的文件用一种应用程序来打开的方式类型,当该扩展名文件被访问的时候,浏览器会自动使用指定应用程序来打开。多用于指定一些客户端自定义的文件名,以及一些媒体文件打开方式。- 说白了也就是文件的媒体类型。浏览器可以根据它来区分文件,然后决定什么内容用什么形式来显示。
- 部分文件的MIME类型:
- 为什么要获取
MIMEType
是因为在进行文件上传的时候,需要在POST请求体中传递相应的参数,来进行文件的上传操作;
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;
import java.io.InputStream;
import java.util.Iterator;
import java.util.Set;
@WebServlet(name = "Servlet1",value = "/home1")
public class Servlet1 extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//获取全局域对象
ServletContext context = this.getServletContext();
//设置属性
context.setAttribute("username","zhangsan");
//获取web项目下指定资源的路径
String realPath = context.getRealPath("/WEB-INF/web.xml");
System.out.println(realPath);
//指定路径下的所有内容。
Set set = context.getResourcePaths("/WEB-INF");
Iterator iterator = set.iterator();
System.out.println("WEB-INF文件下的所有文件:");
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
//获取web项目下指定资源的内容,返回的是字节输入流
InputStream is = context.getResourceAsStream("/index.jsp");
System.out.println(is);
String mimeType = context.getMimeType("my.txt");
System.out.println(mimeType);
/*E:\myJavaProject\myServlet学习\out\artifacts\myServlet_war_exploded\WEB-INF\web.xml
WEB-INF文件下的所有文件:
/WEB-INF/classes/
/WEB-INF/web.xml
/WEB-INF/a.txt
java.io.ByteArrayInputStream@5d4b7e46
text/plain*/
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
}
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(name = "Servlet2",urlPatterns = "/home2")
public class Servlet2 extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//获取全局域对象
ServletContext context = this.getServletContext();
//获取属性值
String name = (String) context.getAttribute("username");
System.out.println(name);
//移除属性值
context.removeAttribute("username");
//zhangsan
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
}
因为服务器通常是不关闭的,也就是说,你每设置一个属性,他就会一直存在,这样下来数据越存越多,存在资源的消耗,因此域对象不是说范围越大越好,当然你可以手动清理这些数据;
9、访问Web工程下资源的路径问题
现在提出这样一个问题:以前我们使用Java代码访问文件的时候,文件路径有两种写法,一种是绝对路径,一种是相对路径,比如一个文件:
a.txt
存放在项目的根路径下,就可以使用a.txt
直接访问;
现在有三个文件,分别放在项目的src下、web下、WEB-INF下
,如何访问?
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.FileInputStream;
import java.io.IOException;
@WebServlet(name = "MyServlet1",value = "/demo1")
public class MyServlet1 extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
FileInputStream fis1 = new FileInputStream("/src/a.txt");
System.out.println(fis1);
FileInputStream fis2 = new FileInputStream("/web/b.txt");
System.out.println(fis2);
FileInputStream fis3 = new FileInputStream("web/WEB-INF/c.txt");
System.out.println(fis3);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
}
- 程序报错,找不到指定的路径,这是为什么?
这是因为你的web项目最终编译过后整个项目的目录发生了变化,可以在out目录下查看被编译过后的web项目,服务器只认编译过后的文件,他不认源码;
很明显,web项目编译后目录发生了很大的变化,这时候代码还按照原来的路径找资源,肯定是找不到的,怎么处理?我们首先想到的是填写绝对路径:
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.FileInputStream;
import java.io.IOException;
@WebServlet(name = "MyServlet1",value = "/demo1")
public class MyServlet1 extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
FileInputStream fis1 = new FileInputStream("myJavaProject\\myServlet学习2\\out\\artifacts\\myServlet2_war_exploded\\WEB-INF\\classes\\a.txt");
System.out.println(fis1);
FileInputStream fis2 = new FileInputStream("E:\\myJavaProject\\myServlet学习2\\out\\artifacts\\myServlet2_war_exploded\\b.txt");
System.out.println(fis2);
FileInputStream fis3 = new FileInputStream("E:\\myJavaProject\\myServlet学习2\\out\\artifacts\\myServlet2_war_exploded\\WEB-INF\\c.txt");
System.out.println(fis3);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
}
这时候发现后台的访问完全没有问题,但是这样写代码肯定是不行的,因为我们写的项目最终要部署在公司的服务器上,但是这时候我们填写的路径是自己电脑上该web项目编译后其中资源的路径,所以应该动态的获取项目的路径,而不是写死;
- 可以通过全局域对象动态的获取整个web项目的路径,最终达到访问里面资源的目的;
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(name = "MyServlet2",value = "/demo2")
public class MyServlet2 extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
ServletContext context = this.getServletContext();
String rootpath = context.getRealPath("/");
System.out.println(rootpath);
//E:\myJavaProject\myServlet学习2\out\artifacts\myServlet2_war_exploded\
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
}
打印全局域对象获取到的整个路径,可以发现他获取到的是整个web项目配置的上下文路径;
代码如下,顺利访问到所有的资源:
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.FileInputStream;
import java.io.IOException;
@WebServlet(name = "MyServlet2", value = "/demo2")
public class MyServlet2 extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
ServletContext context = this.getServletContext();
String rootpath = context.getRealPath("/");
System.out.println(rootpath);
//E:\myJavaProject\myServlet学习2\out\artifacts\myServlet2_war_exploded\
FileInputStream fis1 = new FileInputStream(rootpath + "WEB-INF\\classes\\a.txt");
System.out.println(fis1);
FileInputStream fis2 = new FileInputStream(rootpath + "b.txt");
System.out.println(fis2);
FileInputStream fis3 = new FileInputStream(rootpath + "WEB-INF\\c.txt");
System.out.println(fis3);
}
}
- 如果我们现在有一个工具类,这个工具类需要读取资源的路径,怎么写代码?前面服务器的路径都是通过
ServletContext全局域对象
获取到的,但是Servlet对象是由服务器创建,普通的类中没办法通过创建它的对象获取全局域对象,ServletContext
只能用于web环境
。如果是非web环境,则使用ClassLoader
来获取真实路径:
方式1:
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.FileInputStream;
import java.io.IOException;
@WebServlet(name = "MyServlet3",value = "/demo3")
public class MyServlet3 extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
FileInputStream stream = MyUtils.test();
System.out.println(stream);
//java.io.FileInputStream@3a259bf6
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
}
public class MyUtils {
public static FileInputStream test() throws FileNotFoundException {
//获取该类对应的类加载器对象
ClassLoader classLoader = MyUtils.class.getClassLoader();
//获取文件运行时的真实路径
URL url = classLoader.getResource("a.txt");
//类加载器获取文件路径是有局限性的,只能获取src目录下的文件
String path = url.getPath();
FileInputStream fis = new FileInputStream(path);
return fis;
}
}
方式2:
public static InputStream show() throws FileNotFoundException {
//获取该类对应的类加载器对象
InputStream stream = MyUtils.class.getClassLoader().getResourceAsStream("/a.txt");
return stream;
}
10、请求对象与响应对象
- 请求对象和响应对象的原理:request 和response 对象都是由服务器创建,管理,销毁,我们拿来使用;
- 继承体系结构:
ServletRequest(接口)<--------继承<-------HttpServletRequest(接口)<--------实现 org.apache.catalina.connector.RequestFacade@6049a827
ServletResponse(接口)<--------继承------HttpServletResponse(接口)<--------实现------org.apache.catalina.connector.ResponseFacade@3bec9d4
- response:设置响应消息
设置响应行: HTTP/1.1 200 ok
设置响应状态码:setStatus(int code);重定向---------302
特点:
1、两次请求,两次响应
2、地址栏发生变化
3、不仅可以访问内部资源,也可访问外部资源
代码:
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//设置状态码302重定向
response.setStatus(302);
//设置响应头location
response.setHeader("location","http://www.baidu.com");
//以上两步可以合成一个简单的方法
response.sendRedirect("http://www.baidu.com");
}
- 设置响应头:
例如:content-type:text/html;charset=utf-8
setHeader(String headName,String headValue); 设置响应头的名称和值
setDateHeader(String name,long date); 设置日期头
setIntHeader(String name,int count); 设置数字头
- request 对象:获取请求消息
1、获取请求行: GET /MyServlet/index.jsp?name=zhangsan&age=23 HTTP/1.1
request.getMethod();//获取请求方式
request.getContextPath();//获取项目名称
request.getRequestURI();//获取URI
request.getRequestURL();//获取URL
request.getRemoteAddr();//获取IP地址
request.getQueryString();//获取请求参数
request.getProtocol();//获取协议版本
2.获取请求头:
request.getHeader("user-agent");//获取请求头的值
request.getDateHeader(name);//获取日期头
request.getIntHeader(name)//获取数字头
3.获取请求体: 请求体是专门用于封装Post请求的请求参数
获取字符数据:
getReader();
返回一个高效的字符流,我们通过一次读取一行的方法来获取请求参数数据,然后拆分字符串获取我们想要的数据;
获取字节数据:getInputStream();
后期上传文件时讲解;
4.通用的方式来获取 请求参数
request.getParameter(name); 通过请求参数的名称来获取值
request.getParameterValues("hobby"); 通过请求参数的名称,来获取值的数组 一般用于复选框
request.getParameterMap(); 获取所有参数的map集合
request.getParameterNames(); 获取所有参数的名称 枚举 不常用 了解