javaweb笔记

javaweb基础

1 web相关的概念

1.1 软件的基本架构

C/S(Client-Server)。比如我们手机上的app QQ软件 飞秋

特点:必须下载特定的客户端程序。服务端升级之后,客户端也需要随着升级。

B/S(Broswer-Server).比如京东网站,腾讯qq官方网站

特点:只需要安装浏览器就可以访问。如果服务器端升级了,浏览器端不需要随之升级。

JavaWeb开发,开发的软件是基于B/S的软件架构。

1.2 资源

资源就是用户想要获取的资源。资源分为两种,一种是静态资源,一种是动态资源。

静态资源是不会经常发生变化的资源,比如HTML CSS JS 图片。

动态资源是会发生变化的资源,比如servlet jsp。如果我们想要获取动态资源,这些动态资源必须通过服务器部署之后才能获取。

1.3 服务器

服务器的种类有很多,有文件服务器、邮件服务器、web服务器、数据库服务器。我们的动态资源是部署在web服务器上的。

web服务器有很多,比如tomcat、jetty、weblogic,jboss。

1.3.1 tomcat服务器
1.3.1 手动启动tomcat服务器

找到tomcat的bin目录,通过startup.bat命令启动。shutdown.bat关闭服务器

1.3.2 使用IDEA整合tomcat服务器
  • 在IDEA中创建JAVAEE项目

在这里插入图片描述

点击next。

在这里插入图片描述

点击Finish。

创建的项目结构如下:

在这里插入图片描述

src:java代码就是定义在src目录下面。

web:静态资源比如 html css js可以定义在web下面。

web-inf:里面的资源不能直接被外界访问 web.xml 是web项目的核心配置文件

lib: 放置jar包。

index.jsp web项目的访问首页,在默认情况我们访问的首页就是index.jsp。

创建web项目需要注意的事项:

在这里插入图片描述

2 Http协议

什么是Http协议: 浏览器和服务器之间进行数据传输需要遵循的格式规范。

2.1 请求行 请求头

如何查看协议内容?在浏览器端按F12。找到网络就可以查看

在这里插入图片描述

2.1.1 获取请求行里面的数据

编写一个servlet,在servlet里面获取请求行相关的数据。

package com.qf.servlet;

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("/demo1")//必须根据这个路径才能够访问当前的servlet
public class ServletDemo1 extends HttpServlet {

    /**
     * @param request 浏览器向服务器发送请求,需要携带一些数据,这些数据就封装在request对象里面
     * @param response 服务器接收用户的请求,并处理用户的请求之后,需要将处理好的数据响应给浏览器。这些处理好的数据就封装到response对象里面。
     * @throws ServletException
     * @throws IOException
     * doGet()方法是tomcat服务器去调用的。服务器接收用户的请求和响应用户的请求都是在doGet方法里面去做的。
     * doGet方法是介绍浏览器发送过来的get方式的请求。
     * get请求: 通过浏览器地址栏直接发送的请求就是get请求。如果需要携带参数,一般会使用?拼接参数,参数与参数之间使用&分割。
     * 使用get方式提交数据,数据不能超过1kb。比如:
     *    http://localhost:8080/hello?id=1&name=eric&age=12
     * post请求:通过表单数据,并且指定表单的属性method ="post"。使用post提交数据,数据不会在浏览器地址栏里面显示出来。提交的数据没有限制
     */
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //获取请求的url(统一资源定位符:用来定位互联网里面的网络资源)
        StringBuffer url = request.getRequestURL();
        System.out.println(url);
        //获取请求方式
        String method = request.getMethod();
        System.out.println(method);
        String protocol = request.getProtocol();
        //获取协议版本 http/1.1
        System.out.println(protocol);
    }
}
2.1.2 获取请求头里面的数据

**Accept:**浏览器能够接收服务器响应的数据类型。

**Accept-Encoding:**浏览器能够接收服务器响应的数据的压缩格式。

Accept-Language: 浏览器能够接收的语言类型。

Host: 当前请求访问的目标地址。

**Cookie:**当前请求需要携带的cookie信息。

@WebServlet("/demo2")//必须根据这个路径才能够访问当前的servlet
public class ServletDemo2 extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //获取Accept请求头相关的信息
        String accept = request.getHeader("Accept");//根据请求头的名称获取请求头的值
        System.out.println(accept);
        String host = request.getHeader("Host");
        System.out.println(host);
        //获取所有请求头的信息
        Enumeration<String> names = request.getHeaderNames();
        while (names.hasMoreElements()){
            String name = names.nextElement();
            String value = request.getHeader(name);
            System.out.println(name + "<---->" + value);
        }
    }
}
2.1.3 获取实体内容

实体内容就是表单提交的数据。它是一个字符串。

  • 定义html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>
    <form action="/demo3" method="post">
        用户名: <input type="text" name="username"> <br>
        密码: <input type="password" name="password"> <br>
        <input type="submit" value="提交">
    </form>
</body>
</html>
  • 定义servlet
@WebServlet("/demo3")
public class ServletDemo3 extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        ServletInputStream in = request.getInputStream();
        byte[] bytes = new byte[1024];
        int length = 0;
        while((length = in.read(bytes)) != -1){
            String str = new String(bytes,0,length);
            System.out.println(str);// username=admin&password=admin123
        }
    }
}

问题:通过获取实体内容里面的数据,我们不能直接使用。如果我们需要获取表单中的数据,我们还需要对这个实体内容字符串进行二次操作。在javaweb里面我们有专门获取请求携带参数的方法。

2.1.4 请求参数的获取

String value = request.getParameter(String name);

@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    /*ServletInputStream in = request.getInputStream();
        byte[] bytes = new byte[1024];
        int length = 0;
        while((length = in.read(bytes)) != -1){
            String str = new String(bytes,0,length);
            System.out.println(str);// username=admin&password=admin123
        }*/
    //获取表单里面提交的参数
    String username = request.getParameter("username");
    String password = request.getParameter("password");
    System.out.println(username);
    System.out.println(password);
}

这个方法不仅仅可以获取表单里面提交的参数信息,也可以获取浏览器地址栏里面通过?拼接的参数。

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    String username = request.getParameter("username");
    System.out.println(username);
}

浏览器地址栏发送请求: localhost:8081/demo3?username=eric

案例:表单提交数据,后台接收

<form action="/demo4" method="post">
    用户名: <input type="text" name="username"> <br>
    密码: <input type="password" name="password"> <br>
    性别: <input type="radio" name="gender" value="man" checked><input type="radio" name="gender" value="woman"> 女
    兴趣爱好: <input type="checkbox" name="hobby" value="football"> 足球
    <input type="checkbox" name="hobby" value="basketball"> 篮球
    <input type="checkbox" name="hobby" value="swimming"> 游泳
    个人简介: <textarea rows="10" cols="30"></textarea> <br>
    <input type="submit" value="提交">
</form>
package com.qf.servlet;

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;
import java.util.Enumeration;

@WebServlet("/demo4")
public class ServletDemo4 extends HttpServlet {

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

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //批量获取表单中所有参数名称
        Enumeration<String> parameterNames = request.getParameterNames();
        while(parameterNames.hasMoreElements()){
            String name = parameterNames.nextElement();
            if(name.equals("hobby")){
                String[] hobbies = request.getParameterValues("hobby");//获取复选框里面的数据
                for(String str : hobbies){
                    System.out.println("hobby" + "--->" + str);
                }
            }else{
                String value = request.getParameter(name);
                System.out.println(name + "--->" + value);
            }
        }
    }
}

如果我们表单提交的是中文数据,在后台servlet里面,会出现中文乱码的情况。这个时候我们需要解决中文乱码问题。

如果是post提交乱码

在这里插入图片描述

如果是get方式乱码

解决方法可以和post解决方式一样。还有一种解决乱码的方式,就是将字符串重新解码再编码

在这里插入图片描述

2.2 响应头

状态码:服务器向浏览器响应数据的状态。

常见的状态:

200:表示请求处理完成并完美返回。

302:表示请求需要进一步细化。

404:表示客户端需要访问的资源找不到。

500:表示服务器内部资源错误。

405:请求方式不正确。

400:请求参数异常。

403:权限不足。

Connection: 表示浏览器和服务器之间的连接状态 keep-alive:表示连接状态。

**Content-Length:**表示服务器向浏览器响应数据的长度。

**Date:**表示服务器向浏览器发送响应的时间。

2.2.1 响应头相关的操作
@WebServlet("/demo5")
public class ServletDemo5 extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //设置响应状态码
        response.setStatus(500);
        //设置响应头
        response.setHeader("username","eric");
        //设置服务器向浏览器响应数据的类型以及数据的编码格式。
        response.setContentType("text/html;charset=utf-8");
        //响应数据 -- 响应纯文本内容
        response.getWriter().write("马上就要放七天,真开心");
        //响应数据 -- 响应HTML类型的文本
        response.getWriter().write("<a href='https://www.qq.com'>[跳转到]</a>");
    }
}
2.2.2 相关案例
  • 使用请求重定向跳转到对应的HTML页面
/**
  * 使用请求重定向实现资源的跳转
  * @param request
  * @param response
  * @throws ServletException
  * @throws IOException
  */
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    //设置响应头
    //response.setStatus(302);
    //告诉浏览器下一次请求资源的真正地址
    //response.setHeader("location","/success.html");
    //上面的两句代码可以合成一行代码
    response.sendRedirect("/success.html");
}
  • 实现当前系统的定时刷新
/**
  * 实现当前系统时间的定时刷新
  * @param request
  * @param response
  * @throws ServletException
  * @throws IOException
  */
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    //设置向浏览器响应内容的格式
    response.setContentType("text/html;charset=utf-8");
    Date date = new Date();
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    String time = sdf.format(date);
    response.getWriter().write("当前系统时间是:" + time);
    //定时刷新网页
    //response.setHeader("Refresh","1");
    //指定时间之后,网页刷新并跳转到指定的页面
    response.setHeader("Refresh","3;url=/success.html");
}
  • 使用服务器向浏览器发送图片信息
@WebServlet("/test3")
public class ServletTest3 extends HttpServlet {

    /**
     * 服务器向浏览器输出图片信息
     * @param request
     * @param response
     * @throws ServletException
     * @throws IOException
     */
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //设置服务器向浏览器响应的数据格式是jpg类型
        response.setContentType("image/jpeg");
        File file = new File("C:\\Users\\kriss\\Pictures\\Saved Pictures\\AudiA7.jpg");
        FileInputStream in = new FileInputStream(file);
        byte[] buf = new byte[1024];
        int len = 0;
        while((len = in.read(buf)) != -1){
            response.getOutputStream().write(buf,0,len);
        }
        in.close();
    }
}

3 Servlet

什么是Servlet?

Servlet就是一个普通的java类。这个java类必须要依赖于服务器才能运行。这种类就必须遵循一定的规则(继承某个类,或者实现某个接口)。

3.1 Servlet快速入门

第一步:创建一个javaee项目。

第二步:定义一个类,实现Servlet接口。

package com.qf.servlet;

import javax.servlet.*;
import javax.servlet.annotation.WebServlet;
import java.io.IOException;
public class ServletDemo1 implements Servlet {

    @Override
    public void init(ServletConfig servletConfig) throws ServletException {

    }

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

    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        servletResponse.setContentType("text/html;charset=utf-8");
        servletResponse.getWriter().write("hello");
    }

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

    @Override
    public void destroy() {

    }
}

第三步:配置servlet

<!--
   配置Servlet
   servlet-name:servlet的名称
   servlet-class:servlet的全限定名
   url-pattern: 访问servlet的url
 -->
<servlet>
    <servlet-name>demo1</servlet-name>
    <servlet-class>com.qf.servlet.ServletDemo1</servlet-class>
</servlet>

<servlet-mapping>
    <servlet-name>demo1</servlet-name>
    <url-pattern>/demo1</url-pattern>
</servlet-mapping>

配置servlet的方式有两种:一种是xml的配置,还有一种是通过注解的方式进行配置。

@WebServlet("/demo1")
public class ServletDemo1 implements Servlet {}

3.2 servlet的生命周期

3.2.1 什么是servlet的生命周期

Servlet的生命周期就是servlet类对象什么时候创建?什么时候调用对应的方法,什么时候销毁。

以前的对象的生命周期:

Student student = new Student(); //创建对象
student.setName("eric"); // 使用对象
student.show();// 使用对象
student = null; // 销毁对象

也就是说自定义对象的生命周期由我们程序员自己手动控制。但是!!!Servlet它不是一个普通的java类。是一个被tomcat服务器调用的。所以Servlet是生命周期是被tomcat服务器去控制的。

3.2.2 servlet生命周期中重要的方法

构造方法:创建servlet的时候调用。默认情况下,第一次访问servlet的时候,会创建servlet对象。此时会有且只会调用1次构造函数。

证明了servlet对象是单实例的。

init方法:创建完servlet对象之后调用,也只是会调用1次。

service方法:提供服务的方法,接收用户的请求,并处理用户的请求,然后响应用户的请求。每次发送请求,都会调用service方法。调用几次,service方法会执行几次。

destroy方法:销毁的方法。销毁servlet对象的时候调用。比如我们停止tomcat服务器或者重新部署tomcat服务器,都会销毁servlet对象,只会调用1次。

我们可以给大家演示servlet的生命周期(tomcat服务器内部执行代码的原理):

1、用户发送请求,tomcat服务器会根据用户发送的请求,解析web.xml配置文件,获取servlet-class的全限定名路径(com.qf.servlet.ServletDemo1)
2、获取字节码对象,然后通过字节码对象获取对应的实例对象
Class clazz = Class.forName("com.qf.servlet.ServletDemo1");
Object o = clazz.newInstance();
3、创建ServletConfig对象,然后调用init方法
Method method = clazz.getDeclaredMethod("init",ServletConfig.class);// 获取方法对象
method.invoke(o,config);
4、创建request对象,创建response对象,然后调用service方法
Method m = clazz.getDeclaredMethod("service",ServletRequest.class,ServletResponse.class);
m.invoke(o,request,response);
5、销毁servlet实例对象,也是通过反射的机制实现的
Method m1 = clazz.getDeclaredMethod("destroy",null);
m1.invoke(o,null);

测试servlet的声明周期:

public class ServletDemo1 implements Servlet {

    public ServletDemo1(){
        System.out.println("ServletDemo1 has run....");
    }

    @Override
    public void init(ServletConfig servletConfig) throws ServletException {
        System.out.println("init method has run....");
    }

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

    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        System.out.println("service method has run.....");
    }

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

    @Override
    public void destroy() {
        System.out.println("destroy method has run.....");
    }
}

3.3 Servlet中的一些细节

3.3.1 Servlet的自动加载

默认情况下,第一次访问servlet的时候,创建servlet对象。如果servlet构造函数里面的代码或者init方法里面的代码比较多,就会导致用户第一次访问servlet的时候比较慢。这个时候,我们可以改变servlet对象的创建时机:提前到加载web应用的时候。在servlet的配置信息中,加上一个<load-on-startup>标签即可。

<servlet>
    <servlet-name>demo1</servlet-name>
    <servlet-class>com.qf.servlet.ServletDemo1</servlet-class>
    <!--数字越小,提前加载时机的概率就会越高-->
    <load-on-startup>1</load-on-startup>
</servlet>

或者在@WebServlet注解里面配置:

@WebServlet(urlPatterns = "/demo1",loadOnStartup = 1)
public class ServletDemo1 implements Servlet {}

这样配置之后,servlet的构造函数和init方法就会在web应用加载的时候就会执行。

3.3.2 servlet是单实例,多线程的

在这里插入图片描述

问题:如果servlet是多线程访问。所以当多个线程同时访问servlet资源中的共享数据,此时容易出现数据安全问题。

static int count = 500;
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
    System.out.println("service method has run.....");
    synchronized (ServletDemo1.class){
        servletResponse.getWriter().write("num is :" + count);
        count ++;
    }
}
3.3.3 ServletConfig对象

作用:就是用于加载servlet的初始化参数,一个web应用可以存在多个ServletConfig对象(因为一个Servlet就对应一个ServletConfig对象)。

ServletConfig对象的创建时机:在Servlet对象创建之后,init方法调用之前完成创建。

ServletConfig对象如何获取? 在GenericServlet里面定义了:

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

需求:获取servlet里面定义的参数,执行文件读写的操作

<servlet>
    <servlet-name>demo2</servlet-name>
    <servlet-class>com.qf.servlet.ServletDemo2</servlet-class>
    <!--初始化参数的标签-->
    <init-param>
        <param-name>path</param-name>
        <param-value>F:\\upload\\hello.txt</param-value>
    </init-param>
</servlet>
public class ServletDemo2 extends HttpServlet {

    public void init() throws ServletException {
        //
    }

    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        servletResponse.setContentType("text/html;charset=utf-8");
        //获取初始化参数
        //ServletConfig config = this.getServletConfig();
        //String path = config.getInitParameter("path");
        String path = this.getServletConfig().getInitParameter("path");
        File file = new File(path);
        FileInputStream in = new FileInputStream(file);
        int len = 0;
        byte[] bytes = new byte[1024];
        while((len = in.read(bytes)) != -1){
            String msg = new String(bytes);
            System.out.println(msg);
        }
    }

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

    @Override
    public void destroy() {

    }
}

思考:path参数可不可以在另外的Servlet中获取? 不能

3.3.4 ServletContext对象

ServletContext对象,叫做Servlet上下文对象。表示整个web应用环境,一个Web应用只能有1个ServletContext对象。

创建时机:加载web应用的时候,创建ServletContext对象。

如何获取这个对象:

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

这个对象里面也有很多方法:

  • getInitParameter(String name) 获取共享的参数信息
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
    servletResponse.setContentType("text/html;charset=utf-8");
    ServletContext context = this.getServletContext();
    String username = context.getInitParameter("username");
    System.out.println(username);
}
  • getContextPath() 获取web项目部署在tomcat服务器上的名称
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
    servletResponse.setContentType("text/html;charset=utf-8");
    String path = context.getContextPath();
    System.out.println(path);
}
  • service方法和doXX方法

在HttpServlet类里面,重写了service方法。重写的规则是,通过请求方式,来判断用户到底应该调用哪个方法。

在这里插入图片描述

3.4 域对象

域对象:作用是保存数据,获取数据的。可以在不同的动态资源之间实现资源的共享。

需求:实现ServletDemo4和ServletDemo5之间的数据共享。

  • 方案1:使用重定向来实现ServletDemo4到ServletDemo5之间的跳转。并通过?拼接参数的方式来实现数据的传递。

(1) 在ServletDemo4实现重定向,并通过?拼接参数。

@WebServlet("/demo4")
public class ServletDemo4 extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String name = "kobe";
        //需要通过重定向来实现资源跳转
        response.sendRedirect("/demo5?name=" + name);
    }
}

(2) 在ServletDemo5中获取参数。

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

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html;charset=utf-8");
        String name = request.getParameter("name");
        response.getWriter().write("这是ServletDemo5,获取到的参数值是:" + name);
    }
}

这种方式可以实现数据的传递,但是只能传递简单的字符串数据。如果我们想传递更多类型的数据,使用?拼接的方式就不现实了。我们可以通过域对象ServletContext能实现更多类型数据的传递。

通过域对象保存数据:void setAttribute(String name,Object value);

通过域对象获取数据:Object getAttribute(String name);

  • 方案2 通过域对象存取数据

(1) 在ServletDemo4里面通过域对象存储数据

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOExcep{
    //通过域对象存储数据
    Student student = new Student();
    student.setId(10010);
    student.setName("lily");
    student.setAge(18);
    ServletContext context = this.getServletContext();
    context.setAttribute("name","james");//存储简单字符串数据
    context.setAttribute("student",student);//存储对象类型的数据
    response.sendRedirect("/demo5");
}

(2) 在ServletDemo5里面通过域对象获取数据

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    response.setContentType("text/html;charset=utf-8");
    ServletContext context = this.getServletContext();
    System.out.println(context.getAttribute("name").toString());
    Student student = (Student) context.getAttribute("student");
    System.out.println(student);
}

3.5 重定向与转发

3.5.1 重定向

重定向是实现资源的跳转。

需求:通过ServletDemo4重定向到ServletDemo5:

在这里插入图片描述

重定向一共发送了2次request请求。第一个request请求,没有真正的请求到资源。第一次request请求完成之后,服务器会给浏览器响应一个302状态码加上进一步被请求的资源。

然后服务器再次发送第二次request请求,这次请求才真正的请求到资源。

重定向实现资源跳转,浏览器地址栏里面的地址会发生变化。

3.5.2 转发

需求:通过ServletDemo4转发到ServletDemo5:

在这里插入图片描述

转发一共发送了1次request请求。通过转发实现资源跳转,浏览器地址栏不会发生变化。

如何实现转发:

request.getRequestDispatcher(String path).forward(request,response);

思考:基于转发,如何实现参数的传递和共享。

  • 方案1:可以通过?拼接参数的方式,获取请求参数。

(1) 在ServletDemo4里面通过问号拼接参数

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    //通过转发实现资源的跳转
    request.getRequestDispatcher("/demo5?username=kobe").forward(request,response);
}

(2) 在ServletDemo5里面获取参数

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    response.setContentType("text/html;charset=utf-8");
    String username = request.getParameter("username");
    response.getWriter().write("这是ServletDemo5,用户名是:" + username);
}
  • 方案2:可以通过ServletContext域对象实现数据的共享

  • 方案3:可以通过Request作用域实现数据的共享

(1) 在ServletDemo4里面使用Request对象存储数据

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOExcep{
    //通过域对象存储数据
    Student student = new Student();
    student.setId(10010);
    student.setName("lily");
    student.setAge(18);
    request.setAttribute("name","james");//存储简单字符串数据
    request.setAttribute("student",student);//存储对象类型的数据
    //request.removeAttribute("name");//将域对象中的数据移除。
    request.getRequestDispatcher("/demo5").forward(request,response);
}

(2) 在ServletDemo5里面使用Request对象取数据

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    response.setContentType("text/html;charset=utf-8");
    Student student = (Student) request.getAttribute("student");
    System.out.println(student);
    String username = request.getAttribute("name");
    System.out.println(username)
    response.getWriter().write("这是ServletDemo5,用户名是:" + username);
}

注意:在重定向中,千万不要使用Request作用域共享数据,因为共享的数据获取不到。

为什么?

因为重定向发送了两个Request请求。第一次Request请求中,我们存储了数据到Request域对象中。然后我们在第二个Request请求中去取数据。由于存取数据的Request对象不是同一个Request对象。所以获取不到数据。

4 会话管理

软件中的会话:

打开浏览器–>访问服务器里面的一些资源–>关闭浏览器

举例:

打开京东服务器,在京东网站里面搜索商品,并将商品信息保存在购物车里面,这就是一次会话。 保存在购物车里面的商品信息就是会话数据。当我们关闭浏览器,再次打开京东服务器,我们发现购物车里面的数据依然存在。

问题:购物车里面的数据是如何保存的呢?

通过会话技术可以保存数据。

Cookie:将会话数据保存在浏览器端

Session:将会话数据保存在服务器端

4.1 Cookie技术

4.1.1 Cookie使用的核心API

cookie的作用就是将数据保存在浏览器端。如何使用Cookie?

(1) 构建Cookie对象

Cookie cookie = new Cookie(String name,String value);

(2) 设置Cookie信息

setPath(String path) 设置cookie的访问路径
setMaxAge(int expire) 设置Cookie的过期时间
setValue(String newValue) 设置cookie的值

(3) 将Cookie信息发送到浏览器端保存

response.addCookie(Cookie cookie);

(4) 服务器接收cookie

Cookies[] cookies = request.getCookies();
4.1.2 Cookie原理

(1) 服务器创建Cookie对象,用来将会话数据保存在cookie对象中

(2) 服务器将Cookie发送到浏览器端保存。

(3) 浏览器得到服务端发送过来的Cookie,然后再保存在浏览器端。

(4) 浏览器在下一次访问服务器时,会带着cookie信息到服务器。(在请求头里面会携带一个Cookie的头信息。cookie:username=eric)。

(5) 服务器要接收浏览器带来的cookie信息

@WebServlet("/demo1")
public class CookieDemo1 extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html;charset=utf-8");
        // 1.创建Cookie对象
        Cookie cookie = new Cookie("name","eric");
        Cookie cookie1 = new Cookie("password","admin");
        // 2.设置Cookie
        //cookie.setPath("/java2201"); //设置Cookie的有效路径
        cookie.setMaxAge(60*60);//设置Cookie的过期时间  1个小时
        //3. 发送cookie到浏览器端保存
        response.addCookie(cookie);
        response.addCookie(cookie1);
        //4. 服务器获取Cookie
        Cookie[] cookies = request.getCookies();
        if(cookies != null){
            for(Cookie c : cookies){
                String name = c.getName();//获取cookie的名称
                String value = c.getValue();
                response.getWriter().write("cookie名称是:" + name + ",cookie的值是:" + value + "<br>");
            }
        }else{
            response.getWriter().write("没有获取cookie信息");
        }
    }
}

注意:

1.为什么第一次访问页面上没有显示cookie,而第2次 第n次访问才能获取cookie信息。

第一次访问Servlet。是在创建cookie,并将创建好的cookie发送到浏览器端。

第二次访问Servlet。就是将浏览器端保存的cookie信息携带到服务器。

2.使用Cookie保存数据有一定的局限性。那就是只能保存非中文的字符串数据,每个cookie的大小限制在4kb。

4.1.3 cookie的案例–显示用户上次访问的系统时间
package com.qf.cookie;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * 需求:显示用户上次访问的系统时间
 */
@WebServlet("/demo2")
public class CookieDemo2 extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html;charset=utf-8");
        Date date = new Date();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日HH:mm:ss");
        //获取当前系统时间
        String current_time = sdf.format(date);
        //记录上一次访问系统的时间
        String last_time = null;
        //获取所有的cookie信息
        Cookie[] cookies = request.getCookies();
        if(cookies != null){
            for(Cookie cookie : cookies){
                if(cookie.getName().equals("lasttime")){
                    last_time = cookie.getValue();
                    response.getWriter().write("当前访问的系统时间是:" + current_time + "你上一次访问的系统时间是:" + last_time);
                    //更新cookie的值
                    cookie.setValue(current_time);
                    response.addCookie(cookie);
                    break;
                }
            }
        }
        if(cookies == null || last_time == null){ //如果浏览器中不存在任何cookie 或者记录上一次系统时间的cookie值不存在
            response.getWriter().write("当前系统时间是:" + current_time);
            response.addCookie(new Cookie("lasttime",current_time));
        }
    }
}
4.1.4 查看用户浏览过的商品信息
  • 定义实体,描述商品信息
public class Computer {

    private String id;
    private String name;
    private String type;
    private Double price;

    public Computer(){

    }

    public Computer(String id, String name, String type, Double price) {
        this.id = id;
        this.name = name;
        this.type = type;
        this.price = price;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public Double getPrice() {
        return price;
    }

    public void setPrice(Double price) {
        this.price = price;
    }

    @Override
    public String toString() {
        return "Computer{" +
                "id='" + id + '\'' +
                ", name='" + name + '\'' +
                ", type='" + type + '\'' +
                ", price=" + price +
                '}';
    }
}
  • 定义一个类,初始化商品信息
public class ComputerService {

    static List<Computer> list = new ArrayList<Computer>();

    //使用静态代码块初始化商品信息
    static {
        list.add(new Computer("1001","苹果笔记本电脑","苹果(Apple) macbook pro 13.3英寸 2022款",13444.0));
        list.add(new Computer("1002","苹果笔记本电脑","苹果(Apple) macbook air 13.3英寸 2022款",8444.0));
        list.add(new Computer("1003","惠普笔记本电脑","惠普(HP) ENVY14英寸十一代酷睿标压i5/i7 超轻薄2.2K全面屏触控屏设计游戏笔记本电脑",6888.0));
        list.add(new Computer("1004","戴尔笔记本电脑","戴尔(DELL) 灵越15Pro 15.6英寸12代酷睿轻薄窄边框高刷学生商务办公设计笔记本电脑",4674.0));
        list.add(new Computer("1005","外星人笔记本电脑","外星人(alienware) m15 R7高端游戏本全新12代酷笔记本电脑15.6英寸电竞",21999.0));
    }

    //显示所有商品信息
    public List<Computer> findAll(){
        return list;
    }

    //显示商品的详情信息
    public Computer getDetailById(String id){
        for(Computer computer : list){
            if(computer.getId().equals(id)){
                return computer;
            }
        }
        return null;
    }
}
  • 展示所有商品信息和浏览过的商品记录
@WebServlet("/demo3")
public class CookieDemo3 extends HttpServlet {

    ComputerService computerService = new ComputerService();

    /**
     * 展示所有商品信息
     * @param request
     * @param response
     * @throws ServletException
     * @throws IOException
     */
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
       response.setContentType("text/html;charset=utf-8");
        List<Computer> list = computerService.findAll();
        String html = "<body><table border='1' width='800' align='center' cellspacing='0'>" +
                "<tr align='center'><th>商品编号</th><th>商品名称</th><th>商品标题</th><th>商品价格</th></tr>";
        for(Computer p : list){
            html +="<tr align='center'><td>" + p.getId() + "</td><td><a style='text-decoration:none' href='/demo4?id="+p.getId()+"'>" + p.getName() + "</a></td><td>" + p.getType() + "</td><td>"
            + p.getPrice()+"</td></tr>";
        }
        html +="</table></body>";
        //将拼接出来的数据 显示在浏览器端
        response.getWriter().write(html);

        //取出浏览的商品信息,并将其展示出来
        Cookie[] cookies = request.getCookies();
        if(cookies != null){
            response.getWriter().write("<center>查看历史浏览记录</center>");
            for(Cookie cookie : cookies){
                if(cookie.getName().equals("goods")){
                    String value = cookie.getValue();//1002-1004-1005
                    String[] arr = value.split("-");
                    for(String id : arr){
                        Computer com = computerService.getDetailById(id);
                        response.getWriter().write("<table border='1' width='800' align='center' cellspacing='0'><tr>" +
                                "<td>"+com.getId()+"</td><td>"+com.getName()+"</td><td>"+com.getType()+"</td><td>"+com.getPrice()+"</td></tr><table>");
                    }
                    break;
                }
            }
        }
    }
}
  • 展示商品详情、动态获取对应的cookie信息
@WebServlet("/demo4")
public class CookieDemo4 extends HttpServlet {

    ComputerService computerService = new ComputerService();

    /**
     * 通过cookie存储浏览过的商品id
     * goods:1
     * goods(1):2-1  追加开头的
     * goods(2-1):4-2-1 追加开头的
     * goods(4-2-1):5-4-2   删除末尾的 追加开头的
     * -------
     * goods:1 删除原来的id,添加现有的id
     * goods:2-1   首位添加现有的  删除重复的
     * goods(4-2-1) 首位添加现有的  删除重复的
     * @param request
     * @param response
     * @throws ServletException
     * @throws IOException
     */
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html;charset=utf-8");
        String id = request.getParameter("id");
        Cookie cookie = new Cookie("goods",getCookie(id,request));
        response.addCookie(cookie);
        Computer c = computerService.getDetailById(id);
        String html = "<body><table border='1' width='800' align='center' cellspacing='0'>" +
                "<tr align='center'><th>商品编号</th><td>"+c.getId()+"</td></tr>" +
                "<tr align='center'><th>商品名称</th><td>"+c.getName()+"</td></tr>" +
                "<tr align='center'><th>商品类型</th><td>"+c.getType()+"</td></tr>" +
                "<tr align='center'><th>商品类型</th><td>"+c.getPrice()+"</td></tr></table>" +
                "<center><a style='text-decoration:none' href='/demo3'>[返回商品列表]</a></center></body>";
        response.getWriter().write(html);
    }

    //定义cookie的方法
    private String getCookie(String id, HttpServletRequest request) {
        String cookieValue = null; //记录cookie的值
        Cookie[] cookies = request.getCookies();
        if(cookies != null){
            for(Cookie cookie : cookies){
                if(cookie.getName().equals("goods")){
                    cookieValue = cookie.getValue();
                    break;
                }
            }
        }
        if(cookies == null || cookieValue == null){
            return id;
        }
        // 将历史cookie值变成一个字符串数组
        String[] arr = cookieValue.split("-");
        List<String> list = Arrays.asList(arr);
        //将list集合转换成LinkedList
        LinkedList<String> linkedList = new LinkedList<>(list);
        if(linkedList.size() < 3){
            if(linkedList.contains(id)){
                //删除重复的
                linkedList.remove(id);
                //追加新的
                linkedList.addFirst(id);
            }else{
                linkedList.addFirst(id);
            }
        }
        if (linkedList.size() == 3){
            if(linkedList.contains(id)){
                //删除重复的
                linkedList.remove(id);
                //追加新的
                linkedList.addFirst(id);
            }else{
                //添加最新的
                linkedList.addFirst(id);
                //删除最末尾的
                linkedList.removeLast();
            }
        }
        //将LinkedList转换成字符串
        StringBuffer buff = new StringBuffer();
        for(String s : linkedList){
            buff.append(s + "-");
        }
        String str = buff.toString();// 1002-1004-1002-
        str = str.substring(0,str.length()-1);
        return str;
    }
}

4.2 Session技术

Cookie的局限性:

(1) cookie保存的数据类型比较单一,只能保存字符串类型的数据,不能保存引用类型的数据。

(2) 不能存储中文。

(3) 一个cookie值保存的数据不能超过4kb。

解决方案:我们可以使用Session技术保存数据。session保存数据的特点是数据保存在服务器端。

4.2.1 Session技术的核心API

HttpSession类

  • 创建或得到session对象
getSession();
getSession(boolean create);
  • 设置session
setMaxInactiveInterval(int expire) 设置session的有效时间
invalidate() 销毁session
getId() 获取session编号
  • 保存数据到session里面
setAttribute(String name,Object value); 存数据
Object o = getAttribute(String name);  取数据
removeAttribute(String name); 删除数据
4.2.2 Session的原理

问题:服务器内部会保存很多不同的session。如何去识别我们想要的session。

  • 创建session的时候,服务器会给session自动分配一个唯一的标识,这个标识就是sessionid。然后服务器会把这个sessionid以cookie的形式发送到浏览器端保存。
  • 当用户发送请求,会携带sessionid的cookie到服务器端,服务器会解析cookie,并将sessionid拿到,通过这个唯一标识,去找对应的session。
@WebServlet("/session1")
public class SessionDemo1 extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
       response.setContentType("text/html;charset=utf-8");
       //创建session对象(获取session对象 如果session对象存在 则自动创建)
        HttpSession session = request.getSession();
        //设置session  设置session的有效时间 单位秒
        session.setMaxInactiveInterval(10);
        //在session里,设置数据
        session.setAttribute("name","james");
        //获取session的唯一标识
        String id = session.getId();
        System.out.println("JSESSIONID:" + id);
        Cookie cookie = new Cookie("JSESSIONID",id);
        cookie.setMaxAge(60*60);
        response.addCookie(cookie);
    }
}
@WebServlet("/session2")
public class SessionDemo2 extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //获取session对象 false 获取session对象 但是不创建session对象
        HttpSession session = request.getSession(false);
        //获取session中的数据
        String name = (String) session.getAttribute("name");
        System.out.println(name);
    }
}
@WebServlet("/session3")
public class SessionDemo3 extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //获取session对象 false 获取session对象 但是不创建session对象
        HttpSession session = request.getSession(false);
        //session.invalidate();//销毁session
        //移除session中的数据
        session.removeAttribute("name");
    }
}
4.2.3 session的案例

需求:使用session技术,实现用户的登录认证

  • 定义登录表单
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>登录页面</title>
</head>
<body>
    <form action="/login" method="post">
        用户名: <input type="text" name="username"> <br>
        密码: <input type="password" name="password"> <br>
        <input type="submit" value="登录">
    </form>
</body>
</html>
  • 定义登录的Servlet
@WebServlet("/login")
public class LoginServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        //假设用户的登录名eric 密码eric
        if(username.equals("eric") && password.equals("eric")){
            //用户名和密码输入成功 则将用户信息保存在session里面
            HttpSession session = request.getSession();
            session.setAttribute("loginName",username);
            //跳转到主页
            response.sendRedirect("/main");
        }else{
            //登录失败
            response.sendRedirect("/failLogin.html");
        }
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request,response);
    }
}
  • 定义失败页面failLogin.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>
  <h4>登录失败,请<a href="login.html">重新登录</a></h4>
</body>
</html>
  • 定义跳转到主页的Servlet
@WebServlet("/main")
public class MainServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html;charset=utf-8");
        HttpSession session = request.getSession(false);
        if(session == null){
            //如果session为空 重定向到登录页面
            response.sendRedirect("/login.html");
            return;
        }
        //取出会话数据
        String name = (String) session.getAttribute("loginName");
        if(name == null || name.equals("")){
            //name这个会话数据不存在 重定向到登录页面
            response.sendRedirect("/login.html");
            return;
        }
        response.getWriter().write("这是主页,欢迎你," + name + "<br>");
        response.getWriter().write("<a href='/exit'>[注销用户]<a>");
    }
}
  • 定义退出的Servlet
@WebServlet("/exit")
public class ExitServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html;charset=utf-8");
        HttpSession session = request.getSession(false);
        if(session != null){
            session.removeAttribute("loginName");
            //重定向到登录页面
            response.sendRedirect("/login.html");
        }
    }
}

5 JSP技术

5.1 JSP基础

5.1.1 jsp引入

Servlet和jsp语言都是开发动态资源的技术。JSP就是HTML语言嵌套java代码的技术。JSP就是Servlet。

JSP的特点:

JSP是交给服务器运行的,jsp页面上既可以写HTML代码也可以写JAVA代码。在定义JSP页面的时候,jsp都定义在web项目的web目录下面。

在这里插入图片描述

5.1.2 开发一个JSP页面

需求:将当前系统时间显示在页面上。

<%@ page import="java.util.Date" %>
<%@ page import="java.text.SimpleDateFormat" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title>$Title$</title>
  </head>
  <body>
    <%
      Date date = new Date();
      SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
      String time = sdf.format(date);
      //将信息输出到浏览器页面上进行显示
      out.write("当前系统时间是:" + time);
    %>
  </body>
</html>

在jsp页面上定义java代码,必须在<% %>内部定义java代码。

5.1.3 JSP的执行原理

我们发送http://localhost:8081/index.jsp 请求,为什么会显示具体的效果?

  1. 当我们访问index.jsp页面的时候,tomcat服务器会扫描index.jsp文件。然后会把index.jsp文件翻译成index_jsp.java文件。
  2. tomcat服务器会把这个index_jsp.java文件编译成index_jsp.class字节码文件。
  3. tomcat服务器会构造一个index_jsp.class文件的对象。
  4. index_jsp.class文件中的方法调用原理类似于Servlet里面方法的调用原理。

在这里插入图片描述

疑问:为什么说JSP就是Servlet

我们打开index_jsp.java 源文件,我们发现:

public final class index_jsp extends org.apache.jasper.runtime.HttpJspBase
    implements org.apache.jasper.runtime.JspSourceDependent,
                 org.apache.jasper.runtime.JspSourceImports {
                     .......
                 }

这个源文件继承了HttpJspBase类。而HttpJspBase类又是什么?

HttpJspBase属于jasper.jar里面的一个类。这个类位于org.apache.jasper.runtime目录下面,我们找到这个类发现:

public abstract class HttpJspBase extends HttpServlet implements HttpJspPage {
    .......
}

HttpJspBase这个类继承了HttpServlet。所以我们认为jsp源文件间接的继承了HttpServlet。所以我们说JSP就是Servlet。那么Servlet里面技术可以在JSP里面运用。但是JSP技术对Servlet技术进行了一些扩展。

5.1.4 JSP的生命周期

Servlet的生命周期:

(1) 构造Servlet对象(调用Servlet的无参数的构造函数),只会在第一次访问Servlet的时候调用该函数。

(2) 执行带参数的init方法,只会在第一次访问Servlet的时候调用该方法。

(3) 执行service方法。每次调用Servlet的时候,都会执行该方法。

(4) 调用destroy方法。服务器停止或者重新部署的时候,会调用该方法。

JSP的生命周期:

(1) 翻译jsp。 index.jsp 翻译成index_jsp.java源文件

(2)编译源文件。 index_jsp.java --> index_jsp.class 字节码文件

(3) 调用构造函数

(4) 执行init方法。 _jspInit()方法

(5) 执行service方法 _jspService()方法

(6) 调用destroy方法 _jspDestroy()方法

注意:每次更改jsp里面的代码之后,重新部署服务器,此时tomcat会将原先翻译、编译好的java源文件全部删除,再次发送请求,会重新翻译、编译jsp文件。

5.1.5 JSP的语法
5.1.5.1 JSP模板

语法:<%=变量或者表达式%>

作用:就是向浏览器输出变量的值或者表达式的结果。

<%
  String username = "eric";
%>
<%--JSP模板--%>
<%=username%> <br>
<%=10*10%>
5.1.5.2 JSP脚本

语法:<%java代码%>

作用:在JSP页面里面定义java代码,并去执行这些java代码。

注意:tomcat服务器再运行的时候。会将脚本里面的java代码原封不动的拷贝至服务器内部对应的源文件中的_jspService方法中执行。

在这里插入图片描述

我们可以将HTML代码和JAVA代码一起穿插使用。

需求:循环输出h标题。

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    <%
        for(int i = 1;i<=6;i++){
           %>
    <h<%=i%>>标题<%=i%></h<%=i%>>
    <%
        }
    %>
</body>
</html>
5.1.5.3 jsp声明

语法:<%! 变量或方法%>

需要注意的是:JSP声明里面定义的变量是成员变量(在JSP脚本里面定义的变量是局部变量)。在JSP声明里面定义的方法都是成员方法。翻译之后的结果是:

在这里插入图片描述

如果我想调用jsp声明里面的方法,应该怎么做? 在脚本里面调用。

<%
	test();
%>
5.1.5.4 JSP注释

注释的语法:

<%--注释的内容--%>

注意:在JSP页面里面既可以定义HTML类型的注释,也可以定义JSP类型的注释。在翻译的时候,jsp类型的注释不会被翻译进去。但是html类型的注释会被翻译进去。

5.1.5.5 JSP指令
  • include指令

作用:include包含的意思,这个指令就是包含其他jsp页面的意思。

语法:<%@ include file=“jsp页面”%>

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    <%--jsp中的包含指令,在主页面里面包含另外一个jsp页面--%>
    <%@ include file="pages/common.jsp"%>
    <h3>这是一个主页面</h3>
</body>
</html>

注意:使用include指令包含jsp页面。tomcat服务器再翻译jsp页面的时候,不会将包含的jsp页面和被包含的jsp页面单独翻译,而是将其合并在一起翻译,也就是虽然是两个独立的jsp页面,但是翻译的时候是一个jsp的java源文件。

在这里插入图片描述

这种合并在一起翻译的方式叫做静态包含(源码包含)。

  • page指令

语法:<%@ page contentType=“text/html;charset=UTF-8” language=“java” %>

作用:

language=“java” 告诉tomcat服务器,在翻译jsp文件的时候,用什么语言来翻译。

import=“java.util.Date” import属性,就是导包的意思。jsp页面中需要使用的一些类,只有导包之后才能使用。

pageEncoding=“UTF-8” 告诉tomcat服务器使用什么样的编码格式在翻译jsp文件。

contentType=“text/html;charset=UTF-8” 告诉服务器向浏览器响应的数据的类型和编码格式。

errorPage=“pages/error.jsp” 如果当前的jsp页面出现了错误,但是我们又不想让这些错误展示给用户。这个时候可以指定一个专门处理错误的页面,当页面出现问题之后,会自动跳转到这个错误处理页面。

我们在每个jsp页面上配置错误处理页面很麻烦,我们也可以做全局的错误处理配置。在web.xml里面配置:

<!--专门处理500的错误-->
<error-page>
    <error-code>500</error-code>
    <location>/pages/500.jsp</location>
</error-page>

<!--专门处理404的错误-->
<error-page>
    <error-code>404</error-code>
    <location>/pages/404.jsp</location>
</error-page>

isErrorPage=“true”

在当前错误处理的页面上设置,默认值为false(不会处理任何的异常信息,在jsp里面,使用内置对象exception来对异常进行简单的描述)。只有值为true的时候,我们才会使用jsp的内置对象exception对异常进行描述。

<%@ page contentType="text/html;charset=UTF-8" language="java" isErrorPage="true" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
  <h3>页面上出现了错误,请稍后访问</h3>
  <%=exception.getMessage()%>
</body>
</html>

session=“true”

默认值本身就为true。意思在当前jsp页面里面我们可以直接使用session对象,不需要我们直接创建。当session="false"的时候,是不能直接使用session对象的。

  • taglib指令

语法:<%@ taglib prefix=“”%>

后面的jstl表达式里面会用到。

5.2 JSP加强

5.2.1 jsp内置对象

在jsp中,我们会频繁使用一些对象。比如HttpServletRequest,HttpServletResponse,HttpSession,ServletContext等。我们每次使用这些对象,如果都需要我们手动创建,那么会非常繁琐。所以SUN公司在设计jsp的时候,会在jsp加载完毕之后,事先创建好这些对象。对于我们开发者来说,我们不需要关注这些对象是如何创建的,我们直接使用这些对象就可以。这些事先创建好的对象就是内置对象。

JSP中有九大内置对象

内置对象名称内置对象的类型
requestHttpServletRequest
responseHttpServletResponse
configServletConfig
applicationServletContext
sessionHttpSession
exceptionThrowable
pageObject(用来描述当前jsp的类)
pageContextPageContext
out(向浏览器页面输出一些内容的)JspWriter
5.2.1.1 pageContext内置对象

pageContext是一个域对象(application session request pageContext)。也称为是jsp的上下文对象。

pageContext的作用:

1. 可以使用pageContext直接获取其他八大内置对象。

通过源码可知,jsp页面加载完毕之后,jsp的内置对象就在_jspService方法的内部创建了

在这里插入图片描述

这些在_jspService方法内部创建的对象都是局部变量。也就意味着在jsp声明的方法中是无法使用这些内置对象的。那么sun公司将其他八大内置对象都封装到了PageContext对象里面,我们就可以通过pageContext对象获取其他的八大内置对象。

<body>
    <%!
    public void test(PageContext pageContext){
    	Exception exception = pageContext.getException();
    	ServletRequest request = pageContext.getRequest();
    	ServletResponse response = pageContext.getResponse();
    	ServletConfig config = pageContext.getServletConfig();
    	ServletContext application = pageContext.getServletContext();
    	HttpSession session = pageContext.getSession();
    	JspWriter out = pageContext.getOut();
    }
    %>
</body>

2. 可以使用pageContext做作用域

四大作用域及其区别:

pageContext本身就是一个域对象。

request:作用域范围是当前request请求。

application:整个web应用都会起作用。

session:当前会话中有效。

pageContext:在当前jsp页面中有效。

使用PageContext需要注意的事项:

  • 使用pageContext在指定的域对象中获取数据,需要认识几个常量:
pageContext.PAGE_SCOPE  1   在pageContext域对象中获取数据
pageContext.REQUEST_SCOPE  2    在request域对象中获取数据
pageContext.SESSION_SCOPE   3   在session域对象中获取数据
pageContext.APPLICATION_SCOPE  4   在application域对象中获取数据
<%
	//分别在不同的4个域对象中存储数据
	pageContext.setAttribute("name","page_kobe");
	request.setAttribute("name","request_kobe");
	session.setAttribute("name","session_kobe");
	application.setAttribute("name","application_kobe");
%>

<!--使用pageContext域对象取数据-->
<%=pageContext.getAttribute("name",1)%>  <br>
<%=pageContext.getAttribute("name",2)%>  <br>
<%=pageContext.getAttribute("name",3)%>  <br>
<%=pageContext.getAttribute("name",4)%>  <br>
  • 我们也可以通过pageContex自动在作用域里面搜索数据
<%=(String) pageContext.findAttribute("name")%>

它自动搜索的顺序是: pageContext request session application。

5.2.1.2 jsp的编码实战

JSP技术和Servlet技术都是用来开发动态资源的。

Servlet更擅长写java代码,jsp擅长将数据渲染给用户展示。

在开发中根据servlet和jsp的特性,我们一般这样做:

  • servlet
    • 接收前台页面提交的参数
    • 处理业务逻辑
    • 把处理好的数据存放在域对象里面
    • 跳转到其他的页面
  • JSP
    • 从域对象里面获取数据
    • 将数据展示在浏览器端

需求:访问Servlet,将查询出来的数据显示在jsp页面上。

(1) 编写java代码

  • 编写实体类
public class Computer {

    private String id;
    private String name;
    private String type;
    private Double price;

    public Computer(){

    }

    public Computer(String id, String name, String type, Double price) {
        this.id = id;
        this.name = name;
        this.type = type;
        this.price = price;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public Double getPrice() {
        return price;
    }

    public void setPrice(Double price) {
        this.price = price;
    }

    @Override
    public String toString() {
        return "Computer{" +
                "id='" + id + '\'' +
                ", name='" + name + '\'' +
                ", type='" + type + '\'' +
                ", price=" + price +
                '}';
    }
}
  • 编写业务类
public class ComputerService {

    static List<Computer> list = new ArrayList<Computer>();

    //使用静态代码块初始化商品信息
    static {
        list.add(new Computer("1001","苹果笔记本电脑","苹果(Apple) macbook pro 13.3英寸 2022款",13444.0));
        list.add(new Computer("1002","苹果笔记本电脑","苹果(Apple) macbook air 13.3英寸 2022款",8444.0));
        list.add(new Computer("1003","惠普笔记本电脑","惠普(HP) ENVY14英寸十一代酷睿标压i5/i7 超轻薄2.2K全面屏触控屏设计游戏笔记本电脑",6888.0));
        list.add(new Computer("1004","戴尔笔记本电脑","戴尔(DELL) 灵越15Pro 15.6英寸12代酷睿轻薄窄边框高刷学生商务办公设计笔记本电脑",4674.0));
        list.add(new Computer("1005","外星人笔记本电脑","外星人(alienware) m15 R7高端游戏本全新12代酷笔记本电脑15.6英寸电竞",21999.0));
    }

    //显示所有商品信息
    public List<Computer> findAll(){
        return list;
    }

    //显示商品的详情信息
    public Computer getDetailById(String id){
        for(Computer computer : list){
            if(computer.getId().equals(id)){
                return computer;
            }
        }
        return null;
    }
}
  • 编写Servlet
@WebServlet("/product")
public class ProductServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html;charset=utf-8");
        ComputerService computerService = new ComputerService();
        List<Computer> list = computerService.findAll();
        //把数据存到作用域
        request.setAttribute("list",list);
        //实现转发
        request.getRequestDispatcher("/list.jsp").forward(request,response);
    }
}

(2) 编写JSP

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    <table border="1" width="800" cellspacing="0" align="center">
        <tr>
            <th>商品编号</th>
            <th>商品名称</th>
            <th>商品类型</th>
            <th>商品价格</th>
        </tr>
        <%
            List<Computer> list = (List<Computer>) request.getAttribute("list");
            for(Computer c : list){
                %>
                <tr align="center">
                    <td><%=c.getId()%></td>
                    <td><%=c.getName()%></td>
                    <td><%=c.getType()%></td>
                    <td><%=c.getPrice()%></td>
                </tr>
              <%
            }
        %>
    </table>
</body>
</html>

缺陷:在jsp页面中夹杂HTML代码,编写非常不方便,并且可读性非常差。

解决方案:需要使用el表达式和jstl标签来进行优化

5.2.2 EL表达式

作用:尽量减少在jsp页面中写java代码。使用el表达式去替换jsp表达式。

语法:${变量或者表达式}

5.2.2.1 el表达式取数据
<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
   <%
      pageContext.setAttribute("name","eric");
      request.setAttribute("age",12);
      session.setAttribute("gender","nan");
      application.setAttribute("score","100");
   %>
   <%--取数据--%>
   ${name} <br>
   ${age} <br>
   ${gender} <br>
   ${score} <br>
   ${10*12} <br>
</body>
</html>

注意:

  1. el表达式在获取数据的时候,会从域对象里面依次搜索(搜索的顺序是pageContext request session application)。
  2. 如果el表达式在浏览器页面没有被渲染。我们不要忽略el表达式。
<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
  1. 我们也可以指定在某个作用域里面获取数据
${pageScope.name} <br>
${requestScope.age} <br>
${sessionScope.gender} <br>
${applicationScope.score} <br>
  1. 如果我们通过el表达式获取对象,我们通过${对象.属性名称}
商品编号: ${computer.id} <br>
商品名称: ${computer.name} <br>
商品类型: ${computer.type} <br>
商品价格: ${computer.price} <br>
  1. 获取list集合中的数据
<body>
   <%
      Computer c1 = new Computer("1001","苹果笔记本电脑","苹果(Apple) macbook pro 13.3英寸 2022款",13444.0);
      Computer c2 = new Computer("1002","苹果笔记本电脑","苹果(Apple) macbook air 13.3英寸 2022款",8444.0);
      Computer c3 = new Computer("1003","惠普笔记本电脑","惠普(HP) ENVY14英寸十一代酷睿标压i5/i7 超轻薄2.2K全面屏触控屏设计游戏笔记本电脑",6888.0);
      Computer c4 = new Computer("1004","戴尔笔记本电脑","戴尔(DELL) 灵越15Pro 15.6英寸12代酷睿轻薄窄边框高刷学生商务办公设计笔记本电脑",4674.0);
      Computer c5 = new Computer("1005","外星人笔记本电脑","外星人(alienware) m15 R7高端游戏本全新12代酷笔记本电脑15.6英寸电竞",21999.0);
      List<Computer> list = new ArrayList();
      list.add(c1);
      list.add(c2);
      list.add(c3);
      list.add(c4);
      list.add(c5);
      request.setAttribute("list",list);
   %>
  <%--取数据--%>
   ${list[0].id}   &nbsp;&nbsp; ${list[0].name}   &nbsp;&nbsp; ${list[0].type}  &nbsp;&nbsp; ${list[0].price} <br>
   ${list[1].id}   &nbsp;&nbsp; ${list[1].name}   &nbsp;&nbsp; ${list[1].type}  &nbsp;&nbsp; ${list[1].price} <br>
   ${list[2].id}   &nbsp;&nbsp; ${list[2].name}   &nbsp;&nbsp; ${list[2].type}  &nbsp;&nbsp; ${list[2].price} <br>
   ${list[3].id}   &nbsp;&nbsp; ${list[3].name}   &nbsp;&nbsp; ${list[3].type}  &nbsp;&nbsp; ${list[3].price} <br>
   ${list[4].id}   &nbsp;&nbsp; ${list[4].name}   &nbsp;&nbsp; ${list[4].type}  &nbsp;&nbsp; ${list[4].price} <br>
</body>
  1. 获取Map集合中的数据
<%
      Computer c1 = new Computer("1001","苹果笔记本电脑","苹果(Apple) macbook pro 13.3英寸 2022款",13444.0);
      Computer c2 = new Computer("1002","苹果笔记本电脑","苹果(Apple) macbook air 13.3英寸 2022款",8444.0);
      Map<String,Computer> map = new HashMap();
      map.put("1001",c1);
      map.put("1002",c2);
      request.setAttribute("map",map);
%>
<%--map集合中取数据--%>
${map['1001'].id} &nbsp;&nbsp; ${map['1001'].name} <br>
${map['1002'].id} &nbsp;&nbsp; ${map['1002'].name} <br>
5.2.2.2 el表达式进行运算
<%--使用el表达式进行运算--%>
<%--算术运算--%>
${10+10} <br>
${10*10} <br>
${10/2} <br>
${10%2} <br>
<%--比较运算--%>
${10<9} <br>
${10==10}<br>
${10>1}<br>
${10!=10}<br>
<%--逻辑运算--%>
${(10>6) && (10!=6)} <br>
${(10<6) || (10!=6)} <br>
${true && false} <br>
<hr>
<%--字符串判空--%>
<%
String str = "";
request.setAttribute("str",str);
%>
${str==null} <br><!--判断字符串对象是否为空-->
${str==""} <br> <!--判断字符串内容是否为空-->
${str eq null} <br> <!--判断字符串对象是否为空-->
${str==null || str==""} <!--判断字符串对象是否为空 或者 判断字符串内容是否为空-->
5.2.3 jsp标签

jsp标签的作用:直接替换jsp脚本

jsp标签的分类:

  • jsp内置标签,直接在jsp页面上使用。
  • jstl标签,这是一个第三方的标签库,需要在页面上导入之后才可以使用。
  • 自定义标签 开发者自行定义的标签。
5.2.3.1 jsp内置标签
  • 转发标签
<jsp:forward page = "页面"></jsp:forward>
<jsp:forward page="jsp-demo2.jsp"></jsp:forward>
  • 参数标签
<jsp:param name="" value=""></jsp:param>

这个参数可以在<jsp:forward>中使用也可以在<jsp:include>中使用。

在jsp-demo1.jsp中定义:

<jsp:forward page="/jsp-demo2.jsp">
    <jsp:param name="username" value="eric"></jsp:param>
    <jsp:param name="password" value="admin"></jsp:param>
</jsp:forward>

在jsp-demo2.jsp中取数据

<body>
  <h3>这是jsp-demo2页面</h3>
  <%
      String username = request.getParameter("username");
      String password = request.getParameter("password");
      out.write(username);
      out.write(password);
  %>
</body>
  • 动态包含标签
<jsp:include page="页面"></jsp:include>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
 <jsp:include page="pages/common.jsp"></jsp:include>
  这是jsp-demo3页面
</body>
</html>

include指令包含和jsp:include包含的区别:

include指令包含我们又称为静态包含,源码包含,包含的页面和被包含的页面合并在一起翻译。最后独立成一个java源文件。

jsp:include包含我们称为动态包含,将两个页面单独翻译成独立源文件。

参数传递的原理不同,静态包含是不能向被包含的页面传递参数的。动态包含是可以向被包含的页面传递参数的。

在包含的页面:

<body>
 <jsp:include page="pages/common.jsp">
     <jsp:param name="name" value="admin"></jsp:param>
 </jsp:include>
  这是jsp-demo3页面
</body>

在被包含的页面

<body>
  <h5>这是一个公共页面</h5>
  <%
      String name = request.getParameter("name");
      System.out.println(name);
  %>
</body>
5.2.3.2 JSTL标签

JSTL:java standard tag libaray Java标准标签库

JSTL标签的分类:核心标签库 国际化标签 函数标签库

如何使用jstl核心标签库:

(1) 如何使用jstl标签库

第一步:导入jstl相关的依赖

在这里插入图片描述

第二步:使用taglib引入jstl标签库

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>

(2) 使用jstl标签库

  • 保存数据的标签 <c:set>
<body>
  <%--默认保存在pageContext作用域中  var:变量名称 value:值--%>
  <c:set var="name" value="eric"></c:set>
  ${name} <br>
  <%--想将数据保存在指定的作用域里面 scope: 存储的作用域 --%>
  <c:set var="username" value="kobe" scope="request"></c:set>
  ${requestScope.username} <br>

  <c:set var="age" value="12" scope="session"></c:set>
  ${sessionScope.age} <br>

  <c:set var="address" value="USA" scope="application"></c:set>
  ${applicationScope.address} <br>
</body>
  • 获取数据的标签 <c:out>
<c:set var="pname" value="苹果电脑" scope="request"></c:set>
<!--输出内容 value里面定义的是el表达式-->
<c:out value="${requestScope.pname}"></c:out> <br>
<!--default 指的是如果value属性中的el表达式取不到值 就使用默认值-->
<c:out value="${empname}" default="搬砖的"></c:out>
  • 单条件判断
<c:set value="95" var="score"></c:set>
<!--test:条件判断 条件为true 就执行if标签里面的内容 test里面的表达式是一个el表达式-->
<c:if test="${score > 90}">
    优秀
</c:if>
  • 多条件判断
<c:set value="75" var="score"></c:set>
<c:choose>
    <c:when test="${score > 90}">
        优秀
    </c:when>
    <c:when test="${score > 80}">
        良好
    </c:when>
    <c:when test="${score > 60}">
        及格
    </c:when>
    <c:otherwise>
        不及格
    </c:otherwise>
</c:choose>
  • 循环遍历
<!--
     begin:遍历元素的起始索引值
     end:遍历元素结束的索引值
     step:遍历元素的幅度
     items:获取需要遍历的元素对象 使用el表达式来取
     var:给遍历的每个元素取别名
     varStatus:给每个遍历的数据编号
   -->
   <c:forEach begin="0" end="${list.size()-1}" step="1" var="computer" items="${list}" varStatus="status">
       序号:${status.count} &nbsp;&nbsp; 产品编号:${computer.id}  &nbsp;&nbsp;产品名称:${computer.name} &nbsp;&nbsp;产品类型:${computer.type} &nbsp;&nbsp;产品价格:${computer.price}
       <br>
   </c:forEach>

使用jstl标签简化我们之前的案例(展示商品信息的案例)

<table border="1" width="800" cellspacing="0" align="center">
    <tr align="center">
        <th>商品编号</th>
        <th>商品名称</th>
        <th>商品类型</th>
        <th>商品价格</th>
    </tr>

    <c:forEach items="${list}" var="computer">
        <tr align="center">
            <td>${computer.id}</td>
            <td>${computer.name}</td>
            <td>${computer.type}</td>
            <td>${computer.price}</td>
        </tr>
    </c:forEach>
</table>
  • 循环遍历获取map集合中的数据
@WebServlet("/jstl")
public class TestJstlServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        Map<String, Computer> map = new HashMap();
        map.put("1001",new Computer("1001","苹果笔记本电脑","苹果(Apple) macbook pro 13.3英寸 2022款",13444.0));
        map.put("1002",new Computer("1002","苹果笔记本电脑","苹果(Apple) macbook air 13.3英寸 2022款",8444.0));
        request.setAttribute("map",map);
        request.getRequestDispatcher("/mapList.jsp").forward(request,response);
    }
}
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<html>
<head>
    <title>Title</title>
</head>
<body>
    <c:forEach var="entry" items="${map}">
       key值: ${entry.key}  &nbsp;&nbsp;商品id:${entry.value.id} &nbsp;&nbsp;商品名称:${entry.value.name}&nbsp;&nbsp;商品类型:${entry.value.type}
        <br>
    </c:forEach>
</body>
</html>
  • 字符串数据的遍历
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<html>
<head>
    <title>Title</title>
</head>
<body>
    <c:set var="str" value="java-c-python-oracle-mysql"></c:set>
    <c:forTokens items="${str}" delims="-" var="s">
        ${s}  &nbsp;&nbsp;
    </c:forTokens>
</body>
</html>
  • jsp中的重定向标签
<c:redirect url="/el-demo1.jsp?username=lily"></c:redirect>

6 文件上传与下载

6.1 文件上传

文件上传的前提:

  1. 文件上传的前提是必须要存在表单,也就是form标签。并且表单中的method属性是post。

  2. 需要在form表单中使用文件控件 input type是file。

  3. form表单中必须存在一个属性enctype=“multipart/form-data”;

  4. 目前,文件上传的逻辑必须交给Servlet程序处理。

文件上传需要引入的jar包:

  • commons-fileupload.jar

这个jar包是文件上传的核心jar包。但是这个包依赖于commons-io.jar包。所以这两个包我们都需要导入进去。

  • commons-io.jar

commons-fileupload.jar需要依赖的包。

文件上传需要使用的核心类:

ServletFileUpload类:用于解析表单上传的数据

FileItem类:表示每一个表单项。上传数据的表单会封装成FileItem类

文件上传需要用到的核心API:

 ServletFileUpload类中的方法:parseRequest(request);   解析表单提交数据的请求
 FileItem类中的方法isFormField() 判断表单项是否是普通表单
 FileItem类中的方法getFieldName() 获取表单name的属性值
 FileItem类中的方法getString() 获取表单的value属性值
 FileItem类中的方法write() 文件上传的方法 
  • 引入文件上传需要用到的jar包。

在这里插入图片描述

  • 定义文件表单
<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    <form action="${pageContext.request.contextPath}/uploadFile" method="post" enctype="multipart/form-data">
        用户名: <input type="text" name="username"> <br>
        图片: <input type="file" name="pic"> <br>
        <input type="submit" value="提交">
    </form>
</body>
</html>
  • 执行文件上传的操作
@WebServlet("/uploadFile")
public class FileUploadServlet extends HttpServlet {

    /**
     * 执行文件上传的逻辑
     * @param request
     * @param response
     * @throws ServletException
     * @throws IOException
     */
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException,IOException {
        try {
            DiskFileItemFactory factory = new DiskFileItemFactory();
            //创建文件上传的对象 用于解析表单上传的数据
            ServletFileUpload upload = new ServletFileUpload(factory);
            //解析表单提交数据的请求
            List<FileItem> fileItems = upload.parseRequest(request);
            for(FileItem item : fileItems){
                if(item.isFormField()){
                    //判断当前表单控件是否是普通表单项(除了文件控件 其他都是普通)
                    System.out.println("表单的name属性值:" +item.getFieldName());
                    System.out.println("表单的value属性值:" +item.getString("utf-8"));
                }else{
                    //文件表单项 执行文件上传的操作
                    item.write(new File("F:\\upload\\" + item.getName()));
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

6.2 文件下载

需求:实现最简单的文件下载功能。

我们可以使用超链接实现最简单的文件下载功能。

<body>
    <a href="images/AudiA7.jpg" download="images/AudiA7.jpg">[文件下载]</a>
</body>

我们现在需要使用Servlet技术实现文件的下载功能。

@WebServlet("/download")
public class DownloadServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //1.获取下载图片的名称
        String downloadFileName = "AudiA7.jpg";
        //2.读取要下载的文件内容
        ServletContext context = this.getServletContext();
        //3.获取文件的下载类型(image/jpeg)
        String mimeType = context.getMimeType("/images/" + downloadFileName);
        System.out.println(mimeType);
        //4.告诉客户端,要返回给客户端的数据类型
        response.setContentType("image/jpeg");
        //5.告诉客户端,客户端收到的数据是下载类型的数据(响应给客户端一个下载图片的对话框)
        response.setHeader("Content-Disposition","attachment;filename=AudiA7.jpg");
        //6.把下载的文件内容传给客户端(将文件以输入流的形式读进来,读进来之后,再以输出流的形式写出去)
        InputStream resourceAsStream = context.getResourceAsStream("/images/" + downloadFileName);
        ServletOutputStream outputStream = response.getOutputStream();
        //把输入流中的数据复制给输出流,最后输出给客户端
        IOUtils.copy(resourceAsStream,outputStream);
    }
}

7 过滤器和监听器

7.1 过滤器

Web三大组件:Servlet、Filter、 Listener

什么是过滤器:当浏览器向服务器发送请求的时候,过滤器可以将请求拦截下来,完成一些特殊的功能(我们可以将这种操作理解成方法层面的增强)。

过滤器通常可以做什么操作:编码过滤、权限校验、日志记录等。

7.1.1 过滤器的快速入门

步骤:创建一个java类,实现一个Filter接口,然后重写里面的方法即可。

@WebFilter("/demo1") // 当前过滤器拦截/demo1的请求
public class FilterDemo1 implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    //真正执行过滤业务的方法
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("FilterDemo1 is running...");
        filterChain.doFilter(request,response);//过滤器要放行 只有放行之后,过滤器过滤的资源才会执行
        System.out.println("write log to database...");
    }

    @Override
    public void destroy() {

    }
}

使用过滤器需要注意的事项:

  1. 过滤器必须实现Filter接口。
  2. 过滤器拦截的请求执行完毕之后,必须要放行,否则我们的请求就不会被执行。
 filterChain.doFilter(request,response); //过滤器放行
  1. 我们可以使用@WebFilter来配置过滤器要拦截的资源,当然我们也可以通过xml的方式配置过滤器。
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    <!--配置过滤器-->
    <filter>
        <filter-name>demo1</filter-name>
        <filter-class>com.qf.filter.FilterDemo1</filter-class>
    </filter>

    <filter-mapping>
        <filter-name>demo1</filter-name>
        <url-pattern>/demo1</url-pattern>
    </filter-mapping>
</web-app>
7.1.2 理解Filter的生命周期
@WebFilter("/demo1") // 当前过滤器拦截/demo1的请求
public class FilterDemo1 implements Filter {

    public FilterDemo1(){
        System.out.println("FilterDemo1 is created");
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("init method is running");
    }

    //真正执行过滤业务的方法
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("FilterDemo1 is running...");
        filterChain.doFilter(request,response);//过滤器要放行 只有放行之后,过滤器过滤的资源才会执行
        System.out.println("write log to database...");
    }

    @Override
    public void destroy() {
        System.out.println("method destroy is running...");
    }
}
  • 当加载我们的web应用的时候,首先会初始化过滤器的实例对象,然后执行1次init方法。
  • 当我们发送请求的时候,会将doFilter方法执行。
  • 当我们重新部署项目或者停止web服务器的时候,会执行destroy方法。如果重新部署项目,也会初始化过滤器实例对象,还会执行init方法。
7.1.3 过滤器的拦截路径的详细配置
  1. 配置指定的拦截路径
@WebFilter("/demo1") // 当前过滤器指定拦截/demo1的请求
public class FilterDemo1 implements Filter {
    ......
}
  1. 根据目录拦截
  • 定义Servlet
@WebServlet("/user/demo1")
public class ServletDemo1 extends HttpServlet {

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

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("ServletDemo2 is running......");
    }
}
  • 定义过滤器
@WebFilter("/user/*") // 当前过滤器拦截/demo1的请求
public class FilterDemo1 implements Filter {
    ......
}
  1. 根据后缀进行拦截
@WebFilter("*.jsp") // 当前过滤器拦截访问jsp的请求
public class FilterDemo1 implements Filter {
    ......
}
7.1.4 根据请求方式配置过滤器

在默认的情况下,拦截器拦截的资源都是直接请求的资源。在过滤器里面有一个默认值。dispatcherTypes

public enum DispatcherType {
    FORWARD,
    INCLUDE,
    REQUEST,
    ASYNC,
    ERROR;
}
@WebFilter(value = "/user/demo2",dispatcherTypes = DispatcherType.FORWARD) // 当前过滤器拦截/demo1的请求
public class FilterDemo1 implements Filter {
    ......
}

过滤器请求/user/demo2的资源,请求方式必须是转发,如果不使用转发,过滤器不会生效。

我们也可以配置多个请求方式:

@WebFilter(value = "/user/demo2",dispatcherTypes = {DispatcherType.FORWARD,DispatcherType.REQUEST,DispatcherType.ASYNC,DispatcherType.INCLUDE}) 
public class FilterDemo1 implements Filter {
    ......
}
7.1.5 过滤器链

我们可以定义多个过滤器,如果一个请求需要经过多个过滤器,这些过滤器称为过滤器链。

  • 定义过滤器1
@WebFilter(value = "/user/demo2",dispatcherTypes = {DispatcherType.FORWARD,DispatcherType.REQUEST,DispatcherType.ASYNC,DispatcherType.INCLUDE}) // 当前过滤器拦截/demo1的请求
public class FilterDemo1 implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
       
    }

    //真正执行过滤业务的方法
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("FilterDemo1 is running...");
        filterChain.doFilter(request,response);//过滤器要放行 只有放行之后,过滤器过滤的资源才会执行
      
    }

    @Override
    public void destroy() {
       
    }
}
  • 定义过滤器2
@WebFilter(value = "/user/demo2",dispatcherTypes = {DispatcherType.FORWARD,DispatcherType.REQUEST,DispatcherType.ASYNC,DispatcherType.INCLUDE}) // 当前过滤器拦截/demo1的请求
public class FilterDemo2 implements Filter {



    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    //真正执行过滤业务的方法
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("FilterDemo2 is running...");
        filterChain.doFilter(request,response);
    }

    @Override
    public void destroy() {

    }
}
  • 编写Servlet
@WebServlet("/user/demo2")
public class ServletDemo2 extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("ServletDemo2 is running......");
    }
}

发送请求,过滤器执行的顺序是:

先执行过滤器1,在执行过滤器2,最后执行servlet。

思考:如果一个请求存在多个过滤器,那么过滤器执行的优先级是什么?

在默认的情况下,过滤器会按照名称的自然顺序进行执行。如果我们想自定义过滤器的执行顺序呢?

我们在web.xml里面定义过滤器的配置,谁定义在上面谁就先执行。

<!--配置过滤器2 -->
<filter>
    <filter-name>demo2</filter-name>
    <filter-class>com.qf.filter.FilterDemo2</filter-class>
</filter>

<filter-mapping>
    <filter-name>demo2</filter-name>
    <url-pattern>/user/demo1</url-pattern>
</filter-mapping>

<!--配置过滤器1 -->
<filter>
    <filter-name>demo1</filter-name>
    <filter-class>com.qf.filter.FilterDemo1</filter-class>
</filter>

<filter-mapping>
    <filter-name>demo1</filter-name>
    <url-pattern>/user/demo1</url-pattern>
</filter-mapping>

7.2 监听器

监听器就是监听某个事件,当这个事件发生之后,立马执行一些操作。

介绍一个监听器:ServletContextListener。这个监听器负责监听ServletContext容器的创建和销毁。

@WebListener
public class MyListener implements ServletContextListener {

    //监听Servlet上下文对象创建的方法
    @Override
    public void contextInitialized(ServletContextEvent servletContextEvent) {
        System.out.println("contextInitialized is running");
    }

    //监听Servlet上下文对象销毁的方法
    @Override
    public void contextDestroyed(ServletContextEvent servletContextEvent) {
        System.out.println("contextDestroyed is running");
    }
}

配置监听器,我们可以通过@WebListener来进行配置。

我们也可以通过xml进行配置:

<!--配置监听器-->
<listener>
    <listener-class>com.qf.listener.MyListener</listener-class>
</listener>

8 ajax&json

8.1 ajax

8.1.1 什么是ajax

在这里插入图片描述

同步:客户端必须等待服务器响应,在等待的过程中,客户端不能做其他的操作

异步:客户端等待服务器的响应,在等待的过程中,还可以进行其他的操作。

8.1.3 原生ajax的使用

(1) 使用ajax发送k=y的数据

<button id="mybutton" value="异步请求服务器" οnclick="fun1()">发送数据格式为key/value的原生ajax请求</button>
<span id="s2"></span>
<script type="text/javascript">
      function fun1() {
          var value="username=eric&password=admin";
          // 创建一个ajax对象
          var x=new XMLHttpRequest();
          // onreadystatechange 监听服务器向浏览器响应的数据
          x.onreadystatechange = function(){
              // readyState 描述浏览器向服务器发送的请求以及服务器向浏览器响应数据的结果
              // 0 请求没有初始化 1浏览器和服务器之间已经建立连接 2 ajax请求已经被服务器接收 3 服务器处理请求中 4 服务器请求处理完成,并行响应就绪
              if(x.readyState == 4){
                  // 200 服务器向浏览器成功响应数据
                  if(x.status == 200){
                      //解析后台向前台响应的数据
                      var data = x.responseText;
                      console.log(data);
                      document.getElementById("s2").innerHTML = data;
                  }
              }
          }
          //发送ajax请求 参数1 请求方式  参数2:ajax请求的资源路径  参数3:是否是异步请求
          x.open("POST","${pageContext.request.contextPath}/ajaxServlet",true);
          //ajax发送的请求,请求参数会使用问号拼劲的方式传递给后台 比如 /ajaxServlet?username=eric&password=admin
          x.setRequestHeader("Content-type","application/x-www-form-urlencoded");
          //发送ajax请求需要携带的数据
          x.send(value);
      }
  </script>
@WebServlet("/ajaxServlet")
public class AjaxServlet extends HttpServlet {

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html;charset=utf-8");
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        String message = "用户名:" + username + " 密码:" + password;
        response.getWriter().write(message);
    }
}

(2) 使用ajax发送json数据

前期准备:

在这里插入图片描述

<button id="mybutton1" value="异步请求服务器" οnclick="fun2()" >发送数据格式为json的原生ajax请求</button>
<span id="s3"></span>
function fun2() {
    var user = {
        username: "eric",
        password: "admin"
    }
    var x=new XMLHttpRequest();
    x.onreadystatechange = function(){
        if(x.readyState == 4){
            if(x.status == 200){
                var data=JSON.parse(x.responseText);
                console.log(data);
                document.getElementById("s3").innerHTML=data.message+" "+data.user.username+" "+data.user.password;
            }
        }
    }
    //发送ajax请求
    x.open("POST","${pageContext.request.contextPath}/ajaxServlet1",true);
    x.setRequestHeader("Content-type","application/x-www-form-urlencoded");
    //将js对象转换成json格式的数据进行发送
    console.log(JSON.stringify(user));
    x.send(JSON.stringify(user));
}
@WebServlet("/ajaxServlet1")
public class AjaxServlet1 extends HttpServlet {

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("application/json;charset=utf-8");
        JSONObject json = JsonReader.receivePost(request);
        System.out.println(json);
        // 将json对象转换成java对象
        User user = (User) JSONObject.toBean(json, User.class);
        JSONObject result = new JSONObject();
        result.put("user", JSONObject.fromObject(user));
        result.put("message", "返回成功");
        response.getWriter().print(result);
    }
}

(3) 使用ajax不发送数据

<button id="mybutton2" value="异步请求服务器" οnclick="fun3()" >不发送数据</button>
<span id="s4"></span>
function fun3(){
    var x=new XMLHttpRequest();
    x.onreadystatechange = function(){
        if(x.readyState == 4){
            if(x.status == 200){
                var data = x.responseText;
                console.log(data)
                document.getElementById("s3").innerHTML = data;
            }
        }
    }
    x.open("POST","${pageContext.request.contextPath}/ajaxServlet2",true);
    x.setRequestHeader("Content-type","application/x-www-form-urlencoded");
    //发送ajax不携带任何数据
    x.send(null);
}
@WebServlet("/ajaxServlet2")
public class AjaxServlet2 extends HttpServlet {

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html;charset=utf-8");
        String message = "hello,ajax";
        response.getWriter().write(message);
    }
}

8.2 Jquery对ajax的支持

  • 语法1 $.ajax(键值对)
$.ajax({
	type: “GET或POST”,
	url : “请求的服务器地址”,
	data:“仅限于POST提交数据k1=v1&k2=v2…kn=vn”,
	success:function(responseText){做事}
})

需求:用户发送请求到后台,判断输入的用户名是否已经存在

<head>
    <title>Title</title>
    用户名: <input type="text" name="username" id="username"> <span id="s1"></span><br>
</head>
<script src="http://code.jquery.com/jquery-2.1.4.min.js"></script>
<script type="text/javascript">
    $(function(){
        $("#username").blur(function(){
            var username = $("#username").val();
            if(username == ""){
                $("span#s1").html("用户名不能为空");
                return;
            }
            //如果用户名不为空,向后台发送异步请求
            $.ajax({
                //发送的请求方式
                type:"GET",
                //请求后台服务器的资源
                url:"${pageContext.request.contextPath}/checkUserName?username="+username,
                //前台向后台发送ajax请求成功,并且后台服务器给前端响应数据之后,需要执行的回调函数
                // data 后台向前台响应的数据
                success:function(data){
                    $("span#s1").html(data);
                }
            });
        })
    })
</script>
@WebServlet("/checkUserName")
public class CheckUserNameServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html;charset=utf-8");
        String username = request.getParameter("username");
        String msg = "";
        if(username.equals("tom")){
            msg = "<font color='red'>用户名已经存在,请重新输入</font>";
        }else{
            msg = "<font color='green'>用户名输入成功</font>";
        }
        response.getWriter().write(msg);
    }
}
  • 语法2:$.get()
<script type="text/javascript">
    $(function(){
        $("#username").blur(function(){
            //参数1 发送的服务器资源  参数2 携带的参数  参数3:请求成功响应之后需要执行的回调函数  参数4:后台向前台响应的数据类型
            $.get("${pageContext.request.contextPath}/checkUserName1",{"username":"tom"},function(data){
                $("span#s1").html(data);
            },"text")
        })
    })
</script>
  • 语法3:$.post()

语法格式和$.get是一样的,这里省略

8.3 JSON

什么是JSON:

在这里插入图片描述

8.3.1 JSON的语法格式

(1)基本语法:

json的数据本质上就是一个键值对,通过key:value。键的定义很随意,键的描述可以带双引号,也可以不带。值的由数据类型决定的。

  • 值可以是数字(整数、小数)
  • 值也可以是字符串(字符串必须携带双引号)
  • 值可以是布尔值(true false)
  • 值可以是数组(数组是定义在方括号中[])
  • 值可以是对象(对象是定义在花括号中{})
{k1:v1,k2:v2,k3:v3....}

1. 最基本最简单的json数据格式:

var p = {
        name:"eric",
        age:23,
        address:"USA",
        isVip:false
   }

2.复杂的json数据格式:

 var p1 = {
        persons:[
            {name:"eric",age:23,address:"USA",isVip:false},
            {name:"james",age:38,address:"USA",isVip:true},
            {name:"Yaoming",age:38,address:"CHN",isVip:true}
        ]
    }
var p2 = {
        person1:{name:"eric",age:23,address:"USA",isVip:false},
        person2:{name:"james",age:38,address:"USA",isVip:true},
        person3:{name:"Yaoming",age:38,address:"CHN",isVip:true}
    }

(2) 遍历json格式的数据

  • 如果是json数据是简单的json串遍历
var p = {
        name:"eric",
        age:23,
        address:"USA",
        isVip:false
    }

for(var key in p){
    console.log("键:" + key + "   值:"+p[key]);
}
  • 数组遍历
var p1 = {
        persons:[
            {name:"eric",age:23,address:"USA",isVip:false},
            {name:"james",age:38,address:"USA",isVip:true},
            {name:"Yaoming",age:38,address:"CHN",isVip:true}
        ]
    }

for(var key in p1){
    var arr = p1[key];
    for(var i = 0;i<arr.length;i++){
        var person = arr[i];
        for(var key in person){
            console.log("键:" + key + "   值:"+person[key]);
        }
    }
}
  • 对象遍历
var p2 = {
        person1:{name:"eric",age:23,address:"USA",isVip:false},
        person2:{name:"james",age:38,address:"USA",isVip:true},
        person3:{name:"Yaoming",age:38,address:"CHN",isVip:true}
    }

for(var key in p2){
    var person = p2[key];
    for(var key in person){
        console.log("键:" + key + "   值:"+person[key]);
    }
}
8.3.2 json数据和java对象之间的转换

实现json数据和java对象之间的转换技术有很多,比如jackson json-lib fastjson Gjson等。

8.3.2.1 jackson技术的使用

步骤:

  1. 导入jacckson相关的jar包。

在这里插入图片描述

  1. 创建核心对象ObjectMapper。
/*
 * java实体对象和json之间的互相转换
 */
public class TestJson {
    @Test
    public void test01() throws Exception{
        Person person = new Person("eric",32,"USA",new Date());
        //创建操作json的核心对象
        ObjectMapper mapper = new ObjectMapper();
        //将java对象转换成json格式的串
        String json_string = mapper.writeValueAsString(person);
        // {"username":"eric","age":32,"address":"USA"}
        System.out.println(json_string);
        //将一个json串转换成pojo对象
        Person p = mapper.readValue(json_string, Person.class);
        System.out.println(p);
    }
}

需要注意:jackson中有几个常用的注解:

  • @JsonIgnore 忽略对某个属性进行json格式的处理
public class Person {

    private String username;
    private Integer age;
    private String address;
    @JsonIgnore //忽略对该属性进行格式转换
    private Date birthday;
    
    // 省略了构造函数 get set方法 toString方法
}

最后进行json格式的转换结果:

{"username":"eric","age":32,"address":"USA"}
  • @JsonFormat注解 对指定数据进行对应的格式处理
public class Person {

    private String username;
    private Integer age;
    private String address;
    //在进行json格式转换的时候 将date数据类型转换成指定的格式
    @JsonFormat(pattern = "yyyy-MM-dd")
    private Date birthday;
    
    // 省略了构造函数 get set方法 toString方法
}

最后进行json格式的转换结果:

{"username":"eric","age":32,"address":"USA","birthday":"2022-10-12"}
/*
 * List集合和json之间的互相转换
 */
@Test
public void test02() throws Exception{
    List<Person> list = new ArrayList<>();
    Person person1 = new Person("eric",32,"USA",new Date());
    Person person2 = new Person("james",38,"USA",new Date());
    Person person3 = new Person("kbe",44,"USA",new Date());
    list.add(person1);
    list.add(person2);
    list.add(person3);
    ObjectMapper mapper = new ObjectMapper();
    String json_string = mapper.writeValueAsString(list);
    System.out.println(json_string);
    //将json串转换成List集合
    List list1 = mapper.readValue(json_string,new TypeReference<List<Person>>(){});
    System.out.println(list1);
}
/*
 * Map集合和json之间的互相转换
 */
@Test
public void test03() throws Exception{
    Map<String,Person> map = new HashMap<>();
    Person person1 = new Person("eric",32,"USA",new Date());
    Person person2 = new Person("james",38,"USA",new Date());
    Person person3 = new Person("kbe",44,"USA",new Date());
    map.put("p1",person1);
    map.put("p2",person2);
    map.put("p3",person3);
    ObjectMapper mapper = new ObjectMapper();
    //将map转换成json串
    String json_string = mapper.writeValueAsString(map);
    System.out.println(json_string);
    //将json串转换成Map
    Map<String,Person> m = mapper.readValue(json_string, new TypeReference<Map<String, Person>>() {
    });
    System.out.println(m);
}
8.3.2.2 案例

案例:检查用户名是否存在,服务器向浏览器响应json格式的数据

  • 编写ajax请求
<body>
    用户名: <input type="text" name="username" id="username">  <span id="s1"></span>
</body>
<script src="http://code.jquery.com/jquery-2.1.4.min.js"></script>
<script type="text/javascript">
    $(function(){
        $("#username").blur(function(){
            //var username = $("#username").val();
            var username = $(this).val();
            /**
             * 参数1:请求的服务器资源路径
             * 参数2: 发送ajax请求到后台需要携带的参数
             * 参数3:服务器向浏览器响应成功之后,需要执行的回调函数(data 封装的是后台向前台响应的数据)
             * 参数4:指定后台向浏览器响应什么样格式的数据
             */
            $.get("${pageContext.request.contextPath}/findUsername",{username:username},function (data) {
                if(data.flag){
                    $("span#s1").html(data.msg).css("color","red");
                }else{
                    $("span#s1").html(data.msg).css("color","green");
                }
            },"json");
        })
    })
</script>
  • 编写servlet
@WebServlet("/findUsername")
public class FindUsernameServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //response.setContentType("application/json;charset=utf-8");
        response.setContentType("text/html;charset=utf-8");
        String username = request.getParameter("username");
        Map<String,Object> map = new HashMap();
        if(username.equals("tom")){
            map.put("flag",true);
            map.put("msg","用户名已经存在,请重新输入");
        }else{
            map.put("flag",false);
            map.put("msg","用户名可以注册");
        }
        ObjectMapper mapper = new ObjectMapper();
        String json_string = mapper.writeValueAsString(map);
        response.getWriter().write(json_string);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值