Service

1. Service的概述

1.1 Service是什么

  • Servlet是Java提供的一门动态web资源开发技术
  • Servlet是JavaEE 规范之一,其实就是一个接口,将来我们需要定义Servlet类实现Servlet接口,并由web服务器运行Servlet

1.2 Service怎么使用(快速入门)

  1. 创建web项目,导入Servlet依赖坐标
<dependency>
<groupId>javax.servlet</groupld>
<artifactId>javax.servlet-api</artifactld>
<version>3.1.0</version>
<!--运行环境中排除这个jar包,因为tomcat里自带了这个jar包,不然运行时会报错-->
<scope>provided</scope>
</dependency>
  1. 创建: 定义一个类,实现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() { }
}
  1. 配置:在类上使用@WebServlet注解,配置该Servlet的访问路径
@WebServlet("/demo1")
public class ServletDemo1 implements Servlet{
  1. 访问:启动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个阶段:
  1. 加载和实例化:默认情况下,当Servlet第一次被访问时,由容器(Tomcat)创建Servlet对象。

  2. 初始化:在Servlet实例化之后,容器将调用Servlet的init()方法初始化这个对象,完成一些如“加载配置文件、创建连接”等初始化的工作。该方法只会调用一次

也就是说就算多次访问该Servlet对象,init()方法也只会调用一次。而不像service()方法一样,每访问一次Servlet对象就执行一次service()里的程序。

  1. 请求处理:每次请求Servlet时,Servlet容器都会调用Servlet的service()方法对请求进行处理。

  2. 服务终止:当需要释放内存或者容器关闭时,容器就会调用Servlet实例的destroy()方法完成资源的释放。在destroy()方法调用之后,容器会释放这个Servlet实例,该实例随后会被Java的垃圾收集器所回收。

  • 可以在注解@WebServlet()里添加属性loadOnStartup来设置何时创建Servlet对象
@WebServlet(urlPatterns = "/demo", loadOnStartup = 1)
//此时Servlet对象就会在启动tomcat服务器时就会被创建,而不是等到被人访问时才创建
@WebServlet("/")
public class ServletTest implements Servlet {......}
  1. 负整数:第一次被访问web页面时创建Servlet对象
  2. 0或正整数:服务器启动时创建Servlet对象,数字越小优先级越高

3. Servlet体系结构以及方法使用

JavaWeb之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 怎么使用

  1. 创建自定义类并继承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 ...");
    }
}
  1. 在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>
  1. 启动tomcat,访问该html文件,填写完表单后提交就会将数据以post方式发送请求到 "/tomcat-JavaWeb1/servletTest2" 处,进行post方式处理。
    JavaWeb之Servlet

4. Servlet urlPattern配置

Servlet 要想被访问,必须配置其访问路径(urlPattern)。

  • 一个Servlet,可以配置多个 urlPattern
@WebServLet(urLPatterns = {"/demo1","/demo2"})

4.1 urlPattern 配置规则

1. 精确匹配:精确到该资源对象的地址
2. 目录匹配:匹配指定目录下的所有资源
3. 扩展名匹配:匹配以 .xxx 结尾的资源
4. 任意匹配:匹配任意的资源对象
  1. 精确匹配:
    • 配置路径:@WebservLet("/user/seLect")
    • 访问路径:localhost:8080/web-demo/user/selectTest1
  2. 目录匹配:
    • 配置路径: @WebServLet("/user/*")
    • 访问路径:
      • localhost:8080/web-demo/user/
        - 匹配此资源路径下的所有对象
  3. 扩展名匹配:
    • 配置路径: @WebServLet("*.do")
    • 访问路径:
      • localhost:8080/web-demo/aaa.do
      • localhost:8080/web-demo/bbb.do
  4. 任意匹配:
    • 配置路径:
      • @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 配置文件的配置方式
步骤:

  1. 编写Servlet类
  2. 在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 (响应)

JavaWeb之Servlet

  • Request:获取请求数据
  • Response:设置响应数据
    它们是Servlet对象中service()方法里的两个参数,一个用于获取请求数据,一个设置响应数据。

例如在html的表单中设置发送请求的方式为get,当在表单里填写的内容发送到指定资源路径时

<form action="/tomcat-JavaWeb1/servletTest3" method="get" >
  get: <input type="text" name="name">
  <input type="submit" value="提交">
</form>

JavaWeb之Servlet

可以看到末尾跟随的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);
    }
}

JavaWeb之Servlet
JavaWeb之Servlet

6.1 Request

6.1.1 request继承体系

JavaWeb之Servlet

  • 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() 获取请求方式:如:GETPOST
  • 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页面
    JavaWeb之Servlet

使用通用方式获取请求参数后,屏蔽了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);
    }
}
  • 控制台返回
    JavaWeb之Servlet

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的格式解码,从而导致乱码。
JavaWeb之Servlet

6.1.3 请求转发

  • 请求转发(forward): 一种在服务器内部的资源跳转方式
  • 实现方式:
req.getRequestDispatcher("资源B路径").forward(req,resp);

JavaWeb之Servlet

  • 请求转发资源间共享数据:使用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):一种资源跳转方式
    JavaWeb之Servlet
    如上图,浏览器向资源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响应字符数据

  • 使用:
  1. 通过Response对象获取字符输出流
PrintWriter writer = resp.getWriter();
  1. 写数据
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响应字节数据

  • 使用:
  1. 通过Response对象获取字符输出流
ServletOutputStream outputStream = resp.getOutputStream();
  1. 写数据
outputStream.write(字节数据);

因为要将输入流里的字节数据(如图片)拷贝到输出流里(resp.getOutputStream();),因此可以使用Apache的工具类IOUtils。

  • IOUtils工具类使用
  1. 导入坐标
<dependency>
<groupld>commons-io</groupld>
<artifactld>commons-io</artifactld>
<version>2.6</version>
</dependency>
  1. 使用
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);  
    }  
}
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。