重学Servlet

1.JAVA EE 概述

JavaSE 的源码,字节码在哪

  • JAVA_HOME\src.zip 源码
  • JRE_HOME\lib\rt.jar 字节码

JavaEE 版本号

  • 本篇所讲的是 JavaEE 5
  • JavaEE 5 规范下的子规范
    • Servlet 2.5
    • JDBC
    • ...
  • Java EE 6 规范下的子规范
    • Servlet 3.0
    • JDBC
    • ...
  • Tomcat服务器
    • Tomcat 6 实现了 Servlet 2.5 规范
    • Tomcat 7 实现了 Servlet 3.0 规范
    • 本篇使用 Tomcat7

Java EE 的源码,字节码在哪里

2.系统架构分析

系统架构分类

  • C/S Client/Server
    • 优点:
      • 速度快,大部分数据都已集成到客户端软件中,只需要从服务器上传送少量数据即可
      • 相对安全,大部分数据集成在客户端
    • 缺点:
      • 升级麻烦
      • 需要安装特定软件才能访问服务器
    • 适用系统:
      • 娱乐相关的系统
  • B/S Browser/Server
    • 优点:
      • 不需要安装特定客户端软件
      • 升级只需要分级服务器端,升级方便
    • 缺点:
      • 所有数据全部集成在服务器端,一旦发生不可抗力,数据丢失严重,相对不安全
      • 速度慢
    • 适用系统:
      • 企业内部的办公系统等

3.BS架构访问过程

4.BS架构访问过程详解

5.BS架构角色和协议

6.*模拟Servlet本质

sun提供的 Servlet

Servlet.java

/*
	SUN公司制定的JavaEE规范:Servlet规范
	Servlet接口是Servlet规范中核心接口
	接口注意:调用者谁?实现者谁?
*/
// 服务器端的java程序不能随意编写,必须实现Servlet接口
public interface Servlet{
	
    /*
    	服务器端的java小程序必须将service方法实现
    */
    void service();
}

JavaWeb 程序员编写以下代码

编写服务器端java小程序时,不能随意编写,必须实现Servlet接口

LoginServlet.java

public class LoginServlet implements Servlet{
    public void service(){
        System.out.println("连接数据库验证登陆密码");
    }
}

DeleteServlet.java

public class DeleteServlet implements Servlet{
    public void service(){
        System.out.println("连接数据库删除");
    }
}

SaveServlet.java

public class SaveServlet implements Servlet{
    public void service(){
        System.out.println("连接数据库保存");
    }
}

web.xml

在这里维护路径和 servlet 之间的关系

由 JavaWeb程序员编写

/login = LoginServlet
/save = SaveServlet
/delete = DeleteServlet

Tomcat 服务器

Tomcat.java

/*
	Tomcat 
        WebServer
        Web 服务器
        Web Container
        Web 容器
*/
import java.util.Scanner;
import java.util.Properties;
import java.io.FileReader;

public class Tomcat{
    public static void main(String[] args) throws Exception{
        Scanner s = new Scanner(System.in);
        System.out.println("--服务器启动成功--");
        // 使用while来保持一直可以输入的状态
        while(true){
            System.out.println("---请输入请求路径:");
        
            // 程序执行到这里,等待用户的访问
            String requestPath = s.next();
            System.out.println("您访问的资源路径时:" + requestPath);

            // Tomcat 读取 web.xml 文件
            FileReader reader = new FileReader("web.xml");
            Properties pro = new Properties();
            pro.load(reader);
            reader.close();

            // 通过 key(请求路径) 获取 value(编写的Servlet)
            String servletClassName = pro.getProperty(requestPath);

            // 通过反射机制创建对象
            Class c = Class.forName(servletClassName);
            // Object obj = c.newInstance(); 
            // 由于都实现了Servlet接口,可以直接强转为 Servlet
            Servlet servlet = (Servlet)c.newInstance();

            // 面向Servlet接口调用方法
            servlet.service();
        }
    }
}

查看效果

  1. cmd 进入上面文件所在文件夹
  2. javac *.java 编译Java文件
  3. java Tomcat 启动Tomcat主类
  4. 输入 web.xml 中的路径,执行对应的servlet类方法

7.安装Tomcat,配置JAVA_HOME

8.完善Tomcat环境变量

9.Tomcat服务器目录介绍

Tomcat 7

  • bin 命令
  • conf 配置文件
  • lib
  • logs 日志信息
  • temp 临时文件夹
  • webapps 项目文件夹
  • work jsp相关目录

10.开发第一个webapp

tomcat --> webapps --> FirstWebApp --> login.html

tomcat --> webapps --> FirstWebApp --> html --> welcome.html

重启tomcat即可访问tomcat对应目录下的网页

11.开发第一个webapp

同上

12.开发第一个带有Servlet的webapp

新建目录

tomcat --> webapps --> FirstServletWebApp

FirstServletWebApp

  • WEB-INF
    • classes 存放字节码
    • lib 存放jar包,库
    • web.xml servlet配置
  • html
  • css
  • js

编写Servlet

HelloServlet.java 将编译过的class文件放到上面的classes文件夹中

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

public class HelloServlet implements Servlet{
    
    public void init(ServletConfig config) throws ServletException{
        
    }
    
    public void service(ServletRequest request,ServletResponse response) throws IOException,ServletException{
        System.out.println("Hello World");
    }
    
    public void destroy(){
        
    }
    
    public String getServletInfo(){
        return null;
    }
    
    pubilc ServletConfig getServletConfig(){
        return null;
    }
}

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_3_1.xsd"
  version="3.1">

	<!-- 这是一个合肥的web.xml文件 -->
	<!-- 一个webapp只有一个web.xml文件 -->
	<!-- web.xml文件主要配置请求路径和Servlet类名之间的绑定关系 -->
	<!-- web.xml文件在Tomcat服务器启动阶段被解析 -->
	<!-- web.xml文件解析失败,会导致webapp启动失败 -->
	<!-- web.xml文件中的标签不能随意编写,因为tomcat服务器知道该文件中编写了哪些标签 -->
	<!-- web.xml文件中的标签也是SUN公司制定的Servlet规范 -->
    <servlet>
    	<servlet-name>thisIsServletName</servlet-name>
        <servlet-class>HelloServlet</servlet-class>
    </servlet>
    <servlet-mapping>
    	<servlet-name>thisIsServletName</servlet-name>
        <!-- 路径随意编写,但是必须以 / 开始 -->
        <!-- 这个路径是一个虚拟的路径,只是代表一个资源的名称 -->
        <url-pattern>/hel/lo/he</url-pattern>
        <!-- 可以编写多个url-pattern,路径需要以 / 开始 -->
        <url-pattern>/hello</url-pattern>
    </servlet-mapping>
</web-app>

13.将响应结果输出到浏览器中

tomcat --> webapps --> PrintToBrowser

**PrintToBrowser **

  • WEB-INF
    • classes 存放字节码
    • lib 存放jar包,库
    • web.xml servlet配置

编写Servlet

import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
import java.io.PrintWriter;// 标准输出流,不需要关闭

public class WelcomeServlet implements Servlet{
    
    public void init(ServletConfig config) throws ServletException{
        
    }
    
    public void service(ServletRequest request,ServletResponse response) throws IOException,ServletException{
        
        // 解决响应的时候中文乱码问题
        // 设置响应的内容类型以及字符编码方式
        // 在获取响应流之前设置
        response.setContentType("text/html;charset=UTF-8");
        
        // 将信息输出到浏览器上
        // 将HTML字符串输出到浏览器上,浏览器解析执行
        // 获取输出流对象,流直接指向特定的浏览器客户端
        PrintWriter out = response.getWriter();
        
        // 这是再设置的话就晚了,一定要在前面设置
        // response.setContentType("text/html;charset=UTF-8");
        
        // 响应html代码到浏览器
        out.print("<html>");
        out.print("<head>");
        out.print("<title>welcome servlet</title>");
        out.print("</head>");
        out.print("<body>");
        out.print("<h1 align=\"center\">welcome study servlet</h1>");
        out.print("</body>");
        out.print("</html>");
        
        // ln表示将HTML源代码换行,这个没有必要,反而使得html体积变大
        /*
            out.println("<html>");
            out.println("<head>");
            out.println("<title>welcome servlet</title>");
            out.println("</head>");
            out.println("<body>");
            out.println("<h1 align=\"center\">welcome study servlet</h1>");
            out.println("</body>");
            out.println("</html>");
        */
        
    }
    
    public void destroy(){
        
    }
    
    public String getServletInfo(){
        return null;
    }
    
    pubilc ServletConfig getServletConfig(){
        return null;
    }
}

web.xml 同上节

14.IDE环境开发webapp

15.Servlet中编写JDBC连接

16.IDE中部分快捷键

17.Servlet对象生命周期

Servlet对象的生命周期

什么是生命周期

生命周期表示一个java对象从最初被创建到最终被销毁,经历的所有过程

Servlet对象的生命周期是谁来管理的?程序员可以干涉吗?

  • Servlet对象的生命周期,JavaWeb程序员是无权干涉的,包括该Servlet对象相关方法的调用,也是无权干涉的 。

  • Servlet对象从最初的创建开始,方法的调用,以及最后对象的销毁,整个过程,都是由Web容器来管理的。

  • Web Container 管理Servlet生命周期

默认情况下,Servlet对象在Web服务器启动阶段不会被实例化。【若希望在web服务器启动阶段实例化Servlet对象,需要进行特殊的设置】

描述Servlet对象生命周期

  1. 用户在浏览器地址栏输入URL:http://localhost:8080/prj/testlifecycle
  2. web容器截取请求路径 /prj/testlifecycle
  3. web容器在容器上下文中找请求路径 /prj/testlifecycle 对应的Servlet对象
  4. 若没有找到对应的Servlet对象
    1. 通过web.xml文件中相关的配置信息,得到请求路径 /testlifecycle 对应的Servlet完整类名
    2. 通过反射机制,调用Servlet类的无参构造方法,完成Servlet对象的实例化
    3. web容器调用Servlet对象的 init 方法完成初始化操作
    4. web容器调用Servlet对象的 service 方法提供服务
  5. 若找到对应的Servlet对象
    1. web容器直接调用Servlet对象的 service 方法提供服务
  6. web容器关闭的时候‘/webapp重新部署的时候/该Servlet对象长时间没用用户再次访问的时候,web容器会将该Servlet对象销毁,在销毁该对象之前,web容器会调用Servlet对象的 destroy 方法,完成销毁之前的准备。

总结

  • Servlet类的构造方法只执行一次
  • Servlet对象的init方法只执行一次
  • Servlet对象的service方法,只要用户请求一次,则执行一次
  • Servlet对象的destroy方法只执行一次

注意

init方法执行的时候,Servlet对象已经被创建好了

destroy方法执行的时候,Servlet对象还没有被销毁,即将被销毁

Servlet对象是单例,但是不符合单例模式,只能称为伪单例。真单例的构造方法是私有化的。Tomcat服务器是支持多线程的,所以Servlet对象在单实例多线程的环境下运行。那么Servlet对象中若由实例变量,并且实例变量涉及到修改操作,那么这个Servlet对象一定会存在线程安全问题,不建议在Servlet对象中使用实例变量,尽量使用局部变量。

18.web服务器启动加载Servlet

若希望在web服务器启动阶段实例化Servlet对象,需要在web.xml文件中进行相关的配置load-on-startup,越小优先级越高,例如:

<servlet>
	<servlet-name>testlifecycle</servlet-name>
    <servlet-class>com.bj.javaweb.servlet.HelloServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
</servlet>

19.Servlet对象生命周期

Servlet对象实例化之后,这个Servlet对象被存储到哪里了

大多数的web容器都是将该Servlet对象以及对应的url-pattern存储到Map集合中了

在WEB容器中由这样一个Map集合

Map<String, Servlet> 集合

key value
/login LoginServlet对象引用
/delete DeleteServlet对象引用
/save SaveServlet对象引用

服务器在启动的时候就会解析各个webapp的web.xml文件,做了什么

将web.xml文件中的url-pattern 和对一个的 Servlet 完整类名存储到Map集合中:

在web容器中由这样一个map集合

Map<String,String> 集合

key value
/login com.bj.javaweb.servlet.LoginServlet
/delete com.bj.javaweb.servlet.DeleteServlet
/save com.bj.javaweb.servlet.SaveServlet

20.UML

21.UML

22.Servlet 对象生命周期

Servlet接口中的这些方法中填写什么代码?什么时候使用这些方法

  • 无参构造方法 【尽量别动构造函数】

  • init方法

    以上两个方法执行时间几乎是相同的,执行次数都是一次,构造方法执行时对象正在创建,init方法执行时对象已经创建:

    • 若系统要求在对象创建时刻执行一段特殊的程序,这段程序尽量写道init方法中。

      为什么不建议将代码编写到构造函数中:

      存在风险!当程序员编写构造方法的时候,可能会导致无参构造方法不存在。(类不编写任何构造函数,默认又一个无参构造函数,但编写了一个有参构造方法后,系统就不提供无参构造方法了)

    init方法时为javaweb程序员提供的一个初始化时刻。

  • service方法

    这个方法必然要重写,因为需要完成业务逻辑的处理,请求的处理以及完成响应

  • destroy方法

    也是为javaweb程序员提供的一个特殊时刻,被称为对象的销毁时刻

回顾

类加载时刻执行程序,代码写到哪里? -------编写到静态代码块中

23.ServletConfig接口

AServlet

public class HelloServlet implements Servlet{
    
    private ServletConfig config;
    
    @Override
    public void init(ServletConfig config) throws ServletException{
        // 目的:在service方法中也能使用 ServletConfig
        this.config = config;
        
        System.out.println("AServlet's ServletConfig = " + config.toString());
    }
    
    @Override
    public void service(ServletRequest request,ServletResponse response) throws IOException,ServletException{
        System.out.println("Hello World");
    }
    
    @Override
    public void destroy(){
        
    }
    
    @Override
    public String getServletInfo(){
        return null;
    }
    
    // 给子类调用,在子类中若想获取ServletConfig,可以调用这个方法
    @Override
    pubilc ServletConfig getServletConfig(){
        return config;
    }
}

**研究 javax.servlet.ServletConfig 接口: **

  1. javax.servlet.servletConfig 是接口

  2. Apache Tomcat 服务器实现了 Servlet 规范,Tomcat服务器专门写了以个ServletConfig接口的实现类【作为了解,主要是思想】

    实现类的完整类名:org.apache.catalina.core.StandardWrapperFacade

  3. javaweb程序员编程的时候,一直是面向ServletConfig接口去完成调用,不需要关心具体的实现类。

    Webapp 放到 Tomcat服务器中,ServletConfig的实现类是:org.apache.catalina.core.StandardWrapperFacade

    Webapp 放到 JBOSS 服务器中,ServletConfig的实现类可能是另外一个类名

    这些所有的实现类,我们都不需要关心,只需要学习ServletConfig接口中有哪些可以使用的方法。

  4. Tomcat服务器是一个实现了Servlet规范和JSP规范的容器

  5. ServletConfig接口中有哪些常用的方法?

  6. ServletConfig到底是什么

    • ServletConfig 是一个Servlet对象的配置信息对象,ServletConfig 对象中封装了一个 Servlet 对象的配置信息。【Servlet对象的配置信息在web.xml文件中】
    • 一个Servlet对象对应一个ServletConfig对象,100个Servlet对象对应100个ServletConfig对象
  7. 如何将init方法上的ServletConfig参数移动到 service 方法中?因为我们程序员主要编写的方法是 service方法,在 service方法中,我们可能要使用 ServletConfig

    • 在 init方法中完成:局部变量 config赋值个实例变量 config
    • 实现 getServletConfig方法,返回实例变量config。提供公开的get方法目的是供子类使用。

24.ServletConfig接口

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_3_1.xsd"
  version="3.1">
    <servlet>
    	<servlet-name>thisIsServletName</servlet-name>
        <servlet-class>HelloServlet</servlet-class>
        <!-- 初始化参数:被封装到 ServletConfig对象中了 -->
        <init-param>
        	<para-name>driver</para-name>
            <para-value>com.mysql.jdbc.Driver</para-value>
        </init-param>
        <init-param>
        	<para-name>url</para-name>
            <para-value>jdbc:mysql://localhost:3366/x</para-value>
        </init-param>
        <init-param>
        	<para-name>user</para-name>
            <para-value>root</para-value>
        </init-param>
        <init-param>
        	<para-name>password</para-name>
            <para-value>1234</para-value>
        </init-param>
    </servlet>
    <servlet-mapping>
    	<servlet-name>thisIsServletName</servlet-name>
        <url-pattern>/hello</url-pattern>
    </servlet-mapping>
</web-app>

ServletConfig接口中有哪些常用的方法

  • String getInitParameter(String name)

    通过初始化参数的name获取value

  • Enumeration getInitParameterNames()

    获取所有初始化参数的name

  • ServletContext getServletContext()

    获取 ServletContext【Servlet上下文】对象

  • String getServletName()

    获取servletName <servlet-name>servletName</servlet-name>

public class HelloServlet implements Servlet{
    
    private ServletConfig config;
    
    @Override
    public void init(ServletConfig config) throws ServletException{
        this.config = config;
    }
    
    @Override
    public void service(ServletRequest request,ServletResponse response) throws IOException,ServletException{
        // 获取ServletConfig
        ServletConfig config = getServletConfig();
        
        // 通过初始化参数的name获取value
        String driver = config.getInitParameter("driver");
        String url = config.getInitParameter("url");
        String user = config.getInitParameter("user");
        String password = config.getInitParameter("password");
        
        response.setContentType("text/html;charset=UTF-8");
        PrintWrite out = response.getWriter();
        out.print(driver);
        out.print(url);
        out.print(user);
        out.print(password);
        
        // 获取所有初始化参数的name
        Enumeration<String> names = config.getInitParameterNames();
        while(names.hasMoreElements()){
            String name = names.nextElement();
            String value = config.getInitParameter(name);
            //...config.getInitParameter("xxx")...
        }
        
        // 获取servletName
        String servletName = config.getServletName();
        out.print("<servlet-name>" + servletName + "</servlet-name");
       
        // 先记住这个方法,后面讲什么是ServletContext
        ServletContext application = config.getServletContext();
        out.print(application.toString());// org.apache.catalina.core.ApplicationContextFacade@e45ea8
        
    }
    
    @Override
    public void destroy(){
        
    }
    
    @Override
    public ServletConfig getServletConfig(){
        return config;
    }
    
     @Override
    public String getServletInfo(){
        return null;
    }
}

25.ServletContext接口

AServlet.java

public class AServlet implements Servlet{
    
    private ServletConfig config;
    
    @Override
    public void init(ServletConfig config) throws ServletException{
        this.config = config;
    }
    
    @Override
    public void service(ServletRequest request,ServletResponse response) throws IOException,ServletException{
        // 获取 ServletConfig
        ServletConfig config = getServletConfig();
        
        // 获取ServletContext
        ServletContext application = config.getServletContext();
        
        System.out.println("AServlet's ServletContext = " + application);
        // 打印 AServlet's ServletContext = org.apache.catalina.core.ApplicationContextFacade@7e89f2
    }
    
    @Override
    public void destroy(){
        
    }
    
    @Override
    public ServletConfig getServletConfig(){
        return config;
    }
    
     @Override
    public String getServletInfo(){
        return null;
    }
}

BServlet.java

public class BServlet implements Servlet{
    
    private ServletConfig config;
    
    @Override
    public void init(ServletConfig config) throws ServletException{
        this.config = config;
    }
    
    @Override
    public void service(ServletRequest request,ServletResponse response) throws IOException,ServletException{
        // 获取 ServletConfig
        ServletConfig config = getServletConfig();
        
        // 获取ServletContext
        ServletContext application = config.getServletContext();
        
        System.out.println("BServlet's ServletContext = " + application);
        // 打印 AServlet's ServletContext = org.apache.catalina.core.ApplicationContextFacade@7e89f2
    }
    
    @Override
    public void destroy(){
        
    }
    
    @Override
    public ServletConfig getServletConfig(){
        return config;
    }
    
     @Override
    public String getServletInfo(){
        return null;
    }
}

打印结果一样,两个 Servlet获取的是同一个ServletContext

研究:javax.servlet.ServletContext 接口

  1. javax.servlet.ServletContext接口,Servlet规范

  2. Tomcat服务器对ServletContext接口实现类的完整类名:org.apache.catalina.core.ApplicationContextFacade

    JavaWeb程序员只需要面向ServletContext接口调用方法接口,不用关心Tomcat具体的实现

  3. ServletContext到底是什么?什么时候创建?什么时候被销毁?创建几个?

    • ServletContext 被翻译为:Servlet上下文【Context一般都翻译为上下文】
    • 一个webapp只有一个 web.xml 文件,web.xml 文件在服务器启动阶段被解析
    • 一个webapp只有一个 ServletContext对象,ServletContext在服务器启动阶段被实例化
    • ServletContext在服务器关闭的时候会被销毁
    • ServletContext对应的是 web.xml 文件,是 web.xml 文件的代表
    • ServletContext 是所有Servlet对象四周环境的代表【在同一个webapp中,所有的Servlet对象共享同一个四周环境对象,该对象就是ServletContext】
  4. ServletContexte接口中常用的方法有哪些?

    • Object getAttribute(String name)

      从 ServletContext 中获取数据

    • void setAttribute(String name, Object object)

      向 ServletContext 中添加数据

    • void removeAttribute(String name)

      移除 ServletContext 中的数据

    • String getInitParameter(String name)

    • Enumeration getInitParameterNames()

    • String getRealPath(String path)

  5. Servlet, ServletConfig, ServletContext 之间的关系

    • 一个 Servlet 对应一个 ServletConfig ,100个Servlet 对应 100个 ServletConfig
    • 所有的 Servlet 共享一个 ServletContext 对象

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_3_1.xsd"
  version="3.1">
    
    <!-- web.xml 文件中配置上下文参数,这些信息被自动封装到ServletContext对象中 -->
    <context-param>
    	<param-name>username</param-name>
        <param-value>admin</param-value>
    </context-param>
    <context-param>
    	<param-name>password</param-name>
        <param-value>123</param-value>
    </context-param>
    <servlet>
    	<servlet-name>thisIsServletName</servlet-name>
        <servlet-class>HelloServlet</servlet-class>
    </servlet>
    <servlet-mapping>
    	<servlet-name>thisIsServletName</servlet-name>
        <url-pattern>/hello</url-pattern>
    </servlet-mapping>
</web-app>

Servlet.java

public class BServlet implements Servlet{
    
    private ServletConfig config;
    
    @Override
    public void init(ServletConfig config) throws ServletException{
        this.config = config;
    }
    
    @Override
    public void service(ServletRequest request,ServletResponse response) throws IOException,ServletException{
        // 获取 ServletConfig
        ServletConfig config = getServletConfig();
        
        // 获取ServletContext
        ServletContext application = config.getServletContext();
        
        // 获取所有上下文初始化参数的name
        Enumeration<String> names = application.getInitParameterNames();
        while(names.hasMoreElements()){
            String name = names.nextElement();
            // 通过上下文初始化的name获取对应value
            String value = application.getInitParameter(name);
        }
        
        // 获取文件的绝对路径
        String realPath = application.getRealPath("/index.html");
        System.out.println(realPath);
        // 打印 C:\tomcat7\webapps\prj\index.html
    }
    
    @Override
    public void destroy(){
        
    }
    
    @Override
    public ServletConfig getServletConfig(){
        return config;
    }
    
     @Override
    public String getServletInfo(){
        return null;
    }
}

26.ServletContext 接口

  • Object getAttribute(String name)
  • void setAttribute(String name, Object object)
  • void removeAttribute(String name)

ServletContext 范围可以完成跨用户传递数据

总结

到目前位置,所有编写过的路径:

  1. 超链接

    <a href="/webappname/doSome"></a>

  2. web.xml 中的 url-pattern

    <url-pattern>/doSome</url-pattern>

  3. form 表单的 action 属性

    <form action="/webappname/doSome"></form>

  4. String realPath = application.getRealPath("/WEB-INF/resources/db.properties");

27.欢迎页面

关于一个 webapp 欢迎页面的设置:

欢迎页面怎么设置?

  • 假设在WebRoot目录下创建 login.html,想让 login.html 作为整个webapp的欢迎页面,应该做这样的设置, 编写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_3_1.xsd"
      version="3.1">
        
        <!-- 欢迎页面的设置 -->
        <welcome-file-list>
        	<welcome-file>login.html</welcome-file>
            <welcome-file>html/welcome.html</welcome-file>
        </welcome-file-list>
    	
        <!-- 
     	<welcome-file-list>
        	<welcome-file>system/welcome</welcome-file>
        </welcome-file-list>
    	-->
        <servlet>
        	<servlet-name>welcome</servlet-name>
            <servlet-class>com.bj.javaweb.servlet.WelcomeServlet</servlet-class>
        </servlet>
        <servlet-mapping>
        	<servlet-name>welcome</servlet-name>
            <url-pattern>/system/welcome</url-pattern>
        </servlet-mapping>
    </web-app>
    

为什么设置欢迎页面?

  • 为了访问更方便,提高用户体验
  • 设置欢迎页面后,直接在浏览器地址栏上访问该webapp,即可自动定位到欢迎页面,例如:http://localhost:8080/prj/ 直接访问的就是 login.html

欢迎页面可以设置多个,越靠上面的优先级越高

上面的配置,优先选择login.html,若这个资源不存在,才会选择welcome.html

注意:欢迎页面设置的时候,路径不需要以 “/” 开始

  • <welcome-file>login.html</welcome-file>

    要求在webapp的根目录下,必须有一个文件,叫做 login.html

  • <welcome-file>html/welcome.html</welcome-file>

    要求在webapp的根目录下,必须有一个文件夹,叫做html,该文件夹中必须有一个文件叫做:welcome.html

一个webapp的欢迎页面不一定是一个HTML资源,可以是任意一种类型的web资源,欢迎页面可以是Servlet

欢迎页面包括全局配置和局部配置

  • 全局配置:CATALINA_HOME/conf/web.xml
  • 局部配置:CATALINA_HOME/webapps/webapp/WEB-INF/web.xml

注意:就近原则,局部配置优先级大于全局配置

28.HTTP状态码 404 500

在一些错误发生之后,统一进行错误的处理,可以在 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_3_1.xsd"
  version="3.1">
    
    <servlet>
    	<servlet-name>welcome</servlet-name>
        <servlet-class>com.bj.javaweb.servlet.WelcomeServlet</servlet-class>
    </servlet>
    <servlet-mapping>
    	<servlet-name>welcome</servlet-name>
        <url-pattern>/system/welcome</url-pattern>
    </servlet-mapping>
    
    <!-- 配置 -->
    <error-page>
    	<error-code>404</error-code>
        <location>/error/error.html</location>
    </error-page>
    <error-page>
    	<error-code>500</error-code>
        <location>/error/error.html</location>
    </error-page>
</web-app>

路径总结

  • 以 / 开始,加 webapp 名称
  • 以 / 开始,不加 webapp 名称
  • 除以上两类之外,欢迎页面( )比较特殊。不以 / 开始,也不加 webapp 名称

29.适配器

public interface CommonIn{
    void m1();
    void m2();
    void m3();
    void m4();
    void m5();
    void m6();
}

public class A implements CommonIn{
    @Override
    public void m1(){
        // doSome
    };
    
    @Override
    public void m2(){};
    
    @Override
    public void m3(){};
    
    @Override
    public void m4(){};
    
    @Override
    public void m5(){};
    
    @Override
    public void m6(){};
}

public class B implements CommonIn{
    @Override
    public void m1(){};
    
    @Override
    public void m2(){
        // doSome
    };
    
    @Override
    public void m3(){};
    
    @Override
    public void m4(){};
    
    @Override
    public void m5(){};
    
    @Override
    public void m6(){};
}

代码冗余

30.适配器

public interface CommonIn{
    void m1();
    void m2();
    void m3();
    void m4();
    void m5();
    void m6();
}

public abstract class Adapter implements CommonIn{
    @Override
    abstract public void m1(){};
    
    @Override
    abstract public void m2(){};
    
    @Override
    public void m3(){};
    
    @Override
    public void m4(){};
    
    @Override
    public void m5(){};
    
    @Override
    public void m6(){};
}

public class A extends Adapter{
    @Override
    public void m1(){
        // doSome
    };
    
    @Override
    public void m2(){
        // doSome
    };
}

public class B extends Adapter{
    @Override
    public void m1(){
        // doSome
    };
    
    @Override
    public void m2(){
        // doSome
    };
   
}

31.*GenericServlet

GenericServlet.java

/**
 * GenericServlet 是一个适配器,这个适配器是一个Servlet
 * 以后无需直接实现Servlet接口,直接集成这个适配器即可。重写 service方法
 */
public abstract class GenericServlet implements Servlet{
    
    private ServletConfig config;
    
    // 设置成final,防止子类重写,config就无法赋值了
    @Override
    public final void init(ServletConfig config) throws ServletException{
        this.config = config;
        this.init();
    }
    
    // 建议重写一个无参的init初始化方法给子类重写
    public void init(){
        
    }
    
    @Override
    abstract public void service(ServletRequest request,ServletResponse response) throws IOException,ServletException{}
    
    @Override
    public void destroy(){
        
    }
    
    @Override
    public String getServletInfo(){
        return null;
    }
    
    @Override
    pubilc ServletConfig getServletConfig(){
        return config;
    }
    
    // ------- 以下所有方法都是扩展方法,方便子类的使用 ------
    
    public ServletContext getServletContext(){
        return getServletConfig.getServletContext();
    }
}

HelloServlet.java

public class HelloServlet extends GenericServlet{
    
    @Override
    public void init(){
        System.out.println("HelloServlet init ...");
    }
    
    @Override
    public void service(ServletRequest request,ServletResponse response) throws IOException,ServletException{
        // doService
    }
}

32.HTTP 协议 GET和POST

33.HTTP 协议 GET和POST

HTTP协议的详细内容

  • 请求协议包括4部分

    • 请求行
    • 消息报头
    • 空白行
    • 请求体

    请求行 包括:请求方式 URI 协议版本号

    空白行:专门用来分离消息报头和请求体的

  • 响应协议包括4部分

    • 状态行
    • 响应报头
    • 空白行
    • 响应体

    状态行:协议版本号 状态码 状态描述信息

    空白行:专门用来分离响应报头和响应体的

    响应协议中重点掌握状态码 200 404 500

GET 和 POST 的区别

34.HTTP协议 缓存解决方案

浏览器将资源缓存之后,缓存的资源是和某个特定的路径绑定在一起,只要浏览器再发送这个相同的请求路径,这时候就会去缓存中获取资源,不再访问服务器,以这种方式降低服务器的压力,提高用户的体验。

但是有的时候,我们不希望走缓存,希望每一次都访问服务器,可以在请求路径后面添加时间戳,例如:http://ip:port/oa/system?timestamp=1233241432 【JS获取毫秒:new Date().getTime();

35.保证前端请求方式和后台处理程序方式一致

前端的页面发送的请求方式应当与服务器端需要的请求方式一致。

如何完成以上需求?

  • 在javaweb程序中想办法获取该请求是什么类型的请求,POST 还是 GET?

  • 当外卖获取到请求方式之后,在javaweb程序中可以使用java语言中 if语句进行判断

    if("POST".equals(method)){
        //doPost
    }else if("GET".equals(method)){
        //doGet
    }
    

怎么在javaweb中获取请求方式

  • 重点:HTTP的请求协议全部信息被自动封装到 javax.servlet.http.HttpServletRequest 对象中
  • 在 HttpServletRequest 接口类型中有一个方法:String getMethod(); 可以获取请求方式。
  • public interface HttpServletRequest extends ServletRequest

LoginServlet.java

public class LoginServlet extends GenericServlet{
    
    @Override
    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException{
        // 将ServletRequest,ServletResponse强制类型转换成带有Http的接口类型
        HttpServletRequest request = (HttpServletRequest)req;
        HttpServletResponse response = (HttpServletResponse)res;
        
        response.setContentType("text/html;charset=UTF-8");
        PrintWriter out = response.getWriter();
        
        // 获取浏览器发送的请求方式
        String method = request.getMethod();
        //System.out.println(method);// GET 或 POST
        //LoginServlet 是处理登陆的,要求前端必须发送POST请求
        if("GET".equals(method)){
            //后台报错
            out.print("405---您应该发送POST请求");
            throw new RuntimeException("405---您应该发送POST请求");
        }else if("POST".equals(method)){
            // 程序正常执行
            out.print("success...正在登陆...");
        }
    }
}

36.*HttpServlet原理

上节在每一个Servlet中都编写了以下程序阿里保证前端的请求方式和后台需要的请求方式保持一致。

HttpServletRequest request = (HttpServletRequest)req;
String method = request.getMethod();        
if("GET".equals(method)){
    //doGet
}else if("POST".equals(method)){
    //doPost
}

以上代码在每个Servlet类中都要编写,怎样能封装一下才能不写这样的代码

HttpServlet.java

public class HttpServlet extends GenericServlet {
    
    @Override
    public void service(ServletRequest req, ServletRequest res) throws ServletException,IOException{
        
        HttpServletRequest request = (HttpServletRequest)req;
        HttpServletResponse response = (HttpServletResponse)res;
        
        service(request,response);
    }
    
    public void service(HttpServletRequest request,HttpServletRequest response) throws ServletException, IOException{
        
        String method  = request.getMethod();
        if("GET".equals(method)){
            doGet(request,response);
        }else if("POST".equals(method)){
            doPost(request,response);
        }
    }
    
    public void doGet(HttpServletRequest request,HttpServletRequest response) throws ServletException, IOException{
        response.setContextType("text/html;charset=UTF-8");
        PrintWriter out = resposne.getWriter();
        out.print("405-您应该发送POST请求");
        throw new RuntimeException("405-您应该发送POST请求");
    }
    
    public void doPost(HttpServletRequest request,HttpServletRequest response) throws ServletException, IOException{
        response.setContextType("text/html;charset=UTF-8");
        PrintWriter out = resposne.getWriter();
        out.print("405-您应该发送GET请求");
        throw new RuntimeException("405-您应该发送GET请求");
    }
    
}

LoginServlet.java

public class LoginServlet extends HttpServlet{
    
    @Override
    public void doPost(HttpServletRequest request,HttpServletRequest response) throws ServletException, IOException{
        
        response.getWriter().print("login...");
    } 
}

重点

编写一个Servlet类应当集成HttpServlet,get请求就重写 doGet方法,post请求就重写 doPost方法

doPost/doGet 方法可以等同看做main方法

当浏览器发送的请求方式和后台的处理方式不同的话,会出现一个错误,405

37.模板方法设计模式

使用模板方法设计模式

/**
 * 模板方法设计模式,可以做到在不改变算法的前提之下,可以重新定义算法步骤的具体实现。
 */
public abstract class PersonTemplate{
    
    /**
     * templateMethod是一个模板方法,定义核心算法骨架,具体的实现步骤延迟到子类中完成
     * 算法为了受到保护,所以模板方法一般都被final修饰
     * 核心算法骨架不需要每一次都编写,这个核心算法只在模板方法中编写了一次。
     */
	public final void templateMethod(){
        // 核心算法骨架
        do1();
        do2();
        do3();
    }
    
    // 下面是具体的实现步骤之一,具体的实现步骤可以延迟到子类中完成,方法通常是抽象方法
    
    public abstract void do3(){
        
    }
    
    public abstract void do2(){
        
    }
    
    publc abstract void do1(){
        
    }
}

public class Student extends PersonTemplate{
    
    @Override
    public void do1(){
        System.out.println("学生-上学");
    }
    
    @Override
    public void do2(){
        System.out.println("学生-上课");
    }
    
    @Override
    public void do3(){
        System.out.println("学生-放学");
    }
    
}

public class Worker extends PersonTemplate{
    
    @Override
    public void do1(){
        System.out.println("工人-上班");
    }
    
    @Override
    public void do2(){
        System.out.println("工人-工作");
    }
    
    @Override
    public void do3(){
        System.out.println("工人-下班");
    }
    
}

public class Test{
    public static void main(String[] args){
        PersonTemplate p1 = new Student();
        p1.templateMethod();
        
        PersonTemplate p2 = new Worker();
        p2.templateMethod();
    }
}

项目中哪里见过模板方法设计模式

Servlet规范中的:HttpServlet

  • HttpServlet是一个典型的模板方法设计模式

    HttpServlet是一个模板类

  • 其中的service(HttpServletRequest,HttpServletResponse)方法是典型的模板方法

    在该方法中定义了核心算法骨架,doGet,doPost, ... 具体实现步骤延迟到子类中完成。

38.HttpServletRequest接口

研究:javax.servlet.http.HttpServletRequest 接口

HttpServletRequest 是一个接口,Servlet规范中重要的接口之一。

继承关系

public interface HttpServletRequest extends ServletRequest{}

HttpServletRequest 接口的实现类是Web容器(Tomcat)负责实现的。但是程序员还是只需要面向HttpServletRequest接口调用方法即可,不需要关心实现类。

HttpServletRequest 对象中封装了哪些信息

  • 封装了HTTP请求协议的全部内容
    • 请求方式
    • URI
    • 协议版本号
    • 表单提交的数据
    • ...

HttpServletRequest一般变量名叫:request,表示请求。HttpServletRequest对象代表一次请求,一次请求对应一个request对象,100个请求对应100个request对象,所以request的生命周期是短暂的。

HttpServletRequest接口中常用的方法

表单提交的数据信息会被自动封装到request对象中,request对象中有Map集合存储这些数据【Map集合的key是name,value是一个字符串类型的一维数组】

  • String getParameter(String name)

    通过key获取value这个一维数组中的首元素(通常情况下这个一维数组中只有一个元素,所以这个方法使用最多)

  • Map getParameterMap()

    获取整个Map集合

  • Enumeration getParameterNames()

    获取整个Map集合的所有key

  • String[] getParameterValues(String name)

    通过Map集合key获取value(一维数组)

public class SaveUserServlet extends HttpServlet{
    
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
        // 获取用户信息,表单提交的,这些数据信息被自动封装到request对象中了
        // 从request中获取这些信息
        // 表单数据是这样的格式提交的,POST请求,在请求体中提交,数据格式:
        // username=admin&password=123&sex=m&...
        /*
         * Map<String,String[]>
           key                 value
           --------------------------------
           username             {"admin"}
           password             {"123"}
           sex                  {"m"}
           interest             {"sport","music"}
           ...					...
         */
        String username = request.getParameter("username");
        // ...
        
        // 获取所有的兴趣爱好
        // 这个方法适合取checkbox中的数据
        String[] interests = request.getParameterValues("interest");
        for(String in : interesets){
            System.out.println(in);
        }
        
        // 获取整个参数Map集合
        Map<String,String[]> parameterMap = request.getParameterMap();
        Set<String> names = parameterMap.keySet();
        for(String name : names){
            String[] values = parameterMap.get(name);
            System.out.println(name + "=" + value);
        }
        
        // 获取参数Map集合所有的key
        Enumeration<String> keys = request.getParameterNames();
        while(keys.hasMoreElements()){
            String key = keys.nextElement();
            System.out.println(key);
        }
    }
}

39.HttpServletRequest接口

ServletContext是Servlet上下文对象,该接口中页有这样几个方法

  • void setAttribute(String name, Object o) //向ServletContext范围中添加数据
  • Object getAttribute(String name)
  • void removeAttribute(String name)

ServletContext是一个怎样的范围?

​ 所有用户共享的一个范围对象,我们一般把ServletContext变量命名喂:application

可见这个对象代表一个应用。一个webapp只有一个这样的对象,范围极大。

接口中常用方法(二)

  • String getContextpath()

    获取上下文路径,webapp的根路径

  • String getMethod()

    获取浏览器的请求方式

  • String getRequestURI()

    获取URI

  • StringBuffer getRequestURL()

    获取URL

  • String getServletPath()

    获取Servlet Path (url-pattern)

  • String getRemoteAddr()

    获取客户端IP地址

  • void setAttribute(String name, Object o)

    向request范围中添加数据

  • Object getAttribute(String name)

    向request范围中获取数据

  • void removeAttribute(String name)

    向request范围中移除数据

  • RequestDispatcher getRequestDispatcher(String path)

    获取请求转发器,让转发器对象指向某个资源

  • void setCharacterEncoding(String env)

  • Cookie[] getCookies() 【后面讲Cookie的时候讲】

  • HttpSession getSession() 【后面讲Session的时候讲】

public class TestRequestServlet extends HttpServlet{
	
    @Override
    protected void doGet(HttpServletRequest requets, HttpServletResponse response) throws ServletException,IOException{
        
        response.setContextType("text/html;charset=UTF-8");
        PrintWriter out = response.getWriter();
        
        // 获取上下文路径【webapp的根路径】
        String contextPath = request.getContextPath(); // prj
        
        // 获取浏览器请求方式
        String method = request.getMethod();
        
        //获取请求的URI
        String uri = request.getRequestURI();// prj/testServlet
        
        // 获取请求的URL
		String url = request.getRequestURL().toString();// http://localhost:8080/prj/testServlet
        
        // 获取Servlet Path
        String serlvetPath = request.getServletPath();// testServlet
        
        // 获取客户端IP地址
        String clientIp = request.getRemoteAddr();
    }
}

request 范围极小,request只能完成在同一次请求中传递数据

<?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_3_1.xsd"
  version="3.1">
    
    <servlet>
    	<servlet-name>a</servlet-name>
        <servlet-class>com.servlet.AServlet</servlet-class>
    </servlet>
    <servlet-mapping>
    	<servlet-name>a</servlet-name>
        <url-pattern>/a</url-pattern>
    </servlet-mapping>
    
    <servlet>
    	<servlet-name>b</servlet-name>
        <servlet-class>com.servlet.BServlet</servlet-class>
    </servlet>
    <servlet-mapping>
    	<servlet-name>b</servlet-name>
        <url-pattern>/b</url-pattern>
    </servlet-mapping>

</web-app>
public class AServlet extends HttpServlet{
	
    @Override
    protected void doGet(HttpServletRequest requets, HttpServletResponse response) throws ServletException,IOException{
        User user = new User();
        user.setUsercode("111");
        user.setUsername("zhangsan");
        
        //将User对象存储到request范围
        request.setAttribute("userObj",user);
        
        // 跳转
        // 执行完AServlet之后,跳转到BServlet执行,将AServlet,BServlet放到同一个请求中
        // 必须使用转发技术
        // forward 【转发】
        // 1.获取请求转发器对象【以下转发器指向了BServlet】
        RequestDispatcher dispatcher = request.getRequestDispatcher("/b");
        // 2.调用请求转发器的forward方法即可完成转发
        dispatcher.forward(request,response);
        
        // 【转发是一次请求】
        // request.getRequestDispatcher("/b").forward(request,response);
    }
}

40.HttpServletRequest接口

关于范围对象的选择

  • ServletContext 应用范围,可以跨用户传递数据
  • ServletRequest 请求范围,只能在同一个请求中传递数据【可以跨Servlet传数据,但是这多个Servlet必须在同一个请求当中】
  • 优先选择 request范围

41.程序中乱码解决方案

关于项目中出现乱码问题

  • 乱码进程出现在什么位置

    • 数据传递过程中的乱码
    • 数据展示过程中的乱码
    • 数据保存过程中的乱码
  • 数据保存过程中的乱码

    • 最终保存到数据库表中,数据出现乱码
    • 导致数据保存过程中的乱码包括以下两种情况
      • 保存之前,数据本身就是乱码
      • 保存之前,数据不是乱码,但由于本身数据库不支持简体中文,保存之后出现乱码
  • 数据展示过程中的乱码

    • 最终显示到网页上的数据出现中文乱码

    • 怎么解决

      • 设置响应的内容类型,以及对应的字符编码方式

        response.setContentType("text/html;charset=UTF-8");
        
      • HTML静态页面中文乱码

        <meta context="text/html;charset=UTF-8">
        

42.程序中乱码解决方案

数据传递过程中的乱码

  • 将数据从浏览器发送到服务器的时候,服务器接收到的是乱码

  • 浏览器是这样发送数据给服务器的:dname=%E5%E5%E5%E5%E5%E5%E5

    • “市场部”对应的 ISO-8859-1 的编码:%E5%E5%E5%E5%E5%E5%E5

    • ISO-8859-1 是国际标准码,不支持中文编码,兼容ASCII码,又被称为 latin1 编码

    • 不管是哪个国家的文字,在浏览器发送个服务器的时候,都会采用 ISO-8859-1 的编码方式发送

    • 发送给web服务器之后,web服务器不知道这些数据之前是什么类型的文字(中文?日文?英文???)

    • 所以web服务器接收到的数据出现乱码

    • 解决数据传递过程中的乱码

      • 万能方式,既能够解决POST请求乱码,又能解决GET请求乱码

        1. 先将服务器中接收到的数据采用 ISO-8859-1 的方式解码,回归原始状态。
        2. 再给定一种支持简体中文的编码方式重新编码组装。【组装的时候编码方式需要和浏览器的编码方式相同】
      • 第二种解决方式,调用 request的 setCharaterEncoding方法,但是这种方式只适合POST请求,只对请求体编码

        requset.setCharacterEncoding("UTF-8");

        告诉Tomcat服务器请求体中的数据使用 UTF-8 编码

      • 第三种解决方案:专门解决GET请求的乱码问题,因为这种方式只对请求行编码

        修改 CATALINA_HOME/conf/server.xml 文件,添加URIEncoding

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

        Connector标签种都可以编写哪些属性呢?

        帮助文档:CATALINA_HOMT\webapps\docs\config\http.html

public class AServlet extends HttpServlet{
	
    @Override
    protected void doPost(HttpServletRequest requets, HttpServletResponse response) throws ServletException,IOException{
        
        // String value = request.getParameter(name);//从表单上获取数据
        // Object obj = request.getAttribute(name); //从request范围中获取数据(之前一定调用过setAttribute,否则为null)
        
        String dname = request.getParameter("dname");
        System.out.println(dname);// 乱码
        
        // 第一种解决方式: 万能解决方案,post和get都可以使用
        byte[] bytes = dname.getBytes("ISO-8859-1"); // 解码
        dname = new String(bytes,"UTF-8"); // 编码
        System.out.println(dname); // 正常
        
        // ------------------------------------------------------
        
        // 第二种解决方式:调用 request的 setCharaterEncoding方法,但是这种方式只适合POST请求
        // 告诉Tomcat服务器请求体中的数据使用 UTF-8 编码
        requset.setCharacterEncoding("UTF-8");
        String dname = request.getParameter("dname");
        System.out.println(dname); // 正常
        
    }
    
    
    
    @Override
    protected void doGet(HttpServletRequest requets, HttpServletResponse response) throws ServletException,IOException{
        
        String dname = request.getParameter("dname");
        
        // 第一种解决方式: 万能解决方案,post和get都可以使用
        byte[] bytes = dname.getBytes("ISO-8859-1"); // 解码
        dname = new String(bytes,"UTF-8"); // 编码
        System.out.println(dname); // 正常
        
    }
}

43.程序中乱码解决方案

44.Servlet线程安全问题

Servlet是单实例多线程环境下运行的

什么时候存在线程安全问题

  • 多线程并发
  • 有共享数据
  • 共享数据有修改操作

在JVM中,哪些数据可能会存在线程安全问题?

  • 局部变量内部空间不共享,一个线程一个斩,局部变量在栈中存储,局部变量不会存在线程安全问题
  • 常量不会被修改,所以常量不会存在线程安全问题
  • 所有线程共享一个堆
    • 堆内存中new出来的对象,在其中存储,对象内部有“实例变量”,所以实例变量的内容多线程是共享的,实例变量多线程共同访问,并且涉及到修改操作的时候就会存在线程安全问题
  • 所有线程共享一个方法区
    • 方法区中有静态变量,静态变量的内存也是共享的,若涉及到修改操作,静态变量也存在线程安全问题。

线程安全问题不止是体现在JVM中,还有可能发生在数据库中。例如多个线程共享一张表,并且同时区修改表中的一些记录,那么这些记录就会存在线程安全问题。解决方案:

  • 在Java程序中使用 synchronized关键字
  • 行级锁【悲观锁】
  • 事务隔离级别,例如:串行化
  • 乐观锁

怎么解决线程安全问题

  1. 不使用实例变量,尽量使用局部变量
  2. 若必须使用实例变量,可以考虑将该对象变成多例对象,一个线程去对应一个java对象,这样实例变量的内存也不会被共享。
  3. 若必须使用单例,那么就只能使用 Synchronized线程同步机制,线程一旦排队执行,则吞吐量降低,降低用户体验

Servlet怎么解决线程安全问题

  • 不适用实例变量,尽量使用局部变量
  • Servlet必须是单例的,所以剩下的方式只能考虑使用 Synchronized,线程同步机制

45.转发和重定向

关于Web系统中资源跳转

包括两种方式

  • 转发 forward
  • 重定向 redirect

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_3_1.xsd"
  version="3.1">
    
    <servlet>
    	<servlet-name>a</servlet-name>
        <servlet-class>com.servlet.AServlet</servlet-class>
    </servlet>
    <servlet-mapping>
    	<servlet-name>a</servlet-name>
        <url-pattern>/a</url-pattern>
    </servlet-mapping>
    
    <servlet>
    	<servlet-name>b</servlet-name>
        <servlet-class>com.servlet.BServlet</servlet-class>
    </servlet>
    <servlet-mapping>
    	<servlet-name>b</servlet-name>
        <url-pattern>/b</url-pattern>
    </servlet-mapping>
    
    <servlet>
    	<servlet-name>c</servlet-name>
        <servlet-class>com.servlet.CServlet</servlet-class>
    </servlet>
    <servlet-mapping>
    	<servlet-name>c</servlet-name>
        <url-pattern>/c</url-pattern>
    </servlet-mapping>

</web-app>
public class AServlet extends HttpServlet{
	
    @Override
    protected void doGet(HttpServletRequest requets, HttpServletResponse response) throws ServletException,IOException{
        
        // 向request范围中存储数据
        request.setAttribute("username","zhangsan");
        
        // 转发 forward:一次请求
        request.getRequestDispatcher("/b").forward(request,response);
        
    }
}

public class BServlet extends HttpServlet{
	
    @Override
    protected void doGet(HttpServletRequest requets, HttpServletResponse response) throws ServletException,IOException{
        
        // 从request范围中获取数据
        Object obj = request.getAttribute("username");
        System.out.println(obj); // zhangsan
        
        // 重定向 redirect:两次请求
        // 执行到此次之后,将这个路径响应给浏览器,浏览器又向服务器发送了一次全新的请求
        //response.sendRedirect("/prj/c");
        response.sendRedirect(request.getContextPath() + "/c");
        
    }
}

public class CServlet extends HttpServlet{
	
    @Override
    protected void doGet(HttpServletRequest requets, HttpServletResponse response) throws ServletException,IOException{
        
        // 从request范围中获取数据
        Object obj = request.getAttribute("username");
        System.out.println(obj); // null
        
    }
}

转发和重定向代码

  • 转发:request.getRequestDispatcher("/b").forward(request,response);
  • 重定向:response.sendRedirect(request.getContextPath() + "/c");

转发和重定向的区别

  • 相同点:都可以完成资源跳转
  • 不同点:
    • 转发是request对象触发的
    • 重定向是response对象触发的
    • 转发是一次请求,浏览器地址栏不会变化
    • 重定向是两次请求,浏览器地址栏会变化
    • 重定向的路径需要加webapp的根路径
    • 转发是在本项目内部完成资源跳转
    • 重定向可以完成跨app资源跳转

跳转的下一个资源可以是什么

可以是web服务器中的任一资源:可以是Servlet,也可以是HTML,JSP,...

什么时候采用转发,什么时候采用重定向 【大部分情况下都使用重定向】

  • 若想完成跨APP跳转,必须使用重定向
  • 若想在上一个资源中向request范围中存储了数据,希望在下一个资源中从request范围中将数据取出来,必须使用转发
  • 重定向可以解决浏览器的刷新问题

重定向原理是什么

response.sendRedirect("/jd/login");

程序执行到以上代码,将请求路径 /jd/login反馈给浏览器,浏览器自动又向web服务器发送了一次全新的请求:/jd/login ,浏览器地址栏上最终显示的地址是 /jd/login

在浏览器上点击一个超链接,到最终网页停下来时一次请求。这句话不正确,因为有重定向机制存在,这个过程可能时多个请求

46.重定向解决刷新问题

浏览器刷新的是最后一次请求,如果是使用转发跳转,刷新的话会重复添加数据;使用重定向则不会出现重复添加数据的问题。

public class AServlet extends HttpServlet{
	
    @Override
    protected void doPost(HttpServletRequest requets, HttpServletResponse response) throws ServletException,IOException{
        
        // 
        request.setRequestEncoding("UTF-8");
        
        // 获取表单提交的用户数据
        String usercode = requst.getParameter("1234");
        String username = requst.getParameter("username");
        
        // JDBC 保存信息
        // xxx...
        
        if(count == 1){
            // 保存成功,“跳转”到成功页面
            // 转发
            // request.getRequestDispatcher("/success.html").forward(request,response);
            // 重定向
            response.sendRedirect(request.getContextPath + "/success.html")
        }
    }
}

47.登陆

48.Cookie概述

Cookie是什么,有什么作用,保存在哪里

  • 可以保存会话状态,但是着会话状态是保留在客户端上的

  • 只要Cookie清除,或者Cookie失效,这个会话状态就没有了

  • Cookie 是保存在浏览器客户端上的

  • Cookie 可以保存在浏览器的缓存中,浏览器关闭Cookie消失

  • Cookie也可以保存在客户端的硬盘文件中,浏览器关闭Cookie还在,除非Cookie失效

Cookie只有在javaweb中有吗

  • 不止在Javaweb中存在
  • 只要是Web开发,只要是 B/S架构的系统,只要是基于Http协议,就有Cookie的存在
  • Cookie这种机制是HTTP协议规定的

Cookie实现的功能,常见的有哪些?

  • 十天内免登录
  • ...

在Java中,Cllike被当作类来处理,使用 new运算符可以创建Cookie对象,而且Cookie由两部分组成,分别是Cookie的name和value, name和value都是 字符串类型String

在 Java程序中怎么创建 Cookie

Cookie cookie = new Cookie(String cookieName, String cookieValue);

服务器可以一次向浏览器发送多个Cookie

默认情况下,服务器发送Cookie给浏览器之后,浏览器将Cookie保存在缓存当中,只要不关闭浏览器,Cookie永远存在,并且有效。当浏览器关闭之后,缓存中的Cookie将被清除

在浏览器客户端无论是硬盘文件中还是缓存中保存的Cookie,什么时候会再次发送给服务器呢

  • 浏览器会不会提交发送这些Cookie给服务器,和请求路径有关系
  • 请求路径和Cookie是紧密关联的
  • 不同的请求路径会发送提交不同的Cookie

默认情况下(没有设置 Cookie的关联路径)Cookie会和哪些路径绑定在一起?

  • /prj/test/createAndSendCookieToBrowser 请求服务器,服务器生成Cookie,并将Cookie发送给浏览器客户端

    这个浏览器中的Cookie会默认和 test/ 这个路径绑定在一起

    也就是说,以后只要发送 test/ 请求(test/*),Cookie一定会提交给服务器。test 请求则不会发送Cookie。

  • /prj/a 请求服务器,服务器生成Cookie,并将Cookie发送给浏览器客户端

    这个浏览器中的Cookie会默认和 prj/ 这个路径绑定在一起

public class CreateAndSendCookieToBrowserServlet extends HttpServlet{
    
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
        
        // 创建Cookie对象
        Cookie cookie1 = new Cookie("username","zhangsan");
        Cookie cookie2 = new Cookie("password","123");
        
        // 设置 Cookie的关联路径
        cookie1.setPath(request.getContextPath() + "/king");
        cookie2.setPath(request.getContextPath() + "/king");
        
        // 设置 Cookie 有效期(秒)
        cookie1.setMaxAge(60 * 60);
        cookie2.setMaxAge(60 * 60 * 24);
        
        // 将Cookie对象发送给浏览器客户端
        response.addCookie(cookie1);
        response.addCookie(cookie2);
        
    }
}

其实路径是可以制定的,可以通过 Java程序进行设置,保证Cookie和某个特定的路径绑定在一起。

假设执行了 cookie.setPath("/prj/king");

那么 Cookie将和 “prj/king" 路径绑定在一起

只有发送 ”prj/king" 下的请求路径,浏览器才会提交Cookie给服务器

默认情况下,没有设置Cookie的有效时长,该Cookie被默认保存到浏览器的缓存当中,只要浏览器不关闭,Cookie存在;只要浏览器关闭,Cookie就消失。我们可以通过设置Cookie有效时长,以保证Cookie保存到硬盘文件当中。但是这个有效时长必须是 > 0 的。有效时长过去之后,硬盘中保存的Cookie会失效。

  • cookie 有效时长 = 0 直接被删除

  • cookie 有效时长 < 0 不会被存储

  • cookie 有效时长 > 0 存储到硬盘文件中

    cookie1.setMaxAge(60 * 60); 一小时有效

51.Cookie

浏览器提交Cookie给服务器,服务器怎么接收

public class ReceiveCookieServlet extends HttpServlet{
    
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
        
        // 从 request 对象中获取所有提交的Cookie
        Cookie[] cookies = request.getCookies();
        
        if(cookies != null){
            for(Cookie cookie : cookies){
                String cookieName = cookie.getName();
                String cookieValue = cookie.getValue();
                System.out.println(cookieName + "=" + cookeValue);
            }
        }
        
    }
}

浏览器可以禁用Cookie, 什么意思

  • 表示服务器发送过过来的Cookie,浏览器不要,不接收
  • 服务器还是会发Cookie,只不过浏览器不再接收

52.Cookie实现十天内免登录

53.Cookie实现十天内免登录

<?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_3_1.xsd"
  version="3.1">
    
    <!-- 欢迎页面Servlet验证是否登陆 -->
    <welcome-file-list>
    	<welcome-file>isLogin</welcome-file>
    </welcome-file-list>
    
    <servlet>
    	<servlet-name>login</servlet-name>
        <servlet-class>com.servlet.LoginServlet</servlet-class>
    </servlet>
    <servlet-mapping>
    	<servlet-name>login</servlet-name>
        <url-pattern>/login</url-pattern>
    </servlet-mapping>
    
    <servlet>
    	<servlet-name>isLogin</servlet-name>
        <servlet-class>com.servlet.CheckLoginStatusServlet</servlet-class>
    </servlet>
    <servlet-mapping>
    	<servlet-name>isLogin</servlet-name>
        <url-pattern>/isLogin</url-pattern>
    </servlet-mapping>

</web-app>
public class LoginServlet extends HttpServlet{
    
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
        // ------------------------------
        // 数据库验证账号密码操作,此处省略...
        // ------------------------------
        
        if(loginSuccess){
            // 登陆成功之后,获取用户是否选择了十天内免登陆
            String tenDayAutoLoginFlag = request.getParameter("tenDayAutoLoginFlag");
            if("ok".equals(tenDayAutoLoginFlag)){
                // 创建Cookie对象
                Cookie cookie1 = new Cookie("username",username);
                Cookie cookie2 = new Cookie("password",password);
                // 设置有效时间
                cookie1.setMaxAge(60*60*24*10);
                cookie2.setMaxAge(60*60*24*10);
                // 设置关联路径
                cookie1.setPath(request.getContextPath());
                cookie2.setPath(request.getContextPath());
                // 发送 Cookie给浏览器
                response.addCookie(cookie1);
                response.addCookie(cookie2);
            }
        }
        
        // ------------------------------
        // 登陆成功或失败后的页面跳转,此处省略...
        // ------------------------------
        
    }
}

在欢迎页面获取Cookie,并进行验证,验证成功就不需要跳转到登陆页面了

public class CheckLoginStatusServlet extends HttpServlet{
    
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
        // 从request中获取所有的Cookie
        Cookie[] cookies = request.getCookies();
        String username = null;
        String password = null;
        if(cookies != null){
            // 遍历Cookie
            for(Cookie cookie : cookies){
                String cookieName = cookie.getName();
                String cookieValue = cookie.getValue();
                if("username".equals(cookieName)){
                    username = cookieValue;
                }else if("password".equals(cookieName)){
                    password = cookieValue;
                }
            }
        }
        if(username != null && password != null){
            // ------------------------------
        	// 连接数据库验证账号密码操作,此处省略...
            // ------------------------------
            // 登陆成功则跳转到成功页面
            // 登陆失败则跳转到失败页面
        }else {
            // 跳转到登陆页面
            response.sendRedirect(request.getContextPath() + "login.html");
        }
}

54.路径编写总结

关于url-pattern的编写方式和路径的总结

路径的编写形式

  • <a href="/项目名称/资源路径"></a>

  • <form action="/项目名/资源路径"></form>

  • 重定向:response.sendRedirect("/项目名/资源路径");

  • 转发:request.getReqeustDispatcher("/资源路径").forward(request,response);

  • 欢迎页面

    <welcome-file-list>
    	<welcome-file>资源路径</welcome-file>
    </welcome-file-list>
    
  • servlet路径

    <servlet>
        	<servlet-name>hello</servlet-name>
            <servlet-class>com.HelloServlet</servlet-class>
        </servlet>
        <servlet-mapping>
        	<servlet-name>hello</servlet-name>
            <url-pattern>/资源路径</url-pattern>
        </servlet-mapping>
    
  • Cookie设置path

    cookie.setPath("/项目名/资源路径");

  • ServletContext applicattion =  config.getServletContext();
    
    application.getRealPath("/WEB-INF/classes/db.properties");
    //application.getRealPath("/资源路径");
    

55.url-pattern 的编写方式

  • url-pattern 可以编写多个

  • 精确匹配

    <url-pattern>/hello</url-pattern>
    <url-pattern>/system/hello</url-pattern>
    
  • 扩展匹配

    <url-pattern>/abc/*</url-pattern>
    
  • 后缀匹配

    <url-pattern>*.action</url-pattern>
    <url-pattern>*.do</url-pattern>
    
  • 全部匹配

    <url-pattern>/*</url-pattern>
    

56.HttpSession

  • session 表示会话,不止在JavaWeb中存在,只要是 web开发,都有会话这种机制
  • 在 java中会话对应的类型是:javax.servlet.http.HttpSession 简称 session/会话
  • Cookie可以将会话状态保存在客户端,HttpSession可以将会话状态保存在服务的。
public class AccessMyselfSessionServlet extends HttpServlet{
    
    @Override
    protected void doGet(HttpServletRequset request, HttpServletResponse response) throws ServletException, IOException{
        String ip = request.getRemoteAddr();
        HttpSession session = request.getSession();
        System.out.println(ip + " s session = "+session);
        // 打印:192.16.8.6.92 s session = org.apache.catalina.session.StandardSessionFacade@52e7ad
        // 同一个浏览器访问多次刷新,打印内容一样
        // 换一个用户访问
        // 打印:192.16.8.6.30 s session = org.apache.catalina.session.StandardSessionFacade@d608f6
        
        // 向session中存放数据
        session.setAttribute("username","zhangsan");
    }
}
  • HttpSession 对象是一个会话级别的对象,一次会话对应一个HttpSession对象

  • 什么是一次会话?

    目前可以这样理解:用户打开浏览器,在浏览器上发送多次请求,知道最终关闭浏览器,表示一次完整的会话【58节重新理解什么是一次会话】

  • 在会话进行过程中,web服务器一直为当前这个用户维护着一个会话对象/HttpSession

  • 在web容器中,web容器维护了大量的HttpSession对象,换句话说,在web容器中应该有一个Session列表

    思考:为什么当前会话中的每一次请求可以获取到属于自己的会话对象?

57.HttpSession

思考:为什么当前会话中的每一次请求可以获取到属于自己的会话对象?(每个不同的用户对应不同的会话对象,并且不会出错)Session的实现原理?

  • 浏览器首次发送请求给服务器
  • 服务器会创建一个HttpSession对象,该对象代表一次会话
  • 同时生成 HttpSession 对象对应的 Cookie 对象,并且 Cookie 对象的 name 是 JSESSIONID,Cookie 的 value 是32位长度的字符串
  • 服务器将 Cookie 的 value 和 HttpSession 对象绑定到 session列表中
  • 服务器将 Cookie完成发送给浏览器客户端
  • 浏览器客户端将 Cookie保存到缓存中
  • 只要浏览器不关闭,Cookie不会消失
  • 当再次发送请求时,会自动提交缓存中的Cookie
  • 服务器接收到Cookie,验证该 Cookie的 name 确实是: JSESSIONID,然后获取该 Cookie 的 value
  • 通过 Cookie 的 value 去 session列表中检索对应的 HttpSession对象

和HttpSession对象关联的这个 Cookie的 name是比较特殊的,在Java中就叫做:jsessionid

58.HttpSession

浏览器禁用Cookie会出现什么问题?怎么解决

  • 浏览器禁用 Cookie,则浏览器缓存中不再保存Cookie

  • 导致在同一个会话中,无法获取到对应的会话对象

  • 禁用Cookie之后,每一次获取的会话对象都是新的

  • 浏览器禁用Cookie之后,若还想拿到对应的session对象,必须使用URL重写机制。怎么重写URL:

    http://localhost:8080/prj/user/accessMySelfSession;jsessionid=D3D56FED7T7DS6G75S5W5D 【注意:用 ; 分隔,而不是 ?】【使用重写URL的session即使换了浏览器或地址,访问的还是同一个会话】

    重写URL会给变成带来难度/复杂度,所以一般的web站点是不建议禁用Cookie的。

浏览器关闭之后,服务器端对应的session对象会被销毁吗?为什么

  • 浏览器关闭之后,服务器不会销毁session对象
  • 因为B/S架构的系统基于HTTP协议,而HTTP协议是一种无连接/无状态的协议
  • 什么是无连接/无状态
    • 请求的瞬间浏览器和服务器之间的通道是打开的,请求响应结束之后通道关闭。
    • 这样做的目的是降低服务器的压力

session对象在什么时候会被销毁

  • web系统中引入了 session超时的概念

  • 当很长一段时间(这个时间可以配置)没有用户再访问该对象,此时session对象超时,web服务器自动回收 session对象

  • 可配置:web.xml文件中,默认是30分钟

    <!-- 配置session超时时间2小时 -->
    <session-config>
    	<session-timeout>120</session-timeout>
    </session-config>
    

什么是一次会话

  • 一般多数情况下,是这样描述的:用户打开浏览器,在浏览器上进行一些操作,然后将浏览器关闭,表示一次会话结束。
  • 本质上的描述:从session对象的创建,到最终 session对象超时之后销毁,这个才是真正意义的一次完整会话

59.HttpSession

关于 javax.servlet.http.HttpSession 接口中常用的方法

  • void setAttribute(String name, Object value)
  • Object getAttribute(String name)
  • void removeAttribute(String name)
  • void invalidate() 销毁 session

ServletContext, HttpSession, HttpServletRequest 接口的对比

  • 以上都是范围对象

    • ServletContext application:应用范围

    • HttpSession session:会话范围

    • HttpServletRequest request:请求范围

  • 三个范围的排序:

    application > session > request

  • application 完成跨会话共享数据;

    session完成跨请求共享数据,但是这些请求必须在同一个会话中;

    request完成跨Servlet共享数据,但是这些Servlet必须在同一个请求当中

使用原则:由小到大尝试,优先使用小范围

例如:登陆成功之后,已经登录的的状态需要保存起来,可以将登陆成功的这个状态保存到session对象中。

60.HttpSession

/**
 * HttpServletRequest中获取session方法:
 * public HttpSession getSession(); 
 * 获取session,如果没获取到,新建一个session对象返回
 *
 * public HttpSession getSession(boolean create);
 * 获取session,如果没获取到,根据create判断是否新建一个session返回
 */

public class LoginServlet extends HttpServlet{
    
    @Override
    protected void doGet(HttpServletRequset request, HttpServletResponse response) throws ServletException, IOException{
        
        // 获取session对象,若没用获取到session对象,则新建session对象
        //HttpSession session = requets.getSession();
        
        // 获取session对象,若没用获取到session对象,则新建session对象
        //HttpSession session = request.getSession(true);
        
        // 获取session对象,若没用获取到session对象,则返回null
        HttpSession session = request.getSession(false);
        
        if(session != null){
            // 销毁 session
            session.invalidate();
        }
    }
}

猜你喜欢

转载自www.cnblogs.com/codeclock/p/12908751.html
今日推荐