我只想卷死各位,或者被各位卷死,在此特别感谢黑马程序员的JavaWeb教程


本文摘要:

  • 能够使用 Filter 完成登陆状态校验功能
  • 能够使用 axios 发送 ajax 请求
  • 熟悉 json 格式,并能使用 Fastjson 完成 java 对象和 json 串的相互转换
  • 使用 axios + json 完成综合案例

Filter

Filter概述

Filter 表示过滤器,是 JavaWeb 三大组件(Servlet、Filter、Listener)之一。我们之前都已经学习过了Servlet ,现在我们来学习Filter和Listener。

过滤器可以把对资源的请求拦截下来,从而实现一些特殊的功能。

例如某些网站未登录不能查看评论,不能将商品加入购物车,得把你拦下来,先让你登录,也就是在访问前,先经过Filter。

下面来具体说说,拦截器拦截到后可以做什么功能呢?

过滤器一般完成一些通用的操作。比如每个资源都要写一些代码完成某个功能,我们总不能在每个资源中写这样的代码吧,而此时我们可以将这些代码写在过滤器中,因为请求每一个资源都要经过过滤器。

我们之前做的品牌数据管理的案例中就已经做了登陆的功能,但这个登录功能其实是如同虚设的,我们可以直接访问登录后的页面,所以本文的目标就是完善登录功能,不登录就无法查看数据。

Filter入门

开发步骤

进行Filter开发分为以下三步实现

  1. 定义类,实现Filter接口,并重写其所有方法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    //实现Filter接口,重写所有方法
    public class FilterDemo1 implements Filter {

    public void init(FilterConfig filterConfig) throws ServletException {

    }

    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

    }

    public void destroy() {

    }
    }
  2. 配置Filter拦截路径资源:在类上定义@WebFilter注解。而注解的value属性值/*表示拦截所有资源
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    //这里暂时先拦截所有资源,后面我们会仔细讲拦截路径的配置
    @WebFilter("/*")
    public class FilterDemo1 implements Filter {

    public void init(FilterConfig filterConfig) throws ServletException {

    }

    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

    }

    public void destroy() {

    }
    }
  3. 在doFilter方法中输出一句话,并放行
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    @WebFilter("/*")
    public class FilterDemo1 implements Filter {

    public void init(FilterConfig filterConfig) throws ServletException {

    }

    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
    //这句话只是来测试是否调用了该方法
    System.out.println("doFilter...");
    //一定要放行才能访问资源
    filterChain.doFilter(servletRequest, servletResponse);
    }

    public void destroy() {

    }
    }

代码演示

  • 创建一个web项目,在webapp下创建hello.jsp页面

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
    <title>Title</title>
    </head>
    <body>

    <!--随便输出点什么东西-->
    <h1>HELLO FILTER</h1>

    </body>
    </html>
  • pom.xml配置如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    <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/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>org.example</groupId>
    <artifactId>filter-demo</artifactId>
    <packaging>war</packaging>
    <version>1.0-SNAPSHOT</version>
    <name>filter-demo Maven Webapp</name>
    <url>http://maven.apache.org</url>
    <dependencies>
    <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>3.8.1</version>
    <scope>test</scope>
    </dependency>
    <dependency>
    <groupId>org.mortbay.jetty</groupId>
    <artifactId>servlet-api</artifactId>
    <version>2.5-20081211</version>
    </dependency>
    <dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>3.1.0</version>
    <scope>compile</scope>
    </dependency>
    </dependencies>
    <build>
    <finalName>filter-demo</finalName>
    </build>
    </project>
  • 在java目录下新建com.blog.web.filter包,并新建FilterDemo1

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    @WebFilter("/*")
    public class FilterDemo1 implements Filter {

    public void init(FilterConfig filterConfig) throws ServletException {

    }

    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
    System.out.println("doFilter...");
    filterChain.doFilter(servletRequest, servletResponse);
    }

    public void destroy() {

    }
    }
  • 测试

    • 将放行的代码注释掉,我们访问hello.jsp页面,不会有任何内容,因为被拦截了,且没有放行,控制台会输出doFilter...
    • 打开放行的代码,我们访问hello.jsp页面,会有h1标签正常输出HELLO FILTER

上述效果说明了FilterDemo1这个过滤器的doFilter方法被执行了,且必须添加放行的方法才能访问hello.jsp页面

Filter执行流程

Filter执行流程.png

如上图是使用过滤器的流程,我们通过以下问题来研究过滤器的执行流程:

  • 放行后访问对应资源,资源访问完成后,还会回到Filter中吗?
    • 从上图就可以看出肯定会回到Filter中
  • 如果回到Filter中,是重头执行还是执行放行后的逻辑呢?
    • 如果是重头执行的话,就意味着 放行前逻辑 会被执行两次,肯定不会这样设计了;所以访问完资源后,会回到 放行后逻辑,执行该部分代码。

通过上述的说明,我们可以总结一下Filter的执行流程

  • 执行放行前逻辑 —> 放行 —> 访问资源 —> 执行放行后逻辑

接下来我们通过代码验证一下,在 doFilter() 方法前后都加上输出语句

1
2
3
4
5
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("1.Filter...");
filterChain.doFilter(servletRequest, servletResponse);
System.out.println("3.Filter...");
}

hello.jsp中添加输出语句如下
1
2
3
4
5
6
<body>
<h1>HELLO FILTER</h1>
<%
System.out.println("2.Filter...");
%>
</body>

重启服务器,访问hello.jsp页面,控制台输出结果如下,符合我们的预期结果

1.Filter…
2.Filter…
3.Filter…

Filter拦截路径配置

拦截路径表示 Filter 会对请求的哪些资源进行拦截,使用 @WebFilter 注解进行配置。如:@WebFilter("拦截路径")

拦截路径有如下四种配置方式:

  • 拦截具体的资源:/index.jsp:只有访问index.jsp时才会被拦截
  • 目录拦截:/user/*:访问/user下的所有资源,都会被拦截
  • 后缀名拦截:*.jsp:访问后缀名为jsp的资源,都会被拦截
  • 拦截所有:/*:访问所有资源,都会被拦截

过滤器链

概述

过滤器链是指在一个Web应用,可以配置多个过滤器,这多个过滤器称为过滤器链。

如下图就是一个过滤器链,我们学习过滤器链主要是学习过滤器链执行的流程

过滤器链.png

上图中的过滤器链执行是按照以下流程执行:

  1. 执行 Filter1 的放行前逻辑代码
  2. 执行 Filter1 的放行代码
  3. 执行 Filter2 的放行前逻辑代码
  4. 执行 Filter2 的放行代码
  5. 访问到资源
  6. 执行 Filter2 的放行后逻辑代码
  7. 执行 Filter1 的放行后逻辑代码

以上流程串起来就像一条链子,故称之为过滤器链。

代码演示

  • 我们在com.blog.web.filter包下再新建一个FilterDemo2类,并实现Filter接口,重写其所有方法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    @WebFilter("/*")
    public class FilterDemo2 implements Filter {
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
    System.out.println("2.Filter...");
    filterChain.doFilter(servletRequest, servletResponse);
    System.out.println("4.Filter...");
    }

    public void destroy() {

    }
    }
  • 修改FilterDemo1类的doFilter方法,将输出语句调成我们预期的结果,最终测试的时候,看看是否符合预期
    1
    2
    3
    4
    5
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
    System.out.println("1.Filter...");
    filterChain.doFilter(servletRequest, servletResponse);
    System.out.println("5.Filter...");
    }
  • 修改hello.jsp,将输出语句改为预期结果
    1
    2
    3
    4
    5
    6
    <body>
    <h1>HELLO FILTER</h1>
    <%
    System.out.println("3.Filter...");
    %>
    </body>
  • 重启服务器,访问hello.jsp页面,控制台输出如下,符合我们预期的结果

    1.Filter…
    2.Filter…
    3.Filter…
    4.Filter…
    5.Filter…

问题

上面代码中为什么是先执行 FilterDemo ,后执行 FilterDemo2 呢?

- 我们现在使用的是注解配置Filter,而这种配置方式的优先级是按照过滤器类名(字符串)的自然排序。
- 比如有如下两个名称的过滤器 : `BFilterDemo` 和 `AFilterDemo` 。那一定是 `AFilterDemo` 过滤器先执行。

案例

需求

访问服务器资源时,需要先进行登录验证,如果没有登录,则自动跳转到登录页面

分析

要是搁以前,我们要实现该功能可能得在每一个资源里加入登陆状态校验的代码
但现在,只需要写一个 Filter ,在该过滤器中进行登陆状态校验即可。而在该 Filter 中逻辑如下:

  • 判断访问的是否为登录之后才能看的资源
    • 是:放行
    • 不是:进行登录验证
  • 判断用户是否登录:Session中是否有user对象
    • 登录:放行
    • 未登录:跳转至登录页面,并给出提示信息

代码实现

创建Filter

brand-demo 工程创建 com.itheima.web.filter 包,在该下创建名为 LoginFilter 的过滤器

1
2
3
4
5
6
7
8
9
10
11
12
13
@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() {
}
}

编写逻辑代码

doFilter() 方法中编写登陆状态校验的逻辑代码。

我们首先需要从 session 对象中获取用户信息,但是 ServletRequest 类型的 requset 对象没有获取 session 对象的方法,所以此时需要将 request对象强转成 HttpServletRequest 对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@WebFilter("/*")
public class LoginFilter implements Filter {
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
//强转操作
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpSession session = httpRequest.getSession();
//获取是否有user对象
Object user = session.getAttribute("user");
//如果有user对象,那么就是已登录状态,直接放行
if (user != null) {
chain.doFilter(request, response);
//如果没有user对象,那就是未登录,不允许访问,给出错误信息并跳转到登录页面
} else {
httpRequest.setAttribute("login_msg", "您尚未登录");
httpRequest.getRequestDispatcher("/login.jsp").forward(request, response);
}
}

public void init(FilterConfig config) throws ServletException {
}

public void destroy() {
}
}
测试并抛出问题

重启服务器,访问register.jsp注册页面,竟然访问不了了,也是直接跳转到了登录页面,而且如果你配置了css文件,css样式也显示不出来了,这是怎么回事呢?

问题分析及解决

因为我们配置的是对所有页面进行拦截,但现在需要对所有的登陆和注册相关的资源进行放行。
所以我们需要在判断session中是否包含用户信息之前,应该加上对登陆及注册相关资源放行的逻辑处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
HttpServletRequest httpRequest = (HttpServletRequest) request;
//在数组中存储注册和登录相关的资源路径
String[] urls = {"/login.jsp","/register.jsp","/checkCodeServlet","/registerServlet","/loginServlet"};
//获取当前的访问路径
String url = httpRequest.getRequestURL().toString();
//遍历数组
for (String u : urls) {
/*
判断当前访问的资源路径字符串是否包含要放行的的资源路径字符串
比如当前访问的资源路径是 /brand-demo/login.jsp
而字符串 /brand-demo/login.jsp 包含了字符串 /login.jsp ,所以这个字符串就需要放行
*/
if (url.contains(u)){
//找到了就放行
chain.doFilter(request,response);
return;
}
}

过滤器完整代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@WebFilter("/*")
public class LoginFilter implements Filter {
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
String[] urls = {"/login.jsp","/register.jsp","/checkCodeServlet","/registerServlet","/loginServlet"};
String url = httpRequest.getRequestURL().toString();
for (String u : urls) {
if (url.contains(u)){
chain.doFilter(request,response);
return;
}
}

HttpSession session = httpRequest.getSession();
Object user = session.getAttribute("user");
if (user != null) {
chain.doFilter(request, response);
} else {
httpRequest.setAttribute("login_msg", "您尚未登录");
httpRequest.getRequestDispatcher("/login.jsp").forward(request, response);
}
}

public void init(FilterConfig config) throws ServletException {
}

public void destroy() {
}
}

Listener

概述

  • Listener 表示监听器,是 JavaWeb 三大组件(Servlet、Filter、Listener)之一。

  • 监听器可以监听就是在 applicationsessionrequest 三个对象创建、销毁或者往其中添加修改删除属性时自动执行代码的功能组件。

    request 和 session 我们学习过。而 applicationServletContext 类型的对象。

    ServletContext 代表整个web应用,在服务器启动的时候,tomcat会自动创建该对象。在服务器关闭时会自动销毁该对象。

分类

JavaWeb 提供了8个监听器:

监听器分类 监听器名称 作用
servletContext监听 servletContextListener 用于对ServletContext对象进行监听(创建、销毁)
ServletContextAttributeListener 对ServletContext对象中属性的监听(增删改属性)
session监听 HttpSessionListener 对Session对象的整体状态的监听(创建、销毁)
HttpSessionAttributeListener 对Session对象中的属性监听(增删改属性)
HttpSessionBindingListener 监听对象于Session的绑定和解除
HttpsessionActivationListener 对Session数据的钝化和活化的监听
Request监听 servletRequestListener 对Request对象进行监听(创建、销毁)
servletRequestAttributeListener 对Request对象中属性的监听(增删改属性)

这里面只有 ServletContextListener 这个监听器后期我们会接触到,ServletContextListener 是用来监听 ServletContext 对象的创建和销毁。

ServletContextListener 接口中有以下两个方法

  • void contextInitialized(ServletContextEvent sce)ServletContext 对象被创建了会自动执行的方法
  • void contextDestroyed(ServletContextEvent sce)ServletContext 对象被销毁时会自动执行的方法

代码演示

我们只演示一下 ServletContextListener 监听器

  • 定义一个类,实现ServletContextListener 接口
  • 重写所有的抽象方法
  • 使用 @WebListener 进行配置

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
@WebListener
public class ContextLoaderListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
//加载资源
System.out.println("ContextLoaderListener...");
}

@Override
public void contextDestroyed(ServletContextEvent sce) {
//释放资源
}
}

启动服务器,就可以在控制台输出了 ContextLoaderListener...,同时也说明了 ServletContext 对象在服务器启动的时候被创建了。

Ajax

概述

AJAX (Asynchronous JavaScript And XML):异步的 JavaScript 和 XML。

作用

AJAX 作用有以下两方面:

  1. 与服务器进行数据交换:通过AJAX可以给服务器发送请求,服务器将数据直接响应回给浏览器。
    • 在之前,我们做功能的流程是,Servlet 调用完业务逻辑层,然后将数据存储到域对象中,然后跳转到指定的 jsp 页面,在页面上使用 EL表达式JSTL 标签库进行数据的展示。
    • 而我们学习了AJAX 后,就可以使用AJAX和服务器进行通信,以达到使用HTML+AJAX来替换JSP页面了。浏览器发送请求servlet,servlet 调用完业务逻辑层后将数据直接响应回给浏览器页面,页面使用 HTML 来进行数据展示。
  2. 异步交互:可以在不重新加载整个页面的情况下,与服务器交换数据并更新部分网页的技术,如:搜索联想、用户名是否可用校验,等等…
    • 当我们在百度输入一些关键字(例如 奥运)后就会在下面联想出相关的内容,而联想出来的这部分数据肯定是存储在百度的服务器上,而我们并没有看出页面重新刷新,这就是更新局部页面的效果。
    • 我们在用户名的输入框输入用户名,当输入框一失去焦点,如果用户名已经被占用就会在下方展示提示的信息;在这整个过程中也没有页面的刷新,只是在局部展示出了提示信息,这就是更新局部页面的效果。

同步与异步

知道了局部刷新后,接下来我们再聊聊同步和异步:

  • 同步发送请求过程如下
    同步请求.png
    浏览器页面在发送请求给服务器,在服务器处理请求的过程中,浏览器页面不能做其他的操作。只能等到服务器响应结束后才能,浏览器页面才能继续做其他的操作。

  • 异步发送请求过程如下
    异步请求.png
    浏览器页面发送请求给服务器,在服务器处理请求的过程中,浏览器页面还可以做其他的操作。

2022-09-22复盘补充:
所谓异步,就是主程序一直往下走,延迟和等待程序放在另一个列表中等待执行,不阻塞主程序继续往下进行
JS中大部分都是同步,少数的异步有以下几个

  • 定时器settimeout和steInterval
  • ajax异步请求
  • promise
  • ……

异步的好处是:它不会因为延时和等待阻塞程序
但异步存在一些问题,例如原计划从1到4执行

  1. 挂加速器
  2. 打开steam
  3. 上号
  4. 打派派

但现在3是异步操作,结果变成1243,没登录账号,你打个锤子的派派

快速入门

创建一个ajax-demo的web项目,并导入servlet的坐标

服务端实现

在项目的java目录下创建 com.itheima.blog.servlet包,并在该包下创建名为 AjaxServlet 的servlet

1
2
3
4
5
6
7
8
9
10
11
12
@WebServlet("/ajaxServlet")
public class AjaxServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.getWriter().write("HELLO AJAX");
}

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

客户端实现

webapp 下创建名为 01-ajax-demo1.html 的页面,在该页面书写 ajax 代码

  • 创建核心对象,不同的浏览器创建的对象是不同的

    1
    2
    3
    4
    5
    6
    7
     var xhttp;
    if (window.XMLHttpRequest) {
    xhttp = new XMLHttpRequest();
    } else {
    // code for IE6, IE5
    xhttp = new ActiveXObject("Microsoft.XMLHTTP");
    }
  • 发送请求

    1
    2
    3
    4
    //建立连接,这里要写全地址,因为在后期,前端的代码和后端的代码并不是部署在同一个服务器上
    xhttp.open("GET", "http://localhost:8080/ajax_demo/ajaxServlet");
    //发送请求
    xhttp.send();
  • 获取响应

    1
    2
    3
    4
    5
    6
    xhttp.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 200) {
    // 通过 this.responseText 可以获取到服务端响应的数据
    alert(this.responseText);
    }
    };

完整代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>

</body>

<script>
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);
}
};
</script>
</html>

测试

在浏览器地址栏输入 http://localhost:8080/ajax-demo/01-ajax-demo1.html ,在 01-ajax-demo1.html加载的时候就会发送 ajax 请求,并有一个alert弹窗输出HELLO AJAX

案例

需求:在完成用户注册时,当用户名输入框失去焦点时,校验用户名是否在数据库已存在

分析

  • 前端完成的逻辑
    1. 给用户名输入框绑定光标失去焦点事件 onblur
    2. 发送 ajax请求,携带username参数
    3. 处理响应:是否显示提示信息
  • 后端完成的逻辑
    1. 接收用户名
    2. 调用service查询User。此案例是为了演示前后端异步交互,所以此处我们不做业务逻辑处理
    3. 返回标记

后端实现

com.blog.web.servlet 包中定义名为 SelectUserServlet 的servlet。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@WebServlet("/selectUserServlet")
public class SelectUserServlet extends HttpServlet {

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//接收用户名
String username = request.getParameter("username");
//2. 调用service查询User对象,此处不进行业务逻辑处理,直接给 flag 赋值为 true,表明用户名占用
boolean flag = true;
//响应标记
response.getWriter().write("" + flag);
}

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

前端实现

  • 随便写一个注册的页面register.html
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    <!DOCTYPE html>
    <html>
    <head>
    <title>Title</title>
    <meta charset="utf-8">
    </head>
    <body>
    <form action="/ajax_demo/selectUserServlet" method="get">
    <h1>欢迎注册</h1>
    用户名:<input name="username" type="text" id="username">
    <span id="username_arr" style="display:none;">用户名已被占用</span><br>
    密码:<input name="password" type="password"><br>
    <input value="注册" type="submit">
    </form>
    </body>
    </html>
  • 然后给用户名输入框绑定光标失去焦点事件 onblur
    1
    2
    3
    document.getElementById("username").onblur = function () {

    }
  • 发送 ajax请求,携带username参数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    // 发送ajax请求
    // 获取用户名
    var username = this.value;
    var xhttp;
    if (window.XMLHttpRequest) {
    xhttp = new XMLHttpRequest();
    } else {
    // code for IE6, IE5
    xhttp = new ActiveXObject("Microsoft.XMLHTTP");
    }
    // 发送请求,由于我们发送的是 GET 请求,所以需要在 URL 后拼接从输入框获取的用户名数据。
    xhttp.open("GET", "http://localhost:8080/ajax-demo/selectUserServlet?username=" + username);
    xhttp.send();

    // 获取响应
    xhttp.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 200) {
    //处理响应的结果
    }
    };
  • 处理响应:是否显示提示信息
    this.readyState == 4 && this.status == 200 条件满足时,说明已经成功响应数据了。
    此时需要判断响应的数据是否是 “true” 字符串,如果是说明用户名已经占用给出错误提示;如果不是说明用户名未被占用清除错误提示。代码如下

    1
    2
    3
    4
    5
    6
    7
    8
    //判断
    if(this.responseText == "true"){
    //用户名存在,显示提示信息
    document.getElementById("username_err").style.display = '';
    }else {
    //用户名不存在 ,清楚提示信息
    document.getElementById("username_err").style.display = 'none';
    }
  • 综上所述,前端的完整代码如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    <!DOCTYPE html>
    <html>
    <head>
    <title>Title</title>
    <meta charset="utf-8">
    </head>
    <body>
    <form action="/ajax_demo/selectUserServlet" method="get">
    <h1>欢迎注册</h1>
    用户名:<input name="username" type="text" id="username">
    <span id="username_arr" style="display:none;">用户名已被占用</span><br>
    密码:<input name="password" type="password"><br>
    <input value="注册" type="submit">
    </form>

    <script>
    document.getElementById("username").onblur = function () {
    var xhttp;
    var username = this.value;
    if (window.XMLHttpRequest) {
    xhttp = new XMLHttpRequest();
    } else {
    // code for IE6, IE5
    xhttp = new ActiveXObject("Microsoft.XMLHTTP");
    }
    //建立连接
    xhttp.open("GET", "http://localhost:8080/ajax_demo/selectUserServlet?username=" + username);
    //发送请求
    xhttp.send();
    xhttp.onreadystatechange = function () {
    if (this.readyState == 4 && this.status == 200) {
    // 通过 this.responseText 可以获取到服务端响应的数据
    // alert(this.responseText);
    if (this.responseText == "true") {
    document.getElementById("username_arr").style.display = '';
    } else {
    document.getElementById("username_arr").style.display = 'none';
    }
    }
    };
    }
    </script>
    </body>
    </html>
  • 测试
    重启服务器,访问http://localhost:8080/ajax_demo/register.html,随便输入用户名之后,鼠标点击其他位置失去焦点,会显示用户名已被占用

Axios

Axios 对原生的AJAX进行封装,简化书写。

Axios官网是:https://www.axios-http.cn

基本使用

  • 使用axios 发送请求,并获取响应结果

    • 发送 get 请求

      1
      2
      3
      4
      5
      6
      axios({
      method:"get",
      url:"http://localhost:8080/ajax-demo1/ajaxDemo?username=zhangsan"
      }).then(function (resp){
      alert(resp.data);
      })
    • 发送 post 请求

      1
      2
      3
      4
      5
      6
      7
      axios({
      method:"post",
      url:"http://localhost:8080/ajax-demo1/ajaxDemo",
      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 可以获取到响应的数据。

快速入门

后端实现

  • 定义一个用于接收请求的servlet,代码如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    @WebServlet("/ajaxServlet")
    public class AjaxServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    System.out.println("get...");
    //接收请求参数
    String username = request.getParameter("username");
    System.out.println(username);
    //响应数据
    response.getWriter().write("HELLO AXIOS");
    }

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

前端实现

  • 引入 js 文件
    1
    <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
  • 发送 ajax 请求

    • get 请求
      1
      2
      3
      4
      5
      6
      axios({
      method: "get",
      url: "http://localhost:8080/ajax_demo/ajaxServlet?username=zhangsan"
      }).then(function (resp) {
      alert(resp.data);
      })
    • post 请求
      1
      2
      3
      4
      5
      6
      7
      axios({
      method: "post",
      url: "http://localhost:8080/ajax_demo/ajaxServlet",
      data: "username=zhangsan"
      }).then(function (resp) {
      alert(resp.data);
      })
  • 整体页面代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <title>Title</title>
    </head>
    <body>
    <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
    <script>
    // axios({
    // method: "get",
    // url: "http://localhost:8080/ajax_demo/ajaxServlet?username=zhangsan"
    // }).then(function (resp) {
    // alert(resp.data);
    // })

    axios({
    method: "post",
    url: "http://localhost:8080/ajax_demo/ajaxServlet",
    data: "username=zhangsan"
    }).then(function (resp) {
    alert(resp.data);
    })

    </script>
    </body>
    </html>
  • 分别测试get请求和post请求,

    • get请求会在页面上出现一个alert弹窗显示HELLO AXIOS,同时控制台输出

      get…
      zhangsan

    • post请求会在页面上出现一个alert弹窗显示HELLO AXIOS,同时控制台输出

      post…
      get…
      zhangsan

请求方法别名

为了方便起见, 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 请求代码可以改为如下:
    1
    2
    3
    axios.get("http://localhost:8080/ajax-demo/axiosServlet?username=zhangsan").then(function (resp) {
    alert(resp.data);
    });
  • 入门案例中的 post 请求代码可以改为如下:
    1
    2
    3
    axios.post("http://localhost:8080/ajax-demo/axiosServlet","username=zhangsan").then(function (resp) {
    alert(resp.data);
    })

JSON

概述

概念:JavaScript Object Notation。JavaScript 对象表示法.

如下是 JavaScript 对象的定义格式:

1
2
3
4
5
{
name:"zhangsan",
age:23,
city:"北京"
}

接下来我们再看看 JSON 的格式:

1
2
3
4
5
{
"name":"zhangsan",
"age":23,
"city":"北京"
}

通过上面 js 对象格式和 json 格式进行对比,发现两个格式特别像。只不过 js 对象中的属性名可以使用引号(可以是单引号,也可以是双引号);而 json 格式中的键要求必须使用双引号括起来,这是 json 格式的规定。json 格式的数据有什么作用呢?

作用:由于其语法格式简单,层次结构鲜明,现多用于作为数据载体

JSON基础语法

定义格式

JSON本质就是一个字符串,但是该字符串内容是有一定的格式要求的。 定义格式如下:

1
var 变量名 = {"key":value,"key":value,...};

JSON串的键要求必须使用双引号括起来,而值根据要表示的类型确定。value 的数据类型分为如下

  • 数字(整数或浮点数)
  • 字符串(使用双引号括起来)
  • 逻辑值(true或者false)
  • 数组(在方括号中)
  • 对象(在花括号中)
  • null

代码演示

1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
let jsonStr = '{"name":"zhangsan","age":18,"addr":["北京","上海","广州","深圳"]}';
alert(jsonStr);
</script>
</body>
</html>

通过浏览器打开,浏览器会有一个弹窗显示`{“name”:”zhangsan”,”age”:18,”addr”:[“北京”,”上海”,”广州”,”深圳”]}

现在我们需要获取到该 JSON 串中的 name 属性值,应该怎么处理呢?

  • 如果它是一个 js 对象,我们就可以通过 js对象.属性名 的方式来获取数据。JS 提供了一个对象 JSON ,该对象有如下两个方法:

    • parse(str) :将 JSON串转换为 js 对象。使用方式是:var jsObject = JSON.parse(jsonStr);
    • stringify(obj) :将 js 对象转换为 JSON 串。使用方式是:var jsonStr = JSON.stringify(jsObject)
  • 代码演示

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <title>Title</title>
    </head>
    <body>
    <script>
    let jsonStr = '{"name":"zhangsan","age":18,"addr":["北京","上海","广州","深圳"]}';
    alert(jsonStr);
    //将json字符串转化为js对象
    let jsonObj = JSON.parse(jsonStr);
    alert(jsonObj);
    alert(jsonObj.name);
    alert(jsonObj.age);
    alert(jsonObj.addr);
    //将js对象转化为json字符串
    let jsonStr2 = JSON.stringify(jsonObj);
    alert(jsonStr2);
    </script>
    </body>
    </html>

发送异步请求携带参数

后面我们使用 axios 发送请求时,如果要携带复杂的数据时都会以 JSON 格式进行传递

请求参数不可能由我们自己拼接字符串的,我们可以提前定义一个 js 对象,用来封装需要提交的参数,然后使用 JSON.stringify(js对象) 转换为 JSON 串,再将该 JSON 串作为 axiosdata 属性值进行请求参数的提交。如下:

1
2
3
4
5
6
7
8
9
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 串进行提交。如下:

1
2
3
4
5
6
7
8
9
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 串需要放在请求体中。

JSON串与Java对象的相互转换

学习完 json 后,接下来聊聊 json 的作用。以后我们会以 json 格式的数据进行前后端交互。前端发送请求时,如果是复杂的数据就会以 json 提交给后端;而后端如果需要响应一些复杂的数据时,也需要以 json 格式将数据响应回给浏览器。

在后端我们就需要重点学习以下两部分操作:

  • 请求数据:JSON字符串转为Java对象
  • 响应数据:Java对象转为JSON字符串

阿里提供的一套 API Fastjson,可以帮我们实现上面两部分操作。

Fastjson概述

Fastjson 是阿里巴巴提供的一个Java语言编写的高性能功能完善的 JSON 库,是目前Java语言中最快的 JSON 库,可以实现 Java 对象和 JSON 字符串的相互转换。

Fastjson使用

Fastjson 使用也是比较简单的,分为以下三步完成

  1. 导入坐标
    1
    2
    3
    4
    5
    <dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.62</version>
    </dependency>
  2. Java对象转JSON
    1
    String jsonStr = JSON.toJSONString(obj);
    将 Java 对象转换为 JSON 串,只需要使用 Fastjson 提供的 JSON 类中的 toJSONString() 静态方法即可。
  3. JSON字符串转Java对象
    1
    User user = JSON.parseObject(jsonStr, User.class);
    将 json 转换为 Java 对象,只需要使用 Fastjson 提供的 JSON 类中的 parseObject() 静态方法即可。

代码演示

  • 先创建一个User类
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    package com.blog.web.pojo;

    public class User {
    private Integer id;
    private String username;
    private String password;

    public User() {
    }

    public User(Integer id, String username, String password) {
    this.id = id;
    this.username = username;
    this.password = password;
    }

    public Integer getId() {
    return id;
    }

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

    public String getUsername() {
    return username;
    }

    public void setUsername(String username) {
    this.username = username;
    }

    public String getPassword() {
    return password;
    }

    public void setPassword(String password) {
    this.password = password;
    }

    @Override
    public String toString() {
    return "User{" +
    "id=" + id +
    ", username='" + username + '\'' +
    ", password='" + password + '\'' +
    '}';
    }
    }
  • 测试方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    import com.alibaba.fastjson.JSON;
    import com.blog.web.pojo.User;

    public class FastjsonDemo {
    public static void main(String[] args) {
    //1. 将Java对象转为JSON字符串
    User user = new User();
    user.setId(1);
    user.setUsername("zhangsan");
    user.setPassword("asd123");
    String jsonString = JSON.toJSONString(user);
    System.out.println(jsonString);
    //2. 将JSON字符串转为Java对象
    User u = JSON.parseObject("{\"id\":1,\"password\":\"asd123\",\"username\":\"zhangsan\"}", User.class);
    System.out.println(u);
    }
    }
  • 得到输出结果如下

    {“id”:1,”password”:”asd123”,”username”:”zhangsan”}
    User{id=1, username=’zhangsan’, password=’asd123’}

案例

需求

使用Axios + JSON 完成品牌列表数据查询和添加。

查询所有功能

前后端需以 JSON 格式进行数据的传递;由于此功能是查询所有的功能,前端发送 ajax 请求不需要携带参数,而后端响应数据需为 json 数据

环境准备

依旧是使用我们之前的brand-demo项目,将brand.jsp页面复制一份为brand.html来修改,注意将前面的过滤器的代码注释掉(只保留放行代码即可),这样在我们的测试阶段就不需要登录了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>${user.username},欢迎你</h1>

<input type="button" value="新增" id="add"><br>
<hr>
<table border="1" cellspacing="0" width="1200">
<tr>
<th>序号</th>
<th>品牌名称</th>
<th>企业名称</th>
<th>排序</th>
<th>品牌介绍</th>
<th>状态</th>
<th>操作</th>
</tr>
<c:forEach items="${brands}" var="brand">
<tr align="center">
<td>${brand.id}</td>
<td>${brand.brandName}</td>
<td>${brand.companyName}</td>
<td>${brand.ordered}</td>
<td>${brand.description}</td>
<td>${brand.status == 1 ? "启用" : "禁用"}</td>
<td><a href="/brand_demo/selectByIdServlet?id=${brand.id}">修改</a> <a href="/brand_demo/deleteServlet?id=${brand.id}">删除</a></td>
</tr>
</c:forEach>
</table>
<script>
document.getElementById("add").onclick = function (){
location.href = "/brand_demo/addBrand.jsp";
}
</script>
</body>
</html>

后端实现

修改 com.itheima.web 包下的 SelectAllServlet,具体的逻辑如下:

  • 调用 service 的 selectAll() 方法进行查询所有的逻辑处理
  • 将查询到的集合数据转换为 json 数据。我们将此过程称为序列化;如果是将 json 数据转换为 Java 对象,我们称之为反序列化
  • 将 json 数据响应回给浏览器。这里一定要设置响应数据的类型及字符集 response.setContentType("text/json;charset=utf-8");

SelectAllServlet 代码如下:

之前我们是将获取到的brands对象存入request域中,然后根据JSTL和EL表达式来进行遍历和取值

1
2
3
4
5
6
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
List<Brand> brands = brandService.selectAll();
request.setAttribute("brands", brands);
request.getRequestDispatcher("/brand.jsp").forward(request, response);
}

现在是通过axios来将数据响应给浏览器页面,将Java对象转换为Json字符串,响应给浏览器页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@WebServlet("/selectAllServlet")
public class SelectAllServlet extends HttpServlet {
private BrandService brandService = new BrandService();

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
List<Brand> brands = brandService.selectAll();
String jsonString = JSON.toJSONString(brands);
response.setContentType("text/json;charset=utf-8");
response.getWriter().write(jsonString);
}

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

前端实现

  • brand.html 页面引入 axios 的 js 文件,我这就先不用本地的js了

    1
    <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
  • 绑定页面加载完毕事件
    brand.html 页面绑定加载完毕事件,该事件是在页面加载完毕后被触发,代码如下

    1
    2
    3
    window.onload = function() {

    }
  • 在页面加载完毕事件绑定的匿名函数中发送异步请求,代码如下:

    1
    2
    3
    4
    5
    6
    axios({
    method:"get",
    url:"http://localhost:8080/brand-demo/selectAllServlet"
    }).then(function (resp) {

    });
  • 处理响应数据

  • then 中的回调函数中通过 resp.data 可以获取响应回来的数据.
  • 现在我们需要拼接字符串,将表格中的所有的 tr 拼接到一个字符串中,然后使用 document.getElementById("brandTable").innerHTML = 拼接好的字符串 就可以动态的展示出用户想看到的数据
  • 而表头行是固定的,所以先定义初始值是表头行数据的字符串,如下
    1
    2
    3
    4
    5
    6
    7
    8
    9
    let tableData = "   <tr>\n" +
    " <th>序号</th>\n" +
    " <th>品牌名称</th>\n" +
    " <th>企业名称</th>\n" +
    " <th>排序</th>\n" +
    " <th>品牌介绍</th>\n" +
    " <th>状态</th>\n" +
    " <th>操作</th>\n" +
    " </tr>";
  • 接下来获取并遍历响应回来的数据 brands ,拿到每一条品牌数据
    1
    2
    3
    4
    5
    let brands = resp.data;
    for (let i = 0; i < brands.length ; i++) {
    let brand = brands[i];

    }
  • 紧接着就是从 brand 对象中获取数据并且拼接 数据行,累加到 tableData 字符串变量中

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <c:forEach items="${brands}" var="brand">
    <tr align="center">
    <td>${brand.id}</td>
    <td>${brand.brandName}</td>
    <td>${brand.companyName}</td>
    <td>${brand.ordered}</td>
    <td>${brand.description}</td>
    <td>${brand.status == 1 ? "启用" : "禁用"}</td>
    <td><a href="/brand_demo/selectByIdServlet?id=${brand.id}">修改</a> <a href="/brand_demo/deleteServlet?id=${brand.id}">删除</a></td>
    </tr>
    </c:forEach>

    没有EL表达式和JSTL表达式,用js的for循环和js对象的属性值调用来替换了二者

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    let brands = resp.data;
    for (let i = 0; i < brands.length; i++) {
    let brand = brands[i];
    tableData += "<tr align=\"center\">\n" +
    " <td>" + (i + 1) + "</td>\n" +
    " <td>" + brand.brandName + "</td>\n" +
    " <td>" + brand.companyName + "</td>\n" +
    " <td>" + brand.ordered + "</td>\n" +
    " <td>" + brand.description + "</td>\n" +
    " <td>" + (brand.status == 1 ? "启用" : "禁用") + "</td>\n" +
    "<td><a href=\"/brand_demo/selectByIdServlet?id=" + brand.id + "\">修改</a> <a href=\"/brand_demo/deleteServlet?id=" + brand.id + "\">删除</a></td>" +
    " </tr>"
    }

  • 最后将拼接好的字符串写到表格中就大功告成了,整体的代码如下,我们也就完成了上面说的用HTML + AJAX替换JSP的操作

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <title>Title</title>
    </head>
    <body>
    <table border="1" cellspacing="0" width="1200" id="brandTable">


    </table>

    <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
    <script>
    window.onload = function () {
    axios({
    method: "get",
    url: "http://localhost:8080/brand_demo/selectAllServlet"
    }).then(function (resp) {
    let tableData = " <tr>\n" +
    " <th>序号</th>\n" +
    " <th>品牌名称</th>\n" +
    " <th>企业名称</th>\n" +
    " <th>排序</th>\n" +
    " <th>品牌介绍</th>\n" +
    " <th>状态</th>\n" +
    " <th>操作</th>\n" +
    " </tr>";
    let brands = resp.data;
    for (let i = 0; i < brands.length; i++) {
    let brand = brands[i];
    tableData += "<tr align=\"center\">\n" +
    " <td>" + (i + 1) + "</td>\n" +
    " <td>" + brand.brandName + "</td>\n" +
    " <td>" + brand.companyName + "</td>\n" +
    " <td>" + brand.ordered + "</td>\n" +
    " <td>" + brand.description + "</td>\n" +
    " <td>" + (brand.status == 1 ? "启用" : "禁用") + "</td>\n" +
    "<td><a href=\"/brand_demo/selectByIdServlet?id=" + brand.id + "\">修改</a> <a href=\"/brand_demo/deleteServlet?id=" + brand.id + "\">删除</a></td>" +
    " </tr>"
    }
    document.getElementById("brandTable").innerHTML = tableData;
    })
    }
    </script>
    </body>
    </html>

添加品牌功能

当我们点击 新增 按钮,会跳转到 addBrand.html 页面。在 addBrand.html 页面输入数据后点击 提交 按钮,就会将数据提交到后端,而后端将数据保存到数据库中。
说明:前端需要将用户输入的数据提交到后端,这部分数据需要以 json 格式进行提交

后端实现

修改 com.itheima.web 包下的 AddServlet,具体的逻辑如下:

  • 获取请求参数
    • 由于前端提交的是 json 格式的数据,所以我们不能使用 request.getParameter() 方法获取请求参数
      • 如果提交的数据格式是 username=zhangsan&age=23 ,后端就可以使用 request.getParameter() 方法获取
      • 如果提交的数据格式是 json,后端就需要通过 request.getReader() 来获取输入流,然后通过输入流读取数据
  • 将获取到的请求参数(json格式的数据)转换为 Brand 对象
  • 调用 service 的 add() 方法进行添加数据的逻辑处理
  • 将 json 数据响应回给浏览器。

AddServlet 代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@WebServlet("/addServlet")
public class AddServlet extends HttpServlet {

private BrandService brandService = new BrandService();

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

//1. 接收数据,request.getParameter 不能接收json的数据
/* String brandName = request.getParameter("brandName");
System.out.println(brandName);*/

// 获取请求体数据
BufferedReader br = request.getReader();
String params = br.readLine();
// 将JSON字符串转为Java对象
Brand brand = JSON.parseObject(params, Brand.class);
//2. 调用service 添加
brandService.add(brand);
//3. 响应成功标识
response.getWriter().write("success");
}

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

前端实现

  • 还是先复制一份addBrand.jsp的代码,然后进行修改
    将action路径删掉,然后将提交按钮的类型改为普通的button

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    <head>
    <meta charset="UTF-8">
    <title>添加品牌</title>
    </head>
    <body>
    <h3>添加品牌</h3>
    <form action="" method="post">
    品牌名称:<input id="brandName" name="brandName"><br>
    企业名称:<input id="companyName" name="companyName"><br>
    排序:<input id="ordered" name="ordered"><br>
    描述信息:<textarea rows="5" cols="20" id="description" name="description"></textarea><br>
    状态:
    <input type="radio" name="status" value="0">禁用
    <input type="radio" name="status" value="1">启用<br>

    <input type="button" id="btn" value="提交">
    </form>
  • 然后给提交按钮绑定点击事件,并在绑定的匿名函数中发送异步请求,代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    //1. 给按钮绑定单击事件
    document.getElementById("btn").onclick = function () {
    //2. 发送ajax请求
    axios({
    method:"post",
    url:"http://localhost:8080/brand-demo/addServlet",
    data:???
    }).then(function (resp) {
    // 判断响应数据是否为 success,如果是,则跳转到查询所有页面
    if(resp.data == "success"){
    location.href = "http://localhost:8080/brand-demo/brand.html";
    }
    })
    }
  • 现在我们只需要考虑如何获取页面上用户输入的数据即可。
    首先我们先定义如下的一个 js 对象,该对象是用来封装页面上输入的数据,并将该对象作为上面发送异步请求时 data 属性的值。

    1
    2
    3
    4
    5
    6
    7
    8
    // 将表单数据转为json
    var formData = {
    brandName:"",
    companyName:"",
    ordered:"",
    description:"",
    status:"",
    };
  • 接下来获取输入框输入的数据,并将获取到的数据赋值给 formData 对象指定的属性。比如获取用户名的输入框数据,并把该数据赋值给 formData 对象的 brandName 属性

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
     // 获取表单数据
    let brandName = document.getElementById("brandName").value;
    // 设置数据
    formData.brandName = brandName;

    // 获取表单数据
    let companyName = document.getElementById("companyName").value;
    // 设置数据
    formData.companyName = companyName;

    // 获取表单数据
    let ordered = document.getElementById("ordered").value;
    // 设置数据
    formData.ordered = ordered;

    // 获取表单数据
    let description = document.getElementById("description").value;
    // 设置数据
    formData.description = description;
  • 其中状态数据比较特殊,我们这里来进行特殊处理
    根据name来获取一个status数组,然后进行遍历,status有一个checked的属性,选中了则是true,我们通过它来判断到底选了谁,然后将它的值赋给formData的对应属性

    1
    2
    3
    4
    5
    6
    let status = document.getElementsByName("status");
    for (let i = 0; i < status.length; i++) {
    if(status[i].checked){
    formData.status = status[i].value ;
    }
    }
  • 整体代码如下,至此我们就将addBrand.jsp页面修改为了HTML + AJAX的形式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    <!DOCTYPE html>
    <html lang="en">

    <head>
    <meta charset="UTF-8">
    <title>添加品牌</title>
    </head>
    <body>
    <h3>添加品牌</h3>
    <form action="" method="post">
    品牌名称:<input id="brandName" name="brandName"><br>
    企业名称:<input id="companyName" name="companyName"><br>
    排序:<input id="ordered" name="ordered"><br>
    描述信息:<textarea rows="5" cols="20" id="description" name="description"></textarea><br>
    状态:
    <input type="radio" name="status" value="0">禁用
    <input type="radio" name="status" value="1">启用<br>

    <input type="button" id="btn" value="提交">
    </form>

    <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>

    <script>
    //1. 给按钮绑定单击事件
    document.getElementById("btn").onclick = function () {
    // 将表单数据转为json
    var formData = {
    brandName:"",
    companyName:"",
    ordered:"",
    description:"",
    status:"",
    };
    // 获取表单数据
    let brandName = document.getElementById("brandName").value;
    // 设置数据
    formData.brandName = brandName;

    // 获取表单数据
    let companyName = document.getElementById("companyName").value;
    // 设置数据
    formData.companyName = companyName;

    // 获取表单数据
    let ordered = document.getElementById("ordered").value;
    // 设置数据
    formData.ordered = ordered;

    // 获取表单数据
    let description = document.getElementById("description").value;
    // 设置数据
    formData.description = description;

    let status = document.getElementsByName("status");
    for (let i = 0; i < status.length; i++) {
    if(status[i].checked){
    //
    formData.status = status[i].value ;
    }
    }
    //console.log(formData);
    //2. 发送ajax请求
    axios({
    method:"post",
    url:"http://localhost:8080/brand-demo/addServlet",
    data:formData
    }).then(function (resp) {
    // 判断响应数据是否为 success
    if(resp.data == "success"){
    location.href = "http://localhost:8080/brand-demo/brand.html";
    }
    })
    }
    </script>
    </body>
    </html>

查询所有 功能和 添加品牌 功能就全部实现,现在感觉前端的代码很复杂,但这只是暂时的,下篇文章学习了 vue 前端框架后,这部分前端代码就可以进行很大程度的简化。