Tomcat和Servlet的处理机制
首先我们必须明确知道浏览器和服务器之间的工作原理,每次浏览器都会发送一个请求报文给服务器,tomcat服务端就会返回一个响应报文给浏览器解析,浏览器不需要知道服务端(后端)的逻辑,他到底代码是怎么写的,浏览器只需要负责发送请求和解析响应报文,所以对于请求的处理就发生在了tomcat中
而tomcat是这样处理请求报文的
1.首先tomcat收到了请求报文,将请求报文转化为了HttpServletRequest对象(以下简称request对象),该对象中包含了请求中的所有的信息 请求行,请求头,请求体
2.tomcat同时也创建了一个HttpServletResopnse对象,这个对象用于承载响应给客户端的信息,此后该对象会转化为报文。
3.此时,tomcat会根据请求中的资源路径找到一个自己编写的继承了servlet接口对应资源路径的的对象,里面有一个这样的方法 service(HttpServletRequest request,HttpServletResponse response),这个方法里面包含了对请求报文的处理(需要自己编写逻辑),处理步骤是①从request对象中获取到报文信息②根据参数生成响应给客户端的数据,将相应的数据放入response对象中③当方法执行完毕时tomcat就会将response对象转化为响应报文发送给客户端进行处理
Servlet的流程如下图举例所示
简单地说,按我个人的理解,servlet和tomcat是一个沟通前后端的桥梁
接下来我举几个例子来强化我对servlet的学习
servlet开发流程:
1创建javaWEB项目,同时将tomcat添加为当前项目的依赖
2重写service方法service(HttpServletRequest reg,HttpServletResponse resp)
3在service方法中,定义业务处理代码
4.在web.xml中配置路径(重点)
具体配置内容如下
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
version="6.0">
<!--
1.配置servlet类,并起一个别名
servlet-class 告诉Tomcat对应的要实例化的Servlet类
servlet-name 用于关联请求的映射路径
-->
<servlet>
<servlet-name>userServlet</servlet-name>
<servlet-class>
com.my.servlet.UserServlet
</servlet-class>
</servlet>
<!--
<servlet>标签用于定义一个Servlet。
<servlet-name>标签指定了这个Servlet的名称,这里是userServlet。这个名称是开发者自定义的,用于在配置文件中唯一标识这个Servlet。
<servlet-class>标签指定了这个Servlet的完整类名,这里是com.my.servlet.UserServlet。
这个类需要继承自javax.servlet.http.HttpServlet或其子类,并且实现了doGet、doPost等方法来处理HTTP请求。-->
<servlet-mapping>
<servlet-name>userServlet</servlet-name>
<url-pattern>/userServlet</url-pattern>
</servlet-mapping>
<!-- <servlet-mapping>标签用于将Servlet映射到一个或多个URL模式上。
<servlet-name>标签引用了前面定义的Servlet的名称,这里是userServlet。
这表示下面的URL模式将映射到前面定义的com.my.servlet.UserServlet类上。
<url-pattern>标签定义了访问这个Servlet的URL模式,这里是/userServlet。
当Web应用接收到匹配这个模式的请求时,就会调用com.my.servlet.UserServlet类来处理这个请求。-->
</web-app>
package com.my.servlet;
import jakarta.servlet.Servlet;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
/*
* servlet开发流程:
1创建javaWEB项目,同时将tomcat添加为当前项目的依赖
2重写service方法service(HttpServletRequest reg,HttpServletResponse resp)
3在service方法中,定义业务处理代码
*
* 4.在web.xml中配置路径
*
* */
import java.io.IOException;
import java.io.PrintWriter;
//通过继承HttpServlet来实现servlet接口,因为HttpServlet这个java1已经封装好的类已经实现了servlet接口了,继承之后很多方法就不用自己重写了
public class UserServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1.从request对象中获取请求信息
String username= req.getParameter("username");//根据参数名获取参数值
//2.处理相关的业务
String info= "yes";
if("atguigu".equals(username))
{
info= "No";
}
//3将想要相应的数据放入response中
PrintWriter writer= resp.getWriter();//该方法返回的是一个响应体中打印字符串的打印流
writer.println(info);
}
}
上面这个是servlet的具体业务流程的Java代码
接下来记录两个学习中的重要问题
1.servlet-api.jar包 导入问题
servlet-api仅仅只在编码的时候需要,当我们运行时,在服务器的环境中,由服务器的环境提供依赖,所以我们不需要在整个web文件中导入servlet的jar包因为他在tomcat中存在由服务器提供依赖
2.Content-Type响应头问题
首先我们要明确什么时mime类型响应头
MIME类型响应头 媒体类型,文件类型,等等相应的数据类型,记录了几乎所有形式的文件
MIME类型用于告诉客户端收到的是什么数据,应该以怎么样的方式进行处理,默认时当成HTML类型的文件处理
简单地说,当客户端请求了一个文件,找到这个文件之后,将其放入响应体中,然后再conf/web.xml 文件中寻找文件对应的MIME类型的文件他的类型是什么然后给响应头中的content-type赋值,告诉客户端该如何解析这个响应头以便于正确的显示文件的内容
但是当我们请求servlet类型的文件时,我们是没有响应头的类型的,因为这是一个动态资源,我们会默认吧响应体里面携带的内容当成html来解析,即前文java代码中的info,在里面添加html是能够实现一些效果的
但是呢,我们传输的文件不单单只是html文件,我们还希望传输别的文件,这时候就需要我们主动的再后端的代码中指定要解析的文件的类型是什么具体代码如下
//3将想要相应的数据放入response中
//设置content-type的类型,前面是响应头,后面是文件的类型
resp.setHeader("Content-Type","text/html");
PrintWriter writer= resp.getWriter();//该方法返回的是一个响应体中打印字符串的打印流
writer.println(info);
由于Content-Type实在是太重要了,所以java专门设立了一个api给他
resp.setContentType("text/html");
接下来详细讲讲Servlet的匹配机制:分为两个
1.精确匹配
2.模糊匹配
精确匹配
借用一下尚硅谷的图片
当客户端发送一个请求时,会根据路径寻找想要的东西,http后面首先跟的是服务器的地址,然后服务器后面跟的是应用程序的端口号,然后应用程序(tomcat)在webapps里面寻找想要的文件这里面是demo02,然后demo02后面还跟了一个s1,此时tomcat就会在web.xml里面寻找s1这个映射,首先找的是
servlet-mapping,中的url-parrern,寻找到了s1,然后寻找到了s1之后他会接着寻找别名的名字是什么他发现这个名字是servlet,接着就会在servlet这个别名里面寻找servlet的映射,找到之后接着就在绝对路径里面servlet-calss标签里面寻找这个类的位置,寻找到之后就会通过反射创建一个对象执行对应的逻辑,甚至可以拥有多个url-pattern来进行匹配,即一个servlet-name可以拥有多个url-pattern
上面我们讲了精确匹配接下来讲讲模糊匹配
模糊匹配
1.通配匹配(不包含jsp)
1.当xml文件中出现了如下的代码时 <url-pattern>/</url-pattern>单单的一个斜线,那么这个就代表着通配符,无论什么路径都会匹配上这个servlet1,但注意不包含jsp文件,也就是说你在http://localhost8080/demo02/(这个位置写上任何文字都可以匹配)除了文件中指定的的jsp的匹配路径如http://localhost8080/demo02/aaa.jsp
<servlet-mapping> <servlet-name>servlet1</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
2.通配匹配(包含jsp)
<servlet-mapping> <servlet-name>servlet1</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping>
3.前缀匹配,后缀模糊
<url-pattern>/a/*</url-pattern>
此时/a/后面跟着任何的文字都能匹配上
4.后缀准确,前缀模糊
<url-pattern>*.action</url-pattern>
注意*号前面不可以跟斜线,如果跟了那么就代表着通配匹配了
我真服了经典白雪!!!不用上面那么麻烦的匹配,只需要在后端的代码写一个这样的东西就可以了
@WebServlet("/aaa")
public class Servlet1 extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("执行了");
}
}
@WebServle("/aaa")就可以直接匹配上
但注意使用了这种方式在web.xml里面就不可以使用匹配了,这样会报错的
@WebServlet(urlPatterns = {"/a","/b","/c"})
可以使用这种方式赋予多个别名
Servlet生命周期
生命周期阶段 对应的方法 什么时候进行 进行的次数 * 1.实例化 构造器 第一次请求 1 * 2.初始化 init 构造完毕 1 * 3.接受请求,处理请求,服务 Service 每次请求 n * 3.销毁 destroy 关闭服务 1
1.线程安全问题
当构造过一次后,每次再请求就只会调用service方法,这证明了Servlet是一个单例的对象
接下来讲一下Servlet的注意事项,如果Service()方法里面有修改成员变量的话,就有可能导致严重的线程安全问题
以图例讲解
圆圈所示地方是一个实例的对象堆,Servic对象就保存在里面,当多个客户端同时请求时就会再Tomcat中创建两个线程栈,里面进行Service的逻辑操作,这个方法就来源于对象堆里面所保存的Service对象,如果两个客户端同时对里面的对象进行了++操作那么就会导致变量增加了两次,可能会造成预料之外的错误,所以在Service里面对成员变量进行修改是一件很危险的事情,我们一般不在Service里面对成员变量进行修改,那么这时候就有人说了,我们不可以加个锁吗,如果加了一个锁的话,那么就会严重拖慢服务器的进度,每个客户端都在等待别人使用完资源才能进行业务操作,效率低下,所以不在Service方法里面进行这样的操作就是最好的
2.接下来在讲讲初始化的问题
1.先讲web.xml里面的配置方法
我们在web.xml文件中可以配置这样的代码
<servlet>
<load-on-startup>4</load-on-startup>
</servlet>
其中 <load-on-startup>4</load-on-startup>这个代码的意思对初始化进行了一下配置,当里面的数为负数的时候,就代表着Tomcat启动时不会实例化Servlet对象,当为正数时,Tomcat就会按照数字的大小依次实例化Servlet对象,假如数字相同的话tomcat就会自己去协调
2.注解方式配置初始化
@WebServlet(urlPatterns = {"/a","/b","/c"},loadOnStartup = 1)
通过这样的方式进行配置就可更加方便的配置
loadOnStartup注意事项
由于在tomcat内部有一些已经写好的loadOnStartup数值,1-6都是已经写好的,所以我们自己的Servlet应该写在6以上
DefaultServlet作用
以图例讲解
当我们请求一个没有配置的Servlet的时候,就会走DefaultServlet这个对象去io流中寻找对应的文件,所以对于一些静态资源,我们一般可以通过DefaultServlet进行寻找
Servlet的继承结构
顶层的Servlet接口
public interface Servlet {
void init(ServletConfig var1) throws ServletException;
//获取ServletConfig对象
ServletConfig getServletConfig();
void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;
//返回Servlet字符串形式的描述信息的方法
String getServletInfo();
//一般用于释放资源
void destroy();
}
GenericServlet
public abstract class GenericServlet implements Servlet, ServletConfig, Serializable {
//带参数的init方法,获取到ServletConfig对象,这个对象读取Servlet的配置信息,
//tomcat在调用init方法时会读取配置信息进入一个ServletConfig对象并将该对象传入init方法
public void init(ServletConfig config) throws ServletException {
this.config = config;
this.init();
}
//重载时我们重写的方法
public void init() throws ServletException {
}
//返回ServletConfig的方法
public ServletConfig getServletConfig() {
return this.config;
}
//再次抽象声明Service方法
public abstract void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;
}
HttpServlet
public abstract class HttpServlet extends GenericServlet {
//调用了父接口的方法
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
//调用父类的Service方法将传进来的两个req和res对象进行父转子的强转,这样就能调用更多的api,其中HttpServletRequest和 HttpServletResponse都分别继承了HttpServletRequest接口和HttpServletResponse接口
HttpServletRequest request = (HttpServletRequest)req;
HttpServletResponse response = (HttpServletResponse)res;
//然后通过函数重载调用了自己的Service方法,此时单参数列表变成了HttpServletRequest req, HttpServletResponse resp,遵循了一些http的协议
this.service(request, response);
}
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//获取请求的方式,是 GET POST PUT DELETE OPTION等等
String method = req.getMethod();
if (method.equals("GET")) {
//分别调用了各个方法的do方法
this.doGet(req, resp);
} 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);
}
}
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//doget准备了一个这样的字符串http.method_get_not_supported
String msg = lStrings.getString("http.method_get_not_supported");
//调用了一个这样的方法,我将其复制在下面方便观看和解读
this.sendMethodNotAllowed(req, resp, msg);
}
}
private void sendMethodNotAllowed(HttpServletRequest req, HttpServletResponse resp, String msg) throws IOException {
//在故意响应405 请求方式不允许的信息
resp.sendError(405, msg);
}
//dopost也是在故意响应405 请求方式不允许的信息
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String msg = lStrings.getString("http.method_post_not_supported");
this.sendMethodNotAllowed(req, resp, msg);
}
什么是故意响应405错误信息,意思是,如果你自己编写的Servlet继承了HttpServlet接口但是没有重写Service或者是do****()方法的话,就会跳转到405的错误信息页面,所以,对于在开发中我们的业务处理,可以直接重写do****()方法来进行业务处理,因为Service()方法当中有一些自带的代码处理了别的东西,但是目前重写Service是没有什么错误
ServletConfig对象
使用文件配置
tomcat通过读取xml文件将里面的配置信息赋予ServletConfig然后再init()这个方法里面将config对象赋予给了一个成员变量,注意:ServletConfig只服务一个Servlet,不是一个服务多个
使用注解方式配置
@WebServlet(
urlPatterns = "/servlet1",
initParams = {@WebInitParam(name = "keya",value = "valuea"),@WebInitParam(name = "keyb",value = "valueb")}
)
public class Servlet1 extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//获取配置信息
ServletConfig servletConfig = getServletConfig();
//根据键值对获取值
String key=servletConfig.getInitParameter("keya");
System.out.println(key);
//获取所有参数的名字
Enumeration<String> e=servletConfig.getInitParameterNames();
//hasMoreElements相当于迭代器,观察下一个位置有没有元素,有的话就返回true,没有就返回false
//nextElement() 1.取出下一个元素 2.向下移动游标
while(e.hasMoreElements()){
String pname=e.nextElement();
System.out.println(pname+""+getInitParameter(pname));
}
}
}
ServletContext对象
相较于ServletConfig对象只服务一个Servlet,ServletContext对象服务多个Servlet
//获取ServletContext
ServletContext servletContext1 = servletConfig.getServletContext();
ServletContext servletContext2 = req.getServletContext();
ServletContext servletContext3 = getServletContext();
//这三个变量都是相同的
//获取内部的键值对和config一样
Enumeration<String> e1=servletContext1.getInitParameterNames();
while(e1.hasMoreElements()){
String pname=e1.nextElement();
System.out.println(pname+"="+getInitParameter(pname));
}
这是相关的信息获取代码,但这不是重点,context最重要的是它的其他功能,很多的api非常重要
ServletContext的重要api
1.获取资源目录api
@WebServlet("/Servlet3")
public class Servlet3 extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//向upload文件写入信息
// String path="D:\De_tools\webAPP\out\artifacts\demo03_ServletConfig_war_exploded \ upload"不使用api只能使用这种真实路径,会导致不同电脑不能实现想要获取到的文件
//通过context的一个api获取项目运行时的文件真实路径,因为如果不使用这个api那么就会找不到项目部署的真实路径
String path=getServletContext().getRealPath("upload");
FileOutputStream fos=new FileOutputStream(path+"/test.txt");
System.out.println(path);
//获取项目的上下文路径 访问路径
ServletContext servletContext = getServletContext();
String contextPath = servletContext.getContextPath();
System.out.println(contextPath);
//即获取到了/demo03******这一步,然后再通过资源名称获取路径
}
}
2.域对象api
ֿ域对象:一些用于在一些特定的储存数据和传递数据的对象,传递数据不同的范围称之为不同的域, 不同的ֿ域对象代表不同的ֿ域,共享数据的范围也不同
而ServletContext也代表着整个应用的数据,能够实现整个应用中的数据传递,我们称之为应用域
以下是代码例子
//域对象api
//向context里面存放数据
//void setAttribute(String keyName,Object valuea);
servletContext.setAttribute("keya","valuea");
servletContext.setAttribute("keyb","valueb");
//获取域对象中的数据,Object getAttribute()
String keya=(String)servletContext.getAttribute("keya");
//移除域中的数据
servletContext.removeAttribute("keya");
HttpServletRequest对象
即请求报文传入tomcat中变成了一个这样的对象
其中和请求行和头有关的api如下
public class Servlet4 extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//行相关 GET/POST uri http/1.1
System.out.println(req.getMethod());//获取请求方式
System.out.println(req.getScheme());//获取请求协议
System.out.println(req.getProtocol());//获取请求协议以及版本
System.out.println(req.getRequestURI());//获取请求的uri 项目内部的资源路径
System.out.println(req.getRequestURL());//获取请求的url 项目内的资源的完成路径
System.out.println(req.getLocalPort());//本应用容器的端口号
System.out.println(req.getServerPort());//客户端发送请求时使用的端口号
System.out.println(req.getRemotePort());//客户端软件的端口号
//头相关,获取请求头的内容
String accept=req.getHeader("Accept");
System.out.println("Accept"+accept);
//获取所有的请求头
Enumeration<String> headers=req.getHeaderNames();
while(headers.hasMoreElements()){
String hname=headers.nextElement();
//通过遍历请求头来获取里面的所有内容
System.out.println(hname+":"+req.getHeader(hname));
}
}
}
重点解释下url和uri的区别
简单的来说就是uri一般是项目自己进行测试的时候的路径,只需要在自己的项目内搞清楚他在哪就行了,但是URL不一样,URL是真实的项目路径,是以及部署到各个地方的真实路径,类似于父子的关系,这是浅显的解释,便于自己理解
接下来是有关后端代码的业务处理
package com.my.servlet;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.Map;
import java.util.Set;
@WebServlet("/servlet5")
public class servlet5 extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//获取键值对形式的参数
//根据参数名获取单个参数值
String username = req.getParameter("username");
System.out.println(username);
String password = req.getParameter("userPwd");
System.out.println(password);
//根据参数名获取多个参数
String [] hobbies=req.getParameterValues("hobbies");
System.out.println(Arrays.toString(hobbies));
//当参数名不知道的时候可以通过获取多个参数名然后逐一遍历获取其中的值
Enumeration<String> hobbiesEnum = req.getParameterNames();
while(hobbiesEnum.hasMoreElements()){
String hobby = hobbiesEnum.nextElement();
String[] parameterValues = req.getParameterValues(hobby);//获取到了一个这个关键字的数组
if(parameterValues.length>1){//如果数组里面的内容大于1就代表着有多个元素,用Arrays的api将所有数组元素输出
System.out.println(hobby+"="+Arrays.toString(parameterValues));
}else System.out.println(hobby+"="+parameterValues[0]);
}
//第二种获取全部参数的方法
Map<String,String []> parmater=req.getParameterMap();
Set<Map.Entry<String,String[]>> entries=parmater.entrySet();
for(Map.Entry<String,String[]> entry:entries){
String pname=entry.getKey();
String [] values=entry.getValue();
System.out.println(pname+"="+Arrays.toString(values));
}
}
}
以上API专门用于获取key=value形式的参数,无论这些参数是在url后还是在请求体中
form 表单标签提交GET请求时,参数以键值对形式放在url后,不放在请求体里,GET方式的请求也是可以有请求体
当我们获取的非键值对文件时我们有以下操作
//当获取非键值对文件时我们有以下操作 比如JSON串
//1.从请求体中读取字符的字符输入流
BufferedReader bufferedReader=req.getReader();
//获取一个从请求体中读取二进制文件的输入流,一般用于读取一些文件进行操作
ServletInputStream inputStream=req.getInputStream();
其中最后一段这部分的代码加上了一些我的疑惑以及解答
在Java中,处理
Map
类型的数据时,有几种遍历方式。当你看到通过Set<Map.Entry<String,String[]>>
来遍历Map<String,String[]>
的示例时,这是一种常见且有效的遍历方法。下面我将解释为什么使用Set
和Map.Entry
来遍历,以及它们各自的含义。为什么使用
Set
容器进行遍历输出?在Java中,
Map
接口的实现(如HashMap
、TreeMap
等)内部通常通过某种形式的Set
(具体是EntrySet
,即包含键值对条目的集合)来存储元素。当你调用Map
的entrySet()
方法时,它返回的是一个Set<Map.Entry<K,V>>
,其中K
和V
分别是Map
的键和值的类型。使用
Set
进行遍历的原因是:
- 唯一性:
Set
接口表示的是一个不包含重复元素的集合。这确保了遍历过程中每个键值对只会被处理一次。- 遍历能力:虽然
Set
接口不保证迭代顺序(除非使用LinkedHashSet
或TreeSet
等特定实现),但它确实提供了遍历集合中元素的能力。
Map.Entry
是什么意思?
Map.Entry<K,V>
是Map
接口的一个内部接口,它表示Map
中的一个键值对。这个接口提供了获取键(getKey()
)、值(getValue()
)以及设置值(setValue(V value)
,注意,这会影响原始Map
中的值)的方法。当你遍历
Map
的entrySet()
时,你实际上是在遍历一个包含Map.Entry
对象的集合。每个Map.Entry
对象都代表Map
中的一个键值对,你可以通过调用getKey()
和getValue()
来获取它的键和值
HttpServletRespone对象
有关响应报文设置的api
package com.my.servlet;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
@WebServlet("/servlet6")
public class Servlet6 extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//设置相关行相关的api HTTP/1.1 200/404/405/500/...
resp.setStatus(404);//故意响应405
//设置响应头相关的api
String info="<h1>Hello World</h1>";
resp.setHeader("aaa","valuea");//可以设置任意的头和任意的值
resp.setContentType("text/html");
resp.setContentLength(info.getBytes().length);
//获得一个响应体中输入文本字符输出流
PrintWriter writer=resp.getWriter();
writer.println(info);
//获得一个向响应体中输入二进制信息的字节输出流,这样可携带一些文件类型,比如jpg文件
ServletOutputStream outputStream = resp.getOutputStream();
}
}
请求转发和响应重定向
借用尚硅谷的总结
请求转发过程解析
1.首先客户端向tomcat请求然后tomcat生成了req和resp对象
2.然后找到对应的Servlet对象将其放入进行逻辑操作
3.但是发现当前Servlet处理不了这种业务,于是便将tomcat最开始生成的这两个对象传递给了别的Servlet处理
也就是说从头到尾都只有这两个tomcat生成的req和resp对象
代码如下所示,创建了两个Servlet对象
package com.my.servlet;
import jakarta.servlet.RequestDispatcher;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/ServletA")
public class ServletA extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("A执行了");
//转发给ServletB
//1.获得请求转发器
RequestDispatcher requestDispatcher = req.getRequestDispatcher("/ServletB");
//2.做出转发动作,将req和resp作为参数转发
// requestDispatcher.forward(req, resp);
//同样也可以请求转发到静态资源,直接就可以在客户端打开另一个网页
req.getRequestDispatcher("test.html").forward(req, resp);
//注意,请求转发只是再内部资源请求,尽管你输入了一个外部的网页绝对路径,但是依旧打不开
req.getRequestDispatcher("www.99vv1.com").forward(req, resp);
}
}
package com.my.servlet;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/ServletB")
public class ServletB extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("B执行了");
}
}
请求转发的特性,以一个图来进行解释
响应重定向
以图例解释
1.客户端发送请求,生成了req和resp对象给ServletA但是A发现自己处理不了,于是发送了一个响应报文resp给客户端,并且设置其中的状态码为302,并且还附带了一个location=“目标资源路径”告知了客户端
2.客户端收到请求后解析了响应码和目标路径,于是重新发送了一个请求给ServletB
代码如下
package com.my.servlet;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/Servlet1")
public class Servlet1 extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//相应重定向代码 这一个函数是封装好的api,一个函数就能够实现设置状态码为302并且设置了location为servlet2
// resp.sendRedirect("Servlet2");
//可以是一个html资源
// resp.sendRedirect("test.html");
//不可以找寻web-inf下的资源
// resp.sendRedirect("WEB-INF/web.xml");
//可以根据绝对路径访问别的网站
resp.sendRedirect("http://www.99vv1.com");
}
}
这是重定向的要点
以上是Servlet学习过程中的总结