02、JavaEEServlet开发

Servlet

Servlet是sun公司提供的一门用于开发动态web资源的技术,sun公司在API中提供servlet接口,用户若想发一个动态web资源需要完成如下步骤:

  • 编写一个Java类,实现servlet接口。
  • 把开发好的Java类部署到web服务器中。

按照一种约定俗成的称呼习惯,通常我们也把实现了servlet接口的java程序,称之为Servlet。

Servlet程序是由WEB服务器调用,web服务器收到客户端的Servlet访问请求后:

A、Web服务器首先检查是否已经装载并创建了该Servlet的实例对象。如果是,则直接执行第④步,否则,执行第②步。
B、装载并创建该Servlet的一个实例对象。
C、调用Servlet实例对象的init()方法。
D、创建一个用于封装HTTP请求消息的HttpServletRequest对象和一个代表HTTP响应消息的HttpServletResponse对象,然后调用Servlet的service()
方法并将请求和响应对象作为参数传递进去。
E、WEB应用程序被停止或重新启动之前,Servlet引擎将卸载Servlet,并在卸载之前调用Servlet的destroy()方法。

它的运行流程图如下所示:

它的调用图如下所示:

开发Servlet

在eclipse中新建一个web project工程,eclipse会自动创建下图所示目录结构:

Servlet实现类

Sun公司给Servlet定义了两个默认的实现类,它们分别是:GenericServlet、HttpServlet。

HttpServlet能够处理HTTP请求的Servlet,它在原有Servlet的基础上新增一些处理HTTP协议的方法,经常在开发中使用。

HttpServlet在实现Service接口时,复写service()方法,该方法体内的代码会自动判断出用户的请求方式,比如GET请求则会默认调用HttpServlet的doGet()方法。

如果是POST请求,则调用doPost()方法。因此开发人员在编写Servlet时,通常只需要复写doGet()和doPost()方法即可,不需要复写service()方法。

Servlet创建

Servlet的创建流程如下所示:

这样,我们就通过Eclipse帮我们创建好一个名字为ServletDemo1的Servlet,创建好的ServletDemo01里面会有如下代码:

public class ServletDemo1 extends HttpServlet {
    public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html");
        PrintWriter out = response.getWriter();
        out.println("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">");
        out.println("<HTML>");
        out.println("  <HEAD><TITLE>A Servlet</TITLE></HEAD>");
        out.println("  <BODY>");
        out.print("    This is ");
        out.print(this.getClass());
        out.println(", using the GET method");
        out.println("  </BODY>");
        out.println("</HTML>");
        out.flush();
        out.close();
    }
    public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html");
        PrintWriter out = response.getWriter();
        out.println("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">");
        out.println("<HTML>");
        out.println("  <HEAD><TITLE>A Servlet</TITLE></HEAD>");
        out.println("  <BODY>");
        out.print("    This is ");
        out.print(this.getClass());
        out.println(", using the POST method");
        out.println("  </BODY>");
        out.println("</HTML>");
        out.flush();
        out.close();
    }
}

这些代码都是Eclipse自动生成的,而web.xml文件中也多了<servlet></servlet>和<servlet-mapping></servlet-mapping>两对标签,这两对标签是配置ServletDemo1的:

  <servlet>
    <description>This is the description of my J2EE component</description>
    <display-name>This is the display name of my J2EE component</display-name>
    <servlet-name>ServletDemo1</servlet-name>
    <servlet-class>com.legend.demo.ServletDemo1</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>ServletDemo1</servlet-name>
    <url-pattern>/servlet/ServletDemo1</url-pattern>
  </servlet-mapping>

然后我们就可以通过浏览器访问ServletDemo1这个Servlet:

http://localhost:8080/JavaWebServer/servlet/ServletDemo1

同一个Servlet可以被映射到多个URL上,即多个元素的子元素的设置值可以是同一个Servlet的注册名。 例如:

<servlet>
    <servlet-name>ServletDemo1</servlet-name>
    <servlet-class>com.legend.demo.ServletDemo1</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>ServletDemo1</servlet-name>
    <url-pattern>/servlet/ServletDemo1</url-pattern>
</servlet-mapping>
<servlet-mapping>
    <servlet-name>ServletDemo1</servlet-name>
    <url-pattern>/1.htm</url-pattern>
</servlet-mapping>
<servlet-mapping>
    <servlet-name>ServletDemo1</servlet-name>
    <url-pattern>/2.jsp</url-pattern>
</servlet-mapping>
<servlet-mapping>
    <servlet-name>ServletDemo1</servlet-name>
    <url-pattern>/3.php</url-pattern>
</servlet-mapping>
<servlet-mapping>
    <servlet-name>ServletDemo1</servlet-name>
    <url-pattern>/4.ASPX</url-pattern>
</servlet-mapping>

通过上面的配置,当我们想访问名称是ServletDemo1的Servlet,可以使用如下的几个地址去访问:

http://localhost:8080/JavaWeb_Servlet_Study_20140531/servlet/ServletDemo1
http://localhost:8080/JavaWeb_Servlet_Study_20140531/1.htm
http://localhost:8080/JavaWeb_Servlet_Study_20140531/2.jsp
http://localhost:8080/JavaWeb_Servlet_Study_20140531/3.php
http://localhost:8080/JavaWeb_Servlet_Study_20140531/4.ASPX

Servlet拓展

URL通配符

在Servlet映射到的URL中也可以使用通配符,但是只能有两种固定的格式:一种格式是".扩展名",另一种格式是以正斜杠(/)开头并以"/*"结尾。例如:

配置如下所示:

<servlet>
    <servlet-name>ServletDemo1</servlet-name>
    <servlet-class>com.legend.demo.ServletDemo1</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>ServletDemo1</servlet-name>
<url-pattern>/*</url-pattern>

匹配的规则是:谁长得更像,则匹配谁。

  • Servlet和普通类的区别

    Servlet是一个供其他Java程序(Servlet引擎)调用的Java类,它不能独立运行,它的运行完全由Servlet引擎来控制和调度

针对客户端的多次Servlet请求,通常情况下,服务器只会创建一个Servlet实例对象,也就是说Servlet实例对象一旦创建,它就会驻留在内存中,为后续的其

它请求服务,直至web容器退出,servlet实例对象才会销毁。
  在Servlet的整个生命周期内,Servlet的init方法只被调用一次。而对一个Servlet的每次访问请求都导致Servlet引擎调用一次servlet的service方法。对于

每次访问请求,Servlet引擎都会创建一个新的HttpServletRequest请求对象和一个新的HttpServletResponse响应对象,然后将这两个对象作为参数传递给它

调用的Servlet的service()方法,service方法再根据请求方式分别调用doXXX方法。

如果在<servlet>元素中配置一个<load-on-startup>元素,那么WEB应用程序在启动时,就会装载并创建Servlet的实例对象、以及调用Servlet实例对象的init()方法。

<servlet>
     <servlet-name>invoker</servlet-name>
     <servlet-class>
         org.apache.catalina.servlets.InvokerServlet
     </servlet-class>
     <load-on-startup>1</load-on-startup>
</servlet>

缺省Servlet

如果某个Servlet的映射路径仅仅为一个正斜杠(/),那么这个Servlet就成为当前Web应用程序的缺省Servlet。

  凡是在web.xml文件中找不到匹配的<servlet-mapping>元素的URL,它们的访问请求都将交给缺省Servlet处理,也就是说,缺省Servlet用于处理所有

其他Servlet都不处理的访问请求。 例如:

<servlet>
    <servlet-name>ServletDemo2</servlet-name>
    <servlet-class>com.legend.demo.ServletDemo2</servlet-class>
    <load-on-startup>1</load-on-startup>
</servlet>
  
<!-- 将ServletDemo2配置成缺省Servlet -->
<servlet-mapping>
    <servlet-name>ServletDemo2</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

当访问不存在的Servlet时,就使用配置的默认Servlet进行处理。

在<tomcat的安装目录>\conf\web.xml文件中,注册了一个名称为org.apache.catalina.servlets.DefaultServlet的Servlet,并将这个Servlet设置为缺省Servlet。

<servlet>
      <servlet-name>default</servlet-name>
      <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
      <init-param>
          <param-name>debug</param-name>
          <param-value>0</param-value>
      </init-param>
      <init-param>
          <param-name>listings</param-name>
          <param-value>false</param-value>
      </init-param>
      <load-on-startup>1</load-on-startup>
</servlet>
<!-- The mapping for the default servlet -->
<servlet-mapping>
      <servlet-name>default</servlet-name>
      <url-pattern>/</url-pattern>
</servlet-mapping>

当访问Tomcat服务器中的某个静态HTML文件和图片时,实际上是在访问这个缺省Servlet。

  • Servlet线程安全问题

    当多个客户端并发访问同一个Servlet时,web服务器会为每一个客户端的访问请求创建一个线程,并在这个线程上调用Servlet的service方法,因此service

方法内如果访问了同一个资源的话,就有可能引发线程安全问题。例如下面的代码:

public class ServletDemo3 extends HttpServlet {
    int i=1;
    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        i++;
        try {
            Thread.sleep(1000*4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        response.getWriter().write(i+"");
    }
    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        doGet(request, response);
    }
}

上面的代码则存在线程的安全性问题,下面的没有线程安全问题的代码:

public class ServletDemo3 extends HttpServlet {
    
    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        
        /**
         * 当多线程并发访问这个方法里面的代码时,会存在线程安全问题吗
         * i变量被多个线程并发访问,但是没有线程安全问题,因为i是doGet方法里面的局部变量,
         * 当有多个线程并发访问doGet方法时,每一个线程里面都有自己的i变量,
         * 各个线程操作的都是自己的i变量,所以不存在线程安全问题
         * 多线程并发访问某一个方法的时候,如果在方法内部定义了一些资源(变量,集合等)
         * 那么每一个线程都有这些东西,所以就不存在线程安全问题了
         */
        int i=1;
        i++;
        response.getWriter().write(i);
    }
    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        doGet(request, response);
    }
}

线程安全问题只存在多个线程并发操作同一个资源的情况下,所以在编写Servlet的时候,如果并发访问某一个资源(变量,集合等),就会存在线程安全问题,

那么该如何解决这个问题呢?

public class ServletDemo3 extends HttpServlet {
    int i=1;
    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        /**
         * 加了synchronized后,并发访问i时就不存在线程安全问题了,
         * 为什么加了synchronized后就没有线程安全问题了呢?
         * 假如现在有一个线程访问Servlet对象,那么它就先拿到了Servlet对象的那把锁
         * 等到它执行完之后才会把锁还给Servlet对象,由于是它先拿到了Servlet对象的那把锁,
         * 所以当有别的线程来访问这个Servlet对象时,由于锁已经被之前的线程拿走了,后面的线程只能排队等候了
         */
        synchronized (this) {//在java中,每一个对象都有一把锁,这里的this指的就是Servlet对象
            i++;
            try {
                Thread.sleep(1000 * 4);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            response.getWriter().write(i+"");
        } 
    }
    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        doGet(request, response);
    }
}

现在这种做法是给Servlet对象加了一把锁,保证任何时候都只有一个线程在访问该Servlet对象里面的资源,这样就不存在线程安全问题了。

这种做法虽然解决了线程安全问题,但是编写Servlet却万万不能用这种方式处理线程安全问题,假如有9999个人同时访问这个Servlet,那么

这9999个人必须按先后顺序排队轮流访问。

  • 过时的并发技术

针对Servlet的线程安全问题,Sun公司是提供有解决方案:让Servlet去实现一个SingleThreadModel接口,如果某个Servlet实现了SingleThreadModel接口,

那么Servlet引擎将以单线程模式来调用其service方法。

对于实现了SingleThreadModel接口的Servlet,Servlet引擎仍然支持对该Servlet的多线程并发访问,其采用的方式是产生多个Servlet实例对象,并发的每个线

程分别调用一个独立的Servlet实例对象。

ServletConfig

初始化参数

配置Servlet初始化参数

在Servlet的配置文件web.xml中,可以使用一个或多个<init-param>标签为servlet配置一些初始化参数。

<servlet>
    <servlet-name>ServletConfigDemo1</servlet-name>
    <servlet-class>com.legend.demo.ServletConfigDemo1</servlet-class>
    <!--配置ServletConfigDemo1的初始化参数 -->
    <init-param>
        <param-name>name</param-name>
        <param-value>legend</param-value>
    </init-param>
     <init-param>
        <param-name>password</param-name>
        <param-value>123456</param-value>
    </init-param>
</servlet>

通过ServletConfig获取初始化参数

当servlet配置了初始化参数后,web容器在创建servlet实例对象时,会自动将这些初始化参数封装到ServletConfig对象中,并在调用servlet的init方法时,将

ServletConfig对象传递给servlet。进而,我们通过ServletConfig对象就可以得到当前servlet的初始化参数信息。

public class ServletConfigDemo1 extends HttpServlet {
    /**
     * 定义ServletConfig对象来接收配置的初始化参数
     */
    private ServletConfig config;
    /**
     * 当servlet配置了初始化参数后,web容器在创建servlet实例对象时,
     * 会自动将这些初始化参数封装到ServletConfig对象中,并在调用servlet的init方法时,
     * 将ServletConfig对象传递给servlet。进而,程序员通过ServletConfig对象就可以
     * 得到当前servlet的初始化参数信息。
     */
    @Override
    public void init(ServletConfig config) throws ServletException {
        this.config = config;
    }
    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        //获取在web.xml中配置的初始化参数
        String paramVal = this.config.getInitParameter("name");//获取指定的初始化参数
        response.getWriter().print(paramVal);
        
        response.getWriter().print("<hr/>");
        //获取所有的初始化参数
        Enumeration<String> e = config.getInitParameterNames();
        while(e.hasMoreElements()){
            String name = e.nextElement();
            String value = config.getInitParameter(name);
            response.getWriter().print(name + "=" + value + "<br/>");
        }
    }
    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        this.doGet(request, response);
    }
}

ServletContext

WEB容器在启动时,它会为每个WEB应用程序都创建一个对应的ServletContext对象,它代表当前web应用。

ServletConfig对象中维护了ServletContext对象的引用,开发人员在编写servlet时,可以通过ServletConfig.getServletContext方法获得ServletContext对象。
由于一个WEB应用中的所有Servlet共享同一个ServletContext对象,因此Servlet对象之间可以通过ServletContext对象来实现通讯。ServletContext对象通常也被称之为context域对象。

Servlet间通信

ServletContextDemo1和ServletContextDemo2通过ServletContext对象实现数据共享:

ServletContextDemo1:

public class ServletContextDemo1 extends HttpServlet {
    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        String data = "hello,world";
        /**
         * ServletConfig对象中维护了ServletContext对象的引用,开发人员在编写servlet时,
         * 可以通过ServletConfig.getServletContext方法获得ServletContext对象。
         */
        ServletContext context = this.getServletConfig().getServletContext();//获得ServletContext对象
        context.setAttribute("data", data);  //将data存储到ServletContext对象中
    }
    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        doGet(request, response);
    }
}

ServletContextDemo2:

public class ServletContextDemo2 extends HttpServlet {
    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        ServletContext context = this.getServletContext();
        String data = (String) context.getAttribute("data");//从ServletContext对象中取出数据
        response.getWriter().print("data="+data);
    }
    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        doGet(request, response);
    }
}

运行ServletContextDemo1,将数据data存储到ServletContext对象中,然后运行ServletContextDemo2就可以从ServletContext对象中取出数据了,这样就实现了数据共享。

获取Web应用初始化参数

在web.xml文件中使用标签配置WEB应用的初始化参数,如下所示:

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
    http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
    <display-name></display-name>
    <!-- 配置WEB应用的初始化参数 -->
    <context-param>
        <param-name>url</param-name>
        <param-value>jdbc:mysql://localhost:3306/test</param-value>
    </context-param>
    <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>
</web-app>

获取Web应用的初始化参数,代码如下:

public class ServletContextDemo3 extends HttpServlet {
    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        ServletContext context = this.getServletContext();
        //获取整个web站点的初始化参数
        String contextInitParam = context.getInitParameter("url");
        response.getWriter().print(contextInitParam);
    }
    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        doGet(request, response);
    }
}

ServletContext实现请求转发

ServletContextDemo4:

public class ServletContextDemo4 extends HttpServlet {
    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        String data = "<h1><font color='red'>text info</font></h1>";
        response.getOutputStream().write(data.getBytes());
        ServletContext context = this.getServletContext();//获取ServletContext对象
        RequestDispatcher rd = context.getRequestDispatcher("/servlet/ServletContextDemo5");//获取请求转发对象(RequestDispatcher)
        rd.forward(request, response);//调用forward方法实现请求转发
    }
    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
    }
}

ServletContextDemo5:

public class ServletContextDemo5 extends HttpServlet {
    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        response.getOutputStream().write("servletDemo5".getBytes());
    }
    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        this.doGet(request, response);
    }
}

访问的是ServletContextDemo4,浏览器显示的却是ServletContextDemo5的内容,这就是使用ServletContext实现了请求转发。

ServletContext读取资源文件

代码如下所示:

public class ServletContextDemo6 extends HttpServlet {
    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException { 
        /**
         * response.setContentType("text/html;charset=UTF-8");目的是控制浏览器用UTF-8进行解码;
         * 这样就不会出现中文乱码了
         */
        response.setHeader("content-type","text/html;charset=UTF-8");
        readSrcDirPropCfgFile(response);//读取src目录下的properties配置文件
        response.getWriter().println("<hr/>");
        readWebRootDirPropCfgFile(response);//读取WebRoot目录下的properties配置文件
        response.getWriter().println("<hr/>");
        readPropCfgFile(response);//读取src目录下的db.config包中的db3.properties配置文件
        response.getWriter().println("<hr/>");
        readPropCfgFile2(response);//读取src目录下的gacl.servlet.study包中的db4.properties配置文件
    }
    /**
     * 读取src目录下的gacl.servlet.study包中的db4.properties配置文件
     * @param response
     * @throws IOException
     */
    private void readPropCfgFile2(HttpServletResponse response)
            throws IOException {
        InputStream in = this.getServletContext().getResourceAsStream("/WEB-INF/classes/gacl/servlet/study/db4.properties");
        Properties prop = new Properties();
        prop.load(in);
        String driver = prop.getProperty("driver");
        String url = prop.getProperty("url");
        String username = prop.getProperty("username");
        String password = prop.getProperty("password");
        response.getWriter().println("读取src目录下的gacl.servlet.study包中的db4.properties配置文件:");
        response.getWriter().println(
                MessageFormat.format(
                        "driver={0},url={1},username={2},password={3}", 
                        driver,url, username, password));
    }
    /**
     * 读取src目录下的db.config包中的db3.properties配置文件
     * @param response
     * @throws FileNotFoundException
     * @throws IOException
     */
    private void readPropCfgFile(HttpServletResponse response)
            throws FileNotFoundException, IOException {
        //通过ServletContext获取web资源的绝对路径
        String path = this.getServletContext().getRealPath("/WEB-INF/classes/db/config/db3.properties");
        InputStream in = new FileInputStream(path);
        Properties prop = new Properties();
        prop.load(in);
        String driver = prop.getProperty("driver");
        String url = prop.getProperty("url");
        String username = prop.getProperty("username");
        String password = prop.getProperty("password");
        response.getWriter().println("读取src目录下的db.config包中的db3.properties配置文件:");
        response.getWriter().println(
                MessageFormat.format(
                        "driver={0},url={1},username={2},password={3}", 
                        driver,url, username, password));
    }
    /**
     * 通过ServletContext对象读取WebRoot目录下的properties配置文件
     * @param response
     * @throws IOException
     */
    private void readWebRootDirPropCfgFile(HttpServletResponse response)
            throws IOException {
        /**
         * 通过ServletContext对象读取WebRoot目录下的properties配置文件
         * “/”代表的是项目根目录
         */
        InputStream in = this.getServletContext().getResourceAsStream("/db2.properties");
        Properties prop = new Properties();
        prop.load(in);
        String driver = prop.getProperty("driver");
        String url = prop.getProperty("url");
        String username = prop.getProperty("username");
        String password = prop.getProperty("password");
        response.getWriter().println("读取WebRoot目录下的db2.properties配置文件:");
        response.getWriter().print(
                MessageFormat.format(
                        "driver={0},url={1},username={2},password={3}", 
                        driver,url, username, password));
    }
    /**
     * 通过ServletContext对象读取src目录下的properties配置文件
     * @param response
     * @throws IOException
     */
    private void readSrcDirPropCfgFile(HttpServletResponse response) throws IOException {
        /**
         * 通过ServletContext对象读取src目录下的db1.properties配置文件
         */
        InputStream in = this.getServletContext().getResourceAsStream("/WEB-INF/classes/db1.properties");
        Properties prop = new Properties();
        prop.load(in);
        String driver = prop.getProperty("driver");
        String url = prop.getProperty("url");
        String username = prop.getProperty("username");
        String password = prop.getProperty("password");
        response.getWriter().println("读取src目录下的db1.properties配置文件:");
        response.getWriter().println(
                MessageFormat.format(
                        "driver={0},url={1},username={2},password={3}", 
                        driver,url, username, password));
    }
    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        this.doGet(request, response);
    }
}

类加载器读取资源文件

/**
 * 用类装载器读取资源文件
 * 通过类装载器读取资源文件的注意事项:不适合装载大文件,否则会导致jvm内存溢出
 */
public class ServletContextDemo7 extends HttpServlet {
    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        /**
         * response.setContentType("text/html;charset=UTF-8");目的是控制浏览器用UTF-8进行解码;
         * 这样就不会出现中文乱码了
         */
        response.setHeader("content-type","text/html;charset=UTF-8");
        test1(response);
        response.getWriter().println("<hr/>");
        test2(response);
        response.getWriter().println("<hr/>");
        //test3();
        test4();  
    }
    
    /**
     * 读取类路径下的资源文件
     * @param response
     * @throws IOException
     */
    private void test1(HttpServletResponse response) throws IOException {
        //获取到装载当前类的类装载器
        ClassLoader loader = ServletContextDemo7.class.getClassLoader();
        //用类装载器读取src目录下的db1.properties配置文件
        InputStream in = loader.getResourceAsStream("db1.properties");
        Properties prop = new Properties();
        prop.load(in);
        String driver = prop.getProperty("driver");
        String url = prop.getProperty("url");
        String username = prop.getProperty("username");
        String password = prop.getProperty("password");
        response.getWriter().println("用类装载器读取src目录下的db1.properties配置文件:");
        response.getWriter().println(
                MessageFormat.format(
                        "driver={0},url={1},username={2},password={3}", 
                        driver,url, username, password));
    }
    /**
     * 读取类路径下面、包下面的资源文件
     * @param response
     * @throws IOException
     */
    private void test2(HttpServletResponse response) throws IOException {
        //获取到装载当前类的类装载器
        ClassLoader loader = ServletContextDemo7.class.getClassLoader();
        //用类装载器读取src目录下的gacl.servlet.study包中的db4.properties配置文件
        InputStream in = loader.getResourceAsStream("gacl/servlet/study/db4.properties");
        Properties prop = new Properties();
        prop.load(in);
        String driver = prop.getProperty("driver");
        String url = prop.getProperty("url");
        String username = prop.getProperty("username");
        String password = prop.getProperty("password");
        response.getWriter().println("用类装载器读取src目录下的gacl.servlet.study包中的db4.properties配置文件:");
        response.getWriter().println(
                MessageFormat.format(
                        "driver={0},url={1},username={2},password={3}", 
                        driver,url, username, password));
    }
    
    /**
     * 通过类装载器读取资源文件的注意事项:不适合装载大文件,否则会导致jvm内存溢出
     */
    public void test3() {
        /**
         * 01.avi是一个150多M的文件,使用类加载器去读取这个大文件时会导致内存溢出:
         * java.lang.OutOfMemoryError: Java heap space
         */
        InputStream in = ServletContextDemo7.class.getClassLoader().getResourceAsStream("01.avi");
        System.out.println(in);
    }
    
    /**
     * 读取01.avi,并拷贝到e:\根目录下
     * 01.avi文件太大,只能用servletContext去读取
     * @throws IOException
     */
    public void test4() throws IOException {
        // path=G:\Java学习视频\JavaWeb学习视频\JavaWeb\day05视频\01.avi
        // path=01.avi
        String path = this.getServletContext().getRealPath("/WEB-INF/classes/01.avi");
        /**
         * path.lastIndexOf("\\") + 1是一个非常绝妙的写法
         */
        String filename = path.substring(path.lastIndexOf("\\") + 1);//获取文件名
        InputStream in = this.getServletContext().getResourceAsStream("/WEB-INF/classes/01.avi");
        byte buffer[] = new byte[1024];
        int len = 0;
        OutputStream out = new FileOutputStream("e:\\" + filename);
        while ((len = in.read(buffer)) > 0) {
            out.write(buffer, 0, len);
        }
        out.close();
        in.close();
    }
    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        this.doGet(request, response);
    }
}

客户端缓存Servlet输出

对于不经常变化的数据,在servlet中可以为其设置合理的缓存时间值,以避免浏览器频繁向服务器发送请求,提升服务器的性能。例如:

public class ServletDemo5 extends HttpServlet {
    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        String data = "abcddfwerwesfasfsadf";
        /**
         * 设置数据合理的缓存时间值,以避免浏览器频繁向服务器发送请求,提升服务器的性能
         * 这里是将数据的缓存时间设置为1天
         */
        response.setDateHeader("expires",System.currentTimeMillis() + 24 * 3600 * 1000);
        response.getOutputStream().write(data.getBytes());
    }
    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        this.doGet(request, response);
    }
}

演示如下所示:

HttpServletResponse

Web服务器收到客户端的http请求,会针对每一次请求,分别创建一个用于代表请求的request对象、和代表响应的response对象。

request和response对象即然代表请求和响应,那我们要获取客户机提交过来的数据,只需要找request对象就行了。要向客户机输出数据,只需要找response对象就行了。

HttpServletResponse接口代表服务器的响应。这个对象中封装了向客户端发送数据、发送响应头,发送响应状态码的方法。

它常用的方法如下:

向客户端发送数据的方法

方法 描述
getOutputStream() 返回ServletOutputStream对象来传递二进制数据进行响应。
getWriter() 返回PrintWriter对象来输出文本信息给客户端。

向客户端发送响应头的方法

方法 描述
addDateHeader(String name, long date) 用给定名称和日期值添加响应头。该日期根据距历元时间的毫秒数指定。此方法允许响应头有多个值。
addHeader(String name, String value) 用给定名称和值添加响应头。此方法允许响应头有多个值。
addIntHeader(String name, int value) 用给定名称和整数值添加响应头。此方法允许响应头有多个值。
containsHeader(String name) 返回一个 boolean 值,指示是否已经设置指定的响应头。
setDateHeader(String name, long date) 用给定名称和日期值设置响应头。该日期根据距历元时间的毫秒数指定。如果已经设置了头,则新值将重写以前的值。
setHeader(String name, String value) 用给定名称和值设置响应头。如果已经设置了头,则新值将重写以前的值。containsHeader 方法可用于测试在设置其值之前头是否存在。
setIntHeader(String name, int value) 用给定名称和整数值设置响应头。如果已经设置了头,则新值将重写以前的值。containsHeader 方法可用于测试在设置其值之前头是否存在。

响应码的常量

HttpServletResponse定义了很多状态码的常量(具体可以查看Servlet的API),当需要向客户端发送响应状态码时,可以使用这些常量,避免了直接写数字,

常见的状态码对应的常量:

响应码 描述
SC_NOT_FOUND 状态代码 (404),指示请求的资源不可用。
SC_OK 状态代码 (200),指示请求正常成功。
SC_INTERNAL_SERVER_ERROR 状态代码 (500),指示 HTTP 服务器内存在错误使服务器无法完成请求。

常见应用

输出中文数据

  在服务器端,数据是以哪个码表输出的,那么就要控制客户端浏览器以相应的码表打开,否则显示的时候就会出现中文乱码。

使用OutputStream流向客户端浏览器输出"中国"这两个汉字

public class ResponseDemo01 extends HttpServlet {
    private static final long serialVersionUID = 4312868947607181532L;
    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        outputChineseByOutputStream(response);//使用OutputStream流输出中文
    }
    
    public void outputChineseByOutputStream(HttpServletResponse response) throws IOException{
        /**使用OutputStream输出中文注意问题:
         * 在服务器端,数据是以哪个码表输出的,那么就要控制客户端浏览器以相应的码表打开,
         * 比如:outputStream.write("中国".getBytes("UTF-8"));//使用OutputStream流向客户端浏览器输出中文,以UTF-8的编码进行输出
         * 此时就要控制客户端浏览器以UTF-8的编码打开,否则显示的时候就会出现中文乱码,那么在服务器端如何控制客户端浏览器以以UTF-8的编码显示数据呢?
         * 可以通过设置响应头控制浏览器的行为,例如:
         * response.setHeader("content-type", "text/html;charset=UTF-8");//通过设置响应头控制浏览器以UTF-8的编码显示数据
         */
        String data = "中国";
        OutputStream outputStream = response.getOutputStream();//获取OutputStream输出流
        response.setHeader("content-type", "text/html;charset=UTF-8");//通过设置响应头控制浏览器以UTF-8的编码显示数据,如果不加这句话,那么浏览器显示的将是乱码
        /**
         * data.getBytes()是一个将字符转换成字节数组的过程,这个过程中一定会去查码表,
         * 如果是中文的操作系统环境,默认就是查找查GB2312的码表,
         * 将字符转换成字节数组的过程就是将中文字符转换成GB2312的码表上对应的数字
         * 比如: "中"在GB2312的码表上对应的数字是98
         *         "国"在GB2312的码表上对应的数字是99
         * getBytes()方法如果不带参数,那么就会根据操作系统的语言环境来选择转换码表,如果是中文操作系统,那么就使用GB2312的码表
         */
        byte[] dataByteArr = data.getBytes("UTF-8");//将字符转换成字节数组,指定以UTF-8编码进行转换
        outputStream.write(dataByteArr);//使用OutputStream流向客户端输出字节数组
    }
    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        doGet(request, response);
    }
}

输出中文数据

在获取PrintWriter输出流之前首先使用"response.setCharacterEncoding(charset)"设置字符以什么样的编码输出到浏览器,如:response.setCharacterEncoding("UTF-8");

设置将字符以"UTF-8"编码输出到客户端浏览器,然后再使用response.getWriter();获取PrintWriter输出流,这两个步骤不能颠倒,如下:

response.setCharacterEncoding("UTF-8");//设置将字符以"UTF-8"编码输出到客户端浏览器
/**
* PrintWriter out = response.getWriter();这句代码必须放在response.setCharacterEncoding("UTF-8");之后
* 否则response.setCharacterEncoding("UTF-8")这行代码的设置将无效,浏览器显示的时候还是乱码
*/
PrintWriter out = response.getWriter();//获取PrintWriter输出流

然后再使用response.setHeader("content-type", "text/html;charset=字符编码");设置响应头,控制浏览器以指定的字符编码编码进行显示,例如:

//通过设置响应头控制浏览器以UTF-8的编码显示数据,如果不加这句话,那么浏览器显示的将是乱码
response.setHeader("content-type", "text/html;charset=UTF-8");

除了可以使用response.setHeader("content-type", "text/html;charset=字符编码");设置响应头来控制浏览器以指定的字符编码编码进行显示这种方式之外,

还可以用如下的方式来模拟响应头的作用:

/**
* 多学一招:使用HTML语言里面的<meta>标签来控制浏览器行为,模拟通过设置响应头控制浏览器行为
 *response.getWriter().write("<meta http-equiv='content-type' content='text/html;charset=UTF-8'/>");
* 等同于response.setHeader("content-type", "text/html;charset=UTF-8");
*/
response.getWriter().write("<meta http-equiv='content-type' content='text/html;charset=UTF-8'/>");

使用PrintWriter流向客户端浏览器输出"中国"这两个汉字

public class ResponseDemo01 extends HttpServlet {
    private static final long serialVersionUID = 4312868947607181532L;
    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        outputChineseByPrintWriter(response);//使用PrintWriter流输出中文
    }
    public void outputChineseByPrintWriter(HttpServletResponse response) throws IOException{
        String data = "中国";
        
        //通过设置响应头控制浏览器以UTF-8的编码显示数据,如果不加这句话,那么浏览器显示的将是乱码
        //response.setHeader("content-type", "text/html;charset=UTF-8");
        
        response.setCharacterEncoding("UTF-8");//设置将字符以"UTF-8"编码输出到客户端浏览器
        /**
         * PrintWriter out = response.getWriter();这句代码必须放在response.setCharacterEncoding("UTF-8");之后
         * 否则response.setCharacterEncoding("UTF-8")这行代码的设置将无效,浏览器显示的时候还是乱码
         */
        PrintWriter out = response.getWriter();//获取PrintWriter输出流
        /**
         * 多学一招:使用HTML语言里面的<meta>标签来控制浏览器行为,模拟通过设置响应头控制浏览器行为
         * out.write("<meta http-equiv='content-type' content='text/html;charset=UTF-8'/>");
         * 等同于response.setHeader("content-type", "text/html;charset=UTF-8");
         */
        out.write("<meta http-equiv='content-type' content='text/html;charset=UTF-8'/>");
        out.write(data);//使用PrintWriter流向客户端输出字符
    }
    
    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        doGet(request, response);
    }
}

当需要向浏览器输出字符数据时,使用PrintWriter比较方便,省去了将字符转换成字节数组那一步。

输出数字

public class ResponseDemo01 extends HttpServlet {
    private static final long serialVersionUID = 4312868947607181532L;
    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        outputOneByOutputStream(response);//使用OutputStream输出1到客户端浏览器
    }
    public void outputOneByOutputStream(HttpServletResponse response) throws IOException{
        response.setHeader("content-type", "text/html;charset=UTF-8");
        OutputStream outputStream = response.getOutputStream();
        outputStream.write("使用OutputStream流输出数字1:".getBytes("UTF-8"));
        outputStream.write(1);
    }
}

输出结果如下图所示:

运行的结果和我们想象中的不一样,数字1没有输出来,下面我们修改一下上面的outputOneByOutputStream方法的代码,修改后的代码如下:

public void outputOneByOutputStream(HttpServletResponse response) throws IOException{
    response.setHeader("content-type", "text/html;charset=UTF-8");
    OutputStream outputStream = response.getOutputStream();
    outputStream.write("使用OutputStream流输出数字1:".getBytes("UTF-8"));
    //outputStream.write(1);
    outputStream.write((1+"").getBytes());
}

在开发过程中,如果希望服务器输出什么浏览器就能看到什么,那么在服务器端都要以字符串的形式进行输出。

文件下载

文件下载功能是web开发中经常使用到的功能,使用HttpServletResponse对象就可以实现文件的下载,具体流程如下:

1.获取要下载的文件的绝对路径
2.获取要下载的文件名
3.设置content-disposition响应头控制浏览器以下载的形式打开文件
4.获取要下载的文件输入流
5.创建数据缓冲区
6.通过response对象获取OutputStream流
7.将FileInputStream流写入到buffer缓冲区
8.使用OutputStream将缓冲区的数据输出到客户端浏览器

a) 使用Response实现文件下载:

public class ResponseDemo02 extends HttpServlet {
    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        downloadFileByOutputStream(response);//下载文件,通过OutputStream流
    }
    private void downloadFileByOutputStream(HttpServletResponse response)
            throws FileNotFoundException, IOException {
        //1.获取要下载的文件的绝对路径
        String realPath = this.getServletContext().getRealPath("/download/1.JPG");
        //2.获取要下载的文件名
        String fileName = realPath.substring(realPath.lastIndexOf("\\")+1);
        //3.设置content-disposition响应头控制浏览器以下载的形式打开文件
        response.setHeader("content-disposition", "attachment;filename="+fileName);
        //4.获取要下载的文件输入流
        InputStream in = new FileInputStream(realPath);
        int len = 0;
        //5.创建数据缓冲区
        byte[] buffer = new byte[1024];
        //6.通过response对象获取OutputStream流
        OutputStream out = response.getOutputStream();
        //7.将FileInputStream流写入到buffer缓冲区
        while ((len = in.read(buffer)) > 0) {
        //8.使用OutputStream将缓冲区的数据输出到客户端浏览器
            out.write(buffer,0,len);
        }
        in.close();
    }
    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        doGet(request, response);
    }
}

b) 使用Response实现中文文件下载

下载中文文件时,需要注意的地方就是中文文件名要使用URLEncoder.encode方法进行编码(URLEncoder.encode(fileName, "字符编码")),否则会出现文件名乱码。

public class ResponseDemo02 extends HttpServlet {
    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        downloadChineseFileByOutputStream(response);//下载中文文件
    }
    private void downloadChineseFileByOutputStream(HttpServletResponse response)
            throws FileNotFoundException, IOException {
        String realPath = this.getServletContext().getRealPath("/download/张家界国家森林公园.JPG");
        String fileName = realPath.substring(realPath.lastIndexOf("\\")+1);
        //设置content-disposition响应头控制浏览器以下载的形式打开文件,中文文件名要使用URLEncoder.encode方法进行编码,否则会出现文件名乱码
        response.setHeader("content-disposition", "attachment;filename="+URLEncoder.encode(fileName, "UTF-8"));
        InputStream in = new FileInputStream(realPath);//获取文件输入流
        int len = 0;
        byte[] buffer = new byte[1024];
        OutputStream out = response.getOutputStream();
        while ((len = in.read(buffer)) > 0) {
            out.write(buffer,0,len);//将缓冲区的数据输出到客户端浏览器
        }
        in.close();
    }
    
    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        doGet(request, response);
    }
}

编写文件下载功能时推荐使用OutputStream流,避免使用PrintWriter流,因为OutputStream流是字节流,可以处理任意类型的数据,而PrintWriter流是字符流,

只能处理字符数据,如果用字符流处理字节数据,会导致数据丢失。

c) 使用PrintWriter流下载文件

public class ResponseDemo02 extends HttpServlet {
    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        downloadFileByPrintWriter(response);
    }
    /**
     * 下载文件,通过PrintWriter流,虽然也能够实现下载,但是会导致数据丢失,因此不推荐使用PrintWriter流下载文件
     */
    private void downloadFileByPrintWriter(HttpServletResponse response)
            throws FileNotFoundException, IOException {
        String realPath = this.getServletContext().getRealPath("/download/张家界国家森林公园.JPG");//获取要下载的文件的绝对路径
        String fileName = realPath.substring(realPath.lastIndexOf("\\")+1);//获取要下载的文件名
        //设置content-disposition响应头控制浏览器以下载的形式打开文件,中文文件名要使用URLEncoder.encode方法进行编码
        response.setHeader("content-disposition", "attachment;filename="+URLEncoder.encode(fileName, "UTF-8"));
        FileReader in = new FileReader(realPath);
        int len = 0;
        char[] buffer = new char[1024];
        PrintWriter out = response.getWriter();
        while ((len = in.read(buffer)) > 0) {
            out.write(buffer,0,len);//将缓冲区的数据输出到客户端浏览器
        }
        in.close();
    }
    
    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        doGet(request, response);
    }
}

在编写下载文件功能时,要使用OutputStream流,避免使用PrintWriter流,因为OutputStream流是字节流,可以处理任意类型的数据,而PrintWriter流是字符流,

只能处理字符数据,如果用字符流处理字节数据,会导致数据丢失。

生成验证码

public class ResponseDemo03 extends HttpServlet {
    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        
        response.setHeader("refresh", "5");//设置refresh响应头控制浏览器每隔5秒钟刷新一次
        //1.在内存中创建一张图片
        BufferedImage image = new BufferedImage(80, 20, BufferedImage.TYPE_INT_RGB);
        //2.得到图片
        //Graphics g = image.getGraphics();
        Graphics2D g = (Graphics2D)image.getGraphics();
        g.setColor(Color.WHITE);//设置图片的背景色
        g.fillRect(0, 0, 80, 20);//填充背景色
        //3.向图片上写数据
        g.setColor(Color.BLUE);//设置图片上字体的颜色
        g.setFont(new Font(null, Font.BOLD, 20));
        g.drawString(makeNum(), 0, 20);
        //4.设置响应头控制浏览器浏览器以图片的方式打开
        response.setContentType("image/jpeg");//等同于response.setHeader("Content-Type", "image/jpeg");
        //5.设置响应头控制浏览器不缓存图片数据
        response.setDateHeader("expries", -1);
        response.setHeader("Cache-Control", "no-cache");
        response.setHeader("Pragma", "no-cache");
        //6.将图片写给浏览器
        ImageIO.write(image, "jpg", response.getOutputStream());
    }
    /**
     * 生成随机数字
     * @return
     */
    private String makeNum(){
        Random random = new Random();
        String num = random.nextInt(9999999)+"";
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < 7-num.length(); i++) {
            sb.append("0");
        }
        num = sb.toString()+num;
        return num;
    }
    
    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        doGet(request, response);
    }
}

控制浏览器

(1)禁止缓存

response.setDateHeader("expries", -1);
response.setHeader("Cache-Control", "no-cache");
response.setHeader("Pragma", "no-cache");

(2)刷新网页

response.setHeader("refresh", "5");//设置refresh响应头控制浏览器每隔5秒钟刷新一次

(3)请求重定向

请求重定向指:一个web资源收到客户端请求后,通知客户端去访问另外一个web资源,这称之为请求重定向。

  应用场景:用户登陆,用户首先访问登录页面,登录成功后,就会跳转到某个页面,这个过程就是一个请求重定向的过程

  实现方式:response.sendRedirect(String location),即调用response对象的sendRedirect方法实现请求重定向

  sendRedirect内部的实现原理:使用response设置302状态码和设置location响应头实现重定向

public class ResponseDemo04 extends HttpServlet {
    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        /**
         * 1.调用sendRedirect方法实现请求重定向,
         * sendRedirect方法内部调用了
         * response.setHeader("Location", "/JavaWeb_HttpServletResponse_Study_20140615/index.jsp");
         * response.setStatus(HttpServletResponse.SC_FOUND);//设置302状态码,等同于response.setStatus(302);
         */
        response.sendRedirect("/JavaWeb_HttpServletResponse_Study_20140615/index.jsp");
        
        //2.使用response设置302状态码和设置location响应头实现重定向实现请求重定向
        //response.setHeader("Location", "/JavaWeb_HttpServletResponse_Study_20140615/index.jsp");
        //response.setStatus(HttpServletResponse.SC_FOUND);//设置302状态码,等同于response.setStatus(302);
    }
    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        doGet(request, response);
    }
}

(4)URL格式写法

在JavaWeb开发中,只要是写URL地址,那么建议最好以"/"开头,也就是使用绝对路径的方式,那么这个"/"到底代表什么呢?可以用如下的方式来记忆"/":

如果"/"是给服务器用的,则代表当前的web工程,如果"/"是给浏览器用的,则代表webapps目录。

  • "/"代表当前web工程的常见应用场景

a) ServletContext.getRealPath(String path)获取资源的绝对路径

/**

* 1.ServletContext.getRealPath("/download/1.JPG")是用来获取服务器上的某个资源,
* 那么这个"/"就是给服务器用的,"/"此时代表的就是web工程
 * ServletContext.getRealPath("/download/1.JPG")表示的就是读取web工程下的download文件夹中的1.JPG这个资源
* 只要明白了"/"代表的具体含义,就可以很快写出要访问的web资源的绝对路径
  */
  this.getServletContext().getRealPath("/download/1.JPG");

b) 在服务器端forward到其他页面

/**
* 2.forward
 * 客户端请求某个web资源,服务器跳转到另外一个web资源,这个forward也是给服务器用的,
* 那么这个"/"就是给服务器用的,所以此时"/"代表的就是web工程
*/
this.getServletContext().getRequestDispatcher("/index.jsp").forward(request, response);

c) 使用include指令或者jsp:include标签引入页面

<%@include file="/jspfragments/head.jspf" %>
或
<jsp:include page="/jspfragments/demo.jsp" />

此时"/"代表的都是web工程。

  • "/"代表webapps目录的常见应用场景

a) 使用sendRedirect实现请求重定向

response.sendRedirect("/JavaWebDemo/index.jsp");

服务器发送一个URL地址给浏览器,浏览器拿到URL地址之后,再去请求服务器,所以这个"/"是给浏览器使用的,此时"/"代表的就是webapps目录,

"/JavaWeb_HttpServletResponse_Study_20140615/index.jsp"这个地址指的就是"webapps\JavaWebDemo\index.jsp"response.sendRedirect("/项目名称/文件夹

目录/页面");这种写法是将项目名称写死在程序中的做法,不灵活,万一哪天项目名称变了,此时就得改程序,所以推荐使用下面的灵活写法:

response.sendRedirect("/JavaWebDemo/index.jsp");
修改为:
response.sendRedirect(request.getContextPath()+"/index.jsp");

request.getContextPath()获取到的内容就是"/JavaWebDemo",这样就比较灵活了,使用request.getContextPath()代替

"/项目名称",推荐使用这种方式,灵活方便!

b) 使用超链接跳转

<a href="/JavaWebDemo/index.jsp">跳转到首页</a>

这是客户端浏览器使用的超链接跳转,这个"/"是给浏览器使用的,此时"/"代表的就是webapps目录。使用超链接访问web资源,绝对路径的写法推荐使用下面的

写法改进:

<a href="${pageContext.request.contextPath}/index.jsp">跳转到首页</a>

这样就可以避免在路径中出现项目的名称,使用${pageContext.request.contextPath}取代"/JavaWebDemo"

c) Form表单提交

<form action="/JavaWeb_HttpServletResponse_Study_20140615/servlet/CheckServlet" method="post">    
         <input type="submit" value="提交">
</form>

这是客户端浏览器将form表单提交到服务器,所以这个"/"是给浏览器使用的,此时"/"代表的就是webapps目录。

对于form表单提交中action属性绝对路径的写法,也推荐使用如下的方式改进:

<form action="${pageContext.request.contextPath}/servlet/CheckServlet" method="post">
        <input type="submit" value="提交">
</form>

${pageContext.request.contextPath}得到的就是"/JavaWebDemo"

${pageContext.request.contextPath}的效果等同于request.getContextPath(),两者获取到的都是"/项目名称"

d) js脚本和css样式文件的引用

<%--使用绝对路径的方式引用js脚本--%>
 <script type="text/javascript" src="${pageContext.request.contextPath}/js/index.js"></script>
 <%--${pageContext.request.contextPath}与request.getContextPath()写法是得到的效果是一样的--%>
 <script type="text/javascript" src="<%=request.getContextPath()%>/js/login.js"></script>
 <%--使用绝对路径的方式引用css样式--%>
 <link rel="stylesheet" href="${pageContext.request.contextPath}/css/index.css" type="text/css"/>

综合范例:

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
    <title>"/"代表webapps目录的常见应用场景</title>
    <%--使用绝对路径的方式引用js脚本--%>
    <script type="text/javascript" src="${pageContext.request.contextPath}/js/index.js"></script>
    <%--${pageContext.request.contextPath}与request.getContextPath()写法是得到的效果是一样的--%>
    <script type="text/javascript" src="<%=request.getContextPath()%>/js/login.js"></script>
    <%--使用绝对路径的方式引用css样式--%>
      <link rel="stylesheet" href="${pageContext.request.contextPath}/css/index.css" type="text/css"/>
  </head>
  
  <body>
      <%--form表单提交--%>
       <form action="${pageContext.request.contextPath}/servlet/CheckServlet" method="post">
           <input type="submit" value="提交">
       </form>
       <%--超链接跳转页面--%>
       <a href="${pageContext.request.contextPath}/index.jsp">跳转到首页</a>
  </body>
</html>
  • 其他细节补充

getOutputStream和getWriter方法分别用于得到输出二进制数据、输出文本数据的ServletOuputStream、Printwriter对象。
getOutputStream和getWriter这两个方法互相排斥,调用了其中的任何一个方法后,就不能再调用另一方法。
Servlet程序向ServletOutputStream或PrintWriter对象中写入的数据将被Servlet引擎从response里面获取,
Servlet引擎将这些数据当作响应消息的正文,然后再与响应状态行和各响应头组合后输出到客户端。
Serlvet的service方法结束后,Servlet引擎将检查getWriter或getOutputStream方法返回的输出流对象是否已经调用过close方法,如果没有,
Servlet引擎将调用close方法关闭该输出流对象。

HttpServletRequest

HttpServletRequest对象代表客户端的请求,当客户端通过HTTP协议访问服务器时,HTTP请求头中的所有信息都封装在这个对象中,

通过这个对象提供的方法,可以获得客户端请求的所有信息。

Request常用方法

获得客户机信息

方法 描述
getRequestURL() 返回客户端发出请求时的完整URL。
getRequestURI() 返回请求行中的资源名部分。
getQueryString() 返回请求行中的参数部分。
getPathInfo() 返回请求URL中的额外路径信息。额外路径信息是请求URL中的位于Servlet的路径之后和查询参数之前的内容,它以“/”开头。
getRemoteAddr() 返回发出请求的客户机的IP地址。
getRemoteHost() 返回发出请求的客户机的完整主机名。
getRemotePort() 返回客户机所使用的网络端口号。
getLocalAddr() 返回WEB服务器的IP地址。
getLocalName() 返回WEB服务器的主机名。

范例:通过Request对象获取客户端请求信息:

public class RequestDemo01 extends HttpServlet {
    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        String requestUrl = request.getRequestURL().toString();//得到请求的URL地址
        String requestUri = request.getRequestURI();//得到请求的资源
        String queryString = request.getQueryString();//得到请求的URL地址中附带的参数
        String remoteAddr = request.getRemoteAddr();//得到来访者的IP地址
        String remoteHost = request.getRemoteHost();
        int remotePort = request.getRemotePort();
        String remoteUser = request.getRemoteUser();
        String method = request.getMethod();//得到请求URL地址时使用的方法
        String pathInfo = request.getPathInfo();
        String localAddr = request.getLocalAddr();//获取WEB服务器的IP地址
        String localName = request.getLocalName();//获取WEB服务器的主机名
        response.setCharacterEncoding("UTF-8");//设置将字符以"UTF-8"编码输出到客户端浏览器
        //通过设置响应头控制浏览器以UTF-8的编码显示数据,如果不加这句话,那么浏览器显示的将是乱码
        response.setHeader("content-type", "text/html;charset=UTF-8");
        PrintWriter out = response.getWriter();
        out.write("获取到的客户机信息如下:");
        out.write("<hr/>");
        out.write("请求的URL地址:"+requestUrl);
        out.write("<br/>");
        out.write("请求的资源:"+requestUri);
        out.write("<br/>");
        out.write("请求的URL地址中附带的参数:"+queryString);
        out.write("<br/>");
        out.write("来访者的IP地址:"+remoteAddr);
        out.write("<br/>");
        out.write("来访者的主机名:"+remoteHost);
        out.write("<br/>");
        out.write("使用的端口号:"+remotePort);
        out.write("<br/>");
        out.write("remoteUser:"+remoteUser);
        out.write("<br/>");
        out.write("请求使用的方法:"+method);
        out.write("<br/>");
        out.write("pathInfo:"+pathInfo);
        out.write("<br/>");
        out.write("localAddr:"+localAddr);
        out.write("<br/>");
        out.write("localName:"+localName);
    }
    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        doGet(request, response);
    }
}

获得客户机请求头

方法 描述
getHeader(string name) 以 String 的形式返回指定请求头的值。
getHeaders(String name) 以 String 对象的 Enumeration 的形式返回指定请求头的所有值。
getHeaderNames() 返回此请求包含的所有头名称的枚举。

范例:通过Request对象获取客户端请求头信息

public class RequestDemo2 extends HttpServlet {
    public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 解决中文的乱码问题
        response.setCharacterEncoding("UTF-8");
        response.setHeader("content-type", "text/html;charset=UTF-8");
        
        PrintWriter writer = response.getWriter();
        Enumeration<String> headerNames = request.getHeaderNames();
        writer.print("获取到的客户端所有请求头信息如下:");
        writer.write("<hr/>");
        while (headerNames.hasMoreElements()) {
            String headName = (String) headerNames.nextElement();
            String headValue = request.getHeader(headName);
            writer.write("headName:" + headValue);
            writer.write("<br/>");
        }
        writer.write("<br/>");
        writer.write("获取到客户端Accept-Encoding请求头的值:");
        writer.write("<hr/>");
        String value = request.getHeader("Accept-Encoding");
        writer.write(value);
        
        Enumeration<String> headers = request.getHeaders("Accept-Encoding");
        while (headers.hasMoreElements()) {
            String string = (String) headers.nextElement();
            System.out.print(string);
        }
    }
    public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }
}

获得客户机请求参数

方法 描述
getParameter(String) 以 String 形式返回请求参数的值,如果该参数不存在,则返回 null。
getParameterValues(String name) 返回包含给定请求参数拥有的所有值的 String 对象数组。
getParameterNames() 返回包含此请求中所包含参数的名称的 String 对象的 Enumeration。
getParameterMap() 返回此请求的参数的 java.util.Map。请求参数是与请求一起发送的额外信息。

比如现在有如下的form表单:

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
    <head>
        <title>Html的Form表单元素</title>
    </head>
    <fieldset style="500px;">
        <legend>Html的Form表单元素</legend>
        <!--form表单的action属性规定当提交表单时,向何处发送表单数据,method属性指明表单的提交方式,分为get和post,默认为get-->
        <form action="${pageContext.request.contextPath}/servlet/RequestDemo03" method="post">
            <!--输入文本框,SIZE表示显示长度,maxlength表示最多输入长度-->
            编&nbsp;&nbsp;号(文本框):
            <input type="text" name="userid" value="NO." size="2" maxlength="2"><br>
            <!--输入文本框,通过value指定其显示的默认值-->
            用户名(文本框):<input type="text" name="username" value="请输入用户名"><br>
            <!--密码框,其中所有输入的内容都以密文的形式显示-->
            密&nbsp;&nbsp;码(密码框):
            <!--&nbsp;表示的是一个空格-->
            <input type="password" name="userpass" value="请输入密码"><br>
            <!--单选按钮,通过checked指定默认选中,名称必须一样,其中value为真正需要的内容-->
            性&nbsp;&nbsp;别(单选框):
            <input type="radio" name="sex" value="男" checked>男
            <input type="radio" name="sex" value="女">女<br>
            <!--下拉列表框,通过<option>元素指定下拉的选项-->
            部&nbsp;&nbsp;门(下拉框):
            <select name="dept">
                <option value="技术部">技术部</option>
                <option value="销售部" SELECTED>销售部</option>
                <option value="财务部">财务部</option>
            </select><br>
            <!--复选框,可以同时选择多个选项,名称必须一样,其中value为真正需要的内容-->
            兴&nbsp;&nbsp;趣(复选框):
            <input type="checkbox" name="inst" value="唱歌">唱歌
            <input type="checkbox" name="inst" value="游泳">游泳
            <input type="checkbox" name="inst" value="跳舞">跳舞
            <input type="checkbox" name="inst" value="编程" checked>编程
            <input type="checkbox" name="inst" value="上网">上网
            <br>
            <!--大文本输入框,宽度为34列,高度为5行-->
            说&nbsp;&nbsp;明(文本域):
            <textarea name="note" cols="34" rows="5">
         </textarea>
            <br>
            <!--隐藏域,在页面上无法看到,专门用来传递参数或者保存参数-->
            <input type="hidden" name="hiddenField" value="hiddenvalue"/>
            <!--提交表单按钮,当点击提交后,所有填写的表单内容都会被传输到服务器端-->
            <input type="submit" value="提交(提交按钮)">
            <!--重置表单按钮,当点击重置后,所有表单恢复原始显示内容-->
            <input type="reset" value="重置(重置按钮)">
        </form>
    </fieldset>
    </body>
</html>

在Form表单中填写数据,然后提交到RequestDemo03这个Servlet进行处理,填写的表单数据如下:

在服务器端使用getParameter方法和getParameterValues方法接收表单参数,代码如下:

public class RequestDemo03 extends HttpServlet {
    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        //客户端是以UTF-8编码提交表单数据的,所以需要设置服务器端以UTF-8的编码进行接收,否则对于中文数据就会产生乱码
        request.setCharacterEncoding("UTF-8");
        /**
         * 编&nbsp;&nbsp;号(文本框):
           <input type="text" name="userid" value="NO." size="2" maxlength="2">
         */
        String userid = request.getParameter("userid");//获取填写的编号,userid是文本框的名字,<input type="text" name="userid">
        /**
         * 用户名(文本框):<input type="text" name="username" value="请输入用户名">
         */
        String username = request.getParameter("username");//获取填写的用户名
        /**
         * 密&nbsp;&nbsp;码(密码框):<input type="password" name="userpass" value="请输入密码">
         */
        String userpass = request.getParameter("userpass");//获取填写的密码
        String sex = request.getParameter("sex");//获取选中的性别
        String dept = request.getParameter("dept");//获取选中的部门
        //获取选中的兴趣,因为可以选中多个值,所以获取到的值是一个字符串数组,因此需要使用getParameterValues方法来获取
        String[] insts = request.getParameterValues("inst");
        String note = request.getParameter("note");//获取填写的说明信息
        String hiddenField = request.getParameter("hiddenField");//获取隐藏域的内容
        
        String instStr="";
        /**
         * 获取数组数据的技巧,可以避免insts数组为null时引发的空指针异常错误!
         */
        for (int i = 0; insts!=null && i < insts.length; i++) {
            if (i == insts.length-1) {
                instStr+=insts[i];
            }else {
                instStr+=insts[i]+",";
            }
        }
        
        String htmlStr = "<table>" +
                            "<tr><td>填写的编号:</td><td>{0}</td></tr>" +
                            "<tr><td>填写的用户名:</td><td>{1}</td></tr>" +
                            "<tr><td>填写的密码:</td><td>{2}</td></tr>" +
                            "<tr><td>选中的性别:</td><td>{3}</td></tr>" +
                            "<tr><td>选中的部门:</td><td>{4}</td></tr>" +
                            "<tr><td>选中的兴趣:</td><td>{5}</td></tr>" +
                            "<tr><td>填写的说明:</td><td>{6}</td></tr>" +
                            "<tr><td>隐藏域的内容:</td><td>{7}</td></tr>" +
                        "</table>";
        htmlStr = MessageFormat.format(htmlStr, userid,username,userpass,sex,dept,instStr,note,hiddenField);
        
        response.setCharacterEncoding("UTF-8");//设置服务器端以UTF-8编码输出数据到客户端
        response.setContentType("text/html;charset=UTF-8");//设置客户端浏览器以UTF-8编码解析数据
        response.getWriter().write(htmlStr);//输出htmlStr里面的内容到客户端浏览器显示
    }
    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        doGet(request, response);
    }
}

在服务器端使用getParameterNames方法接收表单参数,会导致集合型的数据接收不全 ,代码如下:

Enumeration<String> paramNames = request.getParameterNames();//获取所有的参数名
while (paramNames.hasMoreElements()) {
    String name = paramNames.nextElement();//得到参数名
    String value = request.getParameter(name);//通过参数名获取对应的值
    System.out.println(MessageFormat.format("{0}={1}", name,value));
}

在服务器端使用getParameterMap方法接收表单参数,可以接收到完整的集合参数,代码如下:

//request对象封装的参数是以Map的形式存储的
Map<String, String[]> paramMap = request.getParameterMap();
for(Map.Entry<String, String[]> entry :paramMap.entrySet()){
    String paramName = entry.getKey();
    String paramValue = "";
    String[] paramValueArr = entry.getValue();
    for (int i = 0; paramValueArr!=null && i < paramValueArr.length; i++) {
        if (i == paramValueArr.length-1) {
            paramValue+=paramValueArr[i];
        }else {
            paramValue+=paramValueArr[i]+",";
        }
    }
    System.out.println(MessageFormat.format("{0}={1}", paramName,paramValue));
}

Request乱码解决

POST乱码

  • 以POST提交表单中文参数乱码问题

假设有如下表单页面,代码如下所示:

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
    <title>request接收中文参数乱码问题</title>
  </head>
  
  <body>
      <form action="<%=request.getContextPath()%>/servlet/RequestDemo04" method="post">
          用户名:<input type="text" name="userName"/>
          <input type="submit" value="post方式提交表单"> 
      </form>
  </body>
</html>

此时在服务器端接收中文参数时就会出现中文乱码,如下所示:

POST提交中文数据乱码的解决办法

可以看到,之所以会产生乱码,就是因为服务器和客户端沟通的编码不一致造成的,因此解决的办法是:在客户端和服务器之间设置一个统一的编码,

之后就按照此编码进行数据的传输和接收。

由于客户端是以UTF-8字符编码将表单数据传输到服务器端的,因此服务器也需要设置以UTF-8字符编码进行接收,要想完成此操作,服务器可以直接

使用从ServletRequest接口继承而来的"setCharacterEncoding(charset)"方法进行统一的编码设置。修改后的代码如下:

public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        /**
         * 客户端是以UTF-8编码传输数据到服务器端的,所以需要设置服务器端以UTF-8的编码进行接收,否则对于中文数据就会产生乱码
         */
        request.setCharacterEncoding("UTF-8");
        String userName = request.getParameter("userName");
        System.out.println("userName:"+userName);
}

使用request.setCharacterEncoding("UTF-8");设置服务器以UTF-8的编码接收数据后,此时就不会产生中文乱码问题了,如下所示:

GET乱码

  • GET提交表单中文乱码问题

假设有如下表单页面,代码如下所示:

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
    <title>request接收中文参数乱码问题</title>
  </head>
  
  <body>
        <form action="${pageContext.request.contextPath}/servlet/RequestDemo04" method="get">
          姓名:<input type="text" name="name"/>
          <input type="submit" value="get方式提交表单"> 
      </form>
  </body>
</html>

此时在服务器端接收中文参数时就会出现中文乱码,如下所示:

对于以get方式传输的中文数据,通过request.setCharacterEncoding("UTF-8");这种方式是解决不了中文乱码问题,如下所示:

GET提交中文乱码的解决办法

GET方式传输数据,即使客户端和服务端都设置相同编码还是会出现乱码问题,因为服务端默认会使用ISO8859-1编码接收数据,所以导致编码还是不一致。

解决办法:在接收到数据后,先获取request对象以ISO8859-1字符编码接收到的原始数据的字节数组,然后通过字节数组以指定的编码构建字符串,解决乱码问题。

public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        /**
         *
         * 对于以get方式传输的数据,request即使设置了以指定的编码接收数据也是无效的,默认的还是使用ISO8859-1这个字符编码来接收数据
         */
        String name = request.getParameter("name");//接收数据
        //获取request对象以ISO8859-1字符编码接收到的原始数据的字节数组,然后通过字节数组以指定的编码构建字符串,解决乱码问题
        name =new String(name.getBytes("ISO8859-1"), "UTF-8") ;
        System.out.println("name:"+name);    
}

超链接乱码

  • 超链接导致乱码的原因

    客户端想传输数据到服务器,可以通过表单提交的形式,也可以通过超链接后面加参数的形式,例如:

<a href="${pageContext.request.contextPath}/servlet/RequestDemo05?userName=gacl&name=徐达沛">点击</a>

点击超链接,数据是以get的方式传输到服务器的,所以接收中文数据时也会产生中文乱码问题,而解决中文乱码问题的方式与上述的以get方式提交表单中文数据

乱码处理问题的方式一致,如下所示:

String name = request.getParameter("name");
name = new String(name.getBytes("ISO8859-1"), "UTF-8");

另外,需要提的一点就是URL地址后面如果有中文数据,那么中文参数最好使用URL编码进行处理,如下所示:

<a href="${pageContext.request.contextPath}/servlet/RequestDemo05?userName=gacl&name=<%=URLEncoder.encode("徐达沛", "UTF-8")%>">点击</a>

超链接乱码解决方案

客户端想传输数据到服务器,可以通过表单提交的形式,也可以通过超链接后面加参数的形式,例如:

<a href="${pageContext.request.contextPath}/servlet/RequestDemo05?userName=gacl&name=徐达沛">点击</a>

点击超链接,数据是以get的方式传输到服务器的,所以接收中文数据时也会产生中文乱码问题,而解决中文乱码问题的方式与上述的以get方式提交表单中文数据乱

码处理问题的方式一致,如下所示:

String name = request.getParameter("name");
name =new String(name.getBytes("ISO8859-1"), "UTF-8");

另外,需要提的一点就是URL地址后面如果跟了中文数据,那么中文参数最好使用URL编码进行处理,如下所示:

<a href="${pageContext.request.contextPath}/servlet/RequestDemo05?userName=gacl&name=<%=URLEncoder.encode("徐达沛", "UTF-8")%>">点击</a>

乱码方案总结

a) 如果POST方式提交乱码,只需要在服务端设置Request对象的编码即可:

request.setCharacterEncoding("UTF-8")

b) 如果GET方式提交乱码,设置Request对象编码是无效的,因为Request对象还是默认以ISO8859-1接收数据,需要在接收数据后进行转换:

String data = request.getParameter("paramName");
byte[] source = data.getBytes("ISO8859-1");
data = new String(source, "UTF-8");

Request请求转发

基本概念

请求转发是指一个Web资源受到客户端请求后,通知服务器去调用另外一个web资源进行处理。

在Servlet中实现请求转发有两种方式:

a)通过ServletContext的getRequestDispatcher(String path)方法返回一个RequestDispatcher对象,调用对象的forward方法实现请求转发。

RequestDispatcher reqDispatcher =this.getServletContext().getRequestDispatcher("/test.jsp");
reqDispatcher.forward(request, response);

b) 通过request对象提供的getRequestDispatche(String path)方法返回一个RequestDispatcher对象,调用对象的forward方法实现请求转发。

request.getRequestDispatcher("/test.jsp").forward(request, response);

request对象同时也是一个域对象(Map容器),开发人员通过request对象在实现转发时,把数据通过request对象带给其它web资源处理。

范例:当Web请求Servlet的时候,让该请求转发到index.jsp页面:

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
    <title>Request对象实现请求转发</title>
  </head>
  
  <body>
      使用普通方式取出存储在request对象中的数据:
      <h3 style="color:red;"><%=(String)request.getAttribute("data")%></h3>
     使用EL表达式取出存储在request对象中的数据:
     <h3 style="color:red;">${data}</h3>
  </body>
</html>

服务端代码如下:

public class RequestDemo06 extends HttpServlet {
    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        String data="大家好,我是孤傲苍狼,我正在总结JavaWeb";
        /**
         * 将数据存放到request对象中,此时把request对象当作一个Map容器来使用
         */
        request.setAttribute("data", data);
        //客户端访问Servlet后,通知服务器将请求转发(forward)到index.jsp页面进行处理
        request.getRequestDispatcher("/test.jsp").forward(request, response);
    }
    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        doGet(request, response);
    }
}

request对象作为一个域对象(Map容器)使用时,主要是通过以下的四个方法来操作:

方法 描述
setAttribute(String name,Object o) 将数据作为request对象的一个属性存放到request对象中,例如:request.setAttribute("data", data);
getAttribute(String name) 获取request对象的name属性的属性值,例如:request.getAttribute("data")
removeAttribute(String name) 移除request对象的name属性,例如:request.removeAttribute("data")
getAttributeNames() 获取request对象的所有属性名,返回的是一个,例如:Enumeration attrNames = request.getAttributeNames();
  • 重定向和请求转发的区别
一个web资源收到客户端请求后,通知服务器去调用另外一个web资源进行处理,称之为请求转发307。
一个web资源收到客户端请求后,通知浏览器去访问另外一个web资源进行处理,称之为请求重定向302。

也就是说请求转发是相对于服务器的操作,而重定向是相对于客户端的操作。

原文地址:https://www.cnblogs.com/pengjingya/p/14405919.html