JavaWeb笔记

一、Mybatis

1.Mybatis在Maven下使用步骤(快速入门)

  • 创建模块,导入坐标 在创建好的模块中的 pom.xml 配置文件中添加依赖的坐标
  • <dependencies>
     <!--mybatis 依赖-->
     <dependency> 
     <groupId>org.mybatis</groupId>
     <artifactId>mybatis</artifactId> 
    <version>3.5.5</version> 
    </dependency>
    
    <!--mysql 驱动-->
     <dependency>
    <groupId>mysql</groupId> 
    <artifactId>mysql-connector-java</artifactId>
     <version>5.1.46</version> 
    </dependency> 
    
    <!--junit 单元测试--> 
    <dependency>
     <groupId>junit</groupId> 
    <artifactId>junit</artifactId> 
    <version>4.13</version> 
    <scope>test</scope> 
    </dependency> 
    <!-- 添加slf4j日志api --> 
    <dependency> 
    <groupId>org.slf4j</groupId>
     <artifactId>slf4j-api</artifactId>
     <version>1.7.20</version>
     </dependency> 
    <!-- 添加logback-classic依赖 --> 
    <dependency>
     <groupId>ch.qos.logback</groupId> 
    <artifactId>logback-classic</artifactId>
     <version>1.2.3</version>
     </dependency>
    
     <!-- 添加logback-core依赖 --> 
    <dependency> 
    <groupId>ch.qos.logback</groupId> 
    <artifactId>logback-core</artifactId>
     <version>1.2.3</version>
     </dependency> 
    
    </dependencies>

    注意:需要在项目的 resources 目录下创建logback.xml的配置文件

    <?xml version="1.0" encoding="UTF-8"?>
    <configuration>
        <!--
            CONSOLE :表示当前的日志信息是可以输出到控制台的。
        -->
        <appender name="Console" class="ch.qos.logback.core.ConsoleAppender">
            <encoder>
                <pattern>[%level]  %cyan([%thread]) %boldGreen(%logger{15}) - %msg %n</pattern>
            </encoder>
        </appender>
    
        <logger name="com.stu" level="DEBUG" additivity="false">
            <appender-ref ref="Console"/>
        </logger>
    
    
        <!--
    
          level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF
         , 默认debug
          <root>可以包含零个或多个<appender-ref>元素,标识这个输出位置将会被本日志级别控制。
          -->
        <root level="DEBUG">
            <appender-ref ref="Console"/>
        </root>
    </configuration>
  • 编写 MyBatis 核心配置文件 -- > 替换连接信息 解决硬编码问题
    在模块下的 resources 目录下创建 mybatis 的配置文件 mybatis - config.xml ,内容如下:
    <?xml version="1.0" encoding="UTF-8" ?> 
    <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> 
    <configuration> 
    
    <typeAliases>
     <package name="com.stu.domain"/> 
    </typeAliases> 
    
    <!-- environments:配置数据库连接环境信息。可以配置多个environment,通过default属性切换不同的 environment -->
    
     <environments default="development">
     <environment id="development"> 
    <transactionManager type="JDBC"/> 
    <dataSource type="POOLED">
     <!--数据库连接信息--> 
    <property name="driver" value="com.mysql.jdbc.Driver"/> 
    <property name="url" value="jdbc:mysql:///mybatis?useSSL=false"/> 
    <property name="username" value="root"/>
     <property name="password" value="1234"/>
     </dataSource> 
    
    </environment>
     <environment id="test">
    <transactionManager type="JDBC"/> 
    <dataSource type="POOLED"> 
    <!--数据库连接信息--> 
    <property name="driver" value="com.mysql.jdbc.Driver"/> 
    <property name="url" value="jdbc:mysql:///mybatis?useSSL=false"/> 
    <property name="username" value="root"/> 
    <property name="password" value="1234"/> 
    </dataSource> 
    
    </environment> 
    </environments> 
    <mappers> 
    <!--加载sql映射文件--> 
    <mapper resource="UserMapper.xml"/> 
    </mappers> 
    
    </configuration>
  • 编写 SQL 映射文件 --> 统一管理sql语句,解决硬编码问题 :在模块的 resources 目录下创建映射配置文件 UserMapper.xml ,内容如下:
  •  编码

2.Mybatis配置文件浅解

  • 初始化类型别名:domain包下存放的是基础类对象--->JavaBean类,也可以是domian包名也可以是叫作pojo
  • 初始化dataSource:<!--数据库连接信息-->
  • 初始化映射配置:<!--加载sql映射文件--> 或 <!--要操作的数据层的包-->

3.Mapper代理开发

之前我们写的代码是基本使用方式,它也存在硬编码的问题,如下:

 这里调用 selectList() 方法传递的参数是映射配置文件中的 namespace.id值。这样写也不便于后期的维护。如果使用 Mapper 代理方式(如下图)则不存在硬编码问题。

 通过上面的描述可以看出 Mapper 代理方式的目的:

  • 解决原生方式中的硬编码
  • 简化后期执行SQL

 Mybatis 官网也是推荐使用 Mapper 代理的方式。下图是截止官网的图片

 4.使用Mapper代理要求

使用Mapper代理方式,必须满足以下要求:
  • 定义与SQL映射文件同名的Mapper接口,并且将Mapper接口和SQL映射文件放置在同一目录下。如下图
       

小技巧:在resources下设置SQL映射文件:包名/包名/mapper.xxx.xml
  •  设置SQL映射文件的namespace属性为Mapper接口全限定名

  

  • Mapper 接口中定义方法,方法名就是SQL映射文件中sql语句的id,并保持参数类型和返回值类型一致

             

案例代码实现
  • com.itheima.mapper 包下创建 UserMapper接口,代码如下:
public interface UserMapper { 
  List<User> selectAll(); 
  User selectById(int id); 
}
  • 在 resources 下创建 com/itheima/mapper 目录,并在该目录下创建 UserMapper.xml 映射配置文件
<!--namespace:名称空间。必须是对应接口的全限定名 -->
 <mapper namespace="com.itheima.mapper.UserMapper">
 <select id="selectAll" resultType="com.itheima.pojo.User">
 select * from tb_user; 
 </select>
 </mapper>
  • 在 com.itheima 包下创建 MybatisDemo测试类,代码如下:
/**
* Mybatis 代理开发 
*/ 
public class MyBatisDemo2 {
 public static void main(String[] args) throws IOException {
 //1. 加载mybatis的核心配置文件,获取 SqlSessionFactory
     String resource = "mybatis-config.xml"; 
     InputStream inputStream = Resources.getResourceAsStream(resource);
     SqlSessionFactory sqlSessionFactory = new                 
     SqlSessionFactoryBuilder().build(inputStream); 
//2. 获取SqlSession对象,用它来执行sql SqlSession sqlSession = 
     sqlSessionFactory.openSession(); 
//3. 执行sql 
//3.1 获取UserMapper接口的代理对象 
     UserMapper userMapper = sqlSession.getMapper(UserMapper.class); 
     List<User> users = userMapper.selectAll(); 
     System.out.println(users); 
//4. 释放资源 
     sqlSession.close(); 
  } 
}
  • 如果Mapper接口名称和SQL映射文件名称相同,并在同一目录下,则可以使用包扫描的方式简化SQL映射文件的加载。也就是将核心配置文件的加载映射配置文件的配置修改为
<mappers>
 <!--加载sql映射文件--> 
<!-- 
<mapper resource="com/itheima/mapper/UserMapper.xml"/>
-->
 <!--Mapper代理方式--> 
<package name="com.itheima.mapper"/> </mappers>

核心配置文件:(官网文档)

类型别名

二、HTTP协议下请求数据格式

格式介绍

请求数据总共分为三部分内容,分别是:请求行,请求头,请求体

  • 请求行: HTTP请求中的第一行数据,请求行包含三块内容,分别是 GET[请求方式] /[请求URL路径] HTTP/1.1[HTTP协议及版本]

    请求方式有七种,最常用的是GET和POST

  • 请求头: 第二行开始,格式为key: value形式

       请求头中会包含若干个属性,常见的HTTP请求头有:

Host: 表示请求的主机名
User-Agent: 浏览器版本,例如Chrome浏览器的标识类似Mozilla/5.0 ...Chrome/79,IE浏览器的标识类似Mozilla/5.0 (Windows NT ...)like Gecko;
Accept:表示浏览器能接收的资源类型,如text/*,image/*或者*/*表示所有;
Accept-Language:表示浏览器偏好的语言,服务器可以据此返回不同语言的网页;
Accept-Encoding:表示浏览器可以支持的压缩类型,例如gzip, deflate等

举例说明:服务端可以根据请求头中的内容来获取客户端的相关信息,有了这些信息服务端就可以处理不同的业务需求,比如:

  • 不同浏览器解析HTML和CSS标签的结果会有不一致,所以就会导致相同的代码在不同的浏览器会出现不同的效果
  • 服务端根据客户端请求头中的数据获取到客户端的浏览器类型,就可以根据不同的浏览器设置不同的代码来达到一致的效果
  • 这就是我们常说的浏览器兼容问题

  • 请求体: POST请求的最后一部分,存储请求参数

  • 如上图红线框的内容就是请求体的内容,请求体和请求头之间是有一个空行隔开。此时浏览器发送的是POST请求,为什么不能使用GET呢?这时就需要回顾GET和POST两个请求之间的区别了:

  • GET请求请求参数在请求行中,没有请求体,POST请求请求参数在请求体中
  • GET请求请求参数大小有限制,POST没有

三、HTTP协议下响应数据格式

格式介绍

响应数据总共分为三部分内容,分别是:响应行、响应头、响应体

  • 响应行:响应数据的第一行,响应行包含三块内容,分别是 :

  • HTTP/1.1[HTTP协议及版本]      200[响应状态码]       ok[状态码的描述]

  • 响应头:第二行开始,格式为key:value形式

    响应头中会包含若干个属性,常见的HTTP响应头有:

    Content-Type:表示该响应内容的类型,例如text/html,image/jpeg;
    Content-Length:表示该响应内容的长度(字节数);
    Content-Encoding:表示该响应压缩算法,例如gzip;
    Cache-Control:指示客户端应如何缓存,例如max-age=300表示可以最多缓存300秒
  • 响应体: 最后一部分。存放响应数据

    上图中...这部分内容就是响应体,它和响应头之间有一个空行隔开。

 响应状态码

  • 200 ok 客户端请求成功
  • 404 Not Found 请求资源不存在
  • 500 Internal Server Error 服务端发生不可预期的错误

四、Servlet

基本介绍

  • Servlet是JavaWeb最为核心的内容,它是Java提供的一门==动态==web资源开发技术。

  • 使用Servlet就可以实现,根据不同的登录用户在页面上动态显示不同内容。

Servlet是JavaEE规范之一,其实就是一个接口,将来我们需要定义Servlet类实现Servlet接口,并由web服务器运行Servlet

 web项目中使用servlet步骤

①在web-demo项目的pom.xml中导入servlet坐标

 ②创建:定义一个类,实现Servlet接口,并重写接口中所有方法,写入业务逻辑

  (这里演示输出一句话)

public class ServletDemo1 implements Servlet {

    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        System.out.println("servlet hello world~");
    }
    public void init(ServletConfig servletConfig) throws ServletException {

    }

    public ServletConfig getServletConfig() {
        return null;
    }

    public String getServletInfo() {
        return null;
    }

    public void destroy() {

    }
}

③配置:在类上使用@WebServlet注解,配置该Servlet的访问路径

@WebServlet("/demo1")

④访问:启动Tomcat,浏览器中输入URL地址访问该Servlet

http://localhost:8080/web-demo/demo1

⑤浏览器访问后,在控制台会打印servlet hello world~ 说明servlet程序已经成功运行。

Servlet执行流程

  • 浏览器发出http://localhost:8080/web-demo/demo1请求,从请求中可以解析出三部分内容,分别是localhost:8080web-demodemo1
    • 根据localhost:8080可以找到要访问的Tomcat Web服务器
    • 根据web-demo可以找到部署在Tomcat服务器上的web-demo项目
    • 根据demo1可以找到要访问的是项目中的哪个Servlet类,根据@WebServlet后面的值进行匹配
  • 找到ServletDemo1这个类后,Tomcat Web服务器就会为ServletDemo1这个类创建一个对象,然后调用对象中的service方法
    • ServletDemo1实现了Servlet接口,所以类中必然会重写service方法供Tomcat Web服务器进行调用
    • service方法中有ServletRequest和ServletResponse两个参数,ServletRequest封装的是请求数据,ServletResponse封装的是响应数据,后期我们可以通过这两个参数实现前后端的数据交互

Servlet由谁创建?Servlet方法由谁调用?

Servlet由web服务器创建,Servlet方法由web服务器调用

服务器怎么知道Servlet中一定有service方法?

因为我们自定义的Servlet,必须实现Servlet接口并复写其方法,而Servlet接口中有service方法

Tomcat什么时候创建的Servlet对象?------->了解Servlet的生命周期

  • 生命周期: 对象的生命周期指一个对象从被创建到被销毁的整个过程。

  • Servlet运行在Servlet容器(web服务器)中,其生命周期由容器来管理,分为4个阶段:

  • ① 加载和实例化:默认情况下,当Servlet第一次被访问时,由容器创建Servlet对象

    默认情况,Servlet会在第一次访问被容器创建,但是如果创建Servlet比较耗时的话,
    那么第一个访问的人等待的时间就比较长,用户的体验就比较差,
    那么我们能不能把Servlet的创建放到服务器启动的时候来创建,具体如何来配置?
    
    @WebServlet(urlPatterns = "/demo1",loadOnStartup = 1)
    loadOnstartup的取值有两类情况
        (1)负整数:第一次访问时创建Servlet对象
        (2)0或正整数:服务器启动时创建Servlet对象,数字越小优先级越高
  • ② 初始化:在Servlet实例化之后,容器将调用Servlet的init()方法初始化这个对象,完成一些如加载配置文件、创建连接等初始化的工作。该方法只调用一次
  • ③ 请求处理:每次请求Servlet时,Servlet容器都会调用Servlet的service()方法对请求进行处理
  • ④ 服务终止:当需要释放内存或者容器关闭时,容器就会调用Servlet实例的destroy()方法完成资源的释放。在destroy()方法调用之后,容器会释放这个Servlet实例,该实例随后会被Java的垃圾收集器所回收
  • 通过案例演示下上述的生命周期

    /**
    * Servlet生命周期方法
    */
    @WebServlet(urlPatterns = "/demo2",loadOnStartup = 1)
    public class ServletDemo2 implements Servlet {
    
        /**
         *  初始化方法
         *  1.调用时机:默认情况下,Servlet被第一次访问时,调用
         *      * loadOnStartup: 默认为-1,修改为0或者正整数,则会在服务器启动的时候,调用
         *  2.调用次数: 1次
         * @param config
         * @throws ServletException
         */
        public void init(ServletConfig config) throws ServletException {
            System.out.println("init...");
        }
    
        /**
         * 提供服务
         * 1.调用时机:每一次Servlet被访问时,调用
         * 2.调用次数: 多次
         * @param req
         * @param res
         * @throws ServletException
         * @throws IOException
         */
        public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
            System.out.println("servlet hello world~");
        }
    
        /**
         * 销毁方法
         * 1.调用时机:内存释放或者服务器关闭的时候,Servlet对象会被销毁,调用
         * 2.调用次数: 1次
         */
        public void destroy() {
            System.out.println("destroy...");
        }
        public ServletConfig getServletConfig() {
            return null;
        }
    
        public String getServletInfo() {
            return null;
        }
    
注意:如何才能让Servlet中的destroy方法被执行?
在Terminal命令行中,先使用`mvn tomcat7:run`启动,然后再使用`ctrl+c`关闭tomcat
  • Servlet对象在什么时候被创建的? 

           默认是第一次访问的时候被创建,可以使用@WebServlet(urlPatterns = "/demo2",loadOnStartup = 1)的loadOnStartup修改成在服务器启动的时候创建。

  • Servlet生命周期中涉及到的三个方法,这三个方法是什么?什么时候被调用?调用几次?      

         涉及到三个方法,分别是 init()、service()、destroy()

         init方法在Servlet对象被创建的时候执行,只执行1次

         service方法在Servlet被访问的时候调用,每访问1次就调用1次

         destroy方法在Servlet对象被销毁的时候调用,只执行1次

  • Servlet中总共有5个方法
在Servlet被创建时执行,只执行一次
void init(ServletConfig config) 
提供服务方法, 每次Servlet被访问,都会调用该方法
void service(ServletRequest req, ServletResponse res)
销毁方法,当Servlet被销毁时,调用该方法。在内存释放或服务器关闭时销毁Servlet
void destroy() 

                getServletInfo()和getServletConfig()这两个方法使用的不是很多,了解即可。 

获取Servlet信息
String getServletInfo() 
//该方法用来返回Servlet的相关信息,没有什么太大的用处,一般我们返回一个空字符串即可
public String getServletInfo() {
    return "";
}
获取ServletConfig对象
ServletConfig getServletConfig()

 ServletConfig对象,在init方法的参数中有,而Tomcat Web服务器在创建Servlet对象的时候会调用init方法,必定会传入一个ServletConfig对象,我们只需要将服务器传过来的ServletConfig进行返回即可。具体如何操作?

@WebServlet(urlPatterns = "/demo3",loadOnStartup = 1)
public class ServletDemo3 implements Servlet {

    private ServletConfig servletConfig;
    /**
     *  初始化方法
     *  1.调用时机:默认情况下,Servlet被第一次访问时,调用
     *      * loadOnStartup: 默认为-1,修改为0或者正整数,则会在服务器启动的时候,调用
     *  2.调用次数: 1次
     * @param config
     * @throws ServletException
     */
    public void init(ServletConfig config) throws ServletException {
        this.servletConfig = config;
        System.out.println("init...");
    }
    public ServletConfig getServletConfig() {
        return servletConfig;
    }

    /**
     * 提供服务
     * 1.调用时机:每一次Servlet被访问时,调用
     * 2.调用次数: 多次
     * @param req
     * @param res
     * @throws ServletException
     * @throws IOException
     */
    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
        System.out.println("servlet hello world~");
    }

    /**
     * 销毁方法
     * 1.调用时机:内存释放或者服务器关闭的时候,Servlet对象会被销毁,调用
     * 2.调用次数: 1次
     */
    public void destroy() {
        System.out.println("destroy...");
    }

    public String getServletInfo() {
        return "";
    }
}

Servlet的体系结构

   开发B/S架构的web项目,都是针对HTTP协议,我们自定义Servlet,会通过继承HttpServlet

HttpServlet简化Servlet开发

 具体的编写格式如下:

@WebServlet("/demo4")
public class ServletDemo4 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //TODO GET 请求方式处理逻辑
        System.out.println("get...");
    }
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //TODO Post 请求方式处理逻辑
        System.out.println("post...");
    }
}
  • 要想发送一个GET请求,请求该Servlet,只需要通过浏览器发送http://localhost:8080/web-demo/demo4, 就能看到doGet方法被执行了
  • 要想发送一个POST请求,请求该Servlet,单单通过浏览器是无法实现的,这个时候就需要编写一个form表单来发送请求,在webapp下创建一个a.html页面,内容如下:
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
        <form action="/web-demo/demo4" method="post">
            <input name="username"/><input type="submit"/>
        </form>
    </body>
    </html>

 启动测试,即可看到doPost方法被执行了。

Servlet的简化编写就介绍完了,接着需要思考两个问题:

  1. HttpServlet中为什么要根据请求方式的不同,调用不同的方法?
  2. 如何调用?

针对问题一,回顾之前的知识点:前端发送GET和POST请求的时候,参数的位置不一致,GET请求参数在请求行中,POST请求参数在请求体中,为了能处理不同的请求方式,我们得在service方法中进行判断,然后写不同的业务处理,这样能实现,但是每个Servlet类中都将有相似的代码,针对这个问题,有什么可以优化的策略么? 

有,获取请求方式,判断请求方式,根据不同的请求方式进行不同的业务处理。通过HttpServlet,翻开源码,service()方法里面不仅可以处理GET和POST还可以处理其他五种请求方式。

protected void service(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException
    {
        String method = req.getMethod();

        if (method.equals(METHOD_GET)) {
            long lastModified = getLastModified(req);
            if (lastModified == -1) {
                // servlet doesn't support if-modified-since, no reason
                // to go through further expensive logic
                doGet(req, resp);
            } else {
                long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
                if (ifModifiedSince < lastModified) {
                    // If the servlet mod time is later, call doGet()
                    // Round down to the nearest second for a proper compare
                    // A ifModifiedSince of -1 will always be less
                    maybeSetLastModified(resp, lastModified);
                    doGet(req, resp);
                } else {
                    resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
                }
            }

        } else if (method.equals(METHOD_HEAD)) {
            long lastModified = getLastModified(req);
            maybeSetLastModified(resp, lastModified);
            doHead(req, resp);

        } else if (method.equals(METHOD_POST)) {
            doPost(req, resp);

        } else if (method.equals(METHOD_PUT)) {
            doPut(req, resp);

        } else if (method.equals(METHOD_DELETE)) {
            doDelete(req, resp);

        } else if (method.equals(METHOD_OPTIONS)) {
            doOptions(req,resp);

        } else if (method.equals(METHOD_TRACE)) {
            doTrace(req,resp);

        } else {
            //
            // Note that this means NO servlet supports whatever
            // method was requested, anywhere on this server.
            //
            String errMsg = lStrings.getString("http.method_not_implemented");
            Object[] errArgs = new Object[1];
            errArgs[0] = method;
            errMsg = MessageFormat.format(errMsg, errArgs);

            resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
        }
    }
  • HttpServlet的使用步骤

继承HttpServlet

重写doGet和doPost方法

  • HttpServlet原理

获取请求方式,并根据不同的请求方式,调用不同的doXxx方法

  • urlPattern配置 

 Servlet类编写好后,要想被访问到,就需要配置其访问路径(urlPattern)

① 一个Servlet,可以配置多个urlPattern

②源码

@WebServlet(urlPatterns = {"/demo1","/demo2"})
public class ServletDemo7 extends HttpServlet {

    @Override
    protected void doGet(ServletRequest req, ServletResponse res) {

        System.out.println("demo1 get...");
    }
    @Override
    protected void doPost(ServletRequest req, ServletResponse res) {
    }
}

 在浏览器上输入http://localhost:8080/web-demo/demo1,http://localhost:8080/web-demo/demo2这两个地址都能访问到ServletDemo7的doGet方法。

urlPattern配置规则

  • 精确匹配

@WebServlet(urlPatterns = "/user/select")
public class ServletDemo8 extends MyHttpServlet {

    @Override
    protected void doGet(ServletRequest req, ServletResponse res) {

        System.out.println("demo8 get...");
    }
    @Override
    protected void doPost(ServletRequest req, ServletResponse res) {
    }
}
  • 目录匹配

        

@WebServlet(urlPatterns = "/user/*")
public class ServletDemo9 extends MyHttpServlet {

    @Override
    protected void doGet(ServletRequest req, ServletResponse res) {

        System.out.println("demo9 get...");
    }
    @Override
    protected void doPost(ServletRequest req, ServletResponse res) {
    }
}

访问路径http://localhost:8080/web-demo/user/任意

==思考:==

  1. 访问路径http://localhost:8080/web-demo/user是否能访问到demo9的doGet方法?能
  2. 访问路径http://localhost:8080/web-demo/user/a/b是否能访问到demo9的doGet方法?能
  3. 访问路径http://localhost:8080/web-demo/user/select是否能访问到demo9还是demo8的doGet方法?demo8
  4. /user/*中的/*代表的是零或多个层级访问目录同时精确匹配优先级要高于目录匹配。
  • 扩展名匹配

            

@WebServlet(urlPatterns = "*.do")
public class ServletDemo10 extends MyHttpServlet {

    @Override
    protected void doGet(ServletRequest req, ServletResponse res) {

        System.out.println("demo10 get...");
    }
    @Override
    protected void doPost(ServletRequest req, ServletResponse res) {
    }
}

 访问路径http://localhost:8080/web-demo/任意.do

==注意==:

如果路径配置的不是扩展名,那么在路径的前面就必须要加/否则会报错

如果路径配置的是*.do,那么在*.do的前面不能加/,否则会报错

  •  任意匹配

            

/**
 * UrlPattern:
 * * 任意匹配: /
 */
@WebServlet(urlPatterns = "/")
public class ServletDemo11 extends MyHttpServlet {

    @Override
    protected void doGet(ServletRequest req, ServletResponse res) {

        System.out.println("demo11 get...");
    }
    @Override
    protected void doPost(ServletRequest req, ServletResponse res) {
    }
}

   访问路径http://localhost:8080/demo-web/任意

@WebServlet(urlPatterns = "/*")
public class ServletDemo12 extends MyHttpServlet {

    @Override
    protected void doGet(ServletRequest req, ServletResponse res) {

        System.out.println("demo12 get...");
    }
    @Override
    protected void doPost(ServletRequest req, ServletResponse res) {
    }
}

访问路径`http://localhost:8080/demo-web/任意

==注意:==//*的区别?

  1. 当我们的项目中的Servlet配置了 "/",会覆盖掉tomcat中的DefaultServlet,当其他的url-pattern都匹配不上时都会走这个Servlet

  2. 当我们的项目中配置了"/*",意味着匹配任意访问路径

  3. DefaultServlet是用来处理静态资源,如果配置了"/"会把默认的覆盖掉,就会引发请求静态资源的时候没有走默认的而是走了自定义的Servlet类,最终导致静态资源不能被访问

 小结

  1. urlPattern总共有四种配置方式,分别是精确匹配、目录匹配、扩展名匹配、任意匹配

  2. 五种配置的优先级为 精确匹配 > 目录匹配> 扩展名匹配 > /* > / ,无需记,以最终运行结果为准。

XML配置

前面对应Servlet的配置,我们都使用的是@WebServlet,这个是Servlet从3.0版本后开始支持注解配置,3.0版本前只支持XML配置文件的配置方法。

对于XML的配置步骤有两步:

  • 编写Servlet类

public class ServletDemo13 extends MyHttpServlet {

    @Override
    protected void doGet(ServletRequest req, ServletResponse res) {

        System.out.println("demo13 get...");
    }
    @Override
    protected void doPost(ServletRequest req, ServletResponse res) {
    }
}
  • 在web.xml中配置该Servlet
<?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_4_0.xsd"
         version="4.0">



    <!-- 
        Servlet 全类名
    -->
    <servlet>
        <!-- servlet的名称,名字任意-->
        <servlet-name>demo13</servlet-name>
        <!--servlet的类全名-->
        <servlet-class>com.stu.web.ServletDemo13</servlet-class>
    </servlet>

    <!-- 
        Servlet 访问路径
    -->
    <servlet-mapping>
        <!-- servlet的名称,要和上面的名称一致-->
        <servlet-name>demo13</servlet-name>
        <!-- servlet的访问路径-->
        <url-pattern>/demo13</url-pattern>
    </servlet-mapping>
</web-app>

这种配置方式和注解比起来,确认麻烦很多,所以建议大家使用注解来开发。但是大家要认识上面这种配置方式,因为并不是所有的项目都是基于注解开发的。

五、Request&Response

RequestResponse的概述

Request 是请求对象, Response 是响应对象。 这两个对象在我们使用 Servlet 的时候有看到:

 request和response这两个参数的作用是什么?

request:获取请求数据

  • 浏览器会发送HTTP请求到后台服务器[Tomcat]
  • HTTP的请求中会包含很多请求数据[请求行+请求头+请求体]
  • 后台服务器[Tomcat]会对HTTP请求中的数据进行解析并把解析结果存入到一个对象中
  • 所存入的对象即为request对象,所以我们可以从request对象中获取请求的相关参数
  • 获取到数据后就可以继续后续的业务,比如获取用户名和密码就可以实现登录操作的相关业务
response: 设置 响应数据
  • 业务处理完后,后台就需要给前端返回业务处理的结果即响应数据
  • 把响应数据封装到response对象中
  • 后台服务器[Tomcat]会解析response对象,按照[响应行+响应头+响应体]格式拼接结果
  • 浏览器最终解析结果,把内容展示在浏览器给用户浏览
来初步体验下 request response 对象的使用
@WebServlet("/demo3")
public class ServletDemo3 extends HttpServlet {
      @Override
   protected void doGet(HttpServletRequest request, HttpServletResponse
      response) throws ServletException, IOException {
   //使用request对象 获取请求数据
  String name = request.getParameter("name");//url?name=zhangsan

   //使用response对象 设置响应数据
   response.setHeader("content-type","text/html;charset=utf-8");
   response.getWriter().write("<h1>"+name+",欢迎您!</h1>");
 }
 
      @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse
    response) throws ServletException, IOException {
    System.out.println("Post...");
    }
 }
启动成功后就可以通过浏览器来访问,并且根据传入参数的不同就可以在页面上展示不同的内容 :

Request对象

Request继承体系

  • 当我们的Servlet类实现的是Servlet接口的时候,service方法中的参数是ServletRequest ServletResponse
  • 当我们的Servlet类继承的是HttpServlet类的时候,doGetdoPost方法中的参数就变成HttpServletRequestHttpServletReponse
  • ServletRequest和HttpServletRequest的关系是什么?
  • request对象是有谁来创建的?
  • request提供了哪些API,这些API从哪里查?
下面是 Request 的继承体系

 从上图中可以看出,ServletRequestHttpServletRequest都是Java提供的,所以我们可以打

JavaEE 提供的 API 文档 [ 参考 : 资料 /JavaEE7-api.chm], 打开后可以看到 :

 ServletRequest和HttpServletRequest是继承关系,并且两个都是接口,接口是无法创建对象,那么实现和继承了这些ServletRequest和HttpServletRequest的类,方法的参数对象是由谁创建呢?

       

这个时候,我们就需要用到Request继承体系中的RequestFacade :

  • 该类实现了HttpServletRequest接口,也间接实现了ServletRequest接口。
  • Servlet类中的service方法、doGet方法或者是doPost方法最终都是由Web服务器[Tomcat] 来调用的,所以Tomcat提供了方法参数接口的具体实现类,并完成了对象的创建
  • 要想了解RequestFacade中都提供了哪些方法,我们可以直接查看JavaEEAPI文档中关于 ServletRequestHttpServletRequest的接口文档,因为RequestFacade实现了其接口就需要重写接口中的方法 
对于上述结论,要想验证,可以编写一个 Servlet ,在方法中把 request 对象打印下,就能看到最终
的对象是不是 RequestFacade, 代码如下 :
@WebServlet("/demo2")
public class ServletDemo2 extends HttpServlet {
    @Override
protected void doGet(HttpServletRequest request, HttpServletResponse
               response) throws ServletException, IOException {
       System.out.println(request);
  }

    @Override
 protected void doPost(HttpServletRequest request, HttpServletResponse
               response) throws ServletException, IOException {
  }
}

启动服务器,运行访问http://localhost:8080/request-demo/demo2 ,得到运行结果:

小结
  • Request的继承体系为ServletRequest-->HttpServletRequest-->RequestFacade
  • Tomcat需要解析请求数据,封装为request对象,并且创建request对象传递到service方法
  • 使用request对象,可以查阅JavaEE API文档的HttpServletRequest接口中方法说明

Request获取请求数据 

获取请求行数据
请求行包含三块内容,分别是 请求方式、请求资源路径、HTTP协议及版本
对于这三部分内容, request 对象都提供了对应的 API 方法来获取,具体如下 :
  • 获取请求方式: GET 
    String getMethod()
  • 获取虚拟目录(项目访问路径): /request-demo
     String getContextPath()
  • 获取URL(统一资源定位符): http://localhost:8080/request-demo/req1
    StringBuffer getRequestURL()
  • 获取URI(统一资源标识符): /request-demo/req1
     String getRequestURI()
  • 获取请求参数(GET方式): username=zhangsan&password=123
String getQueryString()

获取请求头数据

对于请求头的数据,格式为key: value如下:

 所以根据请求头名称获取对应值的方法为: 

String getHeader(String name)
在代码中如果想要获取客户端浏览器的版本信息,则可以使用
/**
* request 获取请求数据
*/
@WebServlet("/req1")
   public class RequestDemo1 extends HttpServlet {
     @Override
  protected void doGet(HttpServletRequest req, HttpServletResponse resp)
                    throws ServletException, IOException {
           //获取请求头: user-agent: 浏览器的版本信息
         String agent = req.getHeader("user-agent");
         System.out.println(agent);
   }
     @Override
  protected void doPost(HttpServletRequest req, HttpServletResponse resp)
 throws ServletException, IOException {
   }
}

  重新启动服务器后,http://localhost:8080/request-demo/req1?                          username=zhangsan&passwrod=123,获取的结果如下:

 获取请求体数据

浏览器在发送 GET 请求的时候是没有请求体的,所以需要把请求方式变更为POST,请求体中的数据格式如下:
                       

对于请求体中的数据,Request对象提供了如下两种方式来获取其中的数据,分别是: 

获取字节输入流,如果前端发送的是字节数据,比如传递的是文件数据,则使用该方法

ServletInputStream getInputStream()   该方法可以获取字节

获取字符输入流,如果前端发送的是纯文本数据,则使用该方法 

BufferedReader getReader()
获取到请求体的内容该如何实现 ?
具体实现的步骤如下 :
1. 准备一个页面,在页面中添加 form 表单 , 用来发送 post 请求
2. Servlet doPost 方法中获取请求体数据
3. doPost 方法中使用 request getReader() 或者 getInputStream() 来获取
4. 访问测试

1. 在项目的webapp目录下添加一个html页面,名称为:req.html

<!DOCTYPE html> <html lang="en"> <head>
<meta charset="UTF-8"> 
<title>Title</title> 
</head>
<body>
 <!-- 
action:form表单提交的请求地址
method:请求方式,指定为post
 --> 
 <form action="/request-demo/req1" method="post">
 <input type="text" name="username"> 
 <input type="password" name="password"> 
 <input type="submit">
 </form> 
 </body> 
 </html>

 2.ServletdoPost方法中获取数据

/**
 * request 获取请求数据
 */
@WebServlet("/req1")
   public class RequestDemo1 extends HttpServlet {
 @Override
  protected void doGet(HttpServletRequest req, HttpServletResponse resp)
                               throws ServletException, IOException {
      }
@Override
 protected void doPost(HttpServletRequest req, HttpServletResponse resp)
                               throws ServletException, IOException {
            //在此处获取请求体中的数据
       }
 }
3. 调用 getReader() 或者 getInputStream() 方法,因为目前前端传递的是纯文本数据,
    所以我们 采用 getReader()方法来获取
/**
 * request 获取请求数据
 */
@WebServlet("/req1")
   public class RequestDemo1 extends HttpServlet {
 @Override
  protected void doGet(HttpServletRequest req, HttpServletResponse resp)
                               throws ServletException, IOException {
      }
@Override
 protected void doPost(HttpServletRequest req, HttpServletResponse resp)
                               throws ServletException, IOException {
            //在此处获取请求体中的数据
            //获取post 请求体:请求参数 
            //1. 获取字符输入流
            BufferedReader br = req.getReader();
           //2. 读取数据 
            String line = br.readLine(); 
            System.out.println(line);
       }
 }
注意
       BufferedReader 流是通过 request 对象来获取的,当请求完成后 request 对象就会被销毁,
       request 对象被销毁后, BufferedReader 流就会自动关闭,所以此处就不需要手动关闭流了。

启动服务器,通过浏览器访问http://localhost:8080/request-demo/req.html  点击提交按钮后,就可以在控制台看到前端所发送的请求数据

GET 请求和 POST 请求获取请求参数的方式不一样,在获取请求参数这块该如何实现呢 ?
     要想实现,我们就需要 思考 : GET 请求方式和 POST 请求方式区别主要在于获取请求参数的方式不一样,是否可以提供一种 统一 获取 请求参数的方式,从而 统一 doGet doPost 方法内的代码 ?
解决方案一 :
 @WebServlet("/req1")
  public class RequestDemo1 extends HttpServlet {
    @Override
  protected void doGet(HttpServletRequest req, HttpServletResponse resp)
                      throws ServletException, IOException {
        //获取请求方式
        String method = req.getMethod();
       //获取请求参数
         String params = "";
          if("GET".equals(method)){
            params = req.getQueryString();
            }else if("POST".equals(method)){
          BufferedReader reader = req.getReader();
           params = reader.readLine();
        }
           //将请求参数进行打印控制台
          System.out.println(params);

    }
 @Override
 protected void doPost(HttpServletRequest req, HttpServletResponse resp)
                    throws ServletException, IOException {
     this.doGet(req,resp);
   }
 }

使用requestgetMethod()来获取请求方式,根据请求方式的不同分别获取请求参数值,这样就可 以解决上述问题,但是以后每个Servlet都需要这样写代码,实现起来比较麻烦,这种方案我们不采 用

解决方案二 :
request 对象已经将上述获取请求参数的方法进行了封装,并且 request 提供的方法实现的功能更强
大,以后只需要调用 request 提供的方法即可,在 request 的方法中都实现了哪些操作 ?
(1)根据不同的请求方式获取请求参数,获取的内容如下:
                      
(2) 把获取到的内容进行分割,内容如下 :
          

 (3)把分割后端数据,存入到一个Map集合中:  

                                          

注意 : 因为参数的值可能是一个,也可能有多个,所以 Map 的值的类型为 String 数组。
基于上述理论, request 对象为我们提供了如下方法 :
  • 获取所有参数Map集合
Map<String,String[]> getParameterMap()
  • 根据名称获取参数值(数组)
String[] getParameterValues(String name)
  • 根据名称获取参数值(单个值)
String getParameter(String name)

实例演示:

1. 修改 req.html 页面,添加爱好选项,爱好可以同时选多个
 <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/request-demo/req2" method="get">
 <input type="text" name="username"><br>
<input type="password" name="password"><br>
<input type="checkbox" name="hobby" value="1"> 游泳
<input type="checkbox" name="hobby" value="2"> 爬山 <br>
<input type="submit">
</form>
</body>
</html>

2. Servlet 代码中获取页面传递 GET 请求的参数值

2.1获取 GET 方式的所有请求参数
/**
 * request 通用方式获取请求参数
*/
 @WebServlet("/req2")
 public class RequestDemo2 extends HttpServlet {
@Override
 protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
//GET请求逻辑
 System.out.println("get....");
 //1. 获取所有参数的Map集合
 Map<String, String[]> map = req.getParameterMap();
 for (String key : map.keySet()) {
 // username:zhangsan lisi
 System.out.print(key+":");
//获取值
 String[] values = map.get(key);
 for (String value : values) {
 System.out.print(value + " ");
 }
System.out.println();
 }
 }

 @Override
 protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
 }
 }
获取的结果为 :
                       
 
2.2 获取 GET 请求参数中的爱好,结果是数组值
/**
 * request 通用方式获取请求参数
 */
 @WebServlet("/req2")
 public class RequestDemo2 extends HttpServlet {
 @Override
 protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
 //GET请求逻辑
 //...
 System.out.println("------------");
 String[] hobbies = req.getParameterValues("hobby");
 for (String hobby : hobbies) {
 System.out.println(hobby);
 }
 }

 @Override
 protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
 }
 }
获取的结果为 :
                       

2.3获取GET请求参数中的用户名和密码,结果是单个值           

 /**
 * request 通用方式获取请求参数
 */
 @WebServlet("/req2")
 public class RequestDemo2 extends HttpServlet {
 @Override
 protected void doGet(HttpServletRequest req, HttpServletResponse resp)
         throws ServletException, IOException {
 //GET请求逻辑
 //...
 String username = req.getParameter("username");
 String password = req.getParameter("password");
 System.out.println(username);
 System.out.println(password);
 }

 @Override
 protected void doPost(HttpServletRequest req, HttpServletResponse resp)
         throws ServletException, IOException {
 }
 }
获取的结果为:
                 

3. Servlet 代码中获取页面传递 POST 请求的参数值
3.1将 req.html 页面 form 表单的提交方式改成 post
3.2 doGet 方法中的内容复制到 doPost 方法中即可
小结
req.getParameter()方法使用的频率会比较高
以后我们再写代码的时候,就只需要按照如下格式来编写 :
public class RequestDemo1 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
//采用request提供的获取请求参数的通用方式来获取请求参数
//编写其他的业务代码...
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
this.doGet(req,resp);
}
}

请求参数中文乱码问题

POST请求解决方案

  • 分析出现中文乱码的原因:
  • POST的请求参数是通过requestgetReader()来获取流中的数据
  • TOMCAT在获取流的时候采用的编码是ISO-8859-1
  • ISO-8859-1编码是不支持中文的,所以会出现乱码
  • 解决方案:
  • 页面设置的编码格式为UTF-8
  • TOMCAT在获取流数据之前的编码设置为UTF-8

通过request.setCharacterEncoding("UTF-8")设置编码,UTF-8也可以写成小写

/**
* 中文乱码问题解决方案
*/
@WebServlet("/req4")
public class RequestDemo4 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse
response) throws ServletException, IOException {
//1. 解决乱码: POST getReader()
//设置字符输入流的编码,设置的字符集要和页面保持一致
request.setCharacterEncoding("UTF-8");
//2. 获取username
String username = request.getParameter("username");
System.out.println(username);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse
response) throws ServletException, IOException {
this.doGet(request, response);
}
}
 
重新发送 POST 请求,就会在控制台看到正常展示的中文结果。
  • 至此POST请求中文乱码的问题就已经解决,但是这种方案不适用于GET请求,因为两者获取请求参数的方式不同,
    所以 GET 请求不能用设置编码的方式来解决中文乱码问题
    • GET请求获取请求参数的方式是request.getQueryString()
    • POST请求获取请求参数的方式是request.getReader()
    • request.setCharacterEncoding("utf-8")是设置request处理流的编码
    • getQueryString方法并没有通过流的方式获取数据

GET请求中文参数出现乱码的原因

1. 首先我们需要先分析下 GET 请求出现乱码的原因
(1) 浏览器通过 HTTP 协议发送请求和数据给后台服务器( Tomcat)
(2) 浏览器在发送 HTTP 的过程中会对中文数据进行 URL 编码
(3) 在进行 URL 编码的时候会采用页面 <meta> 标签指定的 UTF-8 的方式进行编码, 张三 编码后的结果 为 %E5%BC%A0%E4%B8%89
(4) 后台服务器 (Tomcat) 接收到 %E5%BC%A0%E4%B8%89 后会默认按照 ISO-8859-1 进行
URL 解码
(5) 由于前后编码与解码采用的格式不一样,就会导致后台获取到的数据为乱码。
思考 : 如果把 req.html 页面的 <meta> 标签的 charset 属性改成 ISO - 8859 - 1 , 后台不做操作,能解
决中文乱码问题么 ?
答案是否定的,因为 ISO - 8859 - 1 本身是不支持中文展示的,所以改了标签的 charset 属性后,会导 致页面上的中文内容都无法正常展示。
分析完上面的问题后,我们会发现,其中有两个我们不熟悉的内容就是 URL 编码 URL 解码 ,什么是 URL编码,什么又是 URL 解码呢 ?
URL 编码
这块知识我们只需要了解下即可 , 具体编码过程分两步,分别是 :
(1) 将字符串按照编码方式转为二进制
(2) 每个字节转为 2 16 进制数并在前边加上 %
张三 按照 UTF-8 的方式转换成二进制的结果为:
                  1110 0101 1011 1100 1010 0000 1110 0100 1011 1000 1000 1001
这个结果是如何计算的 ?
使用 http://www.mytju.com/classcode/tools/encode_utf8.asp ,输入 张三

 就可以获取张和三分别对应的10进制,然后在使用计算器,选择程序员模式,计算出对应的二进制数据结果

在计算的十六进制结果中,每两位前面加一个 %, 就可以获取到 %E5%BC%A0%E4%B8%89
当然你从上面所提供的网站中就已经能看到编码 16进制的结果了:
Java 中已经为我们提供了编码和解码的 API 工具类可以让我们更快速的进行编码
和解码 :
编码 :
   java . net . URLEncoder . encode ( " 需要被编码的内容 " , " 字符集 (UTF-8)" )
解码 :
   java . net . URLDecoder . decode ( " 需要被解码的内容 " , " 字符集 (UTF-8)" )

GET请求中文参数出现乱码的原因

  • 浏览器把中文参数按照UTF-8进行URL编码
  • Tomcat对获取到的内容进行了ISO-8859-1URL解码
  • 在控制台就会出现类上å¼ ä¸

为所以我们可以考虑把å¼ ä¸ 转换成字节,在把字节转换成张三,在转换的过程中是它们的编码一致,就可以解决中文乱码问题。

GET请求中文乱码的解决方案

具体的实现步骤为:
1. 按照 ISO-8859-1 编码获取乱码å¼ ä¸对应的字节数组
2. 按照 UTF-8 编码获取字节数组对应的字符串


@WebServlet("/req4")
public class RequestDemo4 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse
response) throws ServletException, IOException {
//1. 解决乱码:POST,getReader()
//request.setCharacterEncoding("UTF-8");//设置字符输入流的编码

 //2. 获取username
 String username = request.getParameter("username");
 System.out.println("解决乱码前:"+username);

 //3. GET,获取参数的方式:getQueryString
 // 乱码原因:tomcat进行URL解码,默认的字符集ISO-8859-1
 /* //3.1 先对乱码数据进行编码:转为字节数组
 byte[] bytes = username.getBytes(StandardCharsets.ISO_8859_1);
 //3.2 字节数组解码
 username = new String(bytes, StandardCharsets.UTF_8);*/

 username = new
String(username.getBytes(StandardCharsets.ISO_8859_1),StandardCharsets.UTF_8) ;

System.out.println("解决乱码后:"+username);

 }

 @Override
 protected void doPost(HttpServletRequest request, HttpServletResponse
response) throws ServletException, IOException {
 this.doGet(request, response);
 }
 }
注意
  • request.setCharacterEncoding("UTF-8")代码注释掉后,会发现GET请求参数乱码解决方案 同时也可也把POST请求参数乱码的问题也解决了
  • 只不过对于POST请求参数一般都会比较多,采用这种方式解决乱码起来比较麻烦,所以对于POST
  • 请求还是建议使用设置编码的方式进行。
  • 另外需要说明一点的是Tomcat8.0之后,已将GET请求乱码问题解决,设置默认的解码方式为UTF-8

小结

1. 中文乱码解决方案

POST请求和GET请求的参数中如果有中文,后台接收数据就会出现中文乱码问题GET请求在Tomcat8.0以后的版本就不会出现了

POST请求解决方案是:设置输入流的编码

request.setCharacterEncoding("UTF-8"); 
注意:设置的字符集要和页面保持一致

通用方式(GET/POST):需要先解码,再编码

new String(username.getBytes("ISO-8859-1"),"UTF-8");

2. URL编码实现方式:

编码:

URLEncoder.encode(str,"UTF-8");

解码:

URLDecoder.decode(s,"ISO-8859-1");

 Request请求转发

1. 请求转发(forward):一种在服务器内部的资源跳转方式。

(1) 浏览器发送请求给服务器,服务器中对应的资源 A 接收到请求
(2) 资源 A 处理完请求后将请求发给资源 B
(3) 资源 B 处理完后将结果响应给浏览器
(4) 请求从资源 A 到资源 B 的过程就叫 请求转发
2. 请求转发的实现方式 :
req.getRequestDispatcher("资源B路径").forward(req,resp);
具体如何来使用,我们先来看下需求:

针对上述需求,具体的实现步骤为 :
1. 创建一个 RequestDemo5 类,接收 /req5 的请求,在 doGet 方法中打印 demo5
2. 创建一个 RequestDemo6 类,接收 /req6 的请求,在 doGet 方法中打印 demo6
3. RequestDemo5 的方法中使用
req.getRequestDispatcher("/req6").forward(req,resp) 进行请求转发
4. 启动测试

(1)创建RequestDemo5 

(2) 创建RequestDemo6类
/**
 * 请求转发
 */
 @WebServlet("/req6")
public class RequestDemo6 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse
response) throws ServletException, IOException {
 System.out.println("demo6...");
 }

 @Override
 protected void doPost(HttpServletRequest request, HttpServletResponse
response) throws ServletException, IOException {
 this.doGet(request, response);
 }
 }

(3) RequestDemo5 doGet 方法中进行请求转发
/**
 * 请求转发
 */
 @WebServlet("/req5")
 public class RequestDemo5 extends HttpServlet {
 @Override
 protected void doGet(HttpServletRequest request, HttpServletResponse
response) throws ServletException, IOException {
 System.out.println("demo5...");
 //请求转发
 request.getRequestDispatcher("/req6").forward(request,response);
 }

 @Override
 protected void doPost(HttpServletRequest request, HttpServletResponse
response) throws ServletException, IOException {
 this.doGet(request, response);
 }
}
(4)启动测试

 说明请求已经转发到了/req6

3. 请求转发资源间共享数据 : 使用 Request 对象
此处主要解决的问题是把请求从 /req5 转发到 /req6 的时候,如何传递数据给 /req6
需要使用 request 对象提供的三个方法 :
  • 存储数据到request[范围,数据是存储在request对象]
    void setAttribute(String name,Object o);
  • 根据key获取值
     Object getAttribute(String name);
  • 根据key删除该键值对
     void removeAttribute(String name);

接着按上面的需求:

1. RequestDemo5 doGet 方法中转发请求之前,将数据存入 request 域对象中
2. RequestDemo6 doGet 方法从 request 域对象中获取数据,并将数据打印到控制台
3. 启动访问测试

 (1)修改RequestDemo5中的方法

@WebServlet("/req5")
 public class RequestDemo5 extends HttpServlet {
 @Override
 protected void doGet(HttpServletRequest request, HttpServletResponse
response) throws ServletException, IOException {
 System.out.println("demo5...");
 //存储数据
 request.setAttribute("msg","hello");
 //请求转发
 request.getRequestDispatcher("/req6").forward(request,response);

 }

 @Override
 protected void doPost(HttpServletRequest request, HttpServletResponse
response) throws ServletException, IOException {
 this.doGet(request, response);
 }
 }
(2) 修改 RequestDemo6 中的方法
/**
* 请求转发
*/
@WebServlet("/req6")
public class RequestDemo6 extends HttpServlet {
 @Override
 protected void doGet(HttpServletRequest request, HttpServletResponse
response) throws ServletException, IOException {
 System.out.println("demo6...");
 //获取数据
 Object msg = request.getAttribute("msg");
 System.out.println(msg);

 }

 @Override
 protected void doPost(HttpServletRequest request, HttpServletResponse
response) throws ServletException, IOException {
 this.doGet(request, response);
 }
 }

(3)启动测试

访问http://localhost:8080/request-demo/req5 ,就可以在控制台看到如下内容:       

此时就可以实现在转发多个资源之间共享数据
4. 请求转发的特点
  • 浏览器地址栏路径不发生变化  
  • 只能转发到当前服务器的内部资源 不能从一个服务器通过转发访问另一台服务器
  • 一次请求,可以在转发资源间使用request共享数据 虽然后台从/req5转发到/req6,但是这个只有一次请求

Response对象 

  • Request:使用request对象来获取请求数据
  • Response:使用response对象来设置响应数据

 Reponse的继承体系和Request的继承体系也非常相似:

 Response设置响应数据功能介绍 

HTTP 响应数据总共分为三部分内容,分别是 响应行、响应头、响应体 ,对于这三部分内容的数据, respone对象都提供了哪些方法来进行设置 ?

1. 响应行

                      

 对于响应头,比较常用的就是设置响应状态码:

void setStatus(int sc); 
2. 响应头
                 

设置响应头键值对:

void setHeader(String name,String value);

 3. 响应体

           

对于响应体,是通过字符、字节输出流的方式往浏览器写,

获取字符输出流:

 PrintWriter getWriter();

获取字节输出流:

ServletOutputStream getOutputStream(); 

 Respones请求重定向

Response重定向(redirect):一种资源跳转方式。

                       

 (1)浏览器发送请求给服务器,服务器中对应的资源A接收到请求

 (2)资源A现在无法处理该请求,就会给浏览器响应一个302的状态码+location的一个访问资源B的 路径

(3) 浏览器接收到响应状态码为 302 就会重新发送请求到 location 对应的访问地址去访问资源 B
(4) 资源 B 接收到请求后进行处理并最终给浏览器响应结果,这整个过程就叫 重定向
2. 重定向的实现方式 :
resp.setStatus(302);
resp.setHeader("location"," 资源 B 的访问路径 ");

具体如何来使用,我们先来看下需求:

针对上述需求,具体的实现步骤为 :
1. 创建一个 ResponseDemo1 类,接收 /resp1 的请求,在 doGet 方法中打印 resp1....
2. 创建一个 ResponseDemo2 类,接收 /resp2 的请求,在 doGet 方法中打印 resp2....
3. ResponseDemo1 的方法中使用
    response.setStatus(302);
    response.setHeader("Location","/request-demo/resp2") 来给前端响应结果数据
4. 启动测试

 (1)创建ResponseDemo1

 @WebServlet("/resp1")
 public class ResponseDemo1 extends HttpServlet {
 @Override
 protected void doGet(HttpServletRequest request, HttpServletResponse
response) throws ServletException, IOException {
 System.out.println("resp1....");
 }

 @Override
 protected void doPost(HttpServletRequest request, HttpServletResponse
response) throws ServletException, IOException {
 this.doGet(request, response);
 }
 }
(2) 创建 ResponseDemo2  
@WebServlet("/resp2")
 public class ResponseDemo2 extends HttpServlet {
 @Override
 protected void doGet(HttpServletRequest request, HttpServletResponse
response) throws ServletException, IOException {
 System.out.println("resp2....");
 }

 @Override
 protected void doPost(HttpServletRequest request, HttpServletResponse
response) throws ServletException, IOException {
 this.doGet(request, response);
 }
 }

(3) ResponseDemo1 doGet 方法中给前端响应数据  
@WebServlet("/resp1")
 public class ResponseDemo1 extends HttpServlet {
 @Override
 protected void doGet(HttpServletRequest request, HttpServletResponse
response) throws ServletException, IOException {
 System.out.println("resp1....");
 //重定向
 //1.设置响应状态码 302
 response.setStatus(302);
 //2. 设置响应头 Location
 response.setHeader("Location","/request-demo/resp2");
 }

 @Override
 protected void doPost(HttpServletRequest request, HttpServletResponse
response) throws ServletException, IOException {
 this.doGet(request, response);
 }
 }
(4) 启动测试
访问 http://localhost:8080/request - demo/resp1 , 就可以在控制台看到如下内容 :
                

虽然功能已经实现,但是从设置重定向的两行代码来看,会发现除了重定向的地址不一样,其他的内容都是一模一样,所以request 对象给我们提供了简化的编写方式为
resposne.sendRedirect("/request-demo/resp2")
所以第 3 步中的代码就可以简化为:
@WebServlet("/resp1")
 public class ResponseDemo1 extends HttpServlet {
 @Override
 protected void doGet(HttpServletRequest request, HttpServletResponse
response) throws ServletException, IOException {
 System.out.println("resp1....");
 //重定向
resposne.sendRedirect("/request-demo/resp2");

 }

 @Override
 protected void doPost(HttpServletRequest request, HttpServletResponse
response) throws ServletException, IOException {
 this.doGet(request, response);
 }
 }
3. 重定向的特点
  • 浏览器地址栏路径发送变化
当进行重定向访问的时候,由于是由浏览器发送的两次请求,所以地址会发生变化
  •  可以重定向到任何位置的资源(服务内容、外部均可)

因为第一次响应结果中包含了浏览器下次要跳转的路径,所以这个路径是可以任意位置资源。

  •  两次请求,不能在多个资源使用request共享数据
因为浏览器发送了两次请求,是两个不同的 request 对象,就无法通过 request 对象进行共享数据

介绍完请求重定向请求转发以后,接下来需要把这两个放在一块对比下: 

 路径问题

1. 问题 1 :转发的时候路径上没有加 /request - demo 而重定向加了,那么到底什么时候需要加,什么
时候不需要加呢?
其实判断的依据很简单,只需要记住下面的规则即可 :
  • 浏览器使用:需要加虚拟目录(项目访问路径)
  • 服务端使用:不需要加虚拟目录
对于转发来说,因为是在服务端进行的,所以不需要加虚拟目录
对于重定向来说,路径最终是由浏览器来发送请求,就需要添加虚拟目录。
掌握了这个规则,接下来就通过一些练习来强化下知识的学习 :
  • <a href='路径'>
  • <form action='路径'>
  • req.getRequestDispatcher("路径")
  • resp.sendRedirect("路径") 

     

2. 问题 2 :在重定向的代码中, /request - demo 是固定编码的,如果后期通过 Tomcat 插件配置了项
目的访问路径,那么所有需要重定向的地方都需要重新修改,该如何优化 ?
                  
可以在代码中动态去获取项目访问的虚拟目录,具体如何获取,我们可以借助前面咱们所学习的request 对象中的 getContextPath() 方法
//简化方式完成重定向
//动态获取虚拟目录
String contextPath = request.getContextPath();
response.sendRedirect(contextPath+"/resp2");

Response响应字符数据

 要想将字符数据写回到浏览器,我们需要两个步骤:

  •  通过Response对象获取字符输出流: PrintWriter writer = resp.getWriter();
  • 通过字符输出流写数据: writer.write();
response.setContentType("text/html;charset=utf-8"); 
 //获取字符输出流 
PrintWriter writer = response.getWriter(); 
1. 返回一个简单的字符串aaa
 writer.write("aaa");
2. 返回一串html字符串,并且能被浏览器解析
PrintWriter writer = response.getWriter(); 
//content-type,告诉浏览器返回的数据类型是HTML类型数据,这样浏览器才会解析HTML标签 response.setHeader("content-type","text/html"); 
writer.write("<h1>aaa</h1>");
3. 返回一个中文的字符串你好,需要注意设置响应数据的编码为utf-8
//设置响应的数据格式及数据的编码 
response.setContentType("text/html;charset=utf-8"); 
writer.write("你好");

 注意:一次请求响应结束后,response对象就会被销毁掉,所以不要手动关闭流。

Response响应字节数据

要想将字节数据写回到浏览器,我们需要两个步骤 :
  • 通过Response对象获取字节输出流:

            ServletOutputStream outputStream = resp.getOutputStream();

  • 通过字节输出流写数据:

            outputStream.write(字节数据);

1. 返回一个图片文件到浏览器
/**
* 响应字节数据:设置字节数据的响应体
 */
 @WebServlet("/resp4")
 public class ResponseDemo4 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse
response) throws ServletException, IOException {
//1. 读取文件
FileInputStream fis = new FileInputStream("文件");
 //2. 获取response字节输出流
 ServletOutputStream os = response.getOutputStream();
 //3. 完成流的copy
 byte[] buff = new byte[1024];
 int len = 0;
 while ((len = fis.read(buff))!= -1){
os.write(buff,0,len);
 }
 fis.close();
 }

 @Override
 protected void doPost(HttpServletRequest request, HttpServletResponse
                 response) throws ServletException, IOException {
      this.doGet(request, response);
  }
}
对于流的 copy的代码还是比较复杂的,所以 可以使用别人提供好的方法来简化代码
的开发,具体的步骤是 :
(1)pom.xml 添加依赖
1 <dependency>
2 <groupId>commons-io</groupId>
3 <artifactId>commons-io</artifactId>
4 <version>2.6</version>
5 </dependency>
(2) 调用工具类方法
1 //fis: 输入流
2 //os: 输出流
3 IOUtils.copy(fis,os);
优化后的代码:
//1. 返回一个图片文件到浏览器
/**
* 响应字节数据:设置字节数据的响应体
 */
 @WebServlet("/resp4")
 public class ResponseDemo4 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse
       response) throws ServletException, IOException {
   //1. 读取文件 
  FileInputStream fis = new FileInputStream("d://a.jpg"); 
  //2. 获取response字节输出流
  ServletOutputStream os = response.getOutputStream(); 
  //3. 完成流的copy 
  IOUtils.copy(fis,os); 
  fis.close();
 }

 @Override
 protected void doPost(HttpServletRequest request, HttpServletResponse
                 response) throws ServletException, IOException {
      this.doGet(request, response);
  }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值