java后端系统学习总结 03_java Web基础学习

猿猿正在系统的学习一些计算机知识,和后端技术栈,目前阶段主要在系统学习java。此专栏,为我学习过程中的学习笔记,便于日后复习回顾来看,也很适合新人学习参考呦。
以下是猿猿对JavaWeb的第一遍学习笔记哦,笔记可以作为文档阅读。

文章目录

学前知识补充

1.DeBUG 断点调试

一、junit单元测试

1.黑盒测试

image-20211111182248939

2.白盒测试

image-20211111182408355
image-20211113104747281
image-20211113105539338
博客详细教程

创建Junit4的测试代码,有两种方法(超级简单方便),第一种直接右键点击被测试类 类名Calculator 使用 Ctrl+Shift+T

image-20211113105629445

代码

calculator

package cn.itcast.junit;

/**
 * 计算器类
 */
public class Calculator {
    public   int add(int a,int b) {
//        int i = 3/0;  //报红
        return a + b;
    }

    public int sub(int a,int b) {
        return  a-b;
    }
}

calculatorTest

package cn.itcast.test;

import cn.itcast.junit.Calculator;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

public class CalculatorTest {
    /**
     * 初始化方法
     */
    @Before
    public void init() {
        System.out.println("init···");
    }

    @After
    public void close() {
        System.out.println("close···");
    }

    @Test
    public void testAdd() {
//        System.out.println("我被执行啦");
        System.out.println("testAdd···");
        Calculator c = new Calculator();
        int result = c.add(1,2);
//        System.out.println(result);
        //断言   断言结果是3
        Assert.assertEquals(3,result);
    }

    @Test
    public void subAdd() {
        Calculator c = new Calculator();
        int result = c.sub(1,2);

        System.out.println("testSub···");
        Assert.assertEquals(-1,result);
    }
}

二、反射

image-20211113112518917

1.java代码在计算机种经历的三个阶段

image-20211113110239748

第一阶段到第二阶段的封装就称为反射机制

运行中操作对象,对象名 点 就是利用反射机制

2.获取Class对象的三种方式

image-20211113112444120

image-20211113111057444

3.Class对象功能

image-20211113112846049

1.Field成员变量
image-20211113145715509
  1. Field [] fields 获取所有public修饰的成员变量
  2. Field [] declaredFields() 获取所有的成员变量,不考虑修饰符,需要进行忽略访问修饰符的安全检查 d.setAccessible(true);//暴力反射

代码

Person.java:

package cn.itcast.domain;

public class Person {
    private String name;
    private int age;

    public String a;
    protected String b;
    String c;
    private String d;

    public Person() {

    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", a='" + a + '\'' +
                ", b='" + b + '\'' +
                ", c='" + c + '\'' +
                ", d='" + d + '\'' +
                '}';
    }
}


ReflectDemo2.java

package cn.itcast.reflect;

import cn.itcast.domain.Person;

import java.lang.reflect.Field;

public class ReflectDemo2 {
    /**
     * 获取class对象的三种方法
     */

    public static void main(String[] args) throws Exception {
        //1.Class.forName("全类名");
        Class cs1 = Class.forName("cn.itcast.domain.Person");
        System.out.println(cs1);
        //2.类名.class
        Class cs2 = Person.class;
        System.out.println(cs2);
        //3.对象.getClass
        Person p = new Person();
        Class cs3 = p.getClass();
        System.out.println(cs3);
        System.out.println("================================三种获取class的方法");

        /**1.Field [] fields  获取所有public修饰的成员变量 */
        Field [] fields = cs2.getFields();
        for (Field field:fields) {
            System.out.println(field);
        }
        System.out.println("-------------------------Field [] fields");
        //获取public修饰的成员变量a 的值并改变
        Field a = cs2.getField("a");
        //获取成员变量 a的值
        Object value = a.get(p);
        System.out.println(value);//null
        //设置a的值
        a.set(p,"张三");
        System.out.println(p);
        System.out.println("====================================");
        /** Field [] declaredFields()  获取所有的成员变量,不考虑修饰符*/
        Field [] declaredFields =  cs2.getDeclaredFields();
        for (Field declaredField:declaredFields) {
            System.out.println(declaredField);
        }
        //Field getDeclaredField(String  name)  需要进行忽略访问修饰符的安全检查
        Field d = cs2.getDeclaredField("d");// 使用私有的不能直接访问
        //忽略访问修饰符的安全检查
        d.setAccessible(true);//暴力反射
        Object value2 = d.get(p);
        System.out.println(value2);
    }
}
输出:
class cn.itcast.domain.Person
class cn.itcast.domain.Person
class cn.itcast.domain.Person
================================三种获取class的方法
public java.lang.String cn.itcast.domain.Person.a
-------------------------Field [] fields
null
Person{name='null', age=0, a='张三', b='null', c='null', d='null'}
====================================
private java.lang.String cn.itcast.domain.Person.name
private int cn.itcast.domain.Person.age
public java.lang.String cn.itcast.domain.Person.a
protected java.lang.String cn.itcast.domain.Person.b
java.lang.String cn.itcast.domain.Person.c
private java.lang.String cn.itcast.domain.Person.d
null

Process finished with exit code 0
2.获取构造方法

image-20211113174023072

ReflectDemo3.java

package cn.itcast.reflect;

import cn.itcast.domain.Person;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;

public class ReflectDemo3 {
    /**
     * class对象获取构造方法们
     */

    public static void main(String[] args) throws Exception {
        //0.获取Person的class对象
        Class personClass = Person.class;

        Constructor constructor = personClass.getConstructor(String.class, int.class);
        System.out.println(constructor);
        //创建对象
        /*有参*/
        Object person = constructor.newInstance("张三", 23);
        System.out.println(person);
        System.out.println("------------------------");

        /*无参*/
        Constructor constructor1 = personClass.getConstructor();
        System.out.println(constructor1);
        //创建对象
        Object person1 = constructor1.newInstance();
        System.out.println(person1);
//如果是空参构造,选择这个写法    不用获得构造器操作
        Object o = personClass.newInstance();
        System.out.println(o);
    }
}

3.获取成员方法们和类名
package cn.itcast.reflect;

import cn.itcast.domain.Person;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

public class ReflectDemo4 {
    /**
     * class对象获取成员方法们和类名
     */
    public static void main(String[] args) throws Exception {
        //0.获取Person的class对象
        Class personClass = Person.class;

        Person p = new Person();

        //获取指定名称的方法
        Method eat_method = personClass.getMethod("eat");
        eat_method.invoke(p);

        Method eat_method2 = personClass.getMethod("eat", String.class);
        eat_method2.invoke(p,"吃饭");
        System.out.println("-----------------------------------");

        //获取所有public修饰的方法
        Method[] methods = personClass.getMethods();
        for (Method method : methods) {
            System.out.println(method);
            //  method.setAccessible(true);//暴力反射
        }

        //获取类名
        String className = personClass.getName();
        System.out.println(className);//cn.itcast.domain.Person


    }
}

案例

image-20211113205928623

image-20211113204651184

代码

reflectTest.java

package cn.itcast.reflect;

import cn.itcast.domain.Person;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.Properties;

public class ReflectTest {
    /**
     *不能改变该类的任何代码前提,可以创建任意对象,执行任意方法
     */
    public static void main(String[] args) throws Exception {
        //1.加载配置文件
        Properties pro = new Properties();
        //加载配置文件 转化为集合      获取class目录下的配置文件
        ClassLoader classLoader = ReflectTest.class.getClassLoader();
        InputStream is = classLoader.getResourceAsStream("pro.properties");
        pro.load(is);

        //2.获取配置文件中定义的数据
        String className = pro.getProperty("className");
        String methodName = pro.getProperty("methodName");

        //3.加载该类进内存
        Class cls = Class.forName(className);
        //4.创建对象
        Object obj = cls.newInstance();
        //5.获取方法对象
        Method method = cls.getMethod(methodName);
        //6.执行方法
        method.invoke(obj);
    }
}

配置文件 pro.properties

className=cn.itcast.domain.Person
methodName=sleep

三、注解

注解本质就是个接口

image-20211113211016904

1.jdk中预定义的三个注解

image-20211113212614313

2.自定义注解
image-20211115090454252 image-20211115090600731 image-20211115091807673 image-20211115220559386

image-20211116214555111

代码

ReflectTest

package cn.itcast.annotation;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.Properties;
/**
 * 框架类
 */
@Pro(className = "cn.itcast.annotation.Demo1", methodName = "show")

public class ReflectTest {
    /**
     *不能改变该类的任何代码前提,可以创建任意对象,执行任意方法
     * 不用写配置文件的调用方法  的方式(注解替代配置文件)
     */

    public static void main(String[] args) throws Exception {
        //1.解析注解
        //1.1获取该类的字节码对象
        Class<ReflectTest> reflectTestClass = ReflectTest.class;
        //2.获得上面的注解对象
        //其实就是在内存中生成了一个该注解的子类实现对象,实现对象中 传入了 className = "cn.itcast.annotation.Demo1" , methodName = "show"
        Pro an = reflectTestClass.getAnnotation(Pro.class);
        //3.调用注解对象中定义的抽象方法,获取返回值
        String className = an.className();
        String methodName = an.methodName();

        System.out.println(className);
        System.out.println(methodName);

//        //1.加载配置文件
//        Properties pro = new Properties();
//        //加载配置文件 转化为集合      获取class目录下的配置文件
//        ClassLoader classLoader = cn.itcast.reflect.ReflectTest.class.getClassLoader();
//        InputStream is = classLoader.getResourceAsStream("pro.properties");
//        pro.load(is);
//
//        //2.获取配置文件中定义的数据
//        String className = pro.getProperty("className");
//        String methodName = pro.getProperty("methodName");

        //3.加载该类进内存
        Class cls = Class.forName(className);
        //4.创建对象
        Object obj = cls.newInstance();
        //5.获取方法对象
        Method method = cls.getMethod(methodName);
        //6.执行方法
        method.invoke(obj);
    }
}

Demo

package cn.itcast.annotation;

public class Demo1 {
    public void show() {
        System.out.println("demo1···show···");
    }
}

Pro

package cn.itcast.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)

public @interface Pro {

    String className();
    String methodName();
}
什么是元注解

元注解是可以注解到注解上的注解,或者说元注解是一种基本注解,但是它能够应用到其它的注解上面。它的作用和目的就是给其他普通的标签进行解释说明的。
元标签有五个:@Retention、@Documented、@Target、@Inherited、@Repeatable。

  1. @Retention 存活时间
    Retention 的英文意为保留期的意思。当 @Retention 应用到一个注解上的时候,它解释说明了这个注解的的存活时间。

它的取值如下:

RetentionPolicy.SOURCE 注解只在源码阶段保留,在编译器进行编译时它将被丢弃忽视。
RetentionPolicy.CLASS 注解只被保留到编译进行的时候,它并不会被加载到 JVM 中。
RetentionPolicy.RUNTIME 注解可以保留到程序运行的时候,它会被加载进入到 JVM 中,所以在程序运行时可以获取到它们。

我们可以这样的方式来加深理解,@Retention 去给一张标签解释的时候,它指定了这张标签张贴的时间。@Retention 相当于给一张标签上面盖了一张时间戳,时间戳指明了标签张贴的时间周期。

@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
}

上面的代码中,我们指定 TestAnnotation 可以在程序运行周期被获取到,因此它的生命周期非常的长。

  1. @Documented 保存到javadoc
    顾名思义,这个元注解肯定是和文档有关。它的作用是能够将注解中的元素包含到 Javadoc 中去。

  2. @Target 限定使用场景
    Target 是目标的意思,@Target 指定了注解运用的地方。当一个注解被 @Target 注解时,这个注解就被限定了运用的场景。
    类比到标签,原本标签是你想张贴到哪个地方就到哪个地方,但是因为 @Target 的存在,它张贴的地方就非常具体了,比如只能张贴到方法上、类上、方法参数上等等。

@Target 有下面的取值:

ElementType.ANNOTATION_TYPE 可以给一个注解进行注解
ElementType.CONSTRUCTOR 可以给构造方法进行注解
ElementType.FIELD 可以给属性进行注解
ElementType.LOCAL_VARIABLE 可以给局部变量进行注解
ElementType.METHOD 可以给方法进行注解
ElementType.PACKAGE 可以给一个包进行注解
ElementType.PARAMETER 可以给一个方法内的参数进行注解
ElementType.TYPE 可以给一个类型进行注解,比如类、接口、枚举
  1. @Inherited 注解继承
    Inherited 是继承的意思,但是它并不是说注解本身可以继承,而是说如果一个超类被 @Inherited 注解过的 注解进行注解的话(真绕弯,就是说:@Inherited注解了B注解,B再注解别人),那么如果它的子类没有被任何注解应用的话,那么这个子类就继承了超类的注解。
    说的比较抽象。代码来解释。
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@interface Test {}

@Test
public class A {}

public class B extends A {}

注解 Test 被 @Inherited 修饰,之后类 A 被 Test 注解,类 B 继承 A,类 B 也拥有 Test 这个注解。

可以这样理解:

老子非常有钱,所以人们给他贴了一张标签叫做富豪。
老子的儿子长大后,只要没有和老子断绝父子关系,虽然别人没有给他贴标签,但是他自然也是富豪。
老子的孙子长大了,自然也是富豪。
这就是人们口中戏称的富一代,富二代,富三代。虽然叫法不同,好像好多个标签,但其实事情的本质也就是他们有一张共同的标签,也就是老子身上的那张富豪的标签。

  1. @Repeatable 重复
    Repeatable 是可重复的意思。@Repeatable 是 Java 1.8 才加进来的,所以算是一个新的特性。
    什么样的注解会多次应用呢?通常是注解的值可以同时取多个。

举个例子,一个人他既是程序员又是产品经理,同时他还是个画家。

@interface Persons {
	Person[]  value();
}

@Repeatable(Persons.class)
@interface Person{
	String role default "";
	}

@Person(role="artist")
@Person(role="coder")
@Person(role="PM")
public class SuperMan{}

注意上面的代码,@Repeatable 注解了 Person。而 @Repeatable(Persons.class) 相当于一个容器注解(就是用来存放其它注解的地方。它本身也是一个注解。)

我们再看看代码中最上边的相关容器注解。

@interface Persons {
	Person[]  value();
}

按照规定,它里面必须要有一个 value 的属性,属性类型是一个被 @Repeatable 注解过的注解数组,注意它是数组。
如果不好理解的话,可以这样理解:Persons 是一张总的标签,上面贴满了 Person 这种同类型但内容不一样的标签。把 Persons 给一个 SuperMan 贴上,相当于同时给他贴了程序员、产品经理、画家的标签。

XML

1.概念

比html更严格

image-20211117104601148

2.语法

image-20211117112050081

3.快速入门
image-20211117112107430
4.组成部分
image-20211117114239198 image-20211117114306882
5.约束
image-20211117123545292
分类
image-20211117123709472 image-20211117124458457 image-20211117124526483
6.解析

image-20211117125028978

image-20211117132325885 image-20211117134218130

image-20211117201916174

image-20211117221002681

image-20211117220932482

image-20211118084003839

image-20211118090814611

jsoup百度百科

主要功能

\1. 从一个URL,文件或字符串中解析HTML;

\2. 使用DOM或CSS选择器来查找、取出数据;

\3. 可操作HTML元素、属性、文本;

代码 Jsoup

JsoupDemo1

package cn.xml.jsoup;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import java.io.File;
import java.io.IOException;

public class JsoupDemo1 {
    public static void main(String[] args) throws IOException {
        //获取document对象,根据xml文档获取
        //2.1获取student.xml的path
        String path = JsoupDemo1.class.getClassLoader().getResource("student.xml").getPath();//字节码,类加载器,资源位置,字符串表示形式的路径
        //解析xml文档,加载文档进内存,获得dom树--->document
        Document document = Jsoup.parse(new File(path), "utf-8");
        //获取元素对象
        Elements elements = document.getElementsByTag("name");
        System.out.println(elements.size());
        //获取第一个name的element对象
        Element element = elements.get(0);
        //获取数据
        String name = element.text();
        System.out.println(name);
    }
}

student.xml

<?xml version="1.0" encoding="utf-8" ?>

<students>
    <student number="heima_0001">
        <name>tom</name>
        <age>18</age>
        <sex>male</sex>
    </student>
    <student number="heima_0002">
        <name>jack</name>
        <age>18</age>
        <sex>female</sex>
    </student>

</students>

JsoupDemo2

package cn.xml.jsoup;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import sun.java2d.pipe.SpanIterator;

import java.io.File;
import java.io.IOException;
import java.net.URL;
/**
 * Jsoup 对象功能
 */
public class JsoupDemo2 {
    public static void main(String[] args) throws IOException {
        //获取document对象,根据xml文档获取
        //2.1获取student.xml的path
        String path = JsoupDemo2.class.getClassLoader().getResource("student.xml").getPath();//字节码,类加载器,资源位置,字符串表示形式的路径
        //1.解析xml文档,加载文档进内存,获得dom树--->document
       /* Document document = Jsoup.parse(new File(path), "utf-8");
        System.out.println(document);*/

        //2.parse (String html):  解析xml或html字符串
      /*  String str = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n" +
                "\n" +
                "<students>\n" +
                "    <student number=\"heima_0001\">\n" +
                "        <name>tom</name>\n" +
                "        <age>18</age>\n" +
                "        <sex>male</sex>\n" +
                "    </student>\n" +
                "    <student number=\"heima_0002\">\n" +
                "        <name>jack</name>\n" +
                "        <age>18</age>\n" +
                "        <sex>female</sex>\n" +
                "    </student>\n" +
                "\n" +
                "</students>";
        Document document = Jsoup.parse(str);
        System.out.println(document);*/

        //3.parse (URL url,int timeoutMillis);  通过网络路径获取指定的html或xml的文档对象
        URL url = new URL("https://baike.baidu.com/item/jsoup/9012509");//代表网络中的一个资源路径
        Document document = Jsoup.parse(url, 10000);//10秒钟加载不出来 就结束
        System.out.println(document);
    }
}
对象功能
package cn.xml.jsoup;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

import java.io.File;
import java.io.IOException;
import java.net.URL;
/**
 * Jsoup 对象功能
 */
public class JsoupDemo3 {
    public static void main(String[] args) throws IOException {
        //获取document对象,根据xml文档获取
        //2.1获取student.xml的path
        String path = JsoupDemo3.class.getClassLoader().getResource("student.xml").getPath();//字节码,类加载器,资源位置,字符串表示形式的路径
       //2.获取Document对象
        Document document = Jsoup.parse(new File(path), "utf-8");
        //3.获取元素对象
        //3.1获取所有的student对象
        Elements elements = document.getElementsByTag("student");
        System.out.println(elements);
        System.out.println("----------------");

        //3.2 获取姓名为id的元素对象们
        Elements elements1 = document.getElementsByAttribute("id");
        System.out.println(elements1);
        System.out.println("----------------");
        //3.2获取number属性值为heima_001的元素对象
        Elements elements2 = document.getElementsByAttributeValue("number", "heima_0001");
        System.out.println(elements2);

        //3.3获取id属性值的元素对象
        Element itcast = document.getElementById("itcast");
        System.out.println(itcast);


    }
}
package cn.xml.jsoup;

import cn.wanghaomiao.xpath.exception.XpathSyntaxErrorException;
import cn.wanghaomiao.xpath.model.JXDocument;
import cn.wanghaomiao.xpath.model.JXNode;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

import java.io.File;
import java.io.IOException;
import java.util.List;

/**
 * Xpath查询 对象功能
 */
public class JsoupDemo4 {
    public static void main(String[] args) throws IOException, XpathSyntaxErrorException {
        //获取document对象,根据xml文档获取
        //2.1获取student.xml的path
        String path = JsoupDemo4.class.getClassLoader().getResource("student.xml").getPath();//字节码,类加载器,资源位置,字符串表示形式的路径
       //2.获取Document对象
        Document document = Jsoup.parse(new File(path), "utf-8");
        //3.创建document对象,创建JXDocument对象
        JXDocument jxDocument = new JXDocument(document);

        //结合xpath语法查询
        //4.1查询所有的student标签
        List<JXNode> jxNodes = jxDocument.selN("//student");
        for (JXNode jxNode : jxNodes) {
            System.out.println(jxNode);
        }
        System.out.println("-----------------");

        //4.2查询所有student标签下的name标签
        List<JXNode> jxNodes1 = jxDocument.selN("//student/name");
        for (JXNode jxNode : jxNodes1) {
            System.out.println(jxNode);
        }
        System.out.println("-----------------");
        //4.3查询student标签下带有id属性的name标签
        List<JXNode> jxNodes2 = jxDocument.selN("//student/name[@id]");
        for (JXNode jxNode : jxNodes2) {
            System.out.println(jxNode);
        }
        System.out.println("-----------------");
        //4.4查询student标签下带有id属性的name标签  变更且id属性值为itcast
        List<JXNode> jxNodes3 = jxDocument.selN("//student/name[@id=’itcast‘]");
        for (JXNode jxNode : jxNodes3) {
            System.out.println(jxNode);
        }

    }
}
package cn.xml.jsoup;

import cn.wanghaomiao.xpath.exception.XpathSyntaxErrorException;
import cn.wanghaomiao.xpath.model.JXDocument;
import cn.wanghaomiao.xpath.model.JXNode;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

import java.io.File;
import java.io.IOException;
import java.util.List;

/**
 * 选择器查询 对象功能
 */
public class JsoupDemo5 {
    public static void main(String[] args) throws IOException, XpathSyntaxErrorException {
        //获取document对象,根据xml文档获取
        //2.1获取student.xml的path
        String path = JsoupDemo5.class.getClassLoader().getResource("student.xml").getPath();//字节码,类加载器,资源位置,字符串表示形式的路径
       //2.获取Document对象
        Document document = Jsoup.parse(new File(path), "utf-8");
        //3.查询name标签
        Elements elements = document.select("name");
        for (Element element : elements) {
            System.out.println(element);
        }
        System.out.println("-------------");
        //4.查询id值为itcast的元素
        Elements elements1 = document.select("#itcast");
        for (Element element : elements1) {
            System.out.println(element);
        }
        System.out.println("-------------");
        //获取student标签,并且number属性值为heima_0001
        Elements elements2 = document.select("student[number='heima_0001']");
        System.out.println(elements2);
        System.out.println("-------------");
        //获取student标签,并且number属性值为heima_0001的age子标签
        Elements elements3 = document.select("student[number='heima_0001']>age");
        System.out.println(elements3);
        System.out.println("-------------");


    }
}

web

查看自己电脑IP地址

ipconfig
image-20211119112217271

web相关概念

image-20211119110244884image-20211119110201985

image-20211119110503666

web服务器软件

image-20211119111009380

Tomcat: web服务器软件

下载、安装、卸载、启动、关闭、配置
image-20211119113901560

image-20211119113948323

image-20211119113730687image-20211119114330287

image-20211119203318460

image-20211120182443772 image-20211120182505985

一般不用第二种。防止配置太多的时候,系统崩溃,使用第三种等效果配对

目录文件

image-20211119112241030

静态项目和动态项目

image-20211119203626942

image-20211119204320231
本地磁盘位置: F:\mySQL\development\apache-tomcat-8.5.73\webapps\ROOT\WEB-INF

项目部署到idea中

官网下载,run添加

image-20211120091805499

更改文件配置

image-20211120091705608

Servlet入门学习

概念

image-20211120092451091

快速入门

image-20211120093842182

4.配置Servlet

image-20211120202126107

执行原理
image-20211120202030575

image-20211120202332958

Servlet中的生命周期
image-20211121110131919

image-20211121121031355

Servlet 3.0
image-20211121124711884
代码
package com.example.sevlet;

import javax.servlet.*;
import javax.servlet.annotation.WebServlet;
import java.io.IOException;

@WebServlet( "/demo2")
public class ServletDemo implements Servlet {
    @Override
    public void init(ServletConfig config) throws ServletException {

    }

    @Override
    public ServletConfig getServletConfig() {
        return null;
    }

    @Override
    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
        System.out.println("servlet3.0  来啦···");
    }

    @Override
    public String getServletInfo() {
        return null;
    }

    @Override
    public void destroy() {
    }
}

idea和tomcat的相关配置

image-20211121130227080

servlet 学习2

image-20211121171559481

image-20211121171730458 image-20211121171636087
Servlet相关配置
image-20211121172918023
代码
package com.example.servlet_http_request;

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

//@WebServlet({"/d4","/dd4","/ddd4"})
//@WebServlet("/user/demo4")
//@WebServlet("/user/*")
//@WebServlet("/*")  
@WebServlet("*.do")  //http://localhost/1.do
public class ServletDemo4 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println( "demo4~~~");
    }
}

HTTP

image-20211122133848489

传输协议
image-20211122133251910
请求消息数据格式

image-20211122135148688

image-20211122140152810

image-20211122140452743

image-20211122140333847

响应消息数据格式
request和response响应原理

image-20211122141709804

image-20211122152411157

request功能

1.获取请求消息数据

三个打星号的方法最常用

image-20211122153508826

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mBPhKSGu-1647178288472)(java Web笔记(黑马).assets/image-20211122155353072.png)]image-20211122201957768

2.其他功能

image-20211122202458181

web学习二q

Maven

Maven是专门用于管理和构建Java项目的工具,它的主要功能有:

  • 提供了一套标准化的项目结构

  • 提供了一套标准化的构建流程(编译,测试,打包,发布……)

  • 提供了一套依赖管理机制

标准化的项目结构:

项目结构我们都知道,每一个开发工具(IDE)都有自己不同的项目结构,它们互相之间不通用。我再eclipse中创建的目录,无法在idea中进行使用,这就造成了很大的不方便,如下图:前两个是以后开发经常使用的开发工具

image-20210726153521381

而Maven提供了一套标准化的项目结构,所有的IDE使用Maven构建的项目完全一样,所以IDE创建的Maven项目可以通用。如下图右边就是Maven构建的项目结构。

image-20210726153815028

标准化的构建流程:

image-20210726154144488

如上图所示我们开发了一套系统,代码需要进行编译、测试、打包、发布,这些操作如果需要反复进行就显得特别麻烦,而Maven提供了一套简单的命令来完成项目构建。

依赖管理:

依赖管理其实就是管理你项目所依赖的第三方资源(jar包、插件)。如之前我们项目中需要使用JDBC和Druid的话,就需要去网上下载对应的依赖包(当前之前是老师已经下载好提供给大家了),复制到项目中,还要将jar包加入工作环境这一系列的操作。如下图所示

image-20210726154753631

而Maven使用标准的 坐标 配置来管理各种依赖,只需要简单的配置就可以完成依赖管理。

image-20210726154922337

如上图右边所示就是mysql驱动包的坐标,在项目中只需要写这段配置,其他都不需要我们担心,Maven都帮我们进行操作了。

市面上有很多构建工具,而Maven依旧还是主流构建工具,如下图是常用构建工具的使用占比

image-20210726155212733

1.1 Maven简介

Apache Maven 是一个项目管理和构建==工具==,它基于项目对象模型(POM)的概念,通过一小段描述信息来管理项目的构建、报告和文档。

官网 :http://maven.apache.org/

通过上面的描述大家只需要知道Maven是一个工具即可。Apache 是一个开源组织,将来我们会学习很多Apache提供的项目。

1.1.1 Maven模型
  • 项目对象模型 (Project Object Model)
  • 依赖管理模型(Dependency)
  • 插件(Plugin)
image-20210726155759621

如上图所示就是Maven的模型,而我们先看紫色框框起来的部分,他就是用来完成 标准化构建流程 。如我们需要编译,Maven提供了一个编译插件供我们使用,我们需要打包,Maven就提供了一个打包插件提供我们使用等。

image-20210726160928515

上图中紫色框起来的部分,项目对象模型就是将我们自己抽象成一个对象模型,有自己专属的坐标,如下图所示是一个Maven项目:

image-20210726161340796

依赖管理模型则是使用坐标来描述当前项目依赖哪儿些第三方jar包,如下图所示

image-20210726161616034

上述Maven模型图中还有一部分是仓库。如何理解仓库呢?

1.1.2 仓库

大家想想这样的场景,我们创建Maven项目,在项目中使用坐标来指定项目的依赖,那么依赖的jar包到底存储在什么地方呢?其实依赖jar包是存储在我们的本地仓库中。而项目运行时从本地仓库中拿需要的依赖jar包。

仓库分类:

  • 本地仓库:自己计算机上的一个目录

  • 中央仓库:由Maven团队维护的全球唯一的仓库

    • 地址: https://repo1.maven.org/maven2/
  • 远程仓库(私服):一般由公司团队搭建的私有仓库

    今天我们只学习远程仓库的使用,并不会搭建。

当项目中使用坐标引入对应依赖jar包后,首先会查找本地仓库中是否有对应的jar包:

  • 如果有,则在项目直接引用;

  • 如果没有,则去中央仓库中下载对应的jar包到本地仓库。

image-20210726162605394

如果还可以搭建远程仓库,将来jar包的查找顺序则变为:

本地仓库 --> 远程仓库–> 中央仓库

image-20210726162815045

1.2 Maven安装配置

image-20220217123410670

  • 解压 apache-maven-3.6.1.rar 既安装完成

    image-20210726163219682

    建议解压缩到没有中文、特殊字符的路径下。如课程中解压缩到 D:\software 下。

    解压缩后的目录结构如下:

    image-20210726163518885
    • bin目录 : 存放的是可执行命令。mvn 命令重点关注。
    • conf目录 :存放Maven的配置文件。settings.xml 配置文件后期需要修改。
    • lib目录 :存放Maven依赖的jar包。Maven也是使用java开发的,所以它也依赖其他的jar包。
  • 配置环境变量 MAVEN_HOME 为安装路径的bin目录

    此电脑 右键 --> 高级系统设置 --> 高级 --> 环境变量

    在系统变量处新建一个变量 MAVEN_HOME

    image-20210726164058589

    Path 中进行配置

    image-20210726164146832

    打开命令提示符进行验证,出现如图所示表示安装成功

    image-20210726164306480
  • 配置本地仓库

    修改 conf/settings.xml 中的 为一个指定目录作为本地仓库,用来存储jar包。

    image-20210726164348048
  • 配置阿里云私服

    中央仓库在国外,所以下载jar包速度可能比较慢,而阿里公司提供了一个远程仓库,里面基本也都有开源项目的jar包。

    修改 conf/settings.xml 中的 标签,为其添加如下子标签:

    <mirror>  
        <id>alimaven</id>  
        <name>aliyun maven</name>  
        <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
        <mirrorOf>central</mirrorOf>          
    </mirror>
    

1.3 Maven基本使用

1.3.1 Maven 常用命令
  • compile :编译

  • clean:清理

  • test:测试

  • package:打包

  • install:安装

命令演示:

资料\代码\maven-project 提供了一个使用Maven构建的项目,项目结构如下:

image-20210726170404545

而我们使用上面命令需要在磁盘上进入到项目的 pom.xml 目录下,打开命令提示符

image-20210726170549907

编译命令演示:

compile :编译

执行上述命令可以看到:

  • 从阿里云下载编译需要的插件的jar包,在本地仓库也能看到下载好的插件
  • 在项目下会生成一个 target 目录
image-20210726171047324

同时在项目下会出现一个 target 目录,编译后的字节码文件就放在该目录下

image-20210726171346824

清理命令演示:

mvn clean

执行上述命令可以看到

  • 从阿里云下载清理需要的插件jar包
  • 删除项目下的 target 目录
image-20210726171558786

打包命令演示:

mvn package

执行上述命令可以看到:

  • 从阿里云下载打包需要的插件jar包
  • 在项目的 terget 目录下有一个jar包(将当前项目打成的jar包)
image-20210726171747125

测试命令演示:

mvn test  

该命令会执行所有的测试代码。执行上述命令效果如下

image-20210726172343933

安装命令演示:

mvn install

该命令会将当前项目打成jar包,并安装到本地仓库。执行完上述命令后到本地仓库查看结果如下:

image-20210726172709112
1.3.2 Maven 生命周期

Maven 构建项目生命周期描述的是一次构建过程经历经历了多少个事件

Maven 对项目构建的生命周期划分为3套:

  • clean :清理工作。
  • default :核心工作,例如编译,测试,打包,安装等。
  • site : 产生报告,发布站点等。这套声明周期一般不会使用。

同一套生命周期内,执行后边的命令,前面的所有命令会自动执行。例如默认(default)生命周期如下:

image-20210726173153576

当我们执行 install(安装)命令时,它会先执行 compile命令,再执行 test 命令,再执行 package 命令,最后执行 install 命令。

当我们执行 package (打包)命令时,它会先执行 compile 命令,再执行 test 命令,最后执行 package 命令。

默认的生命周期也有对应的很多命令,其他的一般都不会使用,我们只关注常用的:

image-20210726173619353

1.4 IDEA使用Maven

以后开发中我们肯定会在高级开发工具中使用Maven管理项目,而我们常用的高级开发工具是IDEA,所以接下来我们会讲解Maven在IDEA中的使用。

1.4.1 IDEA配置Maven环境

我们需要先在IDEA中配置Maven环境:

  • 选择 IDEA中 File --> Settings

    image-20210726174202898
  • 搜索 maven

    image-20210726174229396
  • 设置 IDEA 使用本地安装的 Maven,并修改配置文件路径

    image-20210726174248050
1.4.2 Maven 坐标详解

什么是坐标?

  • Maven 中的坐标是资源的唯一标识
  • 使用坐标来定义项目或引入项目中需要的依赖

Maven 坐标主要组成

  • groupId:定义当前Maven项目隶属组织名称(通常是域名反写,例如:com.itheima)
  • artifactId:定义当前Maven项目名称(通常是模块名称,例如 order-service、goods-service)
  • version:定义当前项目版本号

如下图就是使用坐标表示一个项目:

image-20210726174718176

注意:

  • 上面所说的资源可以是插件、依赖、当前项目。
  • 我们的项目如果被其他的项目依赖时,也是需要坐标来引入的。
1.4.3 IDEA 创建 Maven项目
  • 创建模块,选择Maven,点击Next

    image-20210726175049876
  • 填写模块名称,坐标信息,点击finish,创建完成

    image-20210726175109822

    创建好的项目目录结构如下:

    image-20210726175244826

  • 编写 HelloWorld,并运行

1.4.4 IDEA 导入 Maven项目

大家在学习时可能需要看老师的代码,当然也就需要将老师的代码导入到自己的IDEA中。我们可以通过以下步骤进行项目的导入:

  • 选择右侧Maven面板,点击 + 号

    image-20210726182702336
  • 选中对应项目的pom.xml文件,双击即可

    image-20210726182648891
  • 如果没有Maven面板,选择

    View --> Appearance --> Tool Window Bars

    image-20210726182634466

可以通过下图所示进行命令的操作:

image-20210726182902961

配置 Maven-Helper 插件

  • 选择 IDEA中 File --> Settings

    image-20210726192212026
  • 选择 Plugins

    image-20210726192224914
  • 搜索 Maven,选择第一个 Maven Helper,点击Install安装,弹出面板中点击Accept

    image-20210726192244567
  • 重启 IDEA

安装完该插件后可以通过 选中项目右键进行相关命令操作,如下图所示:

image-20210726192430371

1.5 依赖管理

maven更新导入下载失败

1.5.1 使用坐标引入jar包

Alt+ins

使用坐标引入jar包的步骤:

  • 在项目的 pom.xml 中编写 标签

  • 在 标签中 使用 引入坐标

  • 定义坐标的 groupId,artifactId,version

    image-20210726193105765
  • 点击刷新按钮,使坐标生效

    image-20210726193121384

注意:

  • 具体的坐标我们可以到如下网站进行搜索
  • https://mvnrepository.com/

快捷方式导入jar包的坐标:

每次需要引入jar包,都去对应的网站进行搜索是比较麻烦的,接下来给大家介绍一种快捷引入坐标的方式

  • 在 pom.xml 中 按 alt + insert,选择 Dependency

    image-20210726193603724
  • 在弹出的面板中搜索对应坐标,然后双击选中对应坐标

    image-20210726193625229
  • 点击刷新按钮,使坐标生效

    image-20210726193121384

自动导入设置:

上面每次操作都需要点击刷新按钮,让引入的坐标生效。当然我们也可以通过设置让其自动完成

  • 选择 IDEA中 File --> Settings

    image-20210726193854438
  • 在弹出的面板中找到 Build Tools

    image-20210726193909276
  • 选择 Any changes,点击 ok 即可生效

1.5.2 依赖范围

通过设置坐标的依赖范围(scope),可以设置 对应jar包的作用范围:编译环境、测试环境、运行环境。

如下图所示给 junit 依赖通过 scope 标签指定依赖的作用范围。 那么这个依赖就只能作用在测试环境,其他环境下不能使用。

image-20210726194703845

那么 scope 都可以有哪些取值呢?

依赖范围编译classpath测试classpath运行classpath例子
compileYYYlogback
test-Y-Junit
providedYY-servlet-api
runtime-YYjdbc驱动
systemYY-存储在本地的jar包
  • compile :作用于编译环境、测试环境、运行环境。
  • test : 作用于测试环境。典型的就是Junit坐标,以后使用Junit时,都会将scope指定为该值
  • provided :作用于编译环境、测试环境。我们后面会学习 servlet-api ,在使用它时,必须将 scope 设置为该值,不然运行时就会报错
  • runtime : 作用于测试环境、运行环境。jdbc驱动一般将 scope 设置为该值,当然不设置也没有任何问题

注意:

  • 如果引入坐标不指定 scope 标签时,默认就是 compile 值。以后大部分jar包都是使用默认值。

HTTP&Tomcat&Servlet

今日目标:

  • 了解JavaWeb开发的技术栈
  • 理解HTTP协议和HTTP请求与响应数据的格式
  • 掌握Tomcat的使用
  • 掌握在IDEA中使用Tomcat插件
  • 理解Servlet的执行流程和生命周期
  • 掌握Servlet的使用和相关配置

1,Web概述

1.1 Web和JavaWeb的概念

Web是全球广域网,也称为万维网(www),能够通过浏览器访问的网站。
在我们日常的生活中,经常会使用浏览器去访问百度京东传智官网等这些网站,这些网站统称为Web网站。如下就是通过浏览器访问传智官网的界面:
1627031023395
我们知道了什么是Web,那么JavaWeb又是什么呢?顾名思义JavaWeb就是用Java技术来解决相关web互联网领域的技术栈。
等学习完JavaWeb之后,同学们就可以使用Java语言开发我们上述所说的网站。而国内很多大型网站公司也是首选Java语言来解决web互联网相关的问题。那都有哪些公司的系统是使用Java语言的呢?

使用Java语言开发互联网系统是有很多技术栈需要大家了解,具体都有哪些呢?

1.2 JavaWeb技术栈

了解JavaWeb技术栈之前,有一个很重要的概念要介绍。

1.2.1 B/S架构

什么是B/S架构?
B/S 架构:Browser/Server,浏览器/服务器 架构模式,它的特点是,客户端只需要浏览器,应用程序的逻辑和数据都存储在服务器端。浏览器只需要请求服务器,获取Web资源,服务器把Web资源发送给浏览器即可。大家可以通过下面这张图来回想下我们平常的上网过程:
1627031933553

  • 打开浏览器访问百度首页,输入要搜索的内容,点击回车或百度一下,就可以获取和搜索相关的内容
  • 思考下搜索的内容并不在我们自己的点上,那么这些内容从何而来?答案很明显是从百度服务器返回给我们的
  • 日常百度的小细节,逢年过节百度的logo会更换不同的图片,服务端发生变化,客户端不需做任务事情就能获取最新内容
  • 所以说B/S架构的好处:易于维护升级:服务器端升级后,客户端无需任何部署就可以使用到新的版本。
    了解了什么是B/S架构后,作为后台开发工程师的我们将来主要关注的是服务端的开发和维护工作。在服务端将来会放很多资源,都有哪些资源呢?
1.2.2 静态资源
  • 静态资源主要包含HTML、CSS、JavaScript、图片等,主要负责页面的展示。
  • 我们之前已经学过前端网页制作三剑客(HTML+CSS+JavaScript),使用这些技术我们就可以制作出效果比较丰富的网页,将来展现给用户。但是由于做出来的这些内容都是静态的,这就会导致所有的人看到的内容将是一模一样。
  • 在日常上网的过程中,我们除了看到这些好看的页面以外,还会碰到很多动态内容,比如我们常见的百度登录效果:
    1627037814180
    张三登录以后在网页的右上角看到的是 张三,而李四登录以后看到的则是李四。所以不同的用户访问相同的资源看到的内容大多数是不一样的,要想实现这样的效果,光靠静态资源是无法实现的。
1.2.3 动态资源
  • 动态资源主要包含Servlet、JSP等,主要用来负责逻辑处理。
  • 动态资源处理完逻辑后会把得到的结果交给静态资源来进行展示,动态资源和静态资源要结合一起使用。
  • 动态资源虽然可以处理逻辑,但是当用户来登录百度的时候,就需要输入用户名密码,这个时候我们就又需要解决的一个问题是,用户在注册的时候填入的用户名和密码、以及我们经常会访问到一些数据列表的内容展示(如下图所示),这些数据都存储在哪里?我们需要的时候又是从哪里来取呢?
    1627038674340
1.2.4 数据库
  • 数据库主要负责存储数据。
  • 整个Web的访问过程就如下图所示:
    1627039320220
    (1)浏览器发送一个请求到服务端,去请求所需要的相关资源;
    (2)资源分为动态资源和静态资源,动态资源可以是使用Java代码按照Servlet和JSP的规范编写的内容;
    (3)在Java代码可以进行业务处理也可以从数据库中读取数据;
    (4)拿到数据后,把数据交给HTML页面进行展示,再结合CSS和JavaScript使展示效果更好;
    (5)服务端将静态资源响应给浏览器;
    (6)浏览器将这些资源进行解析;
    (7)解析后将效果展示在浏览器,用户就可以看到最终的结果。
    在整个Web的访问过程中,会设计到很多技术,这些技术有已经学习过的,也有还未涉及到的内容,都有哪些还没有涉及到呢?
1.2.5 HTTP协议
  • HTTP协议:主要定义通信规则
  • 浏览器发送请求给服务器,服务器响应数据给浏览器,这整个过程都需要遵守一定的规则,之前大家学习过TCP、UDP,这些都属于规则,这里我们需要使用的是HTTP协议,这也是一种规则。
1.2.6 Web服务器
  • Web服务器:负责解析 HTTP 协议,解析请求数据,并发送响应数据
  • 浏览器按照HTTP协议发送请求和数据,后台就需要一个Web服务器软件来根据HTTP协议解析请求和数据,然后把处理结果再按照HTTP协议发送给浏览器
  • Web服务器软件有很多,我们课程中将学习的是目前最为常用的Tomcat服务器

到这为止,关于JavaWeb中用到的技术栈我们就介绍完了,这里面就只有HTTP协议、Servlet、JSP以及Tomcat这些知识是没有学习过的,所以整个Web核心主要就是来学习这些技术。

1.3 Web核心课程安排

1627043194238

整个Web核心,我们总共有六天的学习内容,分别是:

  • 第一天:HTTP、Tomcat、Servlet
  • 第二天:Request(请求)、Response(响应)
  • 第三天:JSP、会话技术(Cookie、Session)
  • 第四天:Filter(过滤器)、Listener(监听器)
  • 第五天:Ajax、Vue、ElementUI
  • 第六天:综合案例

(1)Request是从客户端向服务端发出的请求对象,

(2)Response是从服务端响应给客户端的结果对象,

(3)JSP是动态网页技术,

(4)会话技术是用来存储客户端和服务端交互所产生的数据,

(5)过滤器是用来拦截客户端的请求,

(6)监听器是用来监听特定事件,

(7)Ajax、Vue、ElementUI都是属于前端技术

这些技术都该如何来使用,我们后面会一个个进行详细的讲解。接下来我们来学习下HTTP、Tomcat和Servlet。

2, HTTP

2.1 简介

HTTP概念

HyperText Transfer Protocol,超文本传输协议,规定了浏览器和服务器之间数据传输的规则

  • 数据传输的规则指的是请求数据和响应数据需要按照指定的格式进行传输。
  • 如果想知道具体的格式,可以打开浏览器,点击F12打开开发者工具,点击Network来查看某一次请求的请求数据和响应数据具体的格式内容,如下图所示:

1627046235092

注意:在浏览器中如果看不到上述内容,需要清除浏览器的浏览数据。chrome浏览器可以使用ctrl+shift+Del进行清除。

所以学习HTTP主要就是学习请求和响应数据的具体格式内容。

HTTP协议特点

HTTP协议有它自己的一些特点,分别是:

  • 基于TCP协议: 面向连接,安全

    TCP是一种面向连接的(建立连接之前是需要经过三次握手)、可靠的、基于字节流的传输层通信协议,在数据传输方面更安全。

  • 基于请求-响应模型的:一次请求对应一次响应

    请求和响应是一一对应关系

  • HTTP协议是无状态协议:对于事物处理没有记忆能力。每次请求-响应都是独立的

    无状态指的是客户端发送HTTP请求给服务端之后,服务端根据请求响应数据,响应完后,不会记录任何信息。这种特性有优点也有缺点,

    • 缺点:多次请求间不能共享数据
    • 优点:速度快

    请求之间无法共享数据会引发的问题,如:

    • 京东购物,加入购物车去购物车结算是两次请求,
    • HTTP协议的无状态特性,加入购物车请求响应结束后,并未记录加入购物车是何商品
    • 发起去购物车结算的请求后,因为无法获取哪些商品加入了购物车,会导致此次请求无法正确展示数据

    具体使用的时候,我们发现京东是可以正常展示数据的,原因是Java早已考虑到这个问题,并提出了使用会话技术(Cookie、Session)来解决这个问题。具体如何来做,我们后面会详细讲到。刚才提到HTTP协议是规定了请求和响应数据的格式,那具体的格式是什么呢?

2.2 请求数据格式

2.2.1 格式介绍

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

1627050004221

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

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

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

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

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

    这些数据有什么用处?

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

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

    1627050930378

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

    • GET请求请求参数在请求行中,没有请求体,POST请求请求参数在请求体中 请求参数 跳下去
    • GET请求请求参数大小有限制,POST没有
2.2.2 实例演示

代码\http 拷贝到IDEA的工作目录中,比如D:\workspace\web目录,

1627278511902

使用IDEA打开

1627278583127

打开后,可以点击项目中的html\19-表单验证.html,使用浏览器打开,通过修改页面中form表单的method属性来测试GET请求和POST请求的参数携带方式。

1627278725007

小结:

  1. 请求数据中包含三部分内容,分别是请求行、请求头和请求体

  2. POST请求数据在请求体中,GET请求数据在请求行上

2.3 响应数据格式

2.3.1 格式介绍

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

1627053710214

  • 响应行:响应数据的第一行,响应行包含三块内容,分别是 HTTP/1.1[HTTP协议及版本] 200[响应状态码] ok[状态码的描述]

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

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

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

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

2.3.2 响应状态码

参考: 资料/1.HTTP/《响应状态码.md》

关于响应状态码,我们先主要认识三个状态码,其余的等后期用到了再去掌握:

  • 200 ok 客户端请求成功

  • 404 Not Found 请求资源不存在

  • 500 Internal Server Error 服务端发生不可预期的错误

    一、状态码大类
    状态码分类说明
    1xx响应中——临时状态码,表示请求已经接受,告诉客户端应该继续请求或者如果它已经完成则忽略它
    2xx成功——表示请求已经被成功接收,处理已完成
    3xx重定向——重定向到其它地方:它让客户端再发起一个请求以完成整个处理。
    4xx客户端错误——处理发生错误,责任在客户端,如:客户端的请求一个不存在的资源,客户端未被授权,禁止访问等
    5xx服务器端错误——处理发生错误,责任在服务端,如:服务端抛出异常,路由出错,HTTP版本不支持等

    状态码大全:https://cloud.tencent.com/developer/chapter/13553

    二、常见的响应状态码
    状态码英文描述解释
    200OK客户端请求成功,即处理成功,这是我们最想看到的状态码
    302Found指示所请求的资源已移动到由Location响应头给定的 URL,浏览器会自动重新访问到这个页面
    304Not Modified告诉客户端,你请求的资源至上次取得后,服务端并未更改,你直接用你本地缓存吧。隐式重定向
    400Bad Request客户端请求有语法错误,不能被服务器所理解
    403Forbidden服务器收到请求,但是拒绝提供服务,比如:没有权限访问相关资源
    404Not Found请求资源不存在,一般是URL输入有误,或者网站资源被删除了
    428Precondition Required服务器要求有条件的请求,告诉客户端要想访问该资源,必须携带特定的请求头
    429Too Many Requests太多请求,可以限制客户端请求某个资源的数量,配合 Retry-After(多长时间后可以请求)响应头一起使用
    431Request Header Fields Too Large请求头太大,服务器不愿意处理请求,因为它的头部字段太大。请求可以在减少请求头域的大小后重新提交。
    405Method Not Allowed请求方式有误,比如应该用GET请求方式的资源,用了POST
    500Internal Server Error服务器发生不可预期的错误。服务器出异常了,赶紧看日志去吧
    503Service Unavailable服务器尚未准备好处理请求,服务器刚刚启动,还未初始化好
    511Network Authentication Required客户端需要进行身份验证才能获得网络访问权限
2.3.3 自定义服务器

在前面我们导入到IDEA中的http项目中,有一个Server.java类,这里面就是自定义的一个服务器代码,主要使用到的是ServerSocketSocket

package com.itheima;

import sun.misc.IOUtils;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
/*
    自定义服务器
 */
public class Server {
    public static void main(String[] args) throws IOException {
        ServerSocket ss = new ServerSocket(8080); // 监听指定端口
        System.out.println("server is running...");
        while (true){
            Socket sock = ss.accept();
            System.out.println("connected from " + sock.getRemoteSocketAddress());
            Thread t = new Handler(sock);
            t.start();
        }
    }
}

class Handler extends Thread {
    Socket sock;

    public Handler(Socket sock) {
        this.sock = sock;
    }

    public void run() {
        try (InputStream input = this.sock.getInputStream()) {
            try (OutputStream output = this.sock.getOutputStream()) {
                handle(input, output);
            }
        } catch (Exception e) {
            try {
                this.sock.close();
            } catch (IOException ioe) {
            }
            System.out.println("client disconnected.");
        }
    }

    private void handle(InputStream input, OutputStream output) throws IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));
        BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(output, StandardCharsets.UTF_8));
        // 读取HTTP请求:
        boolean requestOk = false;
        String first = reader.readLine();
        if (first.startsWith("GET / HTTP/1.")) {
            requestOk = true;
        }
        for (;;) {
            String header = reader.readLine();
            if (header.isEmpty()) { // 读取到空行时, HTTP Header读取完毕
                break;
            }
            System.out.println(header);
        }
        System.out.println(requestOk ? "Response OK" : "Response Error");
        if (!requestOk) {
            // 发送错误响应:
            writer.write("HTTP/1.0 404 Not Found\r\n");
            writer.write("Content-Length: 0\r\n");
            writer.write("\r\n");
            writer.flush();
        } else {
            // 发送成功响应:

            //读取html文件,转换为字符串
            BufferedReader br = new BufferedReader(new FileReader("http/html/a.html"));
            StringBuilder data = new StringBuilder();
            String line = null;
            while ((line = br.readLine()) != null){
                data.append(line);
            }
            br.close();
            int length = data.toString().getBytes(StandardCharsets.UTF_8).length;

            writer.write("HTTP/1.1 200 OK\r\n");
            writer.write("Connection: keep-alive\r\n");
            writer.write("Content-Type: text/html\r\n");
            writer.write("Content-Length: " + length + "\r\n");
            writer.write("\r\n"); // 空行标识Header和Body的分隔
            writer.write(data.toString());
            writer.flush();
        }
    }
}

上面代码,大家不需要自己写,主要通过上述代码,只需要大家了解到服务器可以使用java完成编写,是可以接受页面发送的请求和响应数据给前端浏览器的,真正用到的Web服务器,我们不会自己写,都是使用目前比较流行的web服务器,比如Tomcat

小结

  1. 响应数据中包含三部分内容,分别是响应行、响应头和响应体

  2. 掌握200,404,500这三个响应状态码所代表含义,分布是成功、所访问资源不存在和服务的错误

3, Tomcat

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gRxqXDsO-1647178288479)(F:\T笔记\Typora\笔记\三月阶段笔记\java Web\java Web笔记(黑马).assets\image-20220217093830645.png)]

3.1 简介

3.1.1 什么是Web服务器

Web服务器是一个应该程序(软件),对HTTP协议的操作进行封装,使得程序员不必直接对协议进行操作,让Web开发更加便捷。主要功能是"提供网上信息浏览服务"。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hpaxilKR-1647178288479)(F:\T笔记\Typora\笔记\三月阶段笔记\java Web\java Web笔记(黑马).assets\1627058356051.png)]

Web服务器是安装在服务器端的一款软件,将来我们把自己写的Web项目部署到Web Tomcat服务器软件中,当Web服务器软件启动后,部署在Web服务器软件中的页面就可以直接通过浏览器来访问了。

Web服务器软件使用步骤

  • 准备静态资源
  • 下载安装Web服务器软件
  • 将静态资源部署到Web服务器上
  • 启动Web服务器使用浏览器访问对应的资源

上述内容在演示的时候,使用的是Apache下的Tomcat软件,至于Tomcat软件如何使用,后面会详细的讲到。而对于Web服务器来说,实现的方案有很多,Tomcat只是其中的一种,而除了Tomcat以外,还有很多优秀的Web服务器,比如:

1627060368806

Tomcat就是一款软件,我们主要是以学习如何去使用为主。具体我们会从以下这些方向去学习:

  1. 简介: 初步认识下Tomcat

  2. 基本使用: 安装、卸载、启动、关闭、配置和项目部署,这些都是对Tomcat的基本操作

  3. IDEA中如何创建Maven Web项目

  4. IDEA中如何使用Tomcat,后面这两个都是我们以后开发经常会用到的方式

首选我们来认识下Tomcat。

Tomcat

Tomcat的相关概念:

  • Tomcat是Apache软件基金会一个核心项目,是一个开源免费的轻量级Web服务器,支持Servlet/JSP少量JavaEE规范。

  • 概念中提到了JavaEE规范,那什么又是JavaEE规范呢?

    JavaEE: Java Enterprise Edition,Java企业版。指Java企业级开发的技术规范总和。包含13项技术规范:JDBC、JNDI、EJB、RMI、JSP、Servlet、XML、JMS、Java IDL、JTS、JTA、JavaMail、JAF。

  • 因为Tomcat支持Servlet/JSP规范,所以Tomcat也被称为Web容器、Servlet容器。Servlet需要依赖Tomcat才能运行

  • Tomcat的官网: https://tomcat.apache.org/ 从官网上可以下载对应的版本进行使用。

  • 学习时使用的时tomcat8

Tomcat的LOGO

1627176045795

小结

通过这一节的学习,我们需要掌握以下内容:

  1. Web服务器的作用

封装HTTP协议操作,简化开发

可以将Web项目部署到服务器中,对外提供网上浏览服务

  1. Tomcat是一个轻量级的Web服务器,支持Servlet/JSP少量JavaEE规范,也称为Web容器,Servlet容器。

3.2 基本使用

Tomcat总共分两部分学习,先来学习Tomcat的基本使用,包括Tomcat的下载、安装、卸载、启动和关闭

3.2.1 下载

直接从官网下载

1627178001030

大家可以自行下载,也可以直接使用资料中已经下载好的资源,

Tomcat的软件程序 资料/2. Tomcat/apache-tomcat-8.5.68-windows-x64.zip

Tomcat的源码 资料/2. Tomcat/tomcat源码/apache-tomcat-8.5.68-src.zip

3.2.2 安装

Tomcat是绿色版,直接解压即可

  • 在D盘的software目录下,将apache-tomcat-8.5.68-windows-x64.zip进行解压缩,会得到一个apache-tomcat-8.5.68的目录,Tomcat就已经安装成功。

    注意,Tomcat在解压缩的时候,解压所在的目录可以任意,但最好解压到一个不包含中文和空格的目录,因为后期在部署项目的时候,如果路径有中文或者空格可能会导致程序部署失败。

  • 打开apache-tomcat-8.5.68目录就能看到如下目录结构,每个目录中包含的内容需要认识下,

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4d767cps-1647178288480)(java Web笔记(黑马).assets/1627178815892.png)]

    bin:目录下有两类文件,一种是以.bat结尾的,是Windows系统的可执行文件,一种是以.sh结尾的,是Linux系统的可执行文件。

    webapps:就是以后项目部署的目录

    到此,Tomcat的安装就已经完成。

3.2.3 卸载

卸载比较简单,可以直接删除目录即可

3.2.4 启动

双击: bin\startup.bat

1627179006011

启动后,通过浏览器访问 http://localhost:8080能看到Apache Tomcat的内容就说明Tomcat已经启动成功。

1627199957728

注意: 启动的过程中,控制台有中文乱码,需要修改conf/logging.prooperties

1627199827589

3.2.5 关闭

关闭有三种方式

  • 直接x掉运行窗口:强制关闭[不建议]
  • bin\shutdown.bat:正常关闭
  • ctrl+c: 正常关闭
3.2.6 配置

修改端口

  • Tomcat默认的端口是8080,要想修改Tomcat启动的端口号,需要修改 conf/server.xml

1627200509883

注: HTTP协议默认端口号为80,如果将Tomcat端口号改为80,则将来访问Tomcat时,将不用输入端口号

启动时可能出现的错误

  • Tomcat的端口号取值范围是0-65535之间任意未被占用的端口,如果设置的端口号被占用,启动的时候就会包如下的错误

    1627200780590

  • Tomcat启动的时候,启动窗口一闪而过: 需要检查JAVA_HOME环境变量是否正确配置

1627201248802

3.2.7 部署
  • Tomcat部署项目: 将项目放置到webapps目录下,即部署完成。

    • 资料/2. Tomcat/hello 目录拷贝到Tomcat的webapps目录下

    • 通过浏览器访问http://localhost/hello/a.html,能看到下面的内容就说明项目已经部署成功。

      1627201572748

      但是呢随着项目的增大,项目中的资源也会越来越多,项目在拷贝的过程中也会越来越费时间,该如何解决呢?

  • 一般JavaWeb项目会被打包称war然后将war包放到Webapps目录下,Tomcat会自动解压缩war文件

    • 资料/2. Tomcat/haha.war目录拷贝到Tomcat的webapps目录下

    • Tomcat检测到war包后会自动完成解压缩,在webapps目录下就会多一个haha目录

    • 通过浏览器访问http://localhost/haha/a.html,能看到下面的内容就说明项目已经部署成功。

      1627201868752

至此,Tomcat的部署就已经完成了,至于如何获得项目对应的war包,后期我们会借助于IDEA工具来生成。

3.3 Maven创建Web项目

介绍完Tomcat的基本使用后,我们来学习在IDEA中如何创建Maven Web项目,学习这种方式的原因是以后Tomcat中运行的绝大多数都是Web项目,而使用Maven工具能更加简单快捷的把Web项目给创建出来,所以Maven的Web项目具体如何来构建呢?

在真正创建Maven Web项目之前,我们先要知道Web项目长什么样子,具体的结构是什么?

3.3.1 Web项目结构

Web项目的结构分为:开发中的项目和开发完可以部署的Web项目,这两种项目的结构是不一样的,我们一个个来介绍下:

  • Maven Web项目结构: 开发中的项目

    1627202865978

    image-20220217102546222 image-20220217102732285
  • 开发完成部署的Web项目

    1627202903750

    • 开发项目通过执行Maven打包命令package,可以获取到部署的Web项目目录
    • 编译后的Java字节码文件和resources的资源文件,会被放到WEB-INF下的classes目录下
    • pom.xml中依赖坐标对应的jar包,会被放入WEB-INF下的lib目录下
3.3.2 创建Maven Web项目

介绍完Maven Web的项目结构后,接下来使用Maven来创建Web项目,创建方式有两种:使用骨架和不使用骨架

使用骨架

具体的步骤包含:

1.创建Maven项目

2.选择使用Web项目骨架

3.输入Maven项目坐标创建项目

4.确认Maven相关的配置信息后,完成项目创建

5.删除pom.xml中多余内容

6.补齐Maven Web项目缺失的目录结构

  1. 创建Maven项目

    1627227574092

  2. 选择使用Web项目骨架

    1627227650406

  3. 输入Maven项目坐标创建项目

    1627228065007

  4. 确认Maven相关的配置信息后,完成项目创建

    1627228413280

  5. 删除pom.xml中多余内容,只留下面的这些内容,注意打包方式 jar和war的区别

    1627228584625

  6. 补齐Maven Web项目缺失的目录结构,默认没有java和resources目录,需要手动完成创建补齐,最终的目录结果如下

不使用骨架

具体的步骤包含:

1.创建Maven项目

2.选择不使用Web项目骨架

3.输入Maven项目坐标创建项目

4.在pom.xml设置打包方式为war

5.补齐Maven Web项目缺失webapp的目录结构

6.补齐Maven Web项目缺失WEB-INF/web.xml的目录结构

  1. 创建Maven项目

    1627229111549

  2. 选择不使用Web项目骨架

    1627229137316

  3. 输入Maven项目坐标创建项目

    1627229371251

  4. 在pom.xml设置打包方式为war,默认是不写代表打包方式为jar

    1627229428161

  5. 补齐Maven Web项目缺失webapp的目录结构

    1627229584134

  6. 补齐Maven Web项目缺失WEB-INF/web.xml的目录结构

    1627229676800

  7. 补充完后,最终的项目结构如下:

    1627229478030

上述两种方式,创建的web项目,都不是很全,需要手动补充内容,至于最终采用哪种方式来创建Maven Web项目,都是可以的,根据各自的喜好来选择使用即可。

小结

1.掌握Maven Web项目的目录结构

2.掌握使用骨架的方式创建Maven Web项目

1627204022604

3.掌握不使用骨架的方式创建Maven Web项目

1627204076090

3.4 IDEA使用Tomcat

  • Maven Web项目创建成功后,通过Maven的package命令可以将项目打包成war包,将war文件拷贝到Tomcat的webapps目录下,启动Tomcat就可以将项目部署成功,然后通过浏览器进行访问即可。
  • 然而我们在开发的过程中,项目中的内容会经常发生变化,如果按照上面这种方式来部署测试,是非常不方便的
  • 如何在IDEA中能快速使用Tomcat呢?

在IDEA中集成使用Tomcat有两种方式,分别是集成本地TomcatTomcat Maven插件

3.4.1 集成本地Tomcat

目标: 将刚才本地安装好的Tomcat8集成到IDEA中,完成项目部署,具体的实现步骤

  1. 打开添加本地Tomcat的面板

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mdAQkxak-1647178288487)(F:\T笔记\Typora\笔记\三月阶段笔记\java Web\java Web笔记(黑马).assets\1627229992900.png)]

  2. 指定本地Tomcat的具体路径

    1627230313062

  3. 修改Tomcat的名称,此步骤可以不改,只是让名字看起来更有意义,HTTP port中的端口也可以进行修改,比如把8080改成80

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nRKxBwOB-1647178288487)(F:\T笔记\Typora\笔记\三月阶段笔记\java Web\java Web笔记(黑马).assets\1627230366658.png)]

  4. 将开发项目部署项目到Tomcat中

    1627230913259

    扩展内容: xxx.war和 xxx.war exploded这两种部署项目模式的区别?

    • war模式是将WEB工程打成war包,把war包发布到Tomcat服务器上

    • war exploded模式是将WEB工程以当前文件夹的位置关系发布到Tomcat服务器上

    • war模式部署成功后,Tomcat的webapps目录下会有部署的项目内容

    • war exploded模式部署成功后,Tomcat的webapps目录下没有,而使用的是项目的target目录下的内容进行部署

    • 建议大家都选war模式进行部署,更符合项目部署的实际情况

  5. 部署成功后,就可以启动项目,为了能更好的看到启动的效果,可以在webapp目录下添加a.html页面

    1627233265351

  6. 启动成功后,可以通过浏览器进行访问测试

    1627232743706

  7. 最终的注意事项

    1627232916624

至此,IDEA中集成本地Tomcat进行项目部署的内容我们就介绍完了,整体步骤如下,大家需要按照流程进行部署操作练习。

1627205657117

3.4.2 Tomcat Maven插件

在IDEA中使用本地Tomcat进行项目部署,相对来说步骤比较繁琐,所以我们需要一种更简便的方式来替换它,那就是直接使用Maven中的Tomcat插件来部署项目,具体的实现步骤,只需要两步,分别是:

  1. 在pom.xml中添加Tomcat插件

    <build>
        <plugins>
        	<!--Tomcat插件 -->
            <plugin>
                <groupId>org.apache.tomcat.maven</groupId>
                <artifactId>tomcat7-maven-plugin</artifactId>
                <version>2.2</version>
            </plugin>
        </plugins>
    </build>
    
  2. 使用Maven Helper插件快速启动项目,选中项目,右键–>Run Maven --> tomcat7:run

1627233963315

注意:

  • 如果选中项目并右键点击后,看不到Run Maven和Debug Maven,这个时候就需要在IDEA中下载Maven Helper插件,具体的操作方式为: File --> Settings --> Plugins --> Maven Helper —> Install,安装完后按照提示重启IDEA,就可以看到了。

1627234184076

  • Maven Tomcat插件目前只有Tomcat7版本,没有更高的版本可以使用
  • 使用Maven Tomcat插件,要想修改Tomcat的端口和访问路径,可以直接修改pom.xml
<build>
    <plugins>
    	<!--Tomcat插件 -->
        <plugin>
            <groupId>org.apache.tomcat.maven</groupId>
            <artifactId>tomcat7-maven-plugin</artifactId>
            <version>2.2</version>
            <configuration>
            	<port>80</port><!--访问端口号 -->
                <!--项目访问路径
					未配置访问路径: http://localhost:80/tomcat-demo2/a.html
					配置/后访问路径: http://localhost:80/a.html
					如果配置成 /hello,访问路径会变成什么?
						答案: http://localhost:80/hello/a.html
				-->
                <path>/</path>
            </configuration>
        </plugin>
    </plugins>
</build>

小结

通过这一节的学习,大家要掌握在IDEA中使用Tomcat的两种方式,集成本地Tomcat和使用Maven的Tomcat插件。后者更简单,推荐大家使用,但是如果对于Tomcat的版本有比较高的要求,要在Tomcat7以上,这个时候就只能用前者了。

4, Servlet

4.1 简介

1627234763207

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

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

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

    1627234972853

介绍完Servlet是什么以后,接下来我们就按照快速入门->执行流程->生命周期->体系结构->urlPattern配置->XML配置的学习步骤,一步步完成对Servlet的知识学习,首选我们来通过一个入门案例来快速把Servlet用起来。

4.2 快速入门

需求分析: 编写一个Servlet类,并使用IDEA中Tomcat插件进行部署,最终通过浏览器访问所编写的Servlet程序。

具体的实现步骤为:

  1. 创建Web项目web-demo,导入Servlet依赖坐标
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>3.1.0</version>
    <!--
      此处为什么需要添加该标签?
      provided指的是在编译和测试过程中有效,最后生成的war包时不会加入
       因为Tomcat的lib目录中已经有servlet-api这个jar包,如果在生成war包的时候生效就会和Tomcat中的jar包冲突,导致报错
    -->
    <scope>provided</scope>
</dependency>
  1. 创建:定义一个类,实现Servlet接口,并重写接口中所有方法,并在service方法中输入一句话
package com.itheima.web;

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

public class ServletDemo1 implements Servlet {

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

    }

    public ServletConfig getServletConfig() {
        return null;
    }

    public String getServletInfo() {
        return null;
    }

    public void destroy() {

    }
}
  1. 配置:在类上使用@WebServlet注解,配置该Servlet的访问路径
@WebServlet("/demo1")
  1. 访问:启动Tomcat,浏览器中输入URL地址访问该Servlet
http://localhost:8080/web-demo/demo1
  1. 器访问后,在控制台会打印servlet hello world~ 说明servlet程序已经成功运行。

至此,Servlet的入门案例就已经完成,大家可以按照上面的步骤进行练习了。

4.3 执行流程

Servlet程序已经能正常运行,但是我们需要思考个问题: 我们并没有创建ServletDemo1类的对象,也没有调用对象中的service方法,为什么在控制台就打印了servlet hello world~这句话呢?

要想回答上述问题,我们就需要对Servlet的执行流程进行一个学习。

1627236923139

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

小结

介绍完Servlet的执行流程,需要大家掌握两个问题:

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

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

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

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

4.4 生命周期

介绍完Servlet的执行流程后,我们知道Servlet是由Tomcat Web服务器帮我们创建的。

接下来咱们再来思考一个问题:Tomcat什么时候创建的Servlet对象?

要想回答上述问题,我们就需要对Servlet的生命周期进行一个学习。

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

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

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

    package com.itheima.web;
    
    import javax.servlet.*;
    import javax.servlet.annotation.WebServlet;
    import java.io.IOException;
    /**
    * Servlet生命周期方法
    */
    @WebServlet(urlPatterns = "/demo2",loadOnStartup = 1)
    public class ServletDemo2 implements Servlet {
    
        /**
         *  初始化方法
         *  1.调用时机:默认情况下,Servlet被第一次访问时,调用
         *      * loadOnStartup: 默认为-1,修改为0或者正整数,则会在服务器启动的时候,调用
         *  2.调用次数: 1次
         * @param config
         * @throws ServletException
         */
        public void init(ServletConfig config) throws ServletException {
            System.out.println("init...");
        }
    
        /**
         * 提供服务
         * 1.调用时机:每一次Servlet被访问时,调用
         * 2.调用次数: 多次
         * @param req
         * @param res
         * @throws ServletException
         * @throws IOException
         */
        public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
            System.out.println("servlet hello world~");
        }
    
        /**
         * 销毁方法
         * 1.调用时机:内存释放或者服务器关闭的时候,Servlet对象会被销毁,调用
         * 2.调用次数: 1次
         */
        public void destroy() {
            System.out.println("destroy...");
        }
        public ServletConfig getServletConfig() {
            return null;
        }
    
        public String getServletInfo() {
            return null;
        }
    
    
    }
    

    注意:如何才能让Servlet中的destroy方法被执行?

    1627239292226

在Terminal命令行中,先使用mvn tomcat7:run启动,然后再使用ctrl+c关闭tomcat

小结

这节中需要掌握的内容是:

  1. Servlet对象在什么时候被创建的?

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

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

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

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

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

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

4.5 方法介绍

Servlet中总共有5个方法,我们已经介绍过其中的三个,剩下的两个方法作用分别是什么?

我们先来回顾下前面讲的三个方法,分别是:

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

剩下的两个方法是:

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

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

package com.itheima.web;

import javax.servlet.*;
import javax.servlet.annotation.WebServlet;
import java.io.IOException;

/**
 * Servlet方法介绍
 */
@WebServlet(urlPatterns = "/demo3",loadOnStartup = 1)
public class ServletDemo3 implements Servlet {

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

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

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

4.6 体系结构

通过上面的学习,我们知道要想编写一个Servlet就必须要实现Servlet接口,重写接口中的5个方法,虽然已经能完成要求,但是编写起来还是比较麻烦的,因为我们更关注的其实只有service方法,那有没有更简单方式来创建Servlet呢?

要想解决上面的问题,我们需要先对Servlet的体系结构进行下了解:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W2nUKECp-1647178288491)(F:\T笔记\Typora\笔记\三月阶段笔记\java Web\java Web笔记(黑马).assets\1627240593506.png)]

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

具体的编写格式如下:

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

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

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

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

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

package com.itheima.web;

import javax.servlet.*;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;


@WebServlet("/demo5")
public class ServletDemo5 implements Servlet {

    public void init(ServletConfig config) throws ServletException {

    }

    public ServletConfig getServletConfig() {
        return null;
    }

    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
        //如何调用?
        //获取请求方式,根据不同的请求方式进行不同的业务处理
        HttpServletRequest request = (HttpServletRequest)req;
       //1. 获取请求方式
        String method = request.getMethod();
        //2. 判断
        if("GET".equals(method)){
            // get方式的处理逻辑
        }else if("POST".equals(method)){
            // post方式的处理逻辑
        }
    }

    public String getServletInfo() {
        return null;
    }

    public void destroy() {

    }
}

要解决上述问题,我们可以对Servlet接口进行继承封装,来简化代码开发。

package com.itheima.web;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

public class MyHttpServlet implements Servlet {
    public void init(ServletConfig config) throws ServletException {

    }

    public ServletConfig getServletConfig() {
        return null;
    }

    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
        HttpServletRequest request = (HttpServletRequest)req;
        //1. 获取请求方式
        String method = request.getMethod();
        //2. 判断
        if("GET".equals(method)){
            // get方式的处理逻辑
            doGet(req,res);
        }else if("POST".equals(method)){
            // post方式的处理逻辑
            doPost(req,res);
        }
    }

    protected void doPost(ServletRequest req, ServletResponse res) {
    }

    protected void doGet(ServletRequest req, ServletResponse res) {
    }

    public String getServletInfo() {
        return null;
    }

    public void destroy() {

    }
}

有了MyHttpServlet这个类,以后我们再编写Servlet类的时候,只需要继承MyHttpServlet,重写父类中的doGet和doPost方法,就可以用来处理GET和POST请求的业务逻辑。接下来,可以把ServletDemo5代码进行改造

@WebServlet("/demo5")
public class ServletDemo5 extends MyHttpServlet {

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

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

将来页面发送的是GET请求,则会进入到doGet方法中进行执行,如果是POST请求,则进入到doPost方法。这样代码在编写的时候就相对来说更加简单快捷。

类似MyHttpServlet这样的类Servlet中已经为我们提供好了,就是HttpServlet,翻开源码,大家可以搜索service()方法,你会发现HttpServlet做的事更多,不仅可以处理GET和POST还可以处理其他五种请求方式。

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

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

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

        } else if (method.equals(METHOD_POST)) {
            doPost(req, resp);
            
        } else if (method.equals(METHOD_PUT)) {
            doPut(req, resp);
            
        } else if (method.equals(METHOD_DELETE)) {
            doDelete(req, resp);
            
        } else if (method.equals(METHOD_OPTIONS)) {
            doOptions(req,resp);
            
        } else if (method.equals(METHOD_TRACE)) {
            doTrace(req,resp);
            
        } else {
            //
            // Note that this means NO servlet supports whatever
            // method was requested, anywhere on this server.
            //

            String errMsg = lStrings.getString("http.method_not_implemented");
            Object[] errArgs = new Object[1];
            errArgs[0] = method;
            errMsg = MessageFormat.format(errMsg, errArgs);
            
            resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
        }
    }

小结

通过这一节的学习,要掌握:

  1. HttpServlet的使用步骤

继承HttpServlet

重写doGet和doPost方法

  1. HttpServlet原理

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

4.7 urlPattern配置

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

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

    1627272805178

    package com.itheima.web;
    
    import javax.servlet.ServletRequest;
    import javax.servlet.ServletResponse;
    import javax.servlet.annotation.WebServlet;
    
    /**
    * urlPattern: 一个Servlet可以配置多个访问路径
    */
    @WebServlet(urlPatterns = {"/demo7","/demo8"})
    public class ServletDemo7 extends MyHttpServlet {
    
        @Override
        protected void doGet(ServletRequest req, ServletResponse res) {
            
            System.out.println("demo7 get...");
        }
        @Override
        protected void doPost(ServletRequest req, ServletResponse res) {
        }
    }
    

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

  • urlPattern配置规则

    • 精确匹配

      1627273174144

      /**
       * UrlPattern:
       * * 精确匹配
       */
      @WebServlet(urlPatterns = "/user/select")
      public class ServletDemo8 extends MyHttpServlet {
      
          @Override
          protected void doGet(ServletRequest req, ServletResponse res) {
      
              System.out.println("demo8 get...");
          }
          @Override
          protected void doPost(ServletRequest req, ServletResponse res) {
          }
      }
      

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

    • 目录匹配

      1627273184095

      package com.itheima.web;
      
      import javax.servlet.ServletRequest;
      import javax.servlet.ServletResponse;
      import javax.servlet.annotation.WebServlet;
      
      /**
       * UrlPattern:
       * * 目录匹配: /user/*
       */
      @WebServlet(urlPatterns = "/user/*")
      public class ServletDemo9 extends MyHttpServlet {
      
          @Override
          protected void doGet(ServletRequest req, ServletResponse res) {
      
              System.out.println("demo9 get...");
          }
          @Override
          protected void doPost(ServletRequest req, ServletResponse res) {
          }
      }
      

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

      思考:

      1. 访问路径http://localhost:8080/web-demo/user是否能访问到demo9的doGet方法?
      2. 访问路径http://localhost:8080/web-demo/user/a/b是否能访问到demo9的doGet方法?
      3. 访问路径http://localhost:8080/web-demo/user/select是否能访问到demo9还是demo8的doGet方法?

      答案是: 能、能、demo8,进而我们可以得到的结论是**/user/*中的/*代表的是零或多个层级访问目录同时精确匹配优先级要高于目录匹配。**

    • 扩展名匹配

      1627273194118

      package com.itheima.web;
      
      import javax.servlet.ServletRequest;
      import javax.servlet.ServletResponse;
      import javax.servlet.annotation.WebServlet;
      
      /**
       * UrlPattern:
       * * 扩展名匹配: *.do
       */
      @WebServlet(urlPatterns = "*.do")
      public class ServletDemo10 extends MyHttpServlet {
      
          @Override
          protected void doGet(ServletRequest req, ServletResponse res) {
      
              System.out.println("demo10 get...");
          }
          @Override
          protected void doPost(ServletRequest req, ServletResponse res) {
          }
      }
      

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

      注意:

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

      1627274483755

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

      1627274368245

    • 任意匹配

      1627273201370

      package com.itheima.web;
      
      import javax.servlet.ServletRequest;
      import javax.servlet.ServletResponse;
      import javax.servlet.annotation.WebServlet;
      
      /**
       * UrlPattern:
       * * 任意匹配: /
       */
      @WebServlet(urlPatterns = "/")
      public class ServletDemo11 extends MyHttpServlet {
      
          @Override
          protected void doGet(ServletRequest req, ServletResponse res) {
      
              System.out.println("demo11 get...");
          }
          @Override
          protected void doPost(ServletRequest req, ServletResponse res) {
          }
      }
      

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

      package com.itheima.web;
      
      import javax.servlet.ServletRequest;
      import javax.servlet.ServletResponse;
      import javax.servlet.annotation.WebServlet;
      
      /**
       * UrlPattern:
       * * 任意匹配: /*
       */
      @WebServlet(urlPatterns = "/*")
      public class ServletDemo12 extends MyHttpServlet {
      
          @Override
          protected void doGet(ServletRequest req, ServletResponse res) {
      
              System.out.println("demo12 get...");
          }
          @Override
          protected void doPost(ServletRequest req, ServletResponse res) {
          }
      }
      
      

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

      注意://*的区别?

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

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

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

小结

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

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

4.8 XML配置

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

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

  • 编写Servlet类
package com.itheima.web;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebServlet;

public class ServletDemo13 extends MyHttpServlet {

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

        System.out.println("demo13 get...");
    }
    @Override
    protected void doPost(ServletRequest req, ServletResponse res) {
    }
}
  • 在web.xml中配置该Servlet
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    
    
    
    <!-- 
        Servlet 全类名
    -->
    <servlet>
        <!-- servlet的名称,名字任意-->
        <servlet-name>demo13</servlet-name>
        <!--servlet的类全名-->
        <servlet-class>com.itheima.web.ServletDemo13</servlet-class>
    </servlet>

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

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

Request&Response

今日目标

  • 掌握Request对象的概念与使用
  • 掌握Response对象的概念与使用
  • 能够完成用户登录注册案例的实现
  • 能够完成SqlSessionFactory工具类的抽取

1,Request和Response的概述

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

此时,我们就需要思考一个问题request和response这两个参数的作用是什么?

1628735746602

  • request:获取请求数据
    • 浏览器会发送HTTP请求到后台服务器[Tomcat]
    • HTTP的请求中会包含很多请求数据[请求行+请求头+请求体]
    • 后台服务器[Tomcat]会对HTTP请求中的数据进行解析并把解析结果存入到一个对象中
    • 所存入的对象即为request对象,所以我们可以从request对象中获取请求的相关参数
    • 获取到数据后就可以继续后续的业务,比如获取用户名和密码就可以实现登录操作的相关业务
  • response:设置响应数据
    • 业务处理完后,后台就需要给前端返回业务处理的结果即响应数据
    • 把响应数据封装到response对象中
    • 后台服务器[Tomcat]会解析response对象,按照[响应行+响应头+响应体]格式拼接结果
    • 浏览器最终解析结果,把内容展示在浏览器给用户浏览

对于上述所讲的内容,我们通过一个案例来初步体验下request和response对象的使用。

@WebServlet("/demo3")
public class ServletDemo3 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //使用request对象 获取请求数据
        String name = request.getParameter("name");//url?name=zhangsan

        //使用response对象 设置响应数据
        response.setHeader("content-type","text/html;charset=utf-8");
        response.getWriter().write("<h1>"+name+",欢迎您!</h1>");
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("Post...");
    }
}

启动成功后就可以通过浏览器来访问,并且根据传入参数的不同就可以在页面上展示不同的内容:

1628738273049

小结

在这节中,我们主要认识了下request对象和reponse对象:

  • request对象是用来封装请求数据的对象
  • response对象是用来封装响应数据的对象

目前我们只知道这两个对象是用来干什么的,那么它们具体是如何实现的,就需要我们继续深入的学习。接下来,就先从Request对象来学习,主要学习下面这些内容:

  • request继承体系

  • request获取请求参数

  • request请求转发

2,Request对象

2.1 Request继承体系

在学习这节内容之前,我们先思考一个问题,前面在介绍Request和Reponse对象的时候,比较细心的同学可能已经发现:

  • 当我们的Servlet类实现的是Servlet接口的时候,service方法中的参数是ServletRequest和ServletResponse
  • 当我们的Servlet类继承的是HttpServlet类的时候,doGet和doPost方法中的参数就变成HttpServletRequest和HttpServletReponse

那么,

  • ServletRequest和HttpServletRequest的关系是什么?
  • request对象是有谁来创建的?
  • request提供了哪些API,这些API从哪里查?

首先,我们先来看下Request的继承体系:

1628740441008

从上图中可以看出,ServletRequest和HttpServletRequest都是Java提供的,所以我们可以打开JavaEE提供的API文档[参考: 资料/JavaEE7-api.chm],打开后可以看到:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-55pJ9gDE-1647178288494)(java Web笔记(黑马).assets/1628741839475.png)]

所以ServletRequest和HttpServletRequest是继承关系,并且两个都是接口,接口是无法创建对象,这个时候就引发了下面这个问题:

1628742224589

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

  • 该类实现了HttpServletRequest接口,也间接实现了ServletRequest接口。
  • Servlet类中的service方法、doGet方法或者是doPost方法最终都是由Web服务器[Tomcat]来调用的,所以Tomcat提供了方法参数接口的具体实现类,并完成了对象的创建
  • 要想了解RequestFacade中都提供了哪些方法,我们可以直接查看JavaEE的API文档中关于ServletRequest和HttpServletRequest的接口文档,因为RequestFacade实现了其接口就需要重写接口中的方法

对于上述结论,要想验证,可以编写一个Servlet,在方法中把request对象打印下,就能看到最终的对象是不是RequestFacade,代码如下:

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

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

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

1628743040046

小结

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

2.2 Request获取请求数据

HTTP请求数据总共分为三部分内容,分别是请求行、请求头、请求体,对于这三部分内容的数据,分别该如何获取,首先我们先来学习请求行数据如何获取?

2.2.1 获取请求行数据

请求行包含三块内容,分别是请求方式请求资源路径HTTP协议及版本

1628748240075

对于这三部分内容,request对象都提供了对应的API方法来获取,具体如下:

  • 获取请求方式: GET
String getMethod()
  • 获取虚拟目录(项目访问路径): /request-demo
String getContextPath()
  • 获取URL(统一资源定位符): http://localhost:8080/request-demo/req1
StringBuffer getRequestURL()
  • 获取URI(统一资源标识符): /request-demo/req1
String getRequestURI()
  • 获取请求参数(GET方式): username=zhangsan&password=123
String getQueryString()		
//post方式

介绍完上述方法后,咱们通过代码把上述方法都使用下:

/**
 * request 获取请求数据
 */
@WebServlet("/req1")
public class RequestDemo1 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // String getMethod():获取请求方式: GET
        String method = req.getMethod();
        System.out.println(method);//GET
        // String getContextPath():获取虚拟目录(项目访问路径):/request-demo
        String contextPath = req.getContextPath();
        System.out.println(contextPath);
        // StringBuffer getRequestURL(): 获取URL(统一资源定位符):http://localhost:8080/request-demo/req1
        StringBuffer url = req.getRequestURL();
        System.out.println(url.toString());
        // String getRequestURI():获取URI(统一资源标识符): /request-demo/req1
        String uri = req.getRequestURI();
        System.out.println(uri);
        // String getQueryString():获取请求参数(GET方式): username=zhangsan
        String queryString = req.getQueryString();
        System.out.println(queryString);
    }
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    }
}

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hHgahAq1-1647178288495)(F:\T笔记\Typora\笔记\三月阶段笔记\java Web\java Web笔记(黑马).assets\1628762794935.png)]

2.2.2 获取请求头数据

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4rkl11RM-1647178288495)(F:\T笔记\Typora\笔记\三月阶段笔记\java Web\java Web笔记(黑马).assets\1628768652535.png)]

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

String getHeader(String name)

接下来,在代码中如果想要获取客户端浏览器的版本信息,则可以使用

/**
 * request 获取请求数据
 */
@WebServlet("/req1")
public class RequestDemo1 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //获取请求头: user-agent: 浏览器的版本信息
        String agent = req.getHeader("user-agent");
		System.out.println(agent);
    }
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    }
}

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bIgeawhc-1647178288496)(java Web笔记(黑马).assets/1628769145524.png)]

2.2.3 获取请求体数据

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

1628768665185

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

  • 获取字节输入流,如果前端发送的是字节数据,比如传递的是文件数据,则使用该方法
ServletInputStream getInputStream()
该方法可以获取字节
  • 获取字符输入流,如果前端发送的是纯文本数据,则使用该方法
BufferedReader getReader()

接下来,大家需要思考,要想获取到请求体的内容该如何实现?

具体实现的步骤如下:

1.准备一个页面,在页面中添加form表单,用来发送post请求

2.在Servlet的doPost方法中获取请求体数据

3.在doPost方法中使用request的getReader()或者getInputStream()来获取

4.访问测试

  1. 在项目的webapp目录下添加一个html页面,名称为:req.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<!-- 
    action:form表单提交的请求地址
    method:请求方式,指定为post
-->
<form action="/request-demo/req1" method="post">
    <input type="text" name="username">
    <input type="password" name="password">
    <input type="submit">
</form>
</body>
</html>
  1. 在Servlet的doPost方法中获取数据
/**
 * request 获取请求数据
 */
@WebServlet("/req1")
public class RequestDemo1 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    }
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //在此处获取请求体中的数据
    }
}
  1. 调用getReader()或者getInputStream()方法,因为目前前端传递的是纯文本数据,所以我们采用getReader()方法来获取
/**
 * request 获取请求数据
 */
@WebServlet("/req1")
public class RequestDemo1 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    }
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
         //获取post 请求体:请求参数
        //1. 获取字符输入流
        BufferedReader br = req.getReader();
        //2. 读取数据
        String line = br.readLine();
        System.out.println(line);
    }
}

注意

BufferedReader流是通过request对象来获取的,当请求完成后request对象就会被销毁,request对象被销毁后,BufferedReader流就会自动关闭,所以此处就不需要手动关闭流了。

  1. 启动服务器,通过浏览器访问http://localhost:8080/request-demo/req.html

1628770516387

点击提交按钮后,就可以在控制台看到前端所发送的请求数据

1628770585480

小结

HTTP请求数据中包含了请求行请求头请求体,针对这三部分内容,Request对象都提供了对应的API方法来获取对应的值:

  • 请求行
    • getMethod()获取请求方式
    • getContextPath()获取项目访问路径
    • getRequestURL()获取请求URL
    • getRequestURI()获取请求URI
    • getQueryString()获取GET请求方式的请求参数
  • 请求头
    • getHeader(String name)根据请求头名称获取其对应的值
  • 请求体
    • 注意: 浏览器发送的POST请求才有请求体
    • 如果是纯文本数据:getReader()
    • 如果是字节数据如文件数据:getInputStream()
2.2.4 获取请求参数的通用方式

在学习下面内容之前,我们先提出两个问题:

  • 什么是请求参数?
  • 请求参数和请求数据的关系是什么?

1.什么是请求参数?

为了能更好的回答上述两个问题,我们拿用户登录的例子来说明

1.1 想要登录网址,需要进入登录页面

1.2 在登录页面输入用户名和密码

1.3 将用户名和密码提交到后台

1.4 后台校验用户名和密码是否正确

1.5 如果正确,则正常登录,如果不正确,则提示用户名或密码错误

上述例子中,用户名和密码其实就是我们所说的请求参数

2.什么是请求数据?

请求数据则是包含请求行、请求头和请求体的所有数据

3.请求参数和请求数据的关系是什么?

3.1 请求参数是请求数据中的部分内容

3.2 如果是GET请求,请求参数在请求行中

3.3 如果是POST请求,请求参数一般在请求体中

请求参数 跳上去

对于请求参数的获取,常用的有以下两种:

  • GET方式:
String getQueryString()
  • POST方式:
BufferedReader getReader();

有了上述的知识储备,我们来实现一个案例需求:

(1)发送一个GET请求并携带用户名,后台接收后打印到控制台

(2)发送一个POST请求并携带用户名,后台接收后打印到控制台

此处大家需要注意的是GET请求和POST请求接收参数的方式不一样,具体实现的代码如下:

@WebServlet("/req1")
public class RequestDemo1 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        String result = req.getQueryString();
        System.out.println(result);

    }
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        BufferedReader br = req.getReader();
        String result = br.readLine();
        System.out.println(result);
    }
}
  • 对于上述的代码,会存在什么问题呢?

1628776252445

  • 如何解决上述重复代码的问题呢?

1628776433318

当然,也可以在doGet中调用doPost,在doPost中完成参数的获取和打印,另外需要注意的是,doGet和doPost方法都必须存在,不能删除任意一个。

GET请求和POST请求获取请求参数的方式不一样,在获取请求参数这块该如何实现呢?

要想实现,我们就需要思考:

GET请求方式和POST请求方式区别主要在于获取请求参数的方式不一样,是否可以提供一种统一获取请求参数的方式,从而统一doGet和doPost方法内的代码?

解决方案一:

@WebServlet("/req1")
public class RequestDemo1 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //获取请求方式
        String method = req.getMethod();
        //获取请求参数
        String params = "";
        if("GET".equals(method)){
            params = req.getQueryString();
        }else if("POST".equals(method)){
            BufferedReader reader = req.getReader();
            params = reader.readLine();
        }
        //将请求参数进行打印控制台
        System.out.println(params);
      
    }
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doGet(req,resp);
    }
}

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

解决方案二:

request对象已经将上述获取请求参数的方法进行了封装,并且request提供的方法实现的功能更强大,以后只需要调用request提供的方法即可,在request的方法中都实现了哪些操作?

(1)根据不同的请求方式获取请求参数,获取的内容如下:

1628778931277

(2)把获取到的内容进行分割,内容如下:

1628779067793

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

1628779368501

注意:因为参数的值可能是一个,也可能有多个,所以Map的值的类型为String数组。

基于上述理论,request对象为我们提供了如下方法:

  • 获取所有参数Map集合
Map<String,String[]> getParameterMap()
  • 根据名称获取参数值(数组)
String[] getParameterValues(String name)
  • 根据名称获取参数值(单个值)
String getParameter(String name)

接下来,我们通过案例来把上述的三个方法进行实例演示:

1.修改req.html页面,添加爱好选项,爱好可以同时选多个

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="/request-demo/req2" method="get">
    <input type="text" name="username"><br>
    <input type="password" name="password"><br>
    <input type="checkbox" name="hobby" value="1"> 游泳
    <input type="checkbox" name="hobby" value="2"> 爬山 <br>
    <input type="submit">

</form>
</body>
</html>

1628780937599

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

2.1获取GET方式的所有请求参数

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

            //获取值
            String[] values = map.get(key);
            for (String value : values) {
                System.out.print(value + " ");
            }

            System.out.println();
        }
    }

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

获取的结果为:

1628780965283

2.2获取GET请求参数中的爱好,结果是数组值

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

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

获取的结果为:

1628781031437

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

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

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

获取的结果为:

1628781176434

3.在Servlet代码中获取页面传递POST请求的参数值

3.1将req.html页面form表单的提交方式改成post

3.2将doGet方法中的内容复制到doPost方法中即可

小结

  • req.getParameter()方法使用的频率会比较高

  • 以后我们再写代码的时候,就只需要按照如下格式来编写:

public class RequestDemo1 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
       //采用request提供的获取请求参数的通用方式来获取请求参数
       //编写其他的业务代码...
    }
    @Override
    protected void doPost(HttpServletRequest req, Http ServletResponse resp) throws ServletException, IOException {
        this.doGet(req,resp);
    }
}

2.3 IDEA快速创建Servlet

使用通用方式获取请求参数后,屏蔽了GET和POST的请求方式代码的不同,则代码可以定义如下格式:

1628781419752

由于格式固定,所以我们可以使用IDEA提供的模板来制作一个Servlet的模板,这样我们后期在创建Servlet的时候就会更高效,具体如何实现:

(1)按照自己的需求,修改Servlet创建的模板内容

1628781545912

(2)使用servlet模板创建Servlet类

1628782117420

2.4 请求参数中文乱码问题

问题展示:

(1)将req.html页面的请求方式修改为get

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="/request-demo/req2" method="get">
    <input type="text" name="username"><br>
    <input type="password" name="password"><br>
    <input type="checkbox" name="hobby" value="1"> 游泳
    <input type="checkbox" name="hobby" value="2"> 爬山 <br>
    <input type="submit">

</form>
</body>
</html>

(2)在Servlet方法中获取参数,并打印

/**
 * 中文乱码问题解决方案
 */
@WebServlet("/req4")
public class RequestDemo4 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
       //1. 获取username
       String username = request.getParameter("username");
       System.out.println(username);
    }

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

(3)启动服务器,页面上输入中文参数

1628784323297

(4)查看控制台打印内容

1628784356157

(5)把req.html页面的请求方式改成post,再次发送请求和中文参数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pzakOBKF-1647178288501)(F:\T笔记\Typora\笔记\三月阶段笔记\java Web\java Web笔记(黑马).assets\1628784425182.png)]

(6)查看控制台打印内容,依然为乱码

1628784356157

通过上面的案例,会发现,不管是GET还是POST请求,在发送的请求参数中如果有中文,在后台接收的时候,都会出现中文乱码的问题。具体该如何解决呢?

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

修改后的代码为:

/**
 * 中文乱码问题解决方案
 */
@WebServlet("/req4")
public class RequestDemo4 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //1. 解决乱码: POST getReader()
        //设置字符输入流的编码,设置的字符集要和页面保持一致
        request.setCharacterEncoding("UTF-8");
       //2. 获取username
       String username = request.getParameter("username");
       System.out.println(username);
    }

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

重新发送POST请求,就会在控制台看到正常展示的中文结果。

至此POST请求中文乱码的问题就已经解决,但是这种方案不适用于GET请求,这个原因是什么呢,咱们下面再分析。

2.4.2 GET请求解决方案

刚才提到一个问题是POST请求的中文乱码解决方案为什么不适用GET请求?

  • GET请求获取请求参数的方式是request.getQueryString()
  • POST请求获取请求参数的方式是request.getReader()
  • request.setCharacterEncoding(“utf-8”)是设置request处理流的编码
  • getQueryString方法并没有通过流的方式获取数据

所以GET请求不能用设置编码的方式来解决中文乱码问题,那问题又来了,如何解决GET请求的中文乱码呢?

  1. 首先我们需要先分析下GET请求出现乱码的原因:

1628829610823

(1)浏览器通过HTTP协议发送请求和数据给后台服务器(Tomcat)

(2)浏览器在发送HTTP的过程中会对中文数据进行URL编码

(3)在进行URL编码的时候会采用页面<meta>标签指定的UTF-8的方式进行编码,张三编码后的结果为%E5%BC%A0%E4%B8%89

(4)后台服务器(Tomcat)接收到%E5%BC%A0%E4%B8%89后会默认按照ISO-8859-1进行URL解码

(5)由于前后编码与解码采用的格式不一样,就会导致后台获取到的数据为乱码。

思考: 如果把req.html页面的<meta>标签的charset属性改成ISO-8859-1,后台不做操作,能解决中文乱码问题么?

答案是否定的,因为ISO-8859-1本身是不支持中文展示的,所以改了标签的charset属性后,会导致页面上的中文内容都无法正常展示。

分析完上面的问题后,我们会发现,其中有两个我们不熟悉的内容就是URL编码URL解码,什么是URL编码,什么又是URL解码呢?

URL编码

这块知识我们只需要了解下即可,具体编码过程分两步,分别是:

(1)将字符串按照编码方式转为二进制

(2)每个字节转为2个16进制数并在前边加上%

张三按照UTF-8的方式转换成二进制的结果为:

1110 0101 1011 1100 1010 0000 1110 0100 1011 1000 1000 1001

这个结果是如何计算的?

使用http://www.mytju.com/classcode/tools/encode_utf8.asp,输入张三

1628833310473

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

1628833496171

在计算的十六进制结果中,每两位前面加一个%,就可以获取到%E5%BC%A0%E4%B8%89

当然你从上面所提供的网站中就已经能看到编码16进制的结果了:

1628833310473

但是对于上面的计算过程,如果没有工具,纯手工计算的话,相对来说还是比较复杂的,我们也不需要进行手动计算,在Java中已经为我们提供了编码和解码的API工具类可以让我们更快速的进行编码和解码:

编码:

java.net.URLEncoder.encode("需要被编码的内容","字符集(UTF-8)")

解码:

java.net.URLDecoder.decode("需要被解码的内容","字符集(UTF-8)")

接下来咱们对张三来进行编码和解码

public class URLDemo {

  public static void main(String[] args) throws UnsupportedEncodingException {
        String username = "张三";
        //1. URL编码
        String encode = URLEncoder.encode(username, "utf-8");
        System.out.println(encode); //打印:%E5%BC%A0%E4%B8%89

       //2. URL解码
       //String decode = URLDecoder.decode(encode, "utf-8");//打印:张三
       String decode = URLDecoder.decode(encode, "ISO-8859-1");//打印:`å¼ ä¸ `
       System.out.println(decode);
    }
}

到这,我们就可以分析出GET请求中文参数出现乱码的原因了,

  • 浏览器把中文参数按照UTF-8进行URL编码
  • Tomcat对获取到的内容进行了ISO-8859-1的URL解码
  • 在控制台就会出现类上å¼ ä¸‰的乱码,最后一位是个空格
  1. 清楚了出现乱码的原因,接下来我们就需要想办法进行解决

1628846824194

从上图可以看住,

  • 在进行编码和解码的时候,不管使用的是哪个字符集,他们对应的%E5%BC%A0%E4%B8%89是一致的

  • 那他们对应的二进制值也是一样的,为:

    • 1110 0101 1011 1100 1010 0000 1110 0100 1011 1000 1000 1001
      
  • 为所以我们可以考虑把å¼ ä¸‰转换成字节,在把字节转换成张三,在转换的过程中是它们的编码一致,就可以解决中文乱码问题。

具体的实现步骤为:

1.按照ISO-8859-1编码获取乱码å¼ ä¸‰对应的字节数组

2.按照UTF-8编码获取字节数组对应的字符串

实现代码如下:

public class URLDemo {

  public static void main(String[] args) throws UnsupportedEncodingException {
        String username = "张三";
        //1. URL编码
        String encode = URLEncoder.encode(username, "utf-8");
        System.out.println(encode);
        //2. URL解码
        String decode = URLDecoder.decode(encode, "ISO-8859-1");

        System.out.println(decode); //此处打印的是对应的乱码数据

        //3. 转换为字节数据,编码
        byte[] bytes = decode.getBytes("ISO-8859-1");
        for (byte b : bytes) {
            System.out.print(b + " ");
        }
		//此处打印的是:-27 -68 -96 -28 -72 -119
        //4. 将字节数组转为字符串,解码
        String s = new String(bytes, "utf-8");
        System.out.println(s); //此处打印的是张三
    }
}

说明:在第18行中打印的数据是-27 -68 -96 -28 -72 -119张三转换成的二进制数据1110 0101 1011 1100 1010 0000 1110 0100 1011 1000 1000 1001为什么不一样呢?

其实打印出来的是十进制数据,我们只需要使用计算机换算下就能得到他们的对应关系,如下图:

1628849231208

至此对于GET请求中文乱码的解决方案,我们就已经分析完了,最后在代码中去实现下:

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

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

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

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

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

    }

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

注意

  • request.setCharacterEncoding("UTF-8")代码注释掉后,会发现GET请求参数乱码解决方案同时也可也把POST请求参数乱码的问题也解决了
  • 只不过对于POST请求参数一般都会比较多,采用这种方式解决乱码起来比较麻烦,所以对于POST请求还是建议使用设置编码的方式进行。

另外需要说明一点的是==Tomcat8.0之后,已将GET请求乱码问题解决,设置默认的解码方式为UTF-8==

小结
  1. 中文乱码解决方案
  • POST请求和GET请求的参数中如果有中文,后台接收数据就会出现中文乱码问题

    GET请求在Tomcat8.0以后的版本就不会出现了

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

    request.setCharacterEncoding("UTF-8");
    注意:设置的字符集要和页面保持一致
    
  • 通用方式(GET/POST):需要先解码,再编码

    new String(username.getBytes("ISO-8859-1"),"UTF-8");
    
  1. URL编码实现方式:
  • 编码:

    URLEncoder.encode(str,"UTF-8");
    
  • 解码:

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

2.5 Request请求转发

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

1628851404283

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

(2)资源A处理完请求后将请求发给资源B

(3)资源B处理完后将结果响应给浏览器

(4)请求从资源A到资源B的过程就叫==请求转发==

  1. 请求转发的实现方式:
req.getRequestDispatcher("资源B路径").forward(req,resp);

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

1628854783523

针对上述需求,具体的实现步骤为:

1.创建一个RequestDemo5类,接收/req5的请求,在doGet方法中打印demo5

2.创建一个RequestDemo6类,接收/req6的请求,在doGet方法中打印demo6

3.在RequestDemo5的方法中使用

​ req.getRequestDispatcher("/req6").forward(req,resp)进行请求转发

4.启动测试

(1)创建RequestDemo5类

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

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

(2)创建RequestDemo6类

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

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

(3)在RequestDemo5的doGet方法中进行请求转发

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

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

(4)启动测试

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hRpHqhuT-1647178288504)(java Web笔记(黑马).assets/1628855192876.png)]

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

  1. 请求转发资源间共享数据:使用Request对象

此处主要解决的问题是把请求从/req5转发到/req6的时候,如何传递数据给/req6

需要使用request对象提供的三个方法:

  • 存储数据到request域[范围,数据是存储在request对象]中
void setAttribute(String name,Object o);
  • 根据key获取值
Object getAttribute(String name);
  • 根据key删除该键值对
void removeAttribute(String name);

接着上个需求来:

1628856995417

1.在RequestDemo5的doGet方法中转发请求之前,将数据存入request域对象中

2.在RequestDemo6的doGet方法从request域对象中获取数据,并将数据打印到控制台

3.启动访问测试

(1)修改RequestDemo5中的方法

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

    }

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

(2)修改RequestDemo6中的方法

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

    }

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

(3)启动测试

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

1628857213364

此时就可以实现在转发多个资源之间共享数据。

  1. 请求转发的特点
  • 浏览器地址栏路径不发生变化

    虽然后台从/req5转发到/req6,但是浏览器的地址一直是/req5,未发生变化

    1628857365153

  • 只能转发到当前服务器的内部资源

    不能从一个服务器通过转发访问另一台服务器

  • 一次请求,可以在转发资源间使用request共享数据

    虽然后台从/req5转发到/req6,但是这个只有一次请求

3,Response对象

前面讲解完Request对象,接下来我们回到刚开始的那张图:

1628857632899

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

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

1628857761317

介绍完Response的相关体系结构后,接下来对于Response我们需要学习如下内容:

  • Response设置响应数据的功能介绍
  • Response完成重定向
  • Response响应字符数据
  • Response响应字节数据

3.1 Response设置响应数据功能介绍

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

  1. 响应行

1628858926498

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

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

1628859051368

设置响应头键值对:

void setHeader(String name,String value);
  1. 响应体

1628859268095

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

获取字符输出流:

PrintWriter getWriter();

获取字节输出流

ServletOutputStream getOutputStream();

介绍完这些方法后,后面我们会通过案例把这些方法都用一用,首先先来完成下重定向的功能开发。

3.2 Respones请求重定向

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

1628859860279

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

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

(3)浏览器接收到响应状态码为302就会重新发送请求到location对应的访问地址去访问资源B

(4)资源B接收到请求后进行处理并最终给浏览器响应结果,这整个过程就叫重定向

  1. 重定向的实现方式:
resp.setStatus(302);
resp.setHeader("location","资源B的访问路径");

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

1628861030429

针对上述需求,具体的实现步骤为:

1.创建一个ResponseDemo1类,接收/resp1的请求,在doGet方法中打印resp1....

2.创建一个ResponseDemo2类,接收/resp2的请求,在doGet方法中打印resp2....

3.在ResponseDemo1的方法中使用

​ response.setStatus(302);

​ response.setHeader(“Location”,"/request-demo/resp2") 来给前端响应结果数据

4.启动测试

(1)创建ResponseDemo1类

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

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

(2)创建ResponseDemo2类

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

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

(3)在ResponseDemo1的doGet方法中给前端响应数据

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

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

(4)启动测试

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

1628861404699

说明/resp1/resp2都被访问到了。到这重定向就已经完成了。

虽然功能已经实现,但是从设置重定向的两行代码来看,会发现除了重定向的地址不一样,其他的内容都是一模一样,所以request对象给我们提供了简化的编写方式为:

resposne.sendRedirect("/request-demo/resp2")

所以第3步中的代码就可以简化为:

@WebServlet("/resp1")
public class ResponseDemo1 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("resp1....");
        //重定向
        resposne.sendRedirect("/request-demo/resp2")}

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doGet(request, response);
    }
}
  1. 重定向的特点
  • 浏览器地址栏路径发送变化

    当进行重定向访问的时候,由于是由浏览器发送的两次请求,所以地址会发生变化

    1628861893130

  • 可以重定向到任何位置的资源(服务内容、外部均可)

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

  • 两次请求,不能在多个资源使用request共享数据

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

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-T5osnZ9l-1647178288507)(F:\T笔记\Typora\笔记\三月阶段笔记\java Web\java Web笔记(黑马).assets\1628862170296.png)]

以后到底用哪个,还是需要根据具体的业务来决定。

3.3 路径问题

要不要加虚拟目录:

  1. 问题1:转发的时候路径上没有加/request-demo而重定向加了,那么到底什么时候需要加,什么时候不需要加呢?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QXWra34K-1647178288507)(F:\T笔记\Typora\笔记\三月阶段笔记\java Web\java Web笔记(黑马).assets\1628862652700.png)]

其实判断的依据很简单,只需要记住下面的规则即可:

  • 浏览器使用:需要加虚拟目录(项目访问路径)
  • 服务端使用:不需要加虚拟目录

对于转发来说,因为是在服务端进行的,所以不需要加虚拟目录

对于重定向来说,路径最终是由浏览器来发送请求,就需要添加虚拟目录。

image-20220218163004305

掌握了这个规则,接下来就通过一些练习来强化下知识的学习:

  • <a href='路劲'>
  • <form action='路径'>
  • req.getRequestDispatcher(“路径”)
  • resp.sendRedirect(“路径”)

答案:

1.超链接,从浏览器发送,需要加
2.表单,从浏览器发送,需要加
3.转发,是从服务器内部跳转,不需要加
4.重定向,是由浏览器进行跳转,需要加。
  1. 问题2:在重定向的代码中,/request-demo是固定编码的,如果后期通过Tomcat插件配置了项目的访问路径,那么所有需要重定向的地方都需要重新修改,该如何优化?

1628863270545

答案也比较简单,我们可以在代码中动态去获取项目访问的虚拟目录,具体如何获取,我们可以借助前面咱们所学习的request对象中的**getContextPath()**方法,修改后的代码如下:

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

        //简化方式完成重定向
        //动态获取虚拟目录
        String contextPath = request.getContextPath();
        response.sendRedirect(contextPath+"/resp2");
    }

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

重新启动访问测试,功能依然能够实现,此时就可以动态获取项目访问的虚拟路径,从而降低代码的耦合度。

3.4 Response响应字符数据

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

  • 通过Response对象获取字符输出流: PrintWriter writer = resp.getWriter();

  • 通过字符输出流写数据: writer.write(“aaa”);

接下来,我们实现通过些案例把响应字符数据给实际应用下:

  1. 返回一个简单的字符串aaa
/**
 * 响应字符数据:设置字符数据的响应体
 */
@WebServlet("/resp3")
public class ResponseDemo3 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html;charset=utf-8");
        //1. 获取字符输出流
        PrintWriter writer = response.getWriter();
		 writer.write("aaa");
    }
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doGet(request, response);
    }
}

1628863905362

  1. 返回一串html字符串,并且能被浏览器解析
PrintWriter writer = response.getWriter();
//content-type,告诉浏览器返回的数据类型是HTML类型数据,这样浏览器才会解析HTML标签
response.setHeader("content-type","text/html");
writer.write("<h1>aaa</h1>");

1628864140820

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

  1. 返回一个中文的字符串你好,需要注意设置响应数据的编码为utf-8
//设置响应的数据格式及数据的编码
response.setContentType("text/html;charset=utf-8");
writer.write("你好");

1628864390263

3.3 Response响应字节数据 (没懂)

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

  • 通过Response对象获取字节输出流:ServletOutputStream outputStream = resp.getOutputStream();

  • 通过字节输出流写数据: outputStream.write(字节数据);

接下来,我们实现通过些案例把响应字符数据给实际应用下:

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

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

1628864883564

上述代码中,对于流的copy的代码还是比较复杂的,所以我们可以使用别人提供好的方法来简化代码的开发,具体的步骤是:

(1)pom.xml添加依赖

<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.6</version>
</dependency>

(2)调用工具类方法

//fis:输入流
//os:输出流
IOUtils.copy(fis,os);

优化后的代码:

/**
 * 响应字节数据:设置字节数据的响应体
 */
@WebServlet("/resp4")
public class ResponseDemo4 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //1. 读取文件
        FileInputStream fis = new FileInputStream("d://a.jpg");
        //2. 获取response字节输出流
        ServletOutputStream os = response.getOutputStream();
        //3. 完成流的copy
      	IOUtils.copy(fis,os);
        fis.close();
    }

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

4,用户注册登录案例

接下来我们通过两个比较常见的案例,一个是注册,一个是登录来对今天学习的内容进行一个实战演练,首先来实现用户登录。

4.1 用户登录

4.1.1 需求分析

1628865728305

  1. 用户在登录页面输入用户名和密码,提交请求给LoginServlet
  2. 在LoginServlet中接收请求和数据[用户名和密码]
  3. 在LoginServlt中通过Mybatis实现调用UserMapper来根据用户名和密码查询数据库表
  4. 将查询的结果封装到User对象中进行返回
  5. 在LoginServlet中判断返回的User对象是否为null
  6. 如果为nul,说明根据用户名和密码没有查询到用户,则登录失败,返回"登录失败"数据给前端
  7. 如果不为null,则说明用户存在并且密码正确,则登录成功,返回"登录成功"数据给前端
4.1.2 环境准备
  1. 复制资料中的静态页面到项目的webapp目录下

参考资料\1. 登陆注册案例\1. 静态页面,拷贝完效果如下:

1628866248169

  1. 创建db1数据库,创建tb_user表,创建User实体类

2.1 将资料\1. 登陆注册案例\2. MyBatis环境\tb_user.sql中的sql语句执行下:

1628866403891

2.2 将资料\1. 登陆注册案例\2. MyBatis环境\User.java拷贝到com.itheima.pojo

1628866560738

  1. 在项目的pom.xml导入Mybatis和Mysql驱动坐标
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.5</version>
</dependency>

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.34</version>
</dependency>
  1. 创建mybatis-config.xml核心配置文件,UserMapper.xml映射文件,UserMapper接口

4.1 将资料\1. 登陆注册案例\2. MyBatis环境\mybatis-config.xml拷贝到resources目录下

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!--起别名-->
    <typeAliases>
        <package name="com.itheima.pojo"/>
    </typeAliases>

    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <!--
                    useSSL:关闭SSL安全连接 性能更高
                    useServerPrepStmts:开启预编译功能
                    &amp; 等同于 & ,xml配置文件中不能直接写 &符号
                -->
                <property name="url" value="jdbc:mysql:///db1?useSSL=false&amp;useServerPrepStmts=true"/>
                <property name="username" value="root"/>
                <property name="password" value="1234"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <!--扫描mapper-->
        <package name="com.itheima.mapper"/>
    </mappers>
</configuration>

4.2 在com.itheima.mapper包下创建UserMapper接口

public interface UserMapper {

}

4.3 将资料\1. 登陆注册案例\2. MyBatis环境\UserMapper.xml拷贝到resources目录下

注意:在resources下创建UserMapper.xml的目录时,要使用/分割

1628867237329

至此我们所需要的环境就都已经准备好了,具体该如何实现?

4.1.3 代码实现
  1. 在UserMapper接口中提供一个根据用户名和密码查询用户对象的方法
/**
     * 根据用户名和密码查询用户对象
     * @param username
     * @param password
     * @return
     */
    @Select("select * from tb_user where username = #{username} and password = #{password}")
    User select(@Param("username") String username,@Param("password")  String password);

说明

@Param注解的作用:用于传递参数,是方法的参数可以与SQL中的字段名相对应。

  1. 修改loign.html
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>login</title>
    <link href="css/login.css" rel="stylesheet">
</head>

<body>
<div id="loginDiv">
    <form action="/request-demo/loginServlet" method="post" id="form">
        <h1 id="loginMsg">LOGIN IN</h1>
        <p>Username:<input id="username" name="username" type="text"></p>

        <p>Password:<input id="password" name="password" type="password"></p>

        <div id="subDiv">
            <input type="submit" class="button" value="login up">
            <input type="reset" class="button" value="reset">&nbsp;&nbsp;&nbsp;
            <a href="register.html">没有账号?点击注册</a>
        </div>
    </form>
</div>

</body>
</html>
  1. 编写LoginServlet
@WebServlet("/loginServlet")
public class LoginServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //1. 接收用户名和密码
        String username = request.getParameter("username");
        String password = request.getParameter("password");

        //2. 调用MyBatis完成查询
        //2.1 获取SqlSessionFactory对象
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        //2.2 获取SqlSession对象
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //2.3 获取Mapper
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        //2.4 调用方法
        User user = userMapper.select(username, password);
        //2.5 释放资源
        sqlSession.close();


        //获取字符输出流,并设置content type
        response.setContentType("text/html;charset=utf-8");
        PrintWriter writer = response.getWriter();
        //3. 判断user释放为null
        if(user != null){
            // 登陆成功
            writer.write("登陆成功");
        }else {
            // 登陆失败
            writer.write("登陆失败");
        }
    }

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

4.1 如果用户名和密码输入错误,则

1628867761245

4.2 如果用户名和密码输入正确,则

1628867801708

至此用户的登录功能就已经完成了~

4.2 用户注册

4.2.1 需求分析

1628867904783

  1. 用户在注册页面输入用户名和密码,提交请求给RegisterServlet
  2. 在RegisterServlet中接收请求和数据[用户名和密码]
  3. 在RegisterServlet中通过Mybatis实现调用UserMapper来根据用户名查询数据库表
  4. 将查询的结果封装到User对象中进行返回
  5. 在RegisterServlet中判断返回的User对象是否为null
  6. 如果为nul,说明根据用户名可用,则调用UserMapper来实现添加用户
  7. 如果不为null,则说明用户不可以,返回"用户名已存在"数据给前端
4.2.2 代码编写
  1. 编写UserMapper提供根据用户名查询用户数据方法和添加用户方法
/**
* 根据用户名查询用户对象
* @param username
* @return
*/
@Select("select * from tb_user where username = #{username}")
User selectByUsername(String username);

/**
* 添加用户
* @param user
*/
@Insert("insert into tb_user values(null,#{username},#{password})")
void add(User user);
  1. 修改register.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>欢迎注册</title>
    <link href="css/register.css" rel="stylesheet">
</head>
<body>

<div class="form-div">
    <div class="reg-content">
        <h1>欢迎注册</h1>
        <span>已有帐号?</span> <a href="login.html">登录</a>
    </div>
    <form id="reg-form" action="/request-demo/registerServlet" method="post">

        <table>

            <tr>
                <td>用户名</td>
                <td class="inputs">
                    <input name="username" type="text" id="username">
                    <br>
                    <span id="username_err" class="err_msg" style="display: none">用户名不太受欢迎</span>
                </td>

            </tr>

            <tr>
                <td>密码</td>
                <td class="inputs">
                    <input name="password" type="password" id="password">
                    <br>
                    <span id="password_err" class="err_msg" style="display: none">密码格式有误</span>
                </td>
            </tr>

        </table>

        <div class="buttons">
            <input value="注 册" type="submit" id="reg_btn">
        </div>
        <br class="clear">
    </form>

</div>
</body>
</html>
  1. 创建RegisterServlet类
@WebServlet("/registerServlet")
public class RegisterServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //1. 接收用户数据
        String username = request.getParameter("username");
        String password = request.getParameter("password");

        //封装用户对象
        User user = new User();
        user.setUsername(username);
        user.setPassword(password);

        //2. 调用mapper 根据用户名查询用户对象
        //2.1 获取SqlSessionFactory对象
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        //2.2 获取SqlSession对象
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //2.3 获取Mapper
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

        //2.4 调用方法
        User u = userMapper.selectByUsername(username);

        //3. 判断用户对象释放为null
        if( u == null){
            // 用户名不存在,添加用户
            userMapper.add(user);

            // 提交事务
            sqlSession.commit();
            // 释放资源
            sqlSession.close();
        }else {
            // 用户名存在,给出提示信息
            response.setContentType("text/html;charset=utf-8");
            response.getWriter().write("用户名已存在");
        }

    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doGet(request, response);
    }
}
  1. 启动服务器进行测试

4.1 如果测试成功,则在数据库中就能查看到新注册的数据

4.2 如果用户已经存在,则在页面上展示 用户名已存在 的提示信息

4.3 SqlSessionFactory工具类抽取

上面两个功能已经实现,但是在写Servlet的时候,因为需要使用Mybatis来完成数据库的操作,所以对于Mybatis的基础操作就出现了些重复代码,如下

String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new 
	SqlSessionFactoryBuilder().build(inputStream);

有了这些重复代码就会造成一些问题:

  • 重复代码不利于后期的维护
  • SqlSessionFactory工厂类进行重复创建
    • 就相当于每次买手机都需要重新创建一个手机生产工厂来给你制造一个手机一样,资源消耗非常大但性能却非常低。所以这么做是不允许的。

那如何来优化呢?

  • 代码重复可以抽取工具类
  • 对指定代码只需要执行一次可以使用静态代码块

有了这两个方向后,代码具体该如何编写?

public class SqlSessionFactoryUtils {

    private static SqlSessionFactory sqlSessionFactory;

    static {
        //静态代码块会随着类的加载而自动执行,且只执行一次
        try {
            String resource = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    public static SqlSessionFactory getSqlSessionFactory(){
        return sqlSessionFactory;
    }
}

工具类抽取以后,以后在对Mybatis的SqlSession进行操作的时候,就可以直接使用

SqlSessionFactory sqlSessionFactory =SqlSessionFactoryUtils.getSqlSessionFactory();

这样就可以很好的解决上面所说的代码重复和重复创建工厂导致性能低的问题了。

jsp概述

另外文件夹里面

会话技术

今日目标

  • 理解什么是会话跟踪技术

  • 掌握Cookie的使用

  • 掌握Session的使用

  • 完善用户登录注册案例的功能

1,会话跟踪技术的概述

对于会话跟踪这四个词,我们需要拆开来进行解释,首先要理解什么是会话,然后再去理解什么是会话跟踪:

  • 会话:用户打开浏览器,访问web服务器的资源,会话建立,直到有一方断开连接,会话结束。在一次会话中可以包含多次请求和响应。

    • 从浏览器发出请求到服务端响应数据给前端之后,一次会话(在浏览器和服务器之间)就被建立了
    • 会话被建立后,如果浏览器或服务端都没有被关闭,则会话就会持续建立着
    • 浏览器和服务器就可以继续使用该会话进行请求发送和响应,上述的整个过程就被称之为会话

    用实际场景来理解下会话,比如在我们访问京东的时候,当打开浏览器进入京东首页后,浏览器和京东的服务器之间就建立了一次会话,后面的搜索商品,查看商品的详情,加入购物车等都是在这一次会话中完成。

    思考:下图中总共建立了几个会话?

    1629382713180

    每个浏览器都会与服务端建立了一个会话,加起来总共是3个会话。

  • 会话跟踪:一种维护浏览器状态的方法,服务器需要识别多次请求是否来自于同一浏览器,以便在同一次会话的多次请求间共享数据

    • 服务器会收到多个请求,这多个请求可能来自多个浏览器,如上图中的6个请求来自3个浏览器
    • 服务器需要用来识别请求是否来自同一个浏览器
    • 服务器用来识别浏览器的过程,这个过程就是会话跟踪
    • 服务器识别浏览器后就可以在同一个会话中多次请求之间来共享数据

    那么我们又有一个问题需要思考,一个会话中的多次请求为什么要共享数据呢?有了这个数据共享功能后能实现哪些功能呢?

    • 购物车: 加入购物车去购物车结算是两次请求,但是后面这次请求要想展示前一次请求所添加的商品,就需要用到数据共享。

      1629383655260

    • 页面展示用户登录信息:很多网站,登录后访问多个功能发送多次请求后,浏览器上都会有当前登录用户的信息[用户名],比如百度、京东、码云等。

      1629383767654

    • 网站登录页面的记住我功能:当用户登录成功后,勾选记住我按钮后下次再登录的时候,网站就会自动填充用户名和密码,简化用户的登录操作,多次登录就会有多次请求,他们之间也涉及到共享数据

      1629383921990

    • 登录页面的验证码功能:生成验证码和输入验证码点击注册这也是两次请求,这两次请求的数据之间要进行对比,相同则允许注册,不同则拒绝注册,该功能的实现也需要在同一次会话中共享数据。

      1629384004179

通过这几个例子的讲解,相信大家对会话追踪技术已经有了一定的理解,该技术在实际开发中也非常重要。那么接下来我们就需要去学习下会话跟踪技术,在学习这些技术之前,我们需要思考:为什么现在浏览器和服务器不支持数据共享呢?

  • 浏览器和服务器之间使用的是HTTP请求来进行数据传输
  • HTTP协议是无状态的,每次浏览器向服务器请求时,服务器都会将该请求视为新的请求
  • HTTP协议设计成无状态的目的是让每次请求之间相互独立,互不影响
  • 请求与请求之间独立后,就无法实现多次请求之间的数据共享

分析完具体的原因后,那么该如何实现会话跟踪技术呢? 具体的实现方式有:

(1)客户端会话跟踪技术:Cookie

(2)服务端会话跟踪技术:Session

这两个技术都可以实现会话跟踪,它们之间最大的区别:Cookie是存储在浏览器端而Session是存储在服务器端

具体的学习思路为:

  • CooKie的基本使用、原理、使用细节
  • Session的基本使用、原理、使用细节
  • Cookie和Session的综合案例

小结

在这节中,我们主要介绍了下什么是会话和会话跟踪技术,需要注意的是:

  • HTTP协议是无状态的,靠HTTP协议是无法实现会话跟踪
  • 想要实现会话跟踪,就需要用到Cookie和Session

这个Cookie和Session具体该如何使用,接下来就先从Cookie来学起。

2,Cookie

学习Cookie,我们主要解决下面几个问题:

  • 什么是Cookie?
  • Cookie如何来使用?
  • Cookie是如何实现的?
  • Cookie的使用注意事项有哪些?

2.1 Cookie的基本使用

1.概念

Cookie:客户端会话技术,将数据保存到客户端,以后每次请求都携带Cookie数据进行访问。

2.Cookie的工作流程

1629386230207

  • 服务端提供了两个Servlet,分别是ServletA和ServletB
  • 浏览器发送HTTP请求1给服务端,服务端ServletA接收请求并进行业务处理
  • 服务端ServletA在处理的过程中可以创建一个Cookie对象并将name=zs的数据存入Cookie
  • 服务端ServletA在响应数据的时候,会把Cookie对象响应给浏览器
  • 浏览器接收到响应数据,会把Cookie对象中的数据存储在浏览器内存中,此时浏览器和服务端就建立了一次会话
  • 在同一次会话中浏览器再次发送HTTP请求2给服务端ServletB,浏览器会携带Cookie对象中的所有数据
  • ServletB接收到请求和数据后,就可以获取到存储在Cookie对象中的数据,这样同一个会话中的多次请求之间就实现了数据共享

3.Cookie的基本使用

对于Cookie的使用,我们更关注的应该是后台代码如何操作Cookie,对于Cookie的操作主要分两大类,本别是发送Cookie获取Cookie,对于上面这两块内容,分别该如何实现呢?

3.1 发送Cookie
  • 创建Cookie对象,并设置数据
Cookie cookie = new Cookie("key","value");
  • 发送Cookie到客户端:使用response对象
response.addCookie(cookie);

介绍完发送Cookie对应的步骤后,接下面通过一个案例来完成Cookie的发送,具体实现步骤为:

需求:在Servlet中生成Cookie对象并存入数据,然后将数据发送给浏览器

1.创建Maven项目,项目名称为cookie-demo,并在pom.xml添加依赖

2.编写Servlet类,名称为AServlet

3.在AServlet中创建Cookie对象,存入数据,发送给前端

4.启动测试,在浏览器查看Cookie对象中的值

(1)创建Maven项目cookie-demo,并在pom.xml添加依赖

<properties>
    <maven.compiler.source>8</maven.compiler.source>
    <maven.compiler.target>8</maven.compiler.target>
</properties>

<dependencies>
    <!--servlet-->
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>3.1.0</version>
        <scope>provided</scope>
    </dependency>
    <!--jsp-->
    <dependency>
        <groupId>javax.servlet.jsp</groupId>
        <artifactId>jsp-api</artifactId>
        <version>2.2</version>
        <scope>provided</scope>
    </dependency>
    <!--jstl-->
    <dependency>
        <groupId>jstl</groupId>
        <artifactId>jstl</artifactId>
        <version>1.2</version>
    </dependency>
    <dependency>
        <groupId>taglibs</groupId>
        <artifactId>standard</artifactId>
        <version>1.1.2</version>
    </dependency>
</dependencies>
<build>
    <plugins>
        <plugin>
            <groupId>org.apache.tomcat.maven</groupId>
            <artifactId>tomcat7-maven-plugin</artifactId>
            <version>2.2</version>
        </plugin>
    </plugins>
</build>

(2)编写Servlet类,名称为AServlet

@WebServlet("/aServlet")
public class AServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    }

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

(3)在Servlet中创建Cookie对象,存入数据,发送给前端

@WebServlet("/aServlet")
public class AServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //发送Cookie
        //1. 创建Cookie对象
        Cookie cookie = new Cookie("username","zs");
        //2. 发送Cookie,response
        response.addCookie(cookie);
    }

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

(4)启动测试,在浏览器查看Cookie对象中的值

访问http://localhost:8080/cookie-demo/aServlet

chrome浏览器查看Cookie的值,有两种方式,分布式:

方式一:

1629389317463

方式二:选中打开开发者工具或者 使用快捷键F12 或者 Ctrl+Shift+I

1629390237936

3.2 获取Cookie
  • 获取客户端携带的所有Cookie,使用request对象
Cookie[] cookies = request.getCookies();
  • 遍历数组,获取每一个Cookie对象:for
  • 使用Cookie对象方法获取数据
cookie.getName();
cookie.getValue();

介绍完获取Cookie对应的步骤后,接下面再通过一个案例来完成Cookie的获取,具体实现步骤为:

需求:在Servlet中获取前一个案例存入在Cookie对象中的数据

1.编写一个新Servlet类,名称为BServlet

2.在BServlet中使用request对象获取Cookie数组,遍历数组,从数据中获取指定名称对应的值

3.启动测试,在控制台打印出获取的值

(1)编写一个新Servlet类,名称为BServlet

@WebServlet("/bServlet")
public class BServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    }

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

(2)在BServlet中使用request对象获取Cookie数组,遍历数组,从数据中获取指定名称对应的值

@WebServlet("/bServlet")
public class BServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //获取Cookie
        //1. 获取Cookie数组
        Cookie[] cookies = request.getCookies();
        //2. 遍历数组
        for (Cookie cookie : cookies) {
            //3. 获取数据
            String name = cookie.getName();
            if("username".equals(name)){
                String value = cookie.getValue();
                System.out.println(name+":"+value);
                break;
            }
        }

    }

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

(3)启动测试,在控制台打印出获取的值

访问http://localhost:8080/cookie-demo/bServlet

1629391020081

在IDEA控制台就能看到输出的结果:

1629391061140

==思考:==测试的时候

  • 在访问AServlet和BServlet的中间把关闭浏览器,重启浏览器后访问BServlet能否获取到Cookie中的数据?

这个问题,我们会在Cookie的使用细节中讲,大家可以动手先试下。

小结

在这节中,我们主要讲解了Cookie的基本使用,包含两部分内容

  • 发送Cookie:
    • 创建Cookie对象,并设置值:Cookie cookie = new Cookie(“key”,“value”);
    • 发送Cookie到客户端使用的是Reponse对象:response.addCookie(cookie);
  • 获取Cookie:
    • 使用Request对象获取Cookie数组:Cookie[] cookies = request.getCookies();
    • 遍历数组
    • 获取数组中每个Cookie对象的值:cookie.getName()和cookie.getValue()

介绍完Cookie的基本使用之后,那么Cookie的底层到底是如何实现一次会话两次请求之间的数据共享呢?

image-20220219102604956

2.2 Cookie的原理分析

对于Cookie的实现原理是基于HTTP协议的,其中设计到HTTP协议中的两个请求头信息:

  • 响应头:set-cookie
  • 请求头: cookie

1629393289338

  • 前面的案例中已经能够实现,AServlet给前端发送Cookie,BServlet从request中获取Cookie的功能
  • 对于AServlet响应数据的时候,Tomcat服务器都是基于HTTP协议来响应数据
  • 当Tomcat发现后端要返回的是一个Cookie对象之后,Tomcat就会在响应头中添加一行数据==Set-Cookie:username=zs==
  • 浏览器获取到响应结果后,从响应头中就可以获取到Set-Cookie对应值username=zs,并将数据存储在浏览器的内存中
  • 浏览器再次发送请求给BServlet的时候,浏览器会自动在请求头中添加==Cookie: username=zs==发送给服务端BServlet
  • Request对象会把请求头中cookie对应的值封装成一个个Cookie对象,最终形成一个数组
  • BServlet通过Request对象获取到Cookie[]后,就可以从中获取自己需要的数据

接下来,使用刚才的案例,把上述结论验证下:

(1)访问AServlet对应的地址http://localhost:8080/cookie-demo/aServlet

使用Chrom浏览器打开开发者工具(F12或Crtl+Shift+I)进行查看响应头中的数据

1629393428733

(2)访问BServlet对应的地址`http://localhost:8080/cookie-demo/bServlet

使用Chrom浏览器打开开发者工具(F12或Crtl+Shift+I)进行查看请求头中的数据

1629393578667

2.3 Cookie的使用细节

在这节我们主要讲解两个知识,第一个是Cookie的存活时间,第二个是Cookie如何存储中文,首先来学习下Cookie的存活时间。

2.3.1 Cookie的存活时间

前面让大家思考过一个问题:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iwXWQrwS-1647178288515)(java Web笔记(黑马).assets/1629423321737.png)]

(1)浏览器发送请求给AServlet,AServlet会响应一个存有usernanme=zs的Cookie对象给浏览器

(2)浏览器接收到响应数据将cookie存入到浏览器内存中

(3)当浏览器再次发送请求给BServlet,BServlet就可以使用Request对象获取到Cookie数据

(4)在发送请求到BServlet之前,如果把浏览器关闭再打开进行访问,BServlet能否获取到Cookie数据?

注意:浏览器关闭再打开不是指打开一个新的选显卡,而且必须是先关闭再打开,顺序不能变。

针对上面这个问题,通过演示,会发现,BServlet中无法再获取到Cookie数据,这是为什么呢?

  • 默认情况下,Cookie存储在浏览器内存中,当浏览器关闭,内存释放,则Cookie被销毁

这个结论就印证了上面的演示效果,但是如果使用这种默认情况下的Cookie,有些需求就无法实现,比如:

1629423629887

上面这个网站的登录页面上有一个记住我的功能,这个功能大家都比较熟悉

  • 第一次输入用户名和密码并勾选记住我然后进行登录
  • 下次再登陆的时候,用户名和密码就会被自动填充,不需要再重新输入登录
  • 比如记住我这个功能需要记住用户名和密码一个星期,那么使用默认情况下的Cookie就会出现问题
  • 因为默认情况,浏览器一关,Cookie就会从浏览器内存中删除,对于记住我功能就无法实现

所以我们现在就遇到一个难题是如何将Cookie持久化存储?

Cookie其实已经为我们提供好了对应的API来完成这件事,这个API就是setMaxAge,

  • 设置Cookie存活时间
setMaxAge(int seconds)

参数值为:

1.正数:将Cookie写入浏览器所在电脑的硬盘,持久化存储。到时间自动删除

2.负数:默认值,Cookie在当前浏览器内存中,当浏览器关闭,则Cookie被销毁

3.零:删除对应Cookie

接下来,咱们就在AServlet中去设置Cookie的存活时间。

@WebServlet("/aServlet")
public class AServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //发送Cookie
        //1. 创建Cookie对象
        Cookie cookie = new Cookie("username","zs");
        //设置存活时间   ,1周 7天
        cookie.setMaxAge(60*60*24*7); //易阅读,需程序计算
		//cookie.setMaxAge(604800); //不易阅读(可以使用注解弥补),程序少进行一次计算
        //2. 发送Cookie,response
        response.addCookie(cookie);
    }

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

修改完代码后,启动测试,访问http://localhost:8080/cookie-demo/aServlet

  • 访问一个AServlet后,把浏览器关闭重启后,再去访问http://localhost:8080/cookie-demo/bServet,能在控制台打印出username:zs,说明Cookie没有随着浏览器关闭而被销毁
  • 通过浏览器查看Cookie的内容,会发现Cookie的相关信息

1629424844041

2.3.2 Cookie存储中文

首先,先来演示一个效果,将之前username=zs的值改成username=张三,把汉字张三存入到Cookie中,看是什么效果:

@WebServlet("/aServlet")
public class AServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
 		//发送Cookie
        String value = "张三";
        Cookie cookie = new Cookie("username",value);
        //设置存活时间   ,1周 7天
        cookie.setMaxAge(60*60*24*7);
        //2. 发送Cookie,response
        response.addCookie(cookie);
    }

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

启动访问测试,访问http://localhost:8080/cookie-demo/aServlet会发现浏览器会提示错误信息

1629425945465

通过上面的案例演示,我们得到一个结论:

  • Cookie不能直接存储中文

Cookie不能存储中文,但是如果有这方面的需求,这个时候该如何解决呢?

这个时候,我们可以使用之前学过的一个知识点叫URL编码,所以如果需要存储中文,就需要进行转码,具体的实现思路为:

1.在AServlet中对中文进行URL编码,采用URLEncoder.encode(),将编码后的值存入Cookie中

2.在BServlet中获取Cookie中的值,获取的值为URL编码后的值

3.将获取的值在进行URL解码,采用URLDecoder.decode(),就可以获取到对应的中文值

(1)在AServlet中对中文进行URL编码

@WebServlet("/aServlet")
public class AServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //发送Cookie
        String value = "张三";
        //对中文进行URL编码
        value = URLEncoder.encode(value, "UTF-8");
        System.out.println("存储数据:"+value);
        //将编码后的值存入Cookie中
        Cookie cookie = new Cookie("username",value);
        //设置存活时间   ,1周 7天
        cookie.setMaxAge(60*60*24*7);
        //2. 发送Cookie,response
        response.addCookie(cookie);
    }

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

(2)在BServlet中获取值,并对值进行解码

@WebServlet("/bServlet")
public class BServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //获取Cookie
        //1. 获取Cookie数组
        Cookie[] cookies = request.getCookies();
        //2. 遍历数组
        for (Cookie cookie : cookies) {
            //3. 获取数据
            String name = cookie.getName();
            if("username".equals(name)){
                String value = cookie.getValue();//获取的是URL编码后的值 %E5%BC%A0%E4%B8%89
                //URL解码
                value = URLDecoder.decode(value,"UTF-8");
                System.out.println(name+":"+value);//value解码后为 张三
                break;
            }
        }

    }

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

至此,我们就可以将中文存入Cookie中进行使用。

小结

Cookie的使用细节中,我们讲了Cookie的存活时间存储中文:

  • 存活时间,需要掌握setMaxAage()API的使用

  • 存储中文,需要掌握URL编码和解码的使用

3,Session

Cookie已经能完成一次会话多次请求之间的数据共享,之前我们还提到过Session也可以实现,那么:

  • 什么是Session?
  • Session如何来使用?
  • Session是如何实现的?
  • Session的使用注意事项有哪些?

3.1 Session的基本使用

1.概念

Session:服务端会话跟踪技术:将数据保存到服务端。

  • Session是存储在服务端而Cookie是存储在客户端
  • 存储在客户端的数据容易被窃取和截获,存在很多不安全的因素
  • 存储在服务端的数据相比于客户端来说就更安全

2.Session的工作流程

1629427173389

  • 在服务端的AServlet获取一个Session对象,把数据存入其中
  • 在服务端的BServlet获取到相同的Session对象,从中取出数据
  • 就可以实现一次会话中多次请求之间的数据共享了
  • 现在最大的问题是如何保证AServlet和BServlet使用的是同一个Session对象(在原理分析会讲解)?

3.Session的基本使用

在JavaEE中提供了HttpSession接口,来实现一次会话的多次请求之间数据共享功能。

具体的使用步骤为:

  • 获取Session对象,使用的是request对象
HttpSession session = request.getSession();
  • Session对象提供的功能:

    • 存储数据到 session 域中

      void setAttribute(String name, Object o)
      
    • 根据 key,获取值

      Object getAttribute(String name)
      
    • 根据 key,删除该键值对

      void removeAttribute(String name)
      

介绍完Session相关的API后,接下来通过一个案例来完成对Session的使用,具体实现步骤为:

需求:在一个Servlet中往Session中存入数据,在另一个Servlet中获取Session中存入的数据

1.创建名为SessionDemo1的Servlet类

2.创建名为SessionDemo2的Servlet类

3.在SessionDemo1的方法中:获取Session对象、存储数据

4.在SessionDemo2的方法中:获取Session对象、获取数据

5.启动测试

(1)创建名为SessionDemo1的Servlet类

@WebServlet("/demo1")
public class SessionDemo1 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    
    }

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

(2)创建名为SessionDemo2的Servlet类

@WebServlet("/demo2")
public class SessionDemo2 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    
    }

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

(3)SessionDemo1:获取Session对象、存储数据

@WebServlet("/demo1")
public class SessionDemo1 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    	//存储到Session中
        //1. 获取Session对象
        HttpSession session = request.getSession();
        //2. 存储数据
        session.setAttribute("username","zs");
    }

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

(4)SessionDemo2:获取Session对象、获取数据

@WebServlet("/demo2")
public class SessionDemo2 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //获取数据,从session中
        //1. 获取Session对象
        HttpSession session = request.getSession();
        //2. 获取数据
        Object username = session.getAttribute("username");
        System.out.println(username);
    }

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

(5)启动测试,

  • 先访问http://localhost:8080/cookie-demo/demo1,将数据存入Session
  • 在访问http://localhost:8080/cookie-demo/demo2,从Session中获取数据
  • 查看控制台

1629428292373

通过案例的效果,能看到Session是能够在一次会话中两次请求之间共享数据。

小结

至此Session的基本使用就已经完成了,重点要掌握的是:

  • Session的获取

    HttpSession session = request.getSession();
    
  • Session常用方法的使用

    void setAttribute(String name, Object o)
    Object getAttribute(String name)
    

    **注意:**Session中可以存储的是一个Object类型的数据,也就是说Session中可以存储任意数据类型。

介绍完Session的基本使用之后,那么Session的底层到底是如何实现一次会话两次请求之间的数据共享呢?

3.2 Session的原理分析

  • Session是基于Cookie实现的

这句话其实不太能详细的说明Session的底层实现,接下来,咱们一步步来分析下Session的具体实现原理:

(1)前提条件

1629429063101

Session要想实现一次会话多次请求之间的数据共享,就必须要保证多次请求获取Session的对象是同一个。

那么它们是一个对象么?要验证这个结论也很简单,只需要在上面案例中的两个Servlet中分别打印下Session对象

SessionDemo1

@WebServlet("/demo1")
public class SessionDemo1 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    	//存储到Session中
        //1. 获取Session对象
        HttpSession session = request.getSession();
        System.out.println(session);
        //2. 存储数据
        session.setAttribute("username","zs");
    }

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

SessionDemo2

@WebServlet("/demo2")
public class SessionDemo2 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //获取数据,从session中
        //1. 获取Session对象
        HttpSession session = request.getSession();
        System.out.println(session);
        //2. 获取数据
        Object username = session.getAttribute("username");
        System.out.println(username);
    }

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

启动测试,分别访问

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

http://localhost:8080/cookie-demo/demo2

1629429239409

通过打印可以得到如下结论:

  • 两个Servlet类中获取的Session对象是同一个
  • 把demo1和demo2请求刷新多次,控制台最终打印的结果都是同一个

那么问题又来了,如果新开一个浏览器,访问demo1或者demo2,打印在控制台的Session还是同一个对象么?

1629429788264

注意:在一台电脑上演示的时候,如果是相同的浏览器必须要把浏览器全部关掉重新打开,才算新开的一个浏览器。

当然也可以使用不同的浏览器进行测试,就不需要把之前的浏览器全部关闭。

测试的结果:如果是不同浏览器或者重新打开浏览器后,打印的Session就不一样了。

所以Session实现的也是一次会话中的多次请求之间的数据共享。

那么最主要的问题就来了,Session是如何保证在一次会话中获取的Session对象是同一个呢?

1629430754825

(1)demo1在第一次获取session对象的时候,session对象会有一个唯一的标识,假如是id:10

(2)demo1在session中存入其他数据并处理完成所有业务后,需要通过Tomcat服务器响应结果给浏览器

(3)Tomcat服务器发现业务处理中使用了session对象,就会把session的唯一标识id:10当做一个cookie,添加Set-Cookie:JESSIONID=10到响应头中,并响应给浏览器

(4)浏览器接收到响应结果后,会把响应头中的coookie数据存储到浏览器的内存中

(5)浏览器在同一会话中访问demo2的时候,会把cookie中的数据按照cookie: JESSIONID=10的格式添加到请求头中并发送给服务器Tomcat

(6)demo2获取到请求后,从请求头中就读取cookie中的JSESSIONID值为10,然后就会到服务器内存中寻找id:10的session对象,如果找到了,就直接返回该对象,如果没有则新创建一个session对象

(7)关闭打开浏览器后,因为浏览器的cookie已被销毁,所以就没有JESSIONID的数据,服务端获取到的session就是一个全新的session对象

至此,Session是基于Cookie来实现的这就话,我们就解释完了,接下来通过实例来演示下:

(1)使用chrome浏览器访问http://localhost:8080/cookie-demo/demo1,打开开发者模式(F12或Ctrl+Shift+I),查看==响应头(Response Headers)==数据:

1629430891071

(2)使用chrome浏览器再次访问http://localhost:8080/cookie-demo/demo2,查看==请求头(Request Headers)==数据:

1629431299195

小结

介绍完Session的原理,我们只需要记住

  • Session是基于Cookie来实现的

3.3 Session的使用细节

这节我们会主要讲解两个知识,第一个是Session的钝化和活化,第二个是Session的销毁,首先来学习什么是Session的钝化和活化?

3.3.1 Session钝化与活化

首先需要大家思考的问题是:

  • 服务器重启后,Session中的数据是否还在?

要想回答这个问题,我们可以先看下下面这幅图,

1629438984314

(1)服务器端AServlet和BServlet共用的session对象应该是存储在服务器的内存中

(2)服务器重新启动后,内存中的数据应该是已经被释放,对象也应该都销毁了

所以session数据应该也已经不存在了。但是如果session不存在会引发什么问题呢?

举个例子说明下,

(1)用户把需要购买的商品添加到购物车,因为要实现同一个会话多次请求数据共享,所以假设把数据存入Session对象中

(2)用户正要付钱的时候接到一个电话,付钱的动作就搁浅了

(3)正在用户打电话的时候,购物网站因为某些原因需要重启

(4)重启后session数据被销毁,购物车中的商品信息也就会随之而消失

(5)用户想再次发起支付,就会出为问题

所以说对于session的数据,我们应该做到就算服务器重启了,也应该能把数据保存下来才对。

分析了这么多,那么Tomcat服务器在重启的时候,session数据到底会不会保存以及是如何保存的,我们可以通过实际案例来演示下:

注意:这里所说的关闭和启动应该要确保是正常的关闭和启动。

那如何才是正常关闭Tomcat服务器呢?

需要使用命令行的方式来启动和停止Tomcat服务器:

启动:进入到项目pom.xml所在目录,执行tomcat7:run

1629439800328

停止:在启动的命令行界面,输入ctrl+c

1629439879596

有了上述两个正常启动和关闭的方式后,接下来的测试流程是:

(1)先启动Tomcat服务器

(2)访问http://localhost:8080/cookie-demo/demo1将数据存入session中

(3)正确停止Tomcat服务器

(4)再次重新启动Tomcat服务器

(5)访问http://localhost:8080/cookie-demo/demo2 查看是否能获取到session中的数据

1629440018238

经过测试,会发现只要服务器是正常关闭和启动,session中的数据是可以被保存下来的。

那么Tomcat服务器到底是如何做到的呢?

具体的原因就是:Session的钝化和活化:

  • 钝化:在服务器正常关闭后,Tomcat会自动将Session数据写入硬盘的文件中

    • 钝化的数据路径为:项目目录\target\tomcat\work\Tomcat\localhost\项目名称\SESSIONS.ser

      1629440576828

  • 活化:再次启动服务器后,从文件中加载数据到Session中

    • 数据加载到Session中后,路径中的SESSIONS.ser文件会被删除掉

对于上述的整个过程,大家只需要了解下即可。因为所有的过程都是Tomcat自己完成的,不需要我们参与。

小结

Session的钝化和活化介绍完后,需要我们注意的是:

  • session数据存储在服务端,服务器重启后,session数据会被保存

  • 浏览器被关闭启动后,重新建立的连接就已经是一个全新的会话,获取的session数据也是一个新的对象

  • session的数据要想共享,浏览器不能关闭,所以session数据不能长期保存数据

  • cookie是存储在客户端,是可以长期保存

3.3.2 Session销毁

session的销毁会有两种方式:

  • 默认情况下,无操作,30分钟自动销毁

    • 对于这个失效时间,是可以通过配置进行修改的

      • 在项目的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">
        
            <session-config>
                <session-timeout>100</session-timeout>
            </session-config>
        </web-app>
        
      • 如果没有配置,默认是30分钟,默认值是在Tomcat的web.xml配置文件中写死的

        1629441687613

  • 调用Session对象的**invalidate()**进行销毁

    • 在SessionDemo2类中添加session销毁的方法

      @WebServlet("/demo2")
      public class SessionDemo2 extends HttpServlet {
          @Override
          protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
              //获取数据,从session中
      
              //1. 获取Session对象
              HttpSession session = request.getSession();
              System.out.println(session);
      
              // 销毁
              session.invalidate();
              //2. 获取数据
              Object username = session.getAttribute("username");
              System.out.println(username);
          }
      
          @Override
          protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
              this.doGet(request, response);
          }
      }
      
    • 启动访问测试,先访问demo1将数据存入到session,再次访问demo2从session中获取数据

      1629441900843

    • 该销毁方法一般会在用户退出的时候,需要将session销毁掉。

Cookie和Session小结

  • Cookie 和 Session 都是来完成一次会话内多次请求间数据共享的。

所需两个对象放在一块,就需要思考:

Cookie和Session的区别是什么?

Cookie和Session的应用场景分别是什么?

  • 区别:
    • 存储位置:Cookie 是将数据存储在客户端,Session 将数据存储在服务端
    • 安全性:Cookie不安全,Session安全
    • 数据大小:Cookie最大3KB,Session无大小限制
    • 存储时间:Cookie可以通过setMaxAge()长期存储,Session默认30分钟
    • 服务器性能:Cookie不占服务器资源,Session占用服务器资源
  • 应用场景:
    • 购物车:使用Cookie来存储
    • 以登录用户的名称展示:使用Session来存储
    • 记住我功能:使用Cookie来存储
    • 验证码:使用session来存储
  • 结论
    • Cookie是用来保证用户在未登录情况下的身份识别,长期存储
    • Session是用来保存用户登录后的数据,浏览器一关闭,session的id会改变,两次不是同一个session了。

介绍完Cookie和Session以后,具体用哪个还是需要根据具体的业务进行具体分析。

Filter&Listener&Ajax

今日目标:

  • 能够使用 Filter 完成登陆状态校验功能
  • 能够使用 axios 发送 ajax 请求
  • 熟悉 json 格式,并能使用 Fastjson 完成 java 对象和 json 串的相互转换
  • 使用 axios + json 完成综合案例

1,Filter

1.1 Filter概述

Filter 表示过滤器,是 **JavaWeb 三大组件(Servlet、Filter、Listener)**之一。Servlet 我们之前都已经学习过了,Filter和Listener 我们今天都会进行学习。

过滤器可以把对资源的请求拦截下来,从而实现一些特殊的功能。

如下图所示,浏览器可以访问服务器上的所有的资源(servlet、jsp、html等)

image-20210823184519509

而在访问到这些资源之前可以使过滤器拦截来下,也就是说在访问资源之前会先经过 Filter,如下图

image-20210823184657328

拦截器拦截到后可以做什么功能呢?

==过滤器一般完成一些通用的操作。==比如每个资源都要写一些代码完成某个功能,我们总不能在每个资源中写这样的代码吧,而此时我们可以将这些代码写在过滤器中,因为请求每一个资源都要经过过滤器。

我们之前做的品牌数据管理的案例中就已经做了登陆的功能,而如果我们不登录能不能访问到数据呢?我们可以在浏览器直接访问首页 ,可以看到 查询所有 的超链接

image-20210823185720197

当我点击该按钮,居然可以看到品牌的数据

image-20210823185932418

这显然和我们的要求不符。我们希望实现的效果是用户如果登陆过了就跳转到品牌数据展示的页面;如果没有登陆就跳转到登陆页面让用户进行登陆,要实现这个效果需要在每一个资源中都写上这段逻辑,而像这种通用的操作,我们就可以放在过滤器中进行实现。这个就是权限控制,以后我们还会进行细粒度权限控制。过滤器还可以做 统一编码处理敏感字符处理 等等…

1.2 Filter快速入门

1.2.1 开发步骤

进行 Filter 开发分成以下三步实现

  • 定义类,实现 Filter接口,并重写其所有方法

    image-20210823191006878
  • 配置Filter拦截资源的路径:在类上定义 @WebFilter 注解。而注解的 value 属性值 /* 表示拦截所有的资源

    image-20210823191037163
  • 在doFilter方法中输出一句话,并放行

    image-20210823191200201

    上述代码中的 chain.doFilter(request,response); 就是放行,也就是让其访问本该访问的资源。

1.2.2 代码演示

创建一个项目,项目下有一个 hello.jsp 页面,项目结构如下:

image-20210823191855765

pom.xml 配置文件内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>filter-demo</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.tomcat.maven</groupId>
                <artifactId>tomcat7-maven-plugin</artifactId>
                <version>2.2</version>
                <configuration>
                    <port>80</port>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

hello.jsp 页面内容如下:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    <h1>hello JSP~</h1>
</body>
</html>

我们现在在浏览器输入 http://localhost/filter-demo/hello.jsp 访问 hello.jsp 页面,这里是可以访问到 hello.jsp 页面内容的。

image-20210823192353031

接下来编写过滤器。过滤器是 Web 三大组件之一,所以我们将 filter 创建在 com.itheima.web.filter 包下,起名为 FilterDemo

@WebFilter("/*")
public class FilterDemo implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("FilterDemo...");
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void destroy() {
    }
}

重启启动服务器,再次重新访问 hello.jsp 页面,这次发现页面没有任何效果,但是在 idea 的控制台可以看到如下内容

image-20210823193759365

上述效果说明 FilterDemo 这个过滤器的 doFilter() 方法执行了,但是为什么在浏览器上看不到 hello.jsp 页面的内容呢?这是因为在 doFilter() 方法中添加放行的方法才能访问到 hello.jsp 页面。那就在 doFilter() 方法中添加放行的代码

//放行
 chain.doFilter(request,response);

再次重启服务器并访问 hello.jsp 页面,发现这次就可以在浏览器上看到页面效果。

FilterDemo 过滤器完整代码如下:

@WebFilter("/*")
public class FilterDemo implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("1.FilterDemo...");
        //放行
        chain.doFilter(request,response);
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void destroy() {
    }
}

1.3 Filter执行流程

image-20210823194830074

如上图是使用过滤器的流程,我们通过以下问题来研究过滤器的执行流程:

  • 放行后访问对应资源,资源访问完成后,还会回到Filter中吗?

    从上图就可以看出肯定 回到Filter中

  • 如果回到Filter中,是重头执行还是执行放行后的逻辑呢?

    如果是重头执行的话,就意味着 放行前逻辑 会被执行两次,肯定不会这样设计了;所以访问完资源后,会回到 放行后逻辑,执行该部分代码。

通过上述的说明,我们就可以总结Filter的执行流程如下:

image-20210823195434581

接下来我们通过代码验证一下,在 doFilter() 方法前后都加上输出语句,如下

image-20210823195828596

同时在 hello.jsp 页面加上输出语句,如下

image-20210823200028284

执行访问该资源打印的顺序是按照我们标记的标号进行打印的话,说明我们上边总结出来的流程是没有问题的。启动服务器访问 hello.jsp 页面,在控制台打印的内容如下:

image-20210823200202153

以后我们可以将对请求进行处理的代码放在放行之前进行处理,而如果请求完资源后还要对响应的数据进行处理时可以在放行后进行逻辑处理。

1.4 Filter拦截路径配置

拦截路径表示 Filter 会对请求的哪些资源进行拦截,使用 @WebFilter 注解进行配置。如:@WebFilter("拦截路径")

拦截路径有如下四种配置方式:

  • 拦截具体的资源:/index.jsp:只有访问index.jsp时才会被拦截
  • 目录拦截:/user/*:访问/user下的所有资源,都会被拦截
  • 后缀名拦截:*.jsp:访问后缀名为jsp的资源,都会被拦截
  • 拦截所有:/*:访问所有资源,都会被拦截

通过上面拦截路径的学习,大家会发现拦截路径的配置方式和 Servlet 的请求资源路径配置方式一样,但是表示的含义不同。

1.5 过滤器链

1.5.1 概述

过滤器链是指在一个Web应用,可以配置多个过滤器,这多个过滤器称为过滤器链。

如下图就是一个过滤器链,我们学习过滤器链主要是学习过滤器链执行的流程

image-20210823215835812

上图中的过滤器链执行是按照以下流程执行:

  1. 执行 Filter1 的放行前逻辑代码
  2. 执行 Filter1 的放行代码
  3. 执行 Filter2 的放行前逻辑代码
  4. 执行 Filter2 的放行代码
  5. 访问到资源
  6. 执行 Filter2 的放行后逻辑代码
  7. 执行 Filter1 的放行后逻辑代码

以上流程串起来就像一条链子,故称之为过滤器链。

image-20220219170748929

image-20220219170955166
1.5.2 代码演示
  • 编写第一个过滤器 FilterDemo ,配置成拦截所有资源

    @WebFilter("/*")
    public class FilterDemo implements Filter {
    
        @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    
            //1. 放行前,对 request数据进行处理
            System.out.println("1.FilterDemo...");
            //放行
            chain.doFilter(request,response);
            //2. 放行后,对Response 数据进行处理
            System.out.println("3.FilterDemo...");
        }
    
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
        }
    
        @Override
        public void destroy() {
        }
    }
    
  • 编写第二个过滤器 FilterDemo2 ,配置炒年糕拦截所有资源

    @WebFilter("/*")
    public class FilterDemo2 implements Filter {
    
        @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    
            //1. 放行前,对 request数据进行处理
            System.out.println("2.FilterDemo...");
            //放行
            chain.doFilter(request,response);
            //2. 放行后,对Response 数据进行处理
            System.out.println("4.FilterDemo...");
        }
    
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
        }
    
        @Override
        public void destroy() {
        }
    }
    
    
  • 修改 hello.jsp 页面中脚本的输出语句

    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
        <title>Title</title>
    </head>
    <body>
        <h1>hello JSP~</h1>
        <%
            System.out.println("3.hello jsp");
        %>
    </body>
    </html>
    
  • 启动服务器,在浏览器输入 http://localhost/filter-demo/hello.jsp 进行测试,在控制台打印内容如下

    image-20210823221222468

    从结果可以看到确实是按照我们之前说的执行流程进行执行的。

1.5.3 问题

上面代码中为什么是先执行 FilterDemo ,后执行 FilterDemo2 呢?

我们现在使用的是注解配置Filter,而这种配置方式的优先级是按照过滤器类名(字符串)的自然排序

比如有如下两个名称的过滤器 : BFilterDemoAFilterDemo 。那一定是 AFilterDemo 过滤器先执行。

1.6 案例

1.6.1 需求

访问服务器资源时,需要先进行登录验证,如果没有登录,则自动跳转到登录页面

1.6.2 分析

我们要实现该功能是在每一个资源里加入登陆状态校验的代码吗?显然是不需要的,只需要写一个 Filter ,在该过滤器中进行登陆状态校验即可。而在该 Filter 中逻辑如下:

image-20210823223214525
1.6.3 代码实现
1.6.3.1 创建Filter

brand-demo 工程创建 com.itheima.web.filter 包,在该下创建名为 LoginFilter 的过滤器

@WebFilter("/*")
public class LoginFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
      
    }

    public void init(FilterConfig config) throws ServletException {
    }

    public void destroy() {
    }
}
1.6.3.2 编写逻辑代码

doFilter() 方法中编写登陆状态校验的逻辑代码。

我们首先需要从 session 对象中获取用户信息,但是 ServletRequest 类型的 requset 对象没有获取 session 对象的方法,所以此时需要将 request对象强转成 HttpServletRequest 对象。

HttpServletRequest req = (HttpServletRequest) request;

然后完成以下逻辑

  • 获取Session对象
  • 从Session对象中获取名为 user 的数据
  • 判断获取到的数据是否是 null
    • 如果不是,说明已经登陆,放行
    • 如果是,说明尚未登陆,将提示信息存储到域对象中并跳转到登陆页面

代码如下:

@WebFilter("/*")
public class LoginFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
        HttpServletRequest req = (HttpServletRequest) request;
   
        //1. 判断session中是否有user
        HttpSession session = req.getSession();
        Object user = session.getAttribute("user");

        //2. 判断user是否为null
        if(user != null){
            // 登录过了
            //放行
            chain.doFilter(request, response);
        }else {
            // 没有登陆,存储提示信息,跳转到登录页面

            req.setAttribute("login_msg","您尚未登陆!");
            req.getRequestDispatcher("/login.jsp").forward(req,response);
        }
    }

    public void init(FilterConfig config) throws ServletException {
    }

    public void destroy() {
    }
}
1.6.3.3 测试并抛出问题

在浏览器上输入 http://localhost:8080/brand-demo/ ,可以看到如下页面效果

image-20210823224843179

从上面效果可以看出没有登陆确实是跳转到登陆页面了,但是登陆页面为什么展示成这种效果了呢?

1.6.3.4 问题分析及解决

因为登陆页面需要 css/login.css 这个文件进行样式的渲染,下图是登陆页面引入的css文件图解

image-20210823225411925

而在请求这个css资源时被过滤器拦截,就相当于没有加载到样式文件导致的。解决这个问题,只需要对所以的登陆相关的资源进行放行即可。还有一种情况就是当我没有用户信息时需要进行注册,而注册时也希望被过滤器放行。

综上,我们需要在判断session中是否包含用户信息之前,应该加上对登陆及注册相关资源放行的逻辑处理

//判断访问资源路径是否和登录注册相关
//1,在数组中存储登陆和注册相关的资源路径
String[] urls = {"/login.jsp","/imgs/","/css/","/loginServlet","/register.jsp","/registerServlet","/checkCodeServlet"};
//2,获取当前访问的资源路径
String url = req.getRequestURL().toString(); 

//3,遍历数组,获取到每一个需要放行的资源路径
for (String u : urls) {
    //4,判断当前访问的资源路径字符串是否包含要放行的的资源路径字符串
    /*
    	比如当前访问的资源路径是  /brand-demo/login.jsp
    	而字符串 /brand-demo/login.jsp 包含了  字符串 /login.jsp ,所以这个字符串就需要放行
    */
    if(url.contains(u)){
        //找到了,放行
        chain.doFilter(request, response);
        //break;
        return;
    }
}
1.6.3.5 过滤器完整代码
@WebFilter("/*")
public class LoginFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
        HttpServletRequest req = (HttpServletRequest) request;
        
        //判断访问资源路径是否和登录注册相关
        //1,在数组中存储登陆和注册相关的资源路径
        String[] urls = {"/login.jsp","/imgs/","/css/","/loginServlet","/register.jsp","/registerServlet","/checkCodeServlet"};
        //2,获取当前访问的资源路径
        String url = req.getRequestURL().toString(); 

        //3,遍历数组,获取到每一个需要放行的资源路径
        for (String u : urls) {
            //4,判断当前访问的资源路径字符串是否包含要放行的的资源路径字符串
            /*
                比如当前访问的资源路径是  /brand-demo/login.jsp
                而字符串 /brand-demo/login.jsp 包含了  字符串 /login.jsp ,所以这个字符串就需要放行
            */
            if(url.contains(u)){
                //找到了,放行
                chain.doFilter(request, response);
                //break;
                return;
            }
        }
   
        //1. 判断session中是否有user
        HttpSession session = req.getSession();
        Object user = session.getAttribute("user");

        //2. 判断user是否为null
        if(user != null){
            // 登录过了
            //放行
            chain.doFilter(request, response);
        }else {
            // 没有登陆,存储提示信息,跳转到登录页面

            req.setAttribute("login_msg","您尚未登陆!");
            req.getRequestDispatcher("/login.jsp").forward(req,response);
        }
    }

    public void init(FilterConfig config) throws ServletException {
    }

    public void destroy() {
    }
}

2,Listener

2.1 概述

  • Listener 表示监听器,是 JavaWeb 三大组件(Servlet、Filter、Listener)之一。

  • 监听器可以监听就是在 applicationsessionrequest 三个对象创建、销毁或者往其中添加修改删除属性时自动执行代码的功能组件。

    request 和 session 我们学习过。而 applicationServletContext 类型的对象。

    ServletContext 代表整个web应用,在服务器启动的时候,tomcat会自动创建该对象。在服务器关闭时会自动销毁该对象。

2.2 分类

JavaWeb 提供了8个监听器:

image-20210823230820586

这里面只有 ServletContextListener 这个监听器后期我们会接触到,ServletContextListener用来监听 ServletContext 对象的创建和销毁

ServletContextListener 接口中有以下两个方法

  • void contextInitialized(ServletContextEvent sce)ServletContext 对象被创建了会自动执行的方法
  • void contextDestroyed(ServletContextEvent sce)ServletContext 对象被销毁时会自动执行的方法

2.3 代码演示

我们只演示一下 ServletContextListener 监听器

  • 定义一个类,实现ServletContextListener 接口
  • 重写所有的抽象方法
  • 使用 @WebListener 进行配置

代码如下:

@WebListener
public class ContextLoaderListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        //加载资源
        System.out.println("ContextLoaderListener...");
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        //释放资源
    }
}

启动服务器,就可以在启动的日志信息中看到 contextInitialized() 方法输出的内容,同时也说明了 ServletContext 对象在服务器启动的时候被创建了。

3,Ajax

3.1 概述

AJAX (Asynchronous JavaScript And XML):异步的 JavaScript 和 XML。

我们先来说概念中的 JavaScriptXMLJavaScript 表明该技术和前端相关;XML 是指以此进行数据交换。而这两个我们之前都学习过。

3.1.1 作用

AJAX 作用有以下两方面:

  1. 与服务器进行数据交换通过AJAX可以给服务器发送请求,服务器将数据直接响应回给浏览器。如下图

我们先来看之前做功能的流程,如下图:

image-20210823235114367

如上图,Servlet 调用完业务逻辑层后将数据存储到域对象中,然后跳转到指定的 jsp 页面,在页面上使用 EL表达式JSTL 标签库进行数据的展示。

我们学习了AJAX 后,就可以使用AJAX和服务器进行通信,以达到使用 HTML+AJAX来替换JSP页面了,不用java工程师一个人开发前后端了。如下图,浏览器发送请求servlet,servlet 调用完业务逻辑层后将数据直接响应回给浏览器页面,页面使用 HTML 来进行数据展示。

image-20210823235006847
  1. 异步交互可以在不重新加载整个页面的情况下,与服务器交换数据并更新部分网页的技术,如:搜索联想、用户名是否可用校验,等等…
image-20210824000706401

上图所示的效果我们经常见到,在我们输入一些关键字(例如 奥运)后就会在下面联想出相关的内容,而联想出来的这部分数据肯定是存储在百度的服务器上,而我们并没有看出页面重新刷新,这就是 更新局部页面 的效果。再如下图:

image-20210824001015706

我们在用户名的输入框输入用户名,当输入框一失去焦点,如果用户名已经被占用就会在下方展示提示的信息;在这整个过程中也没有页面的刷新,只是在局部展示出了提示信息,这就是 更新局部页面 的效果。

3.1.2 同步和异步

知道了局部刷新后,接下来我们再聊聊同步和异步:

  • 同步发送请求过程如下
image-20210824001443897

​ 浏览器页面在发送请求给服务器,在服务器处理请求的过程中,浏览器页面不能做其他的操作。只能等到服务器响应结束后才能,浏览器页面才能继续做其他的操作。

  • 异步发送请求过程如下

    image-20210824001608916

    浏览器页面发送请求给服务器,在服务器处理请求的过程中,浏览器页面还可以做其他的操作。

3.2 快速入门

3.2.1 服务端实现

在项目的创建 com.itheima.web.servlet ,并在该包下创建名为 AjaxServlet 的servlet

@WebServlet("/ajaxServlet")
public class AjaxServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //1. 响应数据
        response.getWriter().write("hello ajax~");
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doGet(request, response);
    }
}
3.2.2 客户端实现

webapp 下创建名为 01-ajax-demo1.html 的页面,在该页面书写 ajax 代码

  • 创建核心对象,不同的浏览器创建的对象是不同的

     var xhttp;
    if (window.XMLHttpRequest) {
        xhttp = new XMLHttpRequest();
    } else {
        // code for IE6, IE5
        xhttp = new ActiveXObject("Microsoft.XMLHTTP");
    }
    
  • 发送请求

    //建立连接
    xhttp.open("GET", "http://localhost:8080/ajax-demo/ajaxServlet");
    //发送请求
    xhttp.send();
    
  • 获取响应

    xhttp.onreadystatechange = function() {
        if (this.readyState == 4 && this.status == 200) {
            // 通过 this.responseText 可以获取到服务端响应的数据
            alert(this.responseText);
        }
    };
    

完整代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<script>
    //1. 创建核心对象
    var xhttp;
    if (window.XMLHttpRequest) {
        xhttp = new XMLHttpRequest();
    } else {
        // code for IE6, IE5
        xhttp = new ActiveXObject("Microsoft.XMLHTTP");
    }
    //2. 发送请求
    xhttp.open("GET", "http://localhost:8080/ajax-demo/ajaxServlet");
    xhttp.send();

    //3. 获取响应
    xhttp.onreadystatechange = function() {
        if (this.readyState == 4 && this.status == 200) {
            alert(this.responseText);
        }
    };
</script>
</body>
</html>
3.2.3 测试

在浏览器地址栏输入 http://localhost:8080/ajax-demo/01-ajax-demo1.html ,在 01-ajax-demo1.html加载的时候就会发送 ajax 请求,效果如下

image-20210824005956117

我们可以通过 开发者模式 查看发送的 AJAX 请求。在浏览器上按 F12 快捷键

image-20210824010247642

这个是查看所有的请求,如果我们只是想看 异步请求的话,点击上图中 All 旁边的 XHR,会发现只展示 Type 是 xhr 的请求。如下图:

image-20210824010438260

3.3 案例

需求:在完成用户注册时,当用户名输入框失去焦点时,校验用户名是否在数据库已存在

image-20210824201415745
3.3.1 分析
  • 前端完成的逻辑
    1. 给用户名输入框绑定光标失去焦点事件 onblur
    2. 发送 ajax请求,携带username参数
    3. 处理响应:是否显示提示信息
  • 后端完成的逻辑
    1. 接收用户名
    2. 调用service查询User。此案例是为了演示前后端异步交互,所以此处我们不做业务逻辑处理
    3. 返回标记

整体流程如下:

image-20210829183854285
3.3.2 后端实现

com.ithiema.web.servlet 包中定义名为 SelectUserServlet 的servlet。代码如下:

@WebServlet("/selectUserServlet")
public class SelectUserServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //1. 接收用户名
        String username = request.getParameter("username");
        //2. 调用service查询User对象,此处不进行业务逻辑处理,直接给 flag 赋值为 true,表明用户名占用
        boolean flag = true;
        //3. 响应标记
        response.getWriter().write("" + flag);
    }

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

04-资料\1. 验证用户名案例\1. 静态页面 下的文件整体拷贝到项目下 webapp 下。并在 register.html 页面的 body 结束标签前编写 script 标签,在该标签中实现如下逻辑

第一步:给用户名输入框绑定光标失去焦点事件 onblur

//1. 给用户名输入框绑定 失去焦点事件
document.getElementById("username").onblur = function () {
    
}

第二步:发送 ajax请求,携带username参数

第一步 绑定的匿名函数中书写发送 ajax 请求的代码

//2. 发送ajax请求
//2.1. 创建核心对象
var xhttp;
if (window.XMLHttpRequest) {
    xhttp = new XMLHttpRequest();
} else {
    // code for IE6, IE5
    xhttp = new ActiveXObject("Microsoft.XMLHTTP");
}
//2.2. 发送请求
xhttp.open("GET", "http://localhost:8080/ajax-demo/selectUserServlet);
xhttp.send();

//2.3. 获取响应
xhttp.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 200) {
        //处理响应的结果
    }
};

由于我们发送的是 GET 请求,所以需要在 URL 后拼接从输入框获取的用户名数据。而我们在 第一步 绑定的匿名函数中通过以下代码可以获取用户名数据

// 获取用户名的值
var username = this.value;  //this : 给谁绑定的事件,this就代表谁

而携带数据需要将 URL 修改为:

xhttp.open("GET", "http://localhost:8080/ajax-demo/selectUserServlet?username="+username);

第三步:处理响应:是否显示提示信息

this.readyState == 4 && this.status == 200 条件满足时,说明已经成功响应数据了。

此时需要判断响应的数据是否是 “true” 字符串,如果是说明用户名已经占用给出错误提示;如果不是说明用户名未被占用清除错误提示。代码如下

//判断
if(this.responseText == "true"){
    //用户名存在,显示提示信息
    document.getElementById("username_err").style.display = '';
}else {
    //用户名不存在 ,清楚提示信息
    document.getElementById("username_err").style.display = 'none';
}

综上所述,前端完成代码如下:

//1. 给用户名输入框绑定 失去焦点事件
document.getElementById("username").onblur = function () {
    //2. 发送ajax请求
    // 获取用户名的值
    var username = this.value;

    //2.1. 创建核心对象
    var xhttp;
    if (window.XMLHttpRequest) {
        xhttp = new XMLHttpRequest();
    } else {
        // code for IE6, IE5
        xhttp = new ActiveXObject("Microsoft.XMLHTTP");
    }
    //2.2. 发送请求
    xhttp.open("GET", "http://localhost:8080/ajax-demo/selectUserServlet?username="+username);
    xhttp.send();

    //2.3. 获取响应
    xhttp.onreadystatechange = function() {
        if (this.readyState == 4 && this.status == 200) {
            //alert(this.responseText);
            //判断
            if(this.responseText == "true"){
                //用户名存在,显示提示信息
                document.getElementById("username_err").style.display = '';
            }else {
                //用户名不存在 ,清楚提示信息
                document.getElementById("username_err").style.display = 'none';
            }
        }
    };
}

4,axios

Axios 对原生的AJAX进行封装,简化书写

Axios官网是:https://www.axios-http.cn

4.1 基本使用

axios 使用是比较简单的,分为以下两步:

  • 引入 axios 的 js 文件

    <script src="js/axios-0.18.0.js"></script>
    
  • 使用axios 发送请求,并获取响应结果

    • 发送 get 请求

      axios({
          method:"get",
          url:"http://localhost:8080/ajax-demo1/aJAXDemo1?username=zhangsan"
      }).then(function (resp){
          alert(resp.data);
      })
      
    • 发送 post 请求

      axios({
          method:"post",
          url:"http://localhost:8080/ajax-demo1/aJAXDemo1",
          data:"username=zhangsan"
      }).then(function (resp){
          alert(resp.data);
      });
      

axios() 是用来发送异步请求的,小括号中使用 js 对象传递请求相关的参数:

  • method 属性:用来设置请求方式的。取值为 get 或者 post
  • url 属性:用来书写请求的资源路径。如果是 get 请求,需要将请求参数拼接到路径的后面,格式为: url?参数名=参数值&参数名2=参数值2
  • data 属性:作为请求体被发送的数据。也就是说如果是 post 请求的话,数据需要作为 data 属性的值。

then() 需要传递一个匿名函数。我们将 then() 中传递的匿名函数称为 回调函数,意思是该匿名函数在发送请求时不会被调用,而是在成功响应后调用的函数。而该回调函数中的 resp 参数是对响应的数据进行封装的对象,通过 resp.data 可以获取到响应的数据。

4.2 快速入门

4.2.1 后端实现

定义一个用于接收请求的servlet,代码如下:

@WebServlet("/axiosServlet")
public class AxiosServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("get...");
        //1. 接收请求参数
        String username = request.getParameter("username");
        System.out.println(username);
        //2. 响应数据
        response.getWriter().write("hello Axios~");
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("post...");
        this.doGet(request, response);
    }
}
4.2.2 前端实现
  • 引入 js 文件

    <script src="js/axios-0.18.0.js"></script>
    
  • 发送 ajax 请求

    • get 请求

      axios({
          method:"get",
          url:"http://localhost:8080/ajax-demo/axiosServlet?username=zhangsan"
      }).then(function (resp) {
          alert(resp.data);
      })
      
    • post 请求

      axios({
          method:"post",
          url:"http://localhost:8080/ajax-demo/axiosServlet",
          data:"username=zhangsan"
      }).then(function (resp) {
          alert(resp.data);
      })
      

整体页面代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<script src="js/axios-0.18.0.js"></script>
<script>
    //1. get
   /* axios({
        method:"get",
        url:"http://localhost:8080/ajax-demo/axiosServlet?username=zhangsan"
    }).then(function (resp) {
        alert(resp.data);
    })*/

    //2. post  在js中{} 表示一个js对象,而这个js对象中有三个属性
    axios({
        method:"post",
        url:"http://localhost:8080/ajax-demo/axiosServlet",
        data:"username=zhangsan"
    }).then(function (resp) {
        alert(resp.data);
    })
</script>
</body>
</html>

4.3 请求方法别名

为了方便起见, Axios 已经为所有支持的请求方法提供了别名。如下:

  • get 请求 : axios.get(url[,config])

  • delete 请求 : axios.delete(url[,config])

  • head 请求 : axios.head(url[,config])

  • options 请求 : axios.option(url[,config])

  • post 请求:axios.post(url[,data[,config])

  • put 请求:axios.put(url[,data[,config])

  • patch 请求:axios.patch(url[,data[,config])

而我们只关注 get 请求和 post 请求。

入门案例中的 get 请求代码可以改为如下:

axios.get("http://localhost:8080/ajax-demo/axiosServlet?username=zhangsan").then(function (resp) {
    alert(resp.data);
});

入门案例中的 post 请求代码可以改为如下:

axios.post("http://localhost:8080/ajax-demo/axiosServlet","username=zhangsan").then(function (resp) {
    alert(resp.data);
})

5,JSON

5.1 概述

概念:JavaScript Object Notation。JavaScript 对象表示法.

如下是 JavaScript 对象的定义格式:

{
	name:"zhangsan",
	age:23,
	city:"北京"
}

接下来我们再看看 JSON 的格式:

{
	"name":"zhangsan",
	"age":23,
	"city":"北京"
}

通过上面 js 对象格式和 json 格式进行对比,发现两个格式特别像。只不过 js 对象中的属性名可以使用引号(可以是单引号,也可以是双引号);而 json 格式中的键要求必须使用双引号括起来,这是 json 格式的规定。json 格式的数据有什么作用呢?

作用:由于其语法格式简单,层次结构鲜明,现多用于作为==数据载体==,在网络中进行数据传输。如下图所示就是服务端给浏览器响应的数据,这个数据比较简单,如果现需要将 JAVA 对象中封装的数据响应回给浏览器的话,应该以何种数据传输呢?

image-20210830232718632

大家还记得 ajax 的概念吗? 是 异步的 JavaScript 和 xml。这里的 xml就是以前进行数据传递的方式,如下:

<student>
    <name>张三</name>
    <age>23</age>
    <city>北京</city>
</student>

再看 json 描述以上数据的写法:

{	
	"name":"张三",
    "age":23,
    "city":"北京"
}

上面两种格式进行对比后就会发现 json 格式数据的简单,以及所占的字节数少等优点。

5.2 JSON 基础语法

5.2.1 定义格式

JSON 本质就是一个字符串,但是该字符串内容是有一定的格式要求的。 定义格式如下:

var 变量名 = '{"key":value,"key":value,...}';

JSON 串的键要求必须使用双引号括起来,而值根据要表示的类型确定。value 的数据类型分为如下

  • 数字(整数或浮点数)
  • 字符串(使用双引号括起来)
  • 逻辑值(true或者false)
  • 数组(在方括号中)
  • 对象(在花括号中)
  • null

示例:

var jsonStr = '{"name":"zhangsan","age":23,"addr":["北京","上海","西安"]}'
5.2.2 代码演示

创建一个页面,在该页面的 <script> 标签中定义json字符串

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<script>
    //1. 定义JSON字符串
    var jsonStr = '{"name":"zhangsan","age":23,"addr":["北京","上海","西安"]}'
    alert(jsonStr);

</script>
</body>
</html>

通过浏览器打开,页面效果如下图所示

image-20210831223339530

现在我们需要获取到该 JSON 串中的 name 属性值,应该怎么处理呢?

如果它是一个 js 对象,我们就可以通过 js对象.属性名 的方式来获取数据。JS 提供了一个对象 JSON ,该对象有如下两个方法:

  • parse(str) :将 JSON串转换为 js 对象。使用方式是: var jsObject = JSON.parse(jsonStr);
  • stringify(obj) :将 js 对象转换为 JSON 串。使用方式是:var jsonStr = JSON.stringify(jsObject)

代码演示:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<script>
    //1. 定义JSON字符串
    var jsonStr = '{"name":"zhangsan","age":23,"addr":["北京","上海","西安"]}'
    alert(jsonStr);

    //2. 将 JSON 字符串转为 JS 对象
    let jsObject = JSON.parse(jsonStr);
    alert(jsObject)
    alert(jsObject.name)
    //3. 将 JS 对象转换为 JSON 字符串
    let jsonStr2 = JSON.stringify(jsObject);
    alert(jsonStr2)
</script>
</body>
</html>
5.2.3 发送异步请求携带参数

后面我们使用 axios 发送请求时,如果要携带复杂的数据时都会以 JSON 格式进行传递,如下

axios({
    method:"post",
    url:"http://localhost:8080/ajax-demo/axiosServlet",
    data:"username=zhangsan"
}).then(function (resp) {
    alert(resp.data);
})

请求参数不可能由我们自己拼接字符串吧?肯定不用,可以提前定义一个 js 对象,用来封装需要提交的参数,然后使用 JSON.stringify(js对象) 转换为 JSON 串,再将该 JSON 串作为 axiosdata 属性值进行请求参数的提交。如下:

var jsObject = {name:"张三"};

axios({
    method:"post",
    url:"http://localhost:8080/ajax-demo/axiosServlet",
    data: JSON.stringify(jsObject)
}).then(function (resp) {
    alert(resp.data);
})

axios 是一个很强大的工具。我们只需要将需要提交的参数封装成 js 对象,并将该 js 对象作为 axiosdata 属性值进行,它会自动将 js 对象转换为 JSON 串进行提交。如下:

var jsObject = {name:"张三"};

axios({
    method:"post",
    url:"http://localhost:8080/ajax-demo/axiosServlet",
    data:jsObject  //这里 axios 会将该js对象转换为 json 串的
}).then(function (resp) {
    alert(resp.data);
})

注意:

  • js 提供的 JSON 对象我们只需要了解一下即可。因为 axios 会自动对 js 对象和 JSON 串进行想换转换。
  • 发送异步请求时,如果请求参数是 JSON 格式,那请求方式必须是 POST。因为 JSON 串需要放在请求体中。

5.3 JSON串和Java对象的相互转换

学习完 json 后,接下来聊聊 json 的作用。以后我们会以 json 格式的数据进行前后端交互。前端发送请求时,如果是复杂的数据就会以 json 提交给后端;而后端如果需要响应一些复杂的数据时,也需要以 json 格式将数据响应回给浏览器。

image-20210831104901912

在后端我们就需要重点学习以下两部分操作:

  • 请求数据:JSON字符串转为Java对象
  • 响应数据:Java对象转为JSON字符串

接下来给大家介绍一套 API,可以实现上面两部分操作。这套 API 就是 Fastjson

5.3.1 Fastjson 概述

Fastjson 是阿里巴巴提供的一个Java语言编写的高性能功能完善的 JSON 库,是目前Java语言中最快的 JSON 库,可以实现 Java 对象和 JSON 字符串的相互转换。

5.3.2 Fastjson 使用

Fastjson 使用也是比较简单的,分为以下三步完成

  1. 导入坐标

    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.62</version>
    </dependency>
    
  2. Java对象转JSON

    String jsonStr = JSON.toJSONString(obj);
    

    将 Java 对象转换为 JSON 串,只需要使用 Fastjson 提供的 JSON 类中的 toJSONString() 静态方法即可。

  3. JSON字符串转Java对象

    User user = JSON.parseObject(jsonStr, User.class);
    

    将 json 转换为 Java 对象,只需要使用 Fastjson 提供的 JSON 类中的 parseObject() 静态方法即可。

5.3.3 代码演示
  • 引入坐标

  • 创建一个类,专门用来测试 Java 对象和 JSON 串的相互转换,代码如下:

    public class FastJsonDemo {
    
        public static void main(String[] args) {
            //1. 将Java对象转为JSON字符串
            User user = new User();
            user.setId(1);
            user.setUsername("zhangsan");
            user.setPassword("123");
    
            String jsonString = JSON.toJSONString(user);
            System.out.println(jsonString);//{"id":1,"password":"123","username":"zhangsan"}
    
    
            //2. 将JSON字符串转为Java对象
            User u = JSON.parseObject("{\"id\":1,\"password\":\"123\",\"username\":\"zhangsan\"}", User.class);
            System.out.println(u);
        }
    }
    

s ServletException, IOException {
System.out.println(“get…”);
//1. 接收请求参数
String username = request.getParameter(“username”);
System.out.println(username);
//2. 响应数据
response.getWriter().write(“hello Axios~”);
}

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

}


#### 4.2.2  前端实现

* 引入 js 文件

  ```js
  <script src="js/axios-0.18.0.js"></script>
  • 发送 ajax 请求

    • get 请求

      axios({
          method:"get",
          url:"http://localhost:8080/ajax-demo/axiosServlet?username=zhangsan"
      }).then(function (resp) {
          alert(resp.data);
      })
      
    • post 请求

      axios({
          method:"post",
          url:"http://localhost:8080/ajax-demo/axiosServlet",
          data:"username=zhangsan"
      }).then(function (resp) {
          alert(resp.data);
      })
      

整体页面代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<script src="js/axios-0.18.0.js"></script>
<script>
    //1. get
   /* axios({
        method:"get",
        url:"http://localhost:8080/ajax-demo/axiosServlet?username=zhangsan"
    }).then(function (resp) {
        alert(resp.data);
    })*/

    //2. post  在js中{} 表示一个js对象,而这个js对象中有三个属性
    axios({
        method:"post",
        url:"http://localhost:8080/ajax-demo/axiosServlet",
        data:"username=zhangsan"
    }).then(function (resp) {
        alert(resp.data);
    })
</script>
</body>
</html>

4.3 请求方法别名

为了方便起见, Axios 已经为所有支持的请求方法提供了别名。如下:

  • get 请求 : axios.get(url[,config])

  • delete 请求 : axios.delete(url[,config])

  • head 请求 : axios.head(url[,config])

  • options 请求 : axios.option(url[,config])

  • post 请求:axios.post(url[,data[,config])

  • put 请求:axios.put(url[,data[,config])

  • patch 请求:axios.patch(url[,data[,config])

而我们只关注 get 请求和 post 请求。

入门案例中的 get 请求代码可以改为如下:

axios.get("http://localhost:8080/ajax-demo/axiosServlet?username=zhangsan").then(function (resp) {
    alert(resp.data);
});

入门案例中的 post 请求代码可以改为如下:

axios.post("http://localhost:8080/ajax-demo/axiosServlet","username=zhangsan").then(function (resp) {
    alert(resp.data);
})

5,JSON

5.1 概述

概念:JavaScript Object Notation。JavaScript 对象表示法.

如下是 JavaScript 对象的定义格式:

{
	name:"zhangsan",
	age:23,
	city:"北京"
}

接下来我们再看看 JSON 的格式:

{
	"name":"zhangsan",
	"age":23,
	"city":"北京"
}

通过上面 js 对象格式和 json 格式进行对比,发现两个格式特别像。只不过 js 对象中的属性名可以使用引号(可以是单引号,也可以是双引号);而 json 格式中的键要求必须使用双引号括起来,这是 json 格式的规定。json 格式的数据有什么作用呢?

作用:由于其语法格式简单,层次结构鲜明,现多用于作为==数据载体==,在网络中进行数据传输。如下图所示就是服务端给浏览器响应的数据,这个数据比较简单,如果现需要将 JAVA 对象中封装的数据响应回给浏览器的话,应该以何种数据传输呢?

image-20210830232718632

大家还记得 ajax 的概念吗? 是 异步的 JavaScript 和 xml。这里的 xml就是以前进行数据传递的方式,如下:

<student>
    <name>张三</name>
    <age>23</age>
    <city>北京</city>
</student>

再看 json 描述以上数据的写法:

{	
	"name":"张三",
    "age":23,
    "city":"北京"
}

上面两种格式进行对比后就会发现 json 格式数据的简单,以及所占的字节数少等优点。

5.2 JSON 基础语法

5.2.1 定义格式

JSON 本质就是一个字符串,但是该字符串内容是有一定的格式要求的。 定义格式如下:

var 变量名 = '{"key":value,"key":value,...}';

JSON 串的键要求必须使用双引号括起来,而值根据要表示的类型确定。value 的数据类型分为如下

  • 数字(整数或浮点数)
  • 字符串(使用双引号括起来)
  • 逻辑值(true或者false)
  • 数组(在方括号中)
  • 对象(在花括号中)
  • null

示例:

var jsonStr = '{"name":"zhangsan","age":23,"addr":["北京","上海","西安"]}'
5.2.2 代码演示

创建一个页面,在该页面的 <script> 标签中定义json字符串

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<script>
    //1. 定义JSON字符串
    var jsonStr = '{"name":"zhangsan","age":23,"addr":["北京","上海","西安"]}'
    alert(jsonStr);

</script>
</body>
</html>

通过浏览器打开,页面效果如下图所示

image-20210831223339530

现在我们需要获取到该 JSON 串中的 name 属性值,应该怎么处理呢?

如果它是一个 js 对象,我们就可以通过 js对象.属性名 的方式来获取数据。JS 提供了一个对象 JSON ,该对象有如下两个方法:

  • parse(str) :将 JSON串转换为 js 对象。使用方式是: var jsObject = JSON.parse(jsonStr);
  • stringify(obj) :将 js 对象转换为 JSON 串。使用方式是:var jsonStr = JSON.stringify(jsObject)

代码演示:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<script>
    //1. 定义JSON字符串
    var jsonStr = '{"name":"zhangsan","age":23,"addr":["北京","上海","西安"]}'
    alert(jsonStr);

    //2. 将 JSON 字符串转为 JS 对象
    let jsObject = JSON.parse(jsonStr);
    alert(jsObject)
    alert(jsObject.name)
    //3. 将 JS 对象转换为 JSON 字符串
    let jsonStr2 = JSON.stringify(jsObject);
    alert(jsonStr2)
</script>
</body>
</html>
5.2.3 发送异步请求携带参数

后面我们使用 axios 发送请求时,如果要携带复杂的数据时都会以 JSON 格式进行传递,如下

axios({
    method:"post",
    url:"http://localhost:8080/ajax-demo/axiosServlet",
    data:"username=zhangsan"
}).then(function (resp) {
    alert(resp.data);
})

请求参数不可能由我们自己拼接字符串吧?肯定不用,可以提前定义一个 js 对象,用来封装需要提交的参数,然后使用 JSON.stringify(js对象) 转换为 JSON 串,再将该 JSON 串作为 axiosdata 属性值进行请求参数的提交。如下:

var jsObject = {name:"张三"};

axios({
    method:"post",
    url:"http://localhost:8080/ajax-demo/axiosServlet",
    data: JSON.stringify(jsObject)
}).then(function (resp) {
    alert(resp.data);
})

axios 是一个很强大的工具。我们只需要将需要提交的参数封装成 js 对象,并将该 js 对象作为 axiosdata 属性值进行,它会自动将 js 对象转换为 JSON 串进行提交。如下:

var jsObject = {name:"张三"};

axios({
    method:"post",
    url:"http://localhost:8080/ajax-demo/axiosServlet",
    data:jsObject  //这里 axios 会将该js对象转换为 json 串的
}).then(function (resp) {
    alert(resp.data);
})

注意:

  • js 提供的 JSON 对象我们只需要了解一下即可。因为 axios 会自动对 js 对象和 JSON 串进行想换转换。
  • 发送异步请求时,如果请求参数是 JSON 格式,那请求方式必须是 POST。因为 JSON 串需要放在请求体中。

5.3 JSON串和Java对象的相互转换

学习完 json 后,接下来聊聊 json 的作用。以后我们会以 json 格式的数据进行前后端交互。前端发送请求时,如果是复杂的数据就会以 json 提交给后端;而后端如果需要响应一些复杂的数据时,也需要以 json 格式将数据响应回给浏览器。

image-20210831104901912

在后端我们就需要重点学习以下两部分操作:

  • 请求数据:JSON字符串转为Java对象
  • 响应数据:Java对象转为JSON字符串

接下来给大家介绍一套 API,可以实现上面两部分操作。这套 API 就是 Fastjson

5.3.1 Fastjson 概述

Fastjson 是阿里巴巴提供的一个Java语言编写的高性能功能完善的 JSON 库,是目前Java语言中最快的 JSON 库,可以实现 Java 对象和 JSON 字符串的相互转换。

5.3.2 Fastjson 使用

Fastjson 使用也是比较简单的,分为以下三步完成

  1. 导入坐标

    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.62</version>
    </dependency>
    
  2. Java对象转JSON

    String jsonStr = JSON.toJSONString(obj);
    

    将 Java 对象转换为 JSON 串,只需要使用 Fastjson 提供的 JSON 类中的 toJSONString() 静态方法即可。

  3. JSON字符串转Java对象

    User user = JSON.parseObject(jsonStr, User.class);
    

    将 json 转换为 Java 对象,只需要使用 Fastjson 提供的 JSON 类中的 parseObject() 静态方法即可。

5.3.3 代码演示
  • 引入坐标

  • 创建一个类,专门用来测试 Java 对象和 JSON 串的相互转换,代码如下:

    public class FastJsonDemo {
    
        public static void main(String[] args) {
            //1. 将Java对象转为JSON字符串
            User user = new User();
            user.setId(1);
            user.setUsername("zhangsan");
            user.setPassword("123");
    
            String jsonString = JSON.toJSONString(user);
            System.out.println(jsonString);//{"id":1,"password":"123","username":"zhangsan"}
    
    
            //2. 将JSON字符串转为Java对象
            User u = JSON.parseObject("{\"id\":1,\"password\":\"123\",\"username\":\"zhangsan\"}", User.class);
            System.out.println(u);
        }
    }
    

以上内容来自黑马学习笔记,笔记上传便于复习回顾。

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

热爱代码的猿猿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值