Service
1. Service的概述
1.1 Service是什么
- Servlet是Java提供的一门动态web资源开发技术
- Servlet是JavaEE 规范之一,其实就是一个接口,将来我们需要定义Servlet类实现Servlet接口,并由web服务器运行Servlet
1.2 Service怎么使用(快速入门)
- 创建web项目,导入Servlet依赖坐标
<dependency>
<groupId>javax.servlet</groupld>
<artifactId>javax.servlet-api</artifactld>
<version>3.1.0</version>
<!--运行环境中排除这个jar包,因为tomcat里自带了这个jar包,不然运行时会报错-->
<scope>provided</scope>
</dependency>
- 创建: 定义一个类,实现Servlet接口,并重写接口中所有方法,并在 service方法中打印输出一句话,用以测试是否访问成功,如果成功访问会在控制台中打印出hello servlet!
@WebServlet("/")
public class ServletTest implements Servlet {
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("hello servlet!");
}
@Override
public void init(ServletConfig servletConfig) throws ServletException { }
@Override
public ServletConfig getServletConfig() { return null; }
@Override
public String getServletInfo() { return null; }
@Override
public void destroy() { }
}
- 配置:在类上使用@WebServlet注解,配置该Servlet的访问路径
@WebServlet("/demo1")
public class ServletDemo1 implements Servlet{
- 访问:启动Tomcat,浏览器输入URL访问该Servlet
http://localhost:8080/web-demo/demo1
注解@WebServlet()里面的访问路径开头记得加"/"
2. Servlet执行流程及生命周期
2.1 执行流程
可以看到上方的快速入门案例,我只创建了Servlet接口的实现类,并没有new任何一个对象,反而在运行tomcat时控制台上却打印了service()方法里的内容。
- Servlet由谁创建?servlet方法由谁调用?
- Servlet由web服务器创建,servlet()方法由web服务器调用。
- 服务器怎么知道servlet种一定有service方法?
- 因为我们自定义的servlet,必须实现servlet接口并复写其方法,而servlet接口中有service方法。
2.2 生命周期
对象的生命周期指一个对象从被创建到被销毁的整个过程
- Servlet运行在Servlet容器(web服务器)中,其生命周期由容器来管理,分为4个阶段:
-
加载和实例化:默认情况下,当Servlet第一次被访问时,由容器(Tomcat)创建Servlet对象。
-
初始化:在Servlet实例化之后,容器将调用Servlet的init()方法初始化这个对象,完成一些如“加载配置文件、创建连接”等初始化的工作。该方法只会调用一次。
也就是说就算多次访问该Servlet对象,init()方法也只会调用一次。而不像service()方法一样,每访问一次Servlet对象就执行一次service()里的程序。
-
请求处理:每次请求Servlet时,Servlet容器都会调用Servlet的service()方法对请求进行处理。
-
服务终止:当需要释放内存或者容器关闭时,容器就会调用Servlet实例的destroy()方法完成资源的释放。在destroy()方法调用之后,容器会释放这个Servlet实例,该实例随后会被Java的垃圾收集器所回收。
- 可以在注解
@WebServlet()
里添加属性loadOnStartup
来设置何时创建Servlet对象
@WebServlet(urlPatterns = "/demo", loadOnStartup = 1)
//此时Servlet对象就会在启动tomcat服务器时就会被创建,而不是等到被人访问时才创建
@WebServlet("/")
public class ServletTest implements Servlet {......}
- 负整数:第一次被访问web页面时创建Servlet对象
- 0或正整数:服务器启动时创建Servlet对象,数字越小优先级越高
3. Servlet体系结构以及方法使用
我们将来开发B/S架构的web项目,都是针对HTTP协议,所以我们自定义的servlet,会继承HttpServlet。
3.1 HttpServlet概述
HttpServlet类是servlet.http包中定义了采用HTTP通信协议的Servlet接口实现类。
因为HTTP不同的请求方式拥有不同的请求数据的格式,因此HttpServlet里有两种方法:
- doGet(HttpServletRequest req, HttpServletResponse resp):获取get请求,进行处理
- doPost(HttpServletRequest req, HttpServletResponse resp):获取post请求,进行处理
3.2 怎么使用
- 创建自定义类并继承HttpServlet,然后实现方法
@WebServlet("/servletTest2")
public class ServletTest2 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 ...");
}
}
- 在webapp目录下创建html文件,并创建表单以post方式访问上边继承HttpServlet的子类
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h3>用以测试HttpServlet的不同请求方式和不同的数据请求格式</h3>
<!--action表单提交的URL,这里的值是 “项目的上下文根 + 需要访问的Servlet头部注解声明的web地址”
1、本项目的上下文根默认为项目名称(tomcat中的默认web.xml配置)
2、继承HttpServlet类的Servlet地址为“/servletTest2” -->
<form action="/tomcat-JavaWeb1/servletTest2" method="post" >
<input type="text" name="test">
<input type="submit" value="提交">
</form>
</body>
</html>
- 启动tomcat,访问该html文件,填写完表单后提交就会将数据以post方式发送请求到 "/tomcat-JavaWeb1/servletTest2" 处,进行post方式处理。
4. Servlet urlPattern配置
Servlet 要想被访问,必须配置其访问路径(urlPattern)。
- 一个Servlet,可以配置多个 urlPattern
@WebServLet(urLPatterns = {"/demo1","/demo2"})
4.1 urlPattern 配置规则
1. 精确匹配:精确到该资源对象的地址
2. 目录匹配:匹配指定目录下的所有资源
3. 扩展名匹配:匹配以 .xxx 结尾的资源
4. 任意匹配:匹配任意的资源对象
- 精确匹配:
- 配置路径:
@WebservLet("/user/seLect")
- 访问路径:localhost:8080/web-demo/user/selectTest1
- 配置路径:
- 目录匹配:
- 配置路径:
@WebServLet("/user/*")
- 访问路径:
- localhost:8080/web-demo/user/
- 匹配此资源路径下的所有对象
- localhost:8080/web-demo/user/
- 配置路径:
- 扩展名匹配:
- 配置路径:
@WebServLet("*.do")
- 访问路径:
- localhost:8080/web-demo/aaa.do
- localhost:8080/web-demo/bbb.do
- 配置路径:
- 任意匹配:
- 配置路径:
@WebServLet("/*")
@WebServLet("/")
- 访问路径:
- localhost:8080/web-demo/随便
- localhost:8080/web-demo/都行
- 配置路径:
4.2 / 和 /* 的区别:
- 当我们的项目中的Servlet配置了
/
,会覆盖掉tomcat中的DefaultServlet,
从而导致html这类的静态资源就无法访问(DefaultServlet会处理项目里的静态资源的访问,被覆盖后访问html文件只会显示空白),
当其他的 url-pattern都匹配不上时都会走这个Servlet。- 当我们的项目中配置了
/*
,意味着匹配任意访问路径
4.3 优先级:
精确路径 > 目录路径 > 扩展名路径 > /* > /
5. 使用XML配置Servlet路径
Servlet从3.0版本后开始支持使用注解配置,3.0版本前只支持 XML 配置文件的配置方式
步骤:
- 编写Servlet类
- 在web.xml中配置该Servlet
<!--Serlet的全类名-->
<servlet>
<servlet-name>demo5</servlet-name>
<servlet-Class>com.lthelma.web.servuet.servuetDemos</servlet-Class>
</servlet>
<!--给指定的Serlet配置访问路径-->
<servlet-mapping>
<servLet-name>demo5</servLet-name>
<url-pattern>/demo5</url-pattern>
</servlet-mapping>
- 相当于配置@WebServlet()中的路径:
@WebServlet("/demo5")
public void servuetDemos() extends HttpServlet{
......
}
6. Request (请求) 和 Response (响应)
- Request:获取请求数据
- Response:设置响应数据
它们是Servlet对象中service()方法里的两个参数,一个用于获取请求数据,一个设置响应数据。
例如在html的表单中设置发送请求的方式为get,当在表单里填写的内容发送到指定资源路径时
<form action="/tomcat-JavaWeb1/servletTest3" method="get" >
get: <input type="text" name="name">
<input type="submit" value="提交">
</form>
可以看到末尾跟随的name=hjj
,就是本次发送的get请求数据,这时就能使用Request对象获取该数据,并使用Response返回响应数据
@WebServlet("/servletTest3")
public class ServletTest3 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//使用Request对象获取请求数据
String name = req.getParameter("name"); //url为:资源路径/?name=tom
//使用Response对象返回响应数据
resp.setHeader("content-type", "text/htmL;charset=utf-8"); //设置响应数据的格式
resp.getWriter().write("<h1>"+name+"欢迎您!</h1>"); //使用输出流返回响应数据
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doPost(req, resp);
}
}
6.1 Request
6.1.1 request继承体系
- Tomcat需要解析请求数据,并封装为request对象,再创建request对象传递到service方法中
@WebServlet("/demo1")
public class ServletDemo1 implements Servlet {
@Override
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
System.out.println(request);
//输出:org.apache.catalina.connector.RequestFacade@5c90f246
//ServletRequest是接口,我并没有实现此接口,所以实现接口并将实现类对象传递到service()方法的是tomcat
}
......
由上而知,Tomcat解析了请求数据并封装为了RequestFacade,而这个RequestFacade实现了ServletRequest接口。
如果这里的自定义Servlet对象是继承HttpServlet类的话,其中doGet或doPost(HttpServletRequest req, HttpServletResponse resp)
里的两个接口参数,也是由tomcat实现并提供的。
- 如果要了解request对象,查阅JavaEE API文档的HttpServletRequest接口即可。
6.1.2Request获取请求数据
请求数据分为三部分
①请求行:
GET /request-demo/req1?username=zhangsanHTTP/1.1
- String getMethod() 获取请求方式:如:
GET
、POST
- String getContextPath(): 获取虚拟目录(项目访问路径):
/request-demo
- StringBuffer getRequestURL(): 获取URL(统一资源定位符):
http://localhost:8080/request-demo/req1
- String getRequestURI(): 获取URI(统一资源标识符):
/request-demo/req1
- String getQueryString()获取请求参数(GET方式):
username=zhangsan&password=123
②请求头:
User-Agent:Mozilla/5.0 Chrome/91.0.4472.106
- String getHeader(String name): 根据请求头名称,获取值
③请求体:
username=superbaby&password=123
- ServletlnputStreamget InputStream(): 获取字节输入流
- BufferedReader getReader():获取字符输入流
6.1.3 Request 通用方式获取请求参数
get请求与post请求都可以使用的获取请求参数的方法。
-
Map<String,String[ ]> getParameterMap():获取所有参数Map集合
-
String[ ] getParameterValues(String name):根据名称获取参数值(数组)
-
String getParameter(String name): 根据名称获取参数值(单个值)
-
web页面
使用通用方式获取请求参数后,屏蔽了GET和POST的请求方式代码的不同,则代码可以定义为如下格式:
@WebServlet("/req2")
public class requestDemo2 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("get...");
//1、获取所有map集合
Map<String, String[]> gpm = req.getParameterMap();
//循环打印map集合里的值
for (String key : gpm.keySet()) { //keySet()返回所有key
System.out.print(key+" : ");
String[] value = gpm.get(key); //根据key返回值(因为值可能有多个所以返回类型为String[])
for (String v: value) {
System.out.print(v+" ");
}
System.out.println();
}
//2、根据名称获取参数值
String[] hobbys = req.getParameterValues("hobby");
System.out.print("兴趣为:");
for (String value: hobbys) {
System.out.print(value+" ");
}
System.out.println();
//3、根据名称获取单个值
System.out.println("=================================");
System.out.println(req.getParameter("username"));
System.out.println(req.getParameter("password"));
System.out.println(req.getParameter("hobby")); //如果有两个值,则就默认选择第一个
System.out.println(req.getParameter("hobby"));
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("post...");
//因为两种请求的处理数据的方式都一样,所以可以将处理方式整合
this.doGet(req,resp);
}
}
- 控制台返回
6.1.4 Request 请求参数中文乱码处理
请求参数如果存在中文数据,则会乱码
- POST请求的解决方案: 设置输入流的编码
req.setCharacterEncoding("UTF-8");
protected void doGet(HttpServletRequest req, HttpServletResponse resp){
//1.解决乱码:POST, getReader()
request.setCharacterEncoding("UTF-8");//设置字符输入流的编码
//2.获取username
String Username = request.getParameter( name: "username");
System.out.printtn(username);
}
- GET请求的解决方案:先对乱码数据进行编码,转换为字节数组。然后再将这些字节数组进行正确格式的解码,从而获得正确的字符串
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
if (req.getMethod().equals("POST")){
//post解决乱码问题
req.setCharacterEncoding("UTF-8");
String str = req.getParameter("username");
System.out.println("post: "+str);
}else if (req.getMethod().equals("GET")){
//终极解决方案,post和get都可以
String str = req.getParameter("username");
//1、先将乱码按解码的格式编码为字节码,以保证字节码还是第一次编码的样子
byte[] bytes = str.getBytes(StandardCharsets.ISO_8859_1);
//2、再将字节码按照utf-8格式编码
str = new String(bytes,StandardCharsets.UTF_8);
System.out.println("get: "+str);
}
}
因为GET的请求方式,浏览器将数据传输给Tomcat是先将数据以UTF-8的格式编码为URL编码,再传输给Tomcat,而后Tomcat会默认以ISO-8859-1的格式解码,从而导致乱码。
6.1.3 请求转发
- 请求转发(forward): 一种在服务器内部的资源跳转方式
- 实现方式:
req.getRequestDispatcher("资源B路径").forward(req,resp);
- 请求转发资源间共享数据:使用Request对象
- void setAttribute(String name, Object o): 存储数据到request域中
- Object getAttribute(Stringname):根据key,获取值
- void removeAttribute(String name): 根据 key,删除该键值对
//获取请求,并转发
@WebServlet("/req3.1")
public class requestDemo3_1 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("req3.1 处理开始的数据-----");
//假如以下是页面获取到的数据
String userName = "hjj";
String password = "123abc";
//存储数据到request域中:以键值对的方式填写参数
req.setAttribute("userName",userName);
req.setAttribute("password",password);
//设置请求转发
req.getRequestDispatcher("/req3.2").forward(req,resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doGet(req,resp);
}
}
//获取其它Servlet传递过来的数据并进行处理
@WebServlet("/req3.2")
public class requestDemo3_2 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("req3.2 处理剩下的数据-----");
String name = (String) req.getAttribute("userName");
String mima = (String) req.getAttribute("password");
//将req3.1 获取到的数据以以下格式输出
System.out.println("账号:"+name);
System.out.println("密码:"+mima);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doGet(req,resp);
}
}
控制台打印的数据:
req3.1 处理开始的数据-----
req3.2 处理剩下的数据-----
账号:hjj
密码:123abc
- 请求转发特点:
- 浏览器地址栏路径不会发生变化
- 只能转发到当前服务器的内部资源
- 一次请求,可以在转发的资源间使用request共享数据
6.2 Response
6.2.1 Response设置响应数据功能
响应数据分为3部分:
- 响应行:
HTTP/1.1 200 OK
void setStatus(int sc) : 设置响应状态码
- 响应头:
Content-Type: text/html
void setHeader(String name, String value):设置响应头键值对
- 响应体:
<html><head>head><body></body></html>
PrintWriter getWriter():获取字符输出流
ServletOutputStream getOutputStream(): 获取字节输出流
6.2.2 Response完成重定向
-
重定向(Redirect):一种资源跳转方式
如上图,浏览器向资源A发起了一次请求,但是资源A处理不了这个请求于是返回响应状态码302(告诉浏览器自己处理不了)和响应头location:xxx(告诉浏览器该位置的资源可以处理)。于是收到资源A响应的浏览器便将请求转向了资源B,让资源B处理这个响应。 -
实现方式:
//普通方式
resp.setStatus(302);
resp.setHeader("location","资源B的路径");
//----------------------------------------
//设置重定向的简化方式(一个方法即可)
resp.sendRedirect("资源B的路径");
- 案例:由responseDemo1_1重定向到responseDemo1_2
//需要加虚拟目录
@WebServlet("/resp-dome/resp1.1")
public class responseDemo1_1 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("resp1.1 -----");
System.out.println("resp1.1:‘处理不了,让resp1.2搞吧!’");
//重定向
//方法一:
//1.设置响应状态码 302
//resp.setStatus(302);
//2.设置响应义 Location
//resp.setHeader( "Location", "/resp1.2");
//方法二:设置重定向的简化方式(一个方法即可) 推荐!
//req.getContextPath:获取虚拟目录(项目访问路径)
resp.sendRedirect(req.getContextPath()+"/resp1.2");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {...}
}
@WebServlet("/resp-dome/resp1.2")
public class responseDemo1_2 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("resp1.2 -----");
System.out.println("resp1.2 :'我来!'");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {...}
}
控制台打印:
resp1.1 -----
resp1.1:‘处理不了,让resp1.2搞吧!’
/tomcat-javaWeb2
filter test1
resp1.2 -----
resp1.2 :'我来!'
- 重定向特点:
- 浏览器地址栏路径发生变化
- 可以重定向到任意位置的资源(服务器内部、外部均可,例如转到百度阿里)
- 两次请求,不能在多个资源使用request共享数据
6.2.4 Response响应字符数据
- 使用:
- 通过Response对象获取字符输出流
PrintWriter writer = resp.getWriter();
- 写数据
writer.write("aaa");
- 注意:
- 该流不需要手动关闭,随着响应结束,response对象销毁,由服务器关闭
- 中文数据乱码:原因通过Response获取的字符输出流默认编码为:ISO-8859-1
中文会乱码,因此需要在Response对象获取字符输出流前,设置resp.setContentType("text/html;charset=utf-8");
从而防止乱码问题,并且可以识别html响应数据。 具体看下方例子。
public class responseDemo2 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1、通过Response对象获取字符输出流
PrintWriter writer = resp.getWriter();
//2、设置头信息,告诉浏览器响应的数据是html,不然浏览器会把响应的数据当作纯文本处理
resp.setHeader("content-type","text/html");
//3、写数据
writer.write("<h1>welcome!</h1>");
/*
- 细节:
1、此处的流对象不用手动关闭,因为PrintWriter对象是随着resp创建而创建的,因此在resp销毁时流也会跟着销毁。
2、中文会乱码,因此需要在Response对象获取字符输出流“前”,设置resp.setContentType("text/html;charset=utf-8");
从而防止乱码问题,并且可以识别html响应数据。相应的,第2步就得删掉,因为以上设置就是用另一种方式在设置头信息。
*/
}
......
--------------------------------处理中文乱码------------------------------------
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1、设置输出流的编码,并设置响应数据的头信息,告诉浏览器响应数据是html
resp.setContentType("text/html;charset=utf-8");
//2、通过Response对象获取字符输出流
PrintWriter writer = resp.getWriter();
//3、写数据
writer.write("<h1>欢迎!</h1>");
}
}
6.2.5 Response响应字节数据
- 使用:
- 通过Response对象获取字符输出流
ServletOutputStream outputStream = resp.getOutputStream();
- 写数据
outputStream.write(字节数据);
因为要将输入流里的字节数据(如图片)拷贝到输出流里(resp.getOutputStream();),因此可以使用Apache的工具类IOUtils。
- IOUtils工具类使用
- 导入坐标
<dependency>
<groupld>commons-io</groupld>
<artifactld>commons-io</artifactld>
<version>2.6</version>
</dependency>
- 使用
IOUtils.copy(输入流,输出流);
@WebServlet("/resp3")
public class responseDemo3 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1、获取字节数据,将其传入输入流
FileInputStream inputStream = new FileInputStream("C:\\Users\\hhhhh\\Pictures\\Saved Pictures\\whw.png");
//2、通过Response对象获取字符输出流
ServletOutputStream outputStream = resp.getOutputStream();
/* 如果没有工具类可以使用以下代码块完成 将输入流里面的数据拷贝到输出流里
byte[] buff = new byte[1024];
int len = O;
white ((len = fis.read(buff))!= -1){
os.write(buff,0,len);
}
*/
//3、完成数据的拷贝 (输入流 -> 输出流)
//使用Apache的工具类
IOUtils.copy(inputStream,outputStream);
//因为响应的是字节流数据,因此没必要写数据。
//outputStream.write(inputStream.readAllBytes());
inputStream.close(); //销毁流
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doGet(req,resp);
}
}