JavaWebComponents

JavaWeb三大组件: Servlet、Filter、Listener

  • Servlet
    • 实现方式
    • SevletConfig & ServletContext
      • ServletConfig
      • ServletContext
      • Servlet中对象的范围
    • Servlet类三大域及三大组件
      • 三大域:
      • 扩展
    • Servlet中的其他细节
      • 如何把一个Servlet类映射成一个对外可访问的路径?
      • 关于Servlet线程安全问题
      • 关于servlet随着项目的启动而创建
      • 两种路径匹配方式
      • 关于tomcat的目录下的web.xml配置(了解)
  • Filter
    • 实现方式
  • Response & Request
    • response
    • request对象
      • 获得表单提交的参数
    • request域的应用
    • 请求转发与请求包含
      • 语法
      • 转发
      • 包含
      • 请求转发与请求包含的区别
      • 请求转发和重定向的的区别
  • Web服务器 - Tomcat
    • Tomcat架构图
    • Tomcat使用
      • 配置虚拟目录
      • 修改tomcat的端口号
      • 配置虚拟主机:
    • JavaWeb路径问题
    • 使用相对路径解决路径写死问题

Servlet

Server Applet是运行在web服务器端容器(tomcat)中一个特殊的java小应用程序,用来接收请求,处理请求,生成响应。

实现方式

实现 Servlet 的三种方式:

  1. 实现javax.servlet.Servlet接口;
  2. 继承javax.servlet.GenericServlet类;
  3. 继承javax.servlet.http.HttpServlet类;

第一种方式实现sevlet接口,需要实现该接口中的五个方法:

package a_servlet_interface;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;

public interface Servlet {

    /* 以下三个为servlet生命周期方法 */

    /*出生
     * 1.该方法由系统调用,构造方法调用之后首次调用,对该Servlet进行初始化;
     * 2.整个生命周期只会被调用一次,可以将资源打开等操作置于其中;
     * 3.ServletConfig参数由系统创建。在该类其他方法中如果要使用,
     * 要先定义一个实例变量,并将该参数传递给该实例变量。
     * */
    void init(ServletConfig var1) throws ServletException;

    /*使命
     * 1.该方法对请求进行处理,其实现基于多线程;
     * 2.Servlet对象是单例的;
     * 3.ServletRequest参数封装了客户端的请求内容,servletResponse对象
     * 封装了响应内容,这两个对象有系统创建。*/
    void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;

    /*销毁
     *该方法在此servlet类的生命周期结束前被调用。当服务器关闭时,销毁servlet,
     *在销毁之前调用释放资源。*/
    void destroy();

    /*ServletConfig是描述Servlet的配置信息的类,该类的对象由Servlet容器创建。
     *通过该方法可以获取到这个对象 。*/
    ServletConfig getServletConfig();

    /*通过该方法可以获取到servlet的信息(作者,版本 ,版权),基本没用。*/
    String getServletInfo();

}

第二种方式继承javax.servlet.GenericServlet类 该类实现了ServletConfig、Servlet、Serializable三个接口,并且替我们保存了ServletConfig对象。注意: 不要重写servlet接口中的init方法,该类第一步已优化,如果要做初始化的动作,需要去重写GenericServlet提供的空参init方法。 源码:

package b_genericServlet;

import javax.servlet.*;
import java.io.IOException;
import java.io.Serializable;
import java.util.Enumeration;

/*优化1,帮我们保管ServletConfig对象;优化2,为调用方便,实现了ServletConfig接口*/
public abstract class GenericServlet implements Servlet, ServletConfig, Serializable {
    //被transient关键字修饰的变量不参与序列化,从而可以保护隐私
    private transient ServletConfig config;

    public GenericServlet() {
    }

    public void destroy() {
    }
    //实现servletConfig中的方法
    public String getInitParameter(String name) {
        return this.getServletConfig().getInitParameter(name);
    }

    public Enumeration getInitParameterNames() {
        return this.getServletConfig().getInitParameterNames();
    }

    public ServletConfig getServletConfig() {
        return this.config;
    }

    public ServletContext getServletContext() {
        return this.getServletConfig().getServletContext();
    }

    public String getServletInfo() {
        return "";
    }

    public void init(ServletConfig config) throws ServletException {
        //子类直接重写接口中的init的方法会把this.config = arg0;
        this.config = config;
        //代码覆盖,所以要提供一个空参init方法用来初始化动作
        this.init();
    }

    public void init() throws ServletException {
    }

    public void log(String msg) {
        this.getServletContext().log(this.getServletName() + ": " + msg);
    }

    public void log(String message, Throwable t) {
        this.getServletContext().log(this.getServletName() + ": " + message, t);
    }
    //service方法处理业务,声明为抽象方法,由子类自己实现
    public abstract void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;

    public String getServletName() {
        return this.config.getServletName();
    }
}

第三种方式继承javax.servlet.http.HttpServlet类(该类只能针对http请求协议的Servlet类)

  • 该类继承了GenericServlet类:
  • 因为web项目基于HTTP协议,所以Service方法中传过来的request,response对象都是基于HTTP协议的,即HttpServletReueqst和HttpServletResponse,它帮我们进行了强转;
  • 我们有可能在不同的请求方式时做不同的事情,根据请求方式不同,调用不同的方法,这些方法需要我们重写,否则调用未被重写的方法会返回405(不支持客户端以此种方式访问)。 i.e. GET –> doGet() POST –> doPost() HttpServletDemo:
package c_httpServlet;

import java.io.IOException;

import javax.servlet.GenericServlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public abstract class HttpServletDemo extends GenericServlet {
  public static final String Method_GET = "GET";
  public static final String Method_POST = "POST";
  @Override
  public void service(ServletRequest req, ServletResponse resp)
      throws ServletException, IOException {
    //优化1:将request和response强转成HTTP的.
    HttpServletRequest request = null;
    HttpServletResponse response = null;
    
    try {
      request = (HttpServletRequest) req;
      response = (HttpServletResponse) resp;
    } catch (Exception e) {
    
    }
    service(request,response);
  }
  public void service(HttpServletRequest req, HttpServletResponse resp)
      throws ServletException, IOException {
    //优化2: 根据请求方式的不同,做不同的事
    //获得客户端的请求方式
    String method = req.getMethod();//GET/POST
    //根据请求方式不同,调用不同的方法
    if(method.equals(Method_GET)){
      doGet(req,resp);
    }else if(method.equals(Method_POST)){
      doPost(req,resp);
    } 
  }
  public void doPost(HttpServletRequest req, HttpServletResponse resp) {
  }
  public void doGet(HttpServletRequest req, HttpServletResponse resp) {
  }
}

SevletConfig & ServletContext

ServletConfig

封装了Servlet在web.xml中配置的初始化信息

<init-param>
    <param-name>name</param-name>
    <param-value>tom</param-value>
</init-param>
  1. getServletName获得配置文件中 <servlet-name> 元素的内容
  2. getInitParameter根据 <init-param>中的 <param-name> 获得 <param-value>
  3. getInitParameterNames返回所有<param-name>
  4. getServletContext获得ServletContext对象

ServletContext

服务器会为每个应用创建有且只有一个的ServletContext对象,该对象封装了web.xml中的配置,并对于整个web应用可见。此对象在服务器启动时创建,在服务器关闭时销毁

<!-- web.xml配置 -->
<context-param>
    <param-name>name</param-name>
    <param-value>jerry</param-value>
</context-param>
<context-param>
    <param-name>password</param-name>
    <param-value>1234</param-value>
</context-param>
  1. 通过ServletConfig对象中的getServletContext()方法获得此对象
  2. 获取web.xml中的配置信息的方法:
    • getInitParameterNames()获得所有键
    • getInitParameter(key)根据键获得对应的值
  3. ServletContext对应着Application域,利用了一个项目中只有一个ServletContext实例的特点,在servletContext中放置了一个map用作数据通信。这个Map就是所谓的域,因此所有的域对象都有存取数据的功能
  4. 获得项目中资源,eclipse工程下的文件路径与真正要操作的部署到Tomcat下的资源文件路径要注意区别

Demo

    //获得全局 init param配置
    //1.获得servletContext
    ServletContext sc = getServletContext();
    //2.操作map
    sc.setAttribute("bag", "Incase");
    sc.setAttribute("car", "BMW");
    sc.setAttribute("passport", "HAWAII");

    String bag = (String) sc.getAttribute("bag");
    //输出到客户端
    response.getWriter().print(bag);
    //删除
    sc.removeAttribute("bag");

    //3.调用getxxx方法
    Enumeration<String> en = sc.getInitParameterNames();
    while (en.hasMoreElements()) {
        String key = en.nextElement();
        String value = sc.getInitParameter(key);
        response.getWriter().print(key + "===>" + value + "<br/>");
    }

Servlet中对象的范围

  • servlet - 项目启动期间,一个servlet只有一个servlet实例
  • request - 项目启动期间,request对象的数量取决于当前有多少个请求正在处理
  • response - 项目启动期间,response对象的数量取决于当前有多少个请求正在处理
  • servletConfig - 一个servlet实例对应一个servletConfig对象
  • servletContext - 整个项目中只有一个servletContext实例存在

Servlet类三大域及三大组件

三大域:

  • application — servletContext,范围是整个项目,只有一个servletContext对象,并且所有servlet的组件都能访问到。应用:保存全局的配置
  • session — HttpSession,范围是一次会话(没有保存sessionID的浏览器连接到服务器就会开启一个会话),有多少个浏览器访问就至少有多少个session。应用:购物车,验证码,登录信息
  • request — HttpServletRequest,范围是一次请求,当前有多少个请求正在处理就存在几个request域。应用:转发中共享数据

关于域的4个操作:

  • void setAttribute(String name, Object value): 存储一个域属性
  • Object getAttribute(String name): 获取数据
  • void removeAttribute(String name): 移除域属性
  • Enumeration getAttributeNames(): 获得所有域属性名称

三大域对象的生命周期长度: request < session < application

扩展

  • javaweb四大域(PageContext、ServletRequest、HttpSession、ServletContext)
  • jsp四大域(page、request、session、application)
  • jsp九大内置对象

Servlet中的其他细节

如何把一个Servlet类映射成一个对外可访问的路径?

  1. 在web.xml中对该Servlet类进行注册并映射,即添加一个<servlet>元素
<servlet>
  <!-- 注册别名常用类名(不包括包名)来命名,在web.xml中不可以有相同的注册命名 -->
  <servlet-name>注册别名</servlet-name >
  <servlet-class>servlet类的完整路径名</servlet-class>
</servlet>
  1. 对前面注册的那个servlet类进行路径映射,即在<servlet>元素的下添加<servlet-mapping>元素
<servelt-mapping>
    <servlet-name>
        已注册别名(根据servlet的别名找到对应的Servlet类,并执行该类中的service方法 )
    </servlet-name >
    <url-pattern>
        该servlet类对象访问所映射的路径(该元素的值必须要以“/”开头,表示相对于本项目的根目录 )
    </url-pattern>
</servlet-mapping>

web.xml是项目的核心配置文件。项目被加载后,首先执行该web.xml文件,针对每一个servlet-mapping元素创建一个对应的实例。

关于Servlet线程安全问题

因为在servlet运行期间只有一个servlet实例存在,可能会同时处理多个请求,那么我们在servlet中声明成员变量来存储用户数据是有线程安全问题的

有两种解决方法,推荐使用第一种方法:

  1. 使用局部变量保存用户数据
  2. 实现SingleThreadModel
public class ServletSafe extends HttpServlet implements SingleThreadModel {
  int i =0 ;
  
  public void doGet(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {
      i++;
      try {
        Thread.sleep(5000);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
      response.getWriter().print("number is"+i);
  }

  public void doPost(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {

  }
}

关于servlet随着项目的启动而创建

使用<load-on-startup>配置来实现。在其中填写一个整数,正数值就表示该Servlet对象会在web.xml被加载的时候就被创建了,负数值表示在用户的第一次请求时被创建。当整数大于0时,整数越小优先级越高,如果优先级一样,启动顺序按照配置顺序。

<servlet>
     <servlet-name>AServlet</servlet-name>
     <servlet-class>servlet.AServlet</servlet-class>
     <load-on-startup>3</load-on-startup>                  
 </servlet>

两种路径匹配方式

<url-pattern>路径匹配:

/AServlet - http://localhost:8080/webproject/AServlet
/ABC/AServlet - http://localhost:8080/webproject/ABC/AServlet
/ABC/ABC/* - http://localhost:8080/webproject/ABC/ABC/oasdojasdjioasd
/* - http://localhost:8080/webproject/asdiojoiajsidojoasd
/ - /*         

后缀名匹配:

*.do ==> struts
*.action ==> struts2
*.html ==>

注意:

  1. 关于路径,配置的路径匹配范围越大优先级越低
  2. 两种匹配模式不能混用,例如错误的例子: /*.do

关于tomcat的目录下的web.xml配置(了解)

  1. default servlet的配置
  2. jsp servlet的配置
  3. 互联网中所有mime类型
  4. 欢迎页面配置
  5. 设置失效时间
<session-config>
    <session-timeout>30</session-timeout>
</session-config>

Filter

可以实现动态拦截请求和响应。

实现方式

Filter技术可以实现用户在访问某个目标资源之前,对访问的请求和响应进行拦截,并且能够拦截web服务器管理的所有资源,从而实现一些特殊的功能,如实现URL级别的权限访问控制、过滤敏感词汇、压缩响应信息等。

原理:Filter接口中有一个doFilter方法,当我们编写好Filter,并配置对哪个web资源进行拦截后,WEB服务器每次在调用web资源的service方法之前,都会先调用一下filter的doFilter方法,因此,在该方法内编写代码可达到如下目的:

  1. 在客户端的请求访问WEB资源之前,拦截这些请求
  2. 决定是否让用户访问WEB资源,即调用filterChain.doFilter()方法
  3. 在服务器的响应发送回客户端之前,处理这些响应

FilterChain类:在一个web应用中,可以开发编写多个Filter,这些Filter组合起来称之为一个Filter链。Web服务器根据Filter在web.xml文件中的注册顺序决定调用Filter的顺序,当第一个Filter的doFilter方法被调用时,Web服务器会创建一个代表Filter链的FilterChain对象传递给该方法。在doFilter方法中,开发人员如果调用了FilterChain对象的doFilter方法,则web服务器会检查FilterChain对象中是否还有filter,如果有,则调用第2个filter,如果没有,则调用目标资源。

Filter实现:

package Filter;

import javax.servlet.*;
import java.io.IOException;
import java.util.Enumeration;

/**
 * 在让目标资源执行之前,可以对request\response作预处理,再让目标资源执行
 * 在目标资源执行之后,可以捕获目标资源的执行结果,从而实现一些特殊的功能
 * 在filter中根据条件决定是否调用chain.doFilter(request, response)方法,即是否让目标资源执行
 */
public class FilterDemo implements Filter {
    @Override
    // Filter的创建和销毁由WEB服务器负责
    public void init(FilterConfig filterConfig) throws ServletException {
        /*web应用程序启动时,web服务器将创建Filter的实例对象,并调用其init方法,完成对象的初始化功能,从而为后续的用户请求作好拦截的准备工作
         * Filter对象只会创建一次,init方法也只会执行一次。
         * 通过init方法的参数,可获得代表当前filter配置信息的FilterConfig对象。*/
        System.out.println("Filter初始化...");

        //利用FilterConfig得到filter配置信息
        //得到过滤器的名字
        String filterName = filterConfig.getFilterName();
        //得到在web.xml文件中配置的初始化参数值,如果不存在返回null
        String initParam1 = filterConfig.getInitParameter("name");
        String initParam2 = filterConfig.getInitParameter("hobby");
        System.out.println(filterName);
        System.out.println(initParam1);
        System.out.println(initParam2);

        //返回Filter的所有初始化参数的名字的枚举集合
        Enumeration<String> initParamterNames = filterConfig.getInitParameterNames();
        while (initParamterNames.hasMoreElements()) {
            String paramName = initParamterNames.nextElement();
            System.out.println(paramName);
        }
        //返回Servlet上下文对象的引用:public ServletContext getServletContext()
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        // 对request和response进行一些预处理
        servletRequest.setCharacterEncoding("UTF-8");
        servletResponse.setCharacterEncoding("UTF-8");
        servletResponse.setContentType("text/html;charset=UTF-8");

        System.out.println("FilterDemo执行前...");
        //调用该方法则web服务器就会调用web资源的service方法,即web资源就会被访问
        filterChain.doFilter(servletRequest, servletResponse); //让目标资源执行,即放行
        System.out.println("FilterDemo执行后...");
    }

    @Override
    public void destroy() {
        /*Web容器调用destroy方法销毁Filter。
         * destroy方法在Filter的生命周期中仅执行一次。
         * 在destroy方法中,可以释放过滤器使用的资源。*/
        System.out.println("Filter销毁...");
    }
}

在web. xml中配置过滤器:

<?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">

    <!-- 当Web容器启动Web应用程序时会为你在部署描述符中声明的每一个过滤器创建一个实例 -->
    <!-- Filter的执行顺序与在web.xml配置文件中的配置顺序一致 -->
    <!-- 一般把Filter配置在所有的Servlet之前。 -->
    <!--配置Filter-->
    <filter>
        <filter-name>FilterDemo</filter-name>
        <filter-class>Filter.FilterDemo</filter-class>
        <!--为过滤器指定初始化参数-->
        <init-param>
            <!--添加描述信-->
            <description>姓名</description>
            <param-name>name</param-name>
            <param-value>wyj</param-value>
        </init-param>
        <init-param>
            <description>爱好</description>
            <param-name>hobby</param-name>
            <param-value>coding</param-value>
        </init-param>
    </filter>


    <!--映射过滤器,即设置一个Filter所负责拦截的资源-->
    <!--一个Filter拦截的资源可通过两种方式来指定:Servlet名称或资源访问的请求路径-->
    <filter-mapping>
        <!--设置filter的注册名称-->
        <filter-name>FilterDemo</filter-name>
        <!--设置filter所拦截的请求路径,"/*"表示拦截所有请求-->
        <url-pattern>/*</url-pattern>
        <!--<servlet-name>指定过滤器所拦截的Servlet名称-->
        <!--指定过滤器所拦截的资源被Servlet容器调用的方式,可设置多个,默认REQUEST-->
        <dispatcher>REQUEST</dispatcher>
        <dispatcher>FORWARD</dispatcher>
    </filter-mapping>
</web-app>

<dispatcher> 子元素可以设置的值及其意义:

  1. REQUEST:当用户直接访问页面时,Web容器将会调用过滤器。如果目标资源是通过RequestDispatcher的include()或forward()方法访问时,那么该过滤器就不会被调用。
  2. INCLUDE:如果目标资源是通过RequestDispatcher的include()方法访问时,那么该过滤器将被调用。除此之外,该过滤器不会被调用。
  3. FORWARD:如果目标资源是通过RequestDispatcher的forward()方法访问时,那么该过滤器将被调用,除此之外,该过滤器不会被调用。
  4. ERROR:如果目标资源是通过声明式异常处理机制调用时,那么该过滤器将被调用。除此之外,过滤器不会被调用。

Response & Request

response

HTTP响应

  • 响应首行:协议/版本号 状态码 状态码描述
//添加状态码和描述:
void setStatus(int sc)  //只设置状态码 
void setStatus(int sc, String sm)  //设置状态码和描述
void sendError(int sc)  //只设置状态码,错误的
void sendError(int sc, String msg)  //设置状态码和描述
  • 响应头
//添加响应头
//设置响应头,如果key一样会覆盖
void setHeader(String name, String value) 
setIntHeader(String name, int value)
setDateHeader(String name, long date)
//设置响应头,无论如何都新增
void addHeader(String name, String value) 
void addIntHeader(String name, int value)
void addDateHeader(String name, long date)
  • 响应空行
  • 响应正文
getOutputStream //发送字节流
getWriter //发送字符流

应用

//例1:手动向浏览器发送404
response.sendError(404, "未找到");

//例2:重定向
//1.返回状态码为302
response.setStatus(302);
//2.告诉浏览器去哪找新的地址  发送一个响应头: Location : http://www.baidu.com
response.setHeader("Location", "www.baidu.com");
//相当于:
response.sendRedirect("www.baidu.com");

//例3:Refresh头,刷新
response.setContentType("text/html;charset=utf-8");//解决乱码
//1.设置响应头为Refresh:5;url=https://w-yj.github.io/Day08-response/AServlet
response.setHeader("Refresh", "5;url=https://w-yj.github.io/RequestAndResponse/AServlet");
//2.使用字符流发送提示,5秒后跳转
response.getWriter().print(
"您将在<span id='one'></span>秒后跳转到AServlet!" +
"<script type='text/javascript'>" +
  "var count = 5;" + 
  "function fun1(){" + 
    "var span = document.getElementById('one');" +
    "span.innerHTML = count--;" + "" +  
    "if(count>0){" + 
      "window.setTimeout(fun1,1000);" +  
    "}" +
  "}" + 
  "fun1();" + 
"</script>");

//例4:发送字节,发送中文.
//1.获得输出字节流
OutputStream os = response.getOutputStream();
//2.输出中文
os.write("你好 世界!".getBytes("UTF-8"));
//如果告诉浏览器使用GBK解码会出现乱码
//os.write("<meta http-equiv='Content-Type' content='text/html;charset=gbk'>".getBytes());
response.setHeader("Content-Type", "text/html;charset=utf-8");

//例5:发送字符流,发送中文.
//控制字符流使用的编码。往上放,在获得字符流时会来取这个编码,如果取完之后设置则没有效果
response.setCharacterEncoding("UTF-8");
//告诉浏览器使用什么码表解码
response.setHeader("Content-Type", "text/html;charset=utf-8");
//JAVAEE提供了一个方法,这个方法同时可以做以上两件事.
response.setContentType("text/html;charset=utf-8");
//1.获得字符流
PrintWriter pw = response.getWriter();
//2.发送中文
pw.print("你好 世界!");
//问题: 同时使用两种流会出现问题
response.getOutputStream().write("haha".getBytes());
//注意:字节流和字符流一定不能同时使用!

//例6:使用字节流发送图片
//告诉浏览器发给你的流的MIME类型.
response.setContentType("image/jpeg");
//1.获得图片的输入流
InputStream in = getServletContext().getResourceAsStream("/WEB-INF/001.jpg");
//2.通过response获得输出字节流
OutputStream out = response.getOutputStream();
//3.两个对接
byte[] buffer = new byte[1024];
int len = 0;
while((len=in.read(buffer))>0){
  out.write(buffer, 0, len);
  out.flush();
}
in.close();

//例7:使用字节流发送文件(文件下载)
//告诉浏览器是什么
//getServletContext().getMimeType(".jar") Context对象根据后缀名去web.xml查找mime类型。
response.setContentType(getServletContext().getMimeType(".jar"));
//告诉浏览器推荐用户使用什么名称下载
response.setHeader("Content-Disposition", "attachment;filename=ValidateCode.jar");
//1.获得输入流
InputStream in = getServletContext().getResourceAsStream("/WEB-INF/ValidateCode.jar");
//2.通过response获得输出字节流
OutputStream out = response.getOutputStream();
//3.两个对接
byte[] buffer = new byte[1024];
int len = 0;
while((len=in.read(buffer))>0){
  out.write(buffer, 0, len);
  out.flush();
}
in.close();

request对象

封装了浏览器发送来的请求信息

HTTP请求

  • 请求首行:请求方式 请求路径 协议/版本号
request.getMethod() // GET
request.getRequestURI() // /Day08-request/AServlet
request.getServletPath() // /AServlet
request.getContextPath() // /Day08-request
request.getScheme() // http
  • 请求头
//原始方式获得请求头
String getHeader(String name) 
long getDateHeader(String name)
int getIntHeader(String name)
Enumeration getHeaders(String name)
Enumeration getHeaderNames()

//javaee封装好的方法
request.getContentLength() // -1
request.getContentType() // null
request.getLocale() // zh_CN
request.getQueryString() // name=tom&age=18
request.getRemoteAddr() // 0:0:0:0:0:0:0:1
request.getRemoteHost() // 0:0:0:0:0:0:0:1
request.getRemotePort() // 52074
request.getServerName() // localhost
request.getServerPort() // 8080
  • 请求空行
  • 请求正文:表单传送过来的键值对
package cn.servlet.request.info;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class AServlet extends HttpServlet {

    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        System.out.println("request.getContentLength(): " + request.getContentLength());
        System.out.println("request.getContentType(): " + request.getContentType());
        //request.getContextPath()返回部署的应用程序名或者是当前的项目名称
        System.out.println("request.getContextPath(): " + request.getContextPath());
        System.out.println("request.getMethod(): " + request.getMethod());
        System.out.println("request.getLocale(): " + request.getLocale());
        System.out.println("request.getQueryString(): " + request.getQueryString());
        System.out.println("request.getRequestURI(): " + request.getRequestURI());
        System.out.println("request.getRequestURL(): " + request.getRequestURL());
        System.out.println("request.getServletPath(): " + request.getServletPath());
        System.out.println("request.getRemoteAddr(): " + request.getRemoteAddr());
        System.out.println("request.getRemoteHost(): " + request.getRemoteHost());
        System.out.println("request.getRemotePort(): " + request.getRemotePort());
        System.out.println("request.getScheme(): " + request.getScheme());
        System.out.println("request.getServerName(): " + request.getServerName());
        System.out.println("request.getServerPort(): " + request.getServerPort());
    }

    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
    }
}

获得表单提交的参数

解决乱码:要确保编码和解码一致.

  • 浏览器编码使用的码表就是表单所在页面的码表
  • 服务器解码请求默认使用ISO-8859-1解码,如下配置的URIEncoding来决定解码码表

服务器端配置:

<Connector port="8080"  protocol="HTTP/1.1"  URIEncoding="UTF-8"  connectionTimeout="20000"  redirectPort="8443" />

以上配置会影响整个服务器不推荐,我们使用如下代码解决:

doGet()方法中解决GET乱码(解码请求头)

//获得表单提交上来的键值对
String name = request.getParameter("name");
System.out.println(name + "," + age);

// doGet()方法中解决GET乱码
// 1.获得参数
String name = request.getParameter("name");
// 2.因为服务器使用了错误的码表,那么我们按照错误的码表原路返回
byte[] nameByte = name.getBytes("ISO-8859-1");
// 3.用正确的码表重新解码
String newName = new String(nameByte,"UTF-8");
System.out.println("解决之后的:"+newName);

doPost()方法中解决post乱码(解码请求体)

//post请求中,因为参数的解码时机在第一次调用getParameter之前,所以在调用该方法之前设置解码的码表即可。
request.setCharacterEncoding("UTF-8");
String name = request.getParameter("name");
System.out.println(name);

涉及到获得表单参数的其他方法:

//String getParameter() 根据键获得值  

//String[] getParameterValues(String name) 根据键获得值,获得一键对应多个值的情况 
String[] habits = request.getParameterValues("habit");
System.out.println(Arrays.toString(habits));
String habit  = request.getParameter("habit");
System.out.println("getParameter获得多个值:"+habit);

//Map getParameterMap() 获得服务器保存表单参数的容器,即map<String,String[]>
Map<String,String[]> map = request.getParameterMap();
for(Entry<String, String[]>  en : map.entrySet()){
  String key = en.getKey();
  String[] value = en.getValue();
  System.out.println(key+" " +Arrays.toString(value));
}

//Enumeration getParameterNames() 获得提交的所有键
Enumeration<String> en = request.getParameterNames();
while(en.hasMoreElements()){
      String key = en.nextElement();
      System.out.println("提交上来的键:"+key);
}

request域的应用

**原理:**在request对象中含有一个map,该map就是request域
**作用:**实际开发中,使用请求转发时,servlet处理完逻辑,处理结果要交给jsp显示,可以使用request域将处理结果由servlet带给jsp显示
**范围:**一个request对象对应一个request域(map),系统当前有多少个request就有多少request域
操作

setAttribute  存入一个键值对
getAttribute  通过键取出值
getAttributeNames 获得域中所有键
removeAttribute 跟据键移除一个键值对       

应用

// 1.获得表单提交的用户名密码
String name = request.getParameter("name");
String password = request.getParameter("password");
Map<String,String> error = new HashMap<String, String>();

// 2.验证
if(!(name!=null && name.trim().length()>0 && name.equals("tom"))){
  error.put("name", "用户名有误!");
}
if(!(password!=null && password.trim().length()>0 && password.equals("1234"))){
  error.put("password", "密码错误!");
}

// 3.将错误信息通过request域带到错误页面
request.setAttribute("error",error );
if(error.size() > 0){
  // 失败:回到登录页面,并显示错误信息
  request.getRequestDispatcher("/login2/login.jsp").forward(request, response);
}else{
  // 成功:成功页面
  request.getRequestDispatcher("/login2/success.jsp").forward(request, response);
}

请求转发与请求包含

语法

RequestDispatcher rd = request.getDispatcher("/MyServlet");

使用👆的方法获取RequestDispathcher对象,参数为被包含或者被转发的目标Servlet路径。

若是请求转发:

rd.forward(request,response);

若是请求包含:

rd.include(resuest,response);

即可完成请求转发与请求包含。

转发

一个Servlet处理完毕交给下面的servlet(JSP)继续处理

作用:实际开发中,没有servlet转发给servlet的情况,都是由servlet转发给JSP,这样可以达到分工的作用,servlet比较适合处理业务,JSP比较适合显示功能。

package cn.servlet.request.forward;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class EServlet extends HttpServlet {

    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        //Servlet中转发后不要做输出正文的动作,没有结果
        //如果放到前面可以但会出现乱码,但是响应头是可以设置
        response.setCharacterEncoding("utf-8");
        response.getWriter().print("哈哈哈");

        // 1.获得表单提交的用户名密码
        String name = request.getParameter("name");
        String password = request.getParameter("password");

        // 2.判断是否正确 tom 1234 才算成功
        if (name != null && name.trim().length() > 0 && name.equals("tom") &&
                password != null && password.trim().length() > 0 && password.equals("1234")) {
            //成功:转发到成功页面
            request.getRequestDispatcher("/login/success.jsp").forward(request, response);
            //自己来做,很多弊端
          /*AServlet a = new AServlet();
            a.service(request, response);*/
        } else {
            //失败:转发到失败页面
            request.getRequestDispatcher("/login/error.jsp").forward(request, response);
        }
    }

    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        doGet(request, response);
    }
}

包含

两个servlet(jsp)共同向浏览器输出内容

作用:实际开发中,多个页面含有相同的内容,我们把相同的内容抽取到一个jsp中,在需要显示这个段内容的jsp中,包含抽取的jsp,可以达到统一管理相同的内容

// GServlet doGet()
response.getWriter().print("这是页脚部分");

// FServlet doGet()
response.setContentType("text/html;charset=utf-8");
response.getWriter().print("这是正文部分<hr>");
request.getRequestDispatcher("/GServlet").include(request, response);

请求转发与请求包含的区别

  • 请求转发:由当前Servlet设置响应头(不能设置响应体),下一个Servlet既可以设置响应头也可设置响应体。
  • 请求包含:当前Servlet和下一个Servlet共同完成响应头和响应体。

无论是请求转发还是请求包含,都在一个请求范围内,所以多个Servlet中使用的是同一个request和response

请求转发和重定向的的区别

  1. 请求转发是一个请求一次响应,而重定向是两次请求两次响应;
  2. 请求转发地址栏不变化,而重定向会显示后一个请求的地址;
  3. 请求转发只能转发到本项目其他Servlet,而重定向不只能重定向到本项目,而且还能定向到其他项目;
  4. 请求转发是服务器端行为,只需给出转发的Servlet路径,而重定向需要给出requestURI,即包含项目名;
  5. 请求转发比重定向效率高,因为请求转发只发出一个请求;
  6. 转发过程中只有一个request对象产生,重定向是两个;
  7. 重定向的第2个请求的请求方式是什么?get
  8. 转发中第2个servlet(jsp)是什么请求方式?第一个servlet是什么第2个就是什么

此外,当项目中需要地址栏发生变化时,必须使用重定向;当需要在下一个Servlet中获取到request域中的数据时,必须使用请求转发。

Web服务器 - Tomcat

Tomcat架构图

Tomcat架构图

Tomcat使用

配置虚拟目录

**方法1:**在tomcat的安装目录下conf目录中修改Server.xml文件,在</Host>标签之前配置子元素<Context>,内容如下:

<Context path="/虚拟目录名称" docBase="web应用的绝对路径" />
<!-- path属性:配置虚拟目录名的,也就是在url中访问该项目的名称  
     docBase属性:是用来配置项目的真实路径的 -->

例如配置

<Context path="/webtest" docBase="d:\secondWeb" />

后,开启tomcat服务器后,访问该项目的url是:http://localhost:8080/webtest 该配置的缺点是每次配置都要重新启动tomcat服务器才能生效(tomcat6之后的版本不推荐使用此方法)

**方法2:**在tomcat安装目录下的conf\Catalina\localhost的文件夹中添加一个扩展名是.xml的文件,文件的名字可以用虚拟名称来命名,并在该文件中添加Context元素

<Contxt docBase="d:\secondWeb" />
  • 此种方式不需要再配置path属性,因为文件名代表的就是path的内容,即虚拟目录。
  • 如果配置多级虚拟目录的话,使用#来区分多级目录名,例如:aaa#bbb#ccc.xml,该文件中的内容依然是,配置后可通过http://127.0.0.1:8080/aaa/bbb/ccc/a.html进行访问

**方法3:**配置默认的web应用:在tomcat安装目录下的conf\Catalina\localhost目录中新建ROOT.xml文件,该文件的内容是:

<Context docBase="d:\secondWeb" />

配置后连接http://localhost:8080访问的是该配置的项目而非默认的tomcat启动目录。

修改tomcat的端口号

在tomcat安装目录下的conf目录中的server.xml文件中找到<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />并修改port属性值,如果改成默认的80端口,访问web应用是就不需要输入端口号。如果启动时提示端口号已被占用,需要再次修改要改端口号。

配置虚拟主机:

在tomcat安装目录下的conf目录中修改server.xml文件,在</Host>后面标签后添加

<Host name="主机名" appBase="项目的绝对路径">  
    <Context path=""   docBase="." />
</Host>

注意:主机名一定要在本机C:\WINDOWS\system32\drivers\etc\hosts目录中进行映射过,也就是该主机名有一个对应IP地址,当服务器启动后,在URL输入一个在本机映射过的主机名,这时候会到server.xml文件中去查找有没有对应的主机名的元素,如果有就运行该元素中的元素指定的路径所对应的项目。

JavaWeb路径问题

路径分为两种:客户端路径和服务器端路径

  1. 客户端(浏览器)路径

    • 带”/“指相对于主机,例如表单所在页面路径为http://localhost:8080/webproject/login.jsp,此时”/” 代表http://localhost:8080/
    • 不带”/“表示从当前目录找,例如表单所在页面路径为http://localhost:8080/webprojec/info/login.jsp ,不带“/”则代表http://localhost:8080/webprojec/info/,不过开发中不要出现不带”/“的情况
  2. 服务器端路径 “/": 相对于项目,"/“即表示http://localhost:8080/webproject/

使用相对路径解决路径写死问题

File file=new File("xxx.xml"),file无法使用相对路径,因为此写法的相对路径是相对于jvm服务器开始运行的地方,即从Tomcat的bin目录下去找。

上述方法不行可以使用servletContext提供的方法:

    // 在servletContext中获得资源的方法
    ServletContext sc = getServletContext();

    // 1.根据相对路径获得指定资源流,相对的是webRoot下
    InputStream is = sc.getResourceAsStream("/WEB-INF/students.xml");
    System.out.println(is);

    // 2.通过相对路径获得绝对路径
    String path = sc.getRealPath("/WEB-INF/students.xml");
    System.out.println(path);

    // path填写相对路径“/”,所有servletContext中关于路径的获得,相对路径都是相对的 WebRoot(项目根)下

但如果资源文件是放在某个包下面,使用servletContext提供的方法可以获得,但填写路径太过麻烦:

//获得cn.iamwyj.servlet.servlet_context包下的资源
getServletContext().getRealPath("/WEB-INF/classes/cn/itcast/servlet/d_servletContext/students.xml");

针对以上问题有两种解决方法:

// 方法一:
InputStream is = this.getClass().getResourceAsStream("students.xml");
// 方法二:
InputStream is = this.getClass().getClassLoader().getResourceAsStream("students.xml");
  1. 方法一使用this.getClass().getResourceAsStream("xxx")获得流,使用this.getClass().getResource("xxx").getPath()获得绝对路径, 相对路径分为两种情况:

    1. 加”/“相对的是classes目录;
      1. 不加”/“相对的是当前类文件所在目录
  2. 方法二使用this.getClass().getClassLoader().getResourceAsStream("xxx")获得流,使用this.getClass().getClassLoader().getResource("xxx").getPath()获得绝对路径, 只有一个相对路径,相对于classes目录

Class和ClassLoader原则上是用来加载字节码文件的,一旦第一次加载完毕就会放在内存中,直到虚拟机关闭

  1. 因为jvm运行期间只加载一次,所以当文件发生变化,使用class和classloader读只能获得第一次加载的,但是使用下面的代码获得绝对路径可以解决这个问题.
//获得绝对路径
String path =this.getClass().getClassLoader().getResource("students.xml").getPath();
File file = new File(path.substring(1, path.length()));
System.out.println(path);
  1. getClassLoader()原本是用来加载.class文件的, 因此缓存设计的很小,不要用来加载较大的资源。

Demo

// 关于Servlet路径配置问题详解
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Set;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class ServletPath extends HttpServlet {

    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        // 在servletContext中获得资源的方法
        ServletContext sc = getServletContext();

        // 1.根据相对路径获得指定资源流,相对的是webRoot下
        InputStream is = sc.getResourceAsStream("/WEB-INF/students.xml");
        System.out.println(is);

        // 2.通过相对路径获得绝对路径
        String path = sc.getRealPath("/WEB-INF/students.xml");
        System.out.println(path);

        // "/"是相对于根目录的
        Set set = sc.getResourcePaths("/");
        for (Object obj : set) {
            System.out.println(obj);
        }

        String path2 = sc.getResource("/WEB-INF/students.xml").getPath();
        System.out.println(path2);

        // 获得lib目录下的资源
        getServletContext().getRealPath("/WEB-INF/lib/students.xml");
        // 获得src下的资源==> 获得classes目录下的资源
        getServletContext().getRealPath("/WEB-INF/classes/students.xml");
 
        // 获得cn.iamwyj.servlet.servlet_context包下的资源
        getServletContext().getRealPath("/WEB-INF/classes/cn/itcast/servlet/d_servletContext/students.xml");
        //如果获得的是包下的资源文件,则路径太麻烦
        /* 解决方法:
        1.使用getClass().getResourceAsStream获得流,使用getClass().getResource.getPath()获得路径
        相对路径分为两种情况:
        1.加"/"相对的是classes目录
        2.不加"/"相对的是本类当前目录*/
        InputStream is2 = this.getClass().getResourceAsStream("students.xml");
        System.out.println(is);

        /*2.使用this.getClass().getClassLoader().getResourceAsStream获得流,使用this.getClass().getClassLoader().getResource.getPath获得路径
        只有一个相对路径,就是相对于 classes目录*/
        InputStream is3 = this.getClass().getClassLoader().getResourceAsStream("students.xml");
        System.out.println(is2);

        //注意: 使用类和类加载器加载资源文件时
        //1.jvm运行期间只加载一次. 但是使用下面的代码可以解决这个问题.
        String path3 = this.getClass().getClassLoader().getResource("students.xml").getPath();
        File file = new File(path.substring(1, path.length()));
        System.out.println(path);
        //2.getClassLoader()原本是用来加载.class文件的, 所以缓存设计的很小.不要用他加载一些别较大的资源.

    }

    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

    }
}