我只想卷死各位,或者被各位卷死,在此特别感谢黑马程序员的JavaWeb教程
本文摘要:
能够使用 Filter 完成登陆状态校验功能
能够使用 axios 发送 ajax 请求
熟悉 json 格式,并能使用 Fastjson 完成 java 对象和 json 串的相互转换
使用 axios + json 完成综合案例
Filter
Filter概述
Filter 表示过滤器,是 JavaWeb 三大组件(Servlet、Filter、Listener)之一。我们之前都已经学习过了Servlet ,现在我们来学习Filter和Listener。
过滤器可以把对资源的请求拦截
下来,从而实现一些特殊的功能。
例如某些网站未登录不能查看评论,不能将商品加入购物车,得把你拦下来,先让你登录,也就是在访问前,先经过Filter。
下面来具体说说,拦截器拦截到后可以做什么功能呢?
过滤器一般完成一些通用的操作。比如每个资源都要写一些代码完成某个功能,我们总不能在每个资源中写这样的代码吧,而此时我们可以将这些代码写在过滤器中,因为请求每一个资源都要经过过滤器。
我们之前做的品牌数据管理的案例中就已经做了登陆的功能,但这个登录功能其实是如同虚设的,我们可以直接访问登录后的页面,所以本文的目标就是完善登录功能,不登录就无法查看数据。
Filter入门
开发步骤
进行Filter
开发分为以下三步实现
定义类,实现Filter接口,并重写其所有方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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 () { } }
配置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 () { } }
在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>
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中吗?
如果回到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应用,可以配置多个过滤器,这多个过滤器称为过滤器链。
如下图就是一个过滤器链,我们学习过滤器链主要是学习过滤器链执行的流程
上图中的过滤器链执行是按照以下流程执行:
执行 Filter1
的放行前逻辑代码
执行 Filter1
的放行代码
执行 Filter2
的放行前逻辑代码
执行 Filter2
的放行代码
访问到资源
执行 Filter2
的放行后逻辑代码
执行 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..." ); }
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(); 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 () { } }
测试并抛出问题
重启服务器,访问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) { 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)之一。
监听器可以监听就是在 application
,session
,request
三个对象创建、销毁或者往其中添加修改删除属性时自动执行代码的功能组件。
request 和 session 我们学习过。而 application
是 ServletContext
类型的对象。
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 作用有以下两方面:
与服务器进行数据交换:通过AJAX可以给服务器发送请求,服务器将数据直接响应回给浏览器。
在之前,我们做功能的流程是,Servlet
调用完业务逻辑层,然后将数据存储到域对象中,然后跳转到指定的 jsp
页面,在页面上使用 EL表达式
和 JSTL
标签库进行数据的展示。
而我们学习了AJAX 后,就可以使用AJAX和服务器进行通信,以达到使用HTML+AJAX来替换JSP页面
了。浏览器发送请求servlet,servlet 调用完业务逻辑层后将数据直接响应回给浏览器页面,页面使用 HTML 来进行数据展示。
异步交互:可以在不重新加载整个页面的情况下,与服务器交换数据并更新部分网页的技术,如:搜索联想、用户名是否可用校验,等等…
当我们在百度输入一些关键字(例如 奥运
)后就会在下面联想出相关的内容,而联想出来的这部分数据肯定是存储在百度的服务器上,而我们并没有看出页面重新刷新,这就是更新局部页面
的效果。
我们在用户名的输入框输入用户名,当输入框一失去焦点,如果用户名已经被占用就会在下方展示提示的信息;在这整个过程中也没有页面的刷新,只是在局部展示出了提示信息,这就是更新局部页面
的效果。
同步与异步
知道了局部刷新后,接下来我们再聊聊同步和异步:
2022-09-22复盘补充:
所谓异步,就是主程序一直往下走,延迟和等待程序放在另一个列表中等待执行,不阻塞主程序继续往下进行
JS中大部分都是同步,少数的异步有以下几个
定时器settimeout和steInterval
ajax异步请求
promise
……
异步的好处是:它不会因为延时和等待阻塞程序
但异步存在一些问题,例如原计划从1到4执行
挂加速器
打开steam
上号
打派派
但现在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 { 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 ) { 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 { 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 ) { alert (this .responseText ); } }; </script > </html >
测试
在浏览器地址栏输入 http://localhost:8080/ajax-demo/01-ajax-demo1.html
,在 01-ajax-demo1.html
加载的时候就会发送 ajax
请求,并有一个alert弹窗输出HELLO AJAX
案例
需求:在完成用户注册时,当用户名输入框失去焦点时,校验用户名是否在数据库已存在
分析
前端完成的逻辑
给用户名输入框绑定光标失去焦点事件 onblur
发送 ajax请求,携带username参数
处理响应:是否显示提示信息
后端完成的逻辑
接收用户名
调用service查询User。此案例是为了演示前后端异步交互,所以此处我们不做业务逻辑处理
返回标记
后端实现
在 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" ); boolean flag = true ; response.getWriter().write("" + flag); } @Override protected void doPost (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this .doGet(request, response); } }
前端实现
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 ( ) { }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 var username = this .value ; var xhttp; if (window .XMLHttpRequest ) { xhttp = new XMLHttpRequest (); } else { 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.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 { 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 ) { 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
可以获取到响应的数据。
快速入门
后端实现
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 请求
1 2 3 4 5 6 axios ({ method : "get" , url : "http://localhost:8080/ajax_demo/ajaxServlet?username=zhangsan" }).then (function (resp ) { alert (resp.data ); })
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 : "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
请求。
1 2 3 axios.get ("http://localhost:8080/ajax-demo/axiosServlet?username=zhangsan" ).then (function (resp ) { alert (resp.data ); });
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
属性值,应该怎么处理呢?
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); let jsonObj = JSON .parse (jsonStr); alert (jsonObj); alert (jsonObj.name ); alert (jsonObj.age ); alert (jsonObj.addr ); let jsonStr2 = JSON .stringify (jsonObj); alert (jsonStr2); </script > </body > </html >
发送异步请求携带参数
后面我们使用 axios
发送请求时,如果要携带复杂的数据时都会以 JSON
格式进行传递
请求参数不可能由我们自己拼接字符串的,我们可以提前定义一个 js 对象,用来封装需要提交的参数,然后使用 JSON.stringify(js对象)
转换为 JSON
串,再将该 JSON
串作为 axios
的 data
属性值进行请求参数的提交。如下:
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 对象作为 axios
的 data
属性值进行,它会自动将 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 }).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 2 3 4 5 <dependency > <groupId > com.alibaba</groupId > <artifactId > fastjson</artifactId > <version > 1.2.62</version > </dependency >
Java对象转JSON 1 String jsonStr = JSON.toJSONString(obj);
将 Java 对象转换为 JSON 串,只需要使用 Fastjson
提供的 JSON
类中的 toJSONString()
静态方法即可。
JSON字符串转Java对象 1 User user = JSON.parseObject(jsonStr, User.class);
将 json 转换为 Java 对象,只需要使用 Fastjson
提供的 JSON
类中的 parseObject()
静态方法即可。
代码演示
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) { User user = new User (); user.setId(1 ); user.setUsername("zhangsan" ); user.setPassword("asd123" ); String jsonString = JSON.toJSONString(user); System.out.println(jsonString); 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 { BufferedReader br = request.getReader(); String params = br.readLine(); Brand brand = JSON.parseObject(params, Brand.class); brandService.add(brand); 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 document .getElementById ("btn" ).onclick = function ( ) { axios ({ method :"post" , url :"http://localhost:8080/brand-demo/addServlet" , data :??? }).then (function (resp ) { if (resp.data == "success" ){ location.href = "http://localhost:8080/brand-demo/brand.html" ; } }) }
现在我们只需要考虑如何获取页面上用户输入的数据即可。
首先我们先定义如下的一个 js 对象,该对象是用来封装页面上输入的数据,并将该对象作为上面发送异步请求时 data
属性的值。
1 2 3 4 5 6 7 8 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 > document .getElementById ("btn" ).onclick = function ( ) { 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 ; } } axios ({ method :"post" , url :"http://localhost:8080/brand-demo/addServlet" , data :formData }).then (function (resp ) { if (resp.data == "success" ){ location.href = "http://localhost:8080/brand-demo/brand.html" ; } }) } </script > </body > </html >
查询所有
功能和 添加品牌
功能就全部实现,现在感觉前端的代码很复杂,但这只是暂时的,下篇文章学习了 vue
前端框架后,这部分前端代码就可以进行很大程度的简化。