14.Ajax
之前浏览器向服务器发送请求,主要关注服务器如何处理数据,之后再返回响应,接下来考虑浏览器的事。
浏览器发送请求到服务器的几种方式:1.a标签 2.location.href(js方式) 3.地址栏直接敲 4.使用form(常见的两种请求方式:get/post) 前面的三种方式都是get,都可在URL后直接拼接参数
浏览器发送请求之后会等待响应。
服务器响应返回之后,解析返回的html代码,并显示到浏览器中(之前不考虑这个事,因为这个事是浏览器写好的功能)。
以上请求方式称为同步请求方式,即浏览器做的这三件事是一步一步执行的,请求发出,就直至等待响应,什么都不能干。每次回来的新页面都会将浏览器的旧页面替换掉。
接下来讲异步请求方式,通过Ajax实现,,它也在浏览器预支好了。主要流程:1.发生请求 2.指定一段js处理返回的数据(怎么去解析返回的数据,作为技术本身讲,并不能处理,故要求自己写一段js,响应回来之后会帮助触发这段js。)
同步请求方式每次都会刷新页面,是将从服务器拿过来的内容替换掉浏览器当前显示页面。
异步请求方式每次只发送请求,浏览器没有等待响应和解析html代码的过程,页面不会刷新。
异步请求有点像java里开子线程处理,js执行引擎是单线程的,但浏览器作为一个进程,允许多线程。js执行引擎只是浏览器使用的线程的其中之一。其它几个线程:
事件触发线程,浏览器会一直监听底层硬件的使用。
定时触发线程,我们要做的是设置函数,告诉浏览器要启动该线程,并把要执行的函数交给它,告诉它多长时间执行。
异步请求线程,按照浏览器的规定,创建异步请求对象,设置相关参数,告诉它响应回来执行哪个函数。浏览器就会开启异步请求线程,配合我们写的js,处理响应的代码。
js执行引擎作为主线程存在,其它三个线程是辅助线程, 当他们有代码要执行时,会推到主线程的执行队列里,最后让主线程执行。故:阻塞主线程的东西不能用,和Ajax配合起来很容易出问题。
Ajax技术纯粹由js实现。
1.什么是ajax
1.AJAX指异步的JavaScript及XML(Asynchronous JavaScript And XML)
2.AJAX 不是编程语言,而是一系列技术的结合,为请求数据方式提供了一种不同的模式
普通数据请求方式:a标签跳转、用户自己输入浏览器地址栏、js中location.href、form提交,模式:服务器接收到请求后,把页面需要展示的数据传回浏览器,浏览器使用这些新的数据完整的替换掉旧的页面数据。
Ajax数据请求方式:Ajax使用XMLHttpRequest 对象发送请求,模式:服务器接收到请求后,把页面需要展示的数据传回浏览器,浏览器读取到这些数据,但是不使用这些数据直接替换掉页面之前的数据,而是允许设置一段js来读取返回的数据,由这段可以自由编辑的js内容决定返回的数据如何处理(一般根据返回数据制作一些页面特效或者修改旧页面中的部分数据),旧的页面数据没有被完整替换。
总结:ajax是一种新的与后台交互模式,一定程度上需要后台部分的配合
2. Ajax原理
浏览器中的线程状态:
js是单线程的,那么是否代表参与js执行过程的线程就只有一个?
不是的,会有四个线程参与该过程,但是永远只有JS执行引擎线程在执行JS脚本程序,其他的三个线程只协助,不参与代码解析与执行。参与js执行过程的线程分别是:
JS执行引擎线程: 也称为JS内核,负责解析执行Javascript脚本程序的主线程(例如V8引擎)
事件触发线程: 归属于浏览器内核进程,不受JS引擎线程控制。主要用于控制事件(例如鼠标,键盘等事件),当该事件被触发时候,事件触发线程就会把该事件的处理函数推进事件队列,等待JS引擎线程执行
定时器触发线程:主要控制计时器setInterval和延时器setTimeout,用于定时器的计时,计时完毕,满足定时器的触发条件,则将定时器的处理函数推进事件队列中,等待JS引擎线程执行。 注:W3C在HTML标准中规定setTimeout低于4ms的时间间隔算为4ms。
HTTP异步请求线程:通过XMLHttpRequest连接后,通过浏览器新开的一个线程,监控readyState状态变更时,如果设置了该状态的回调函数,则将该状态的处理函数推进事件队列中,等待JS引擎线程执行。 注:浏览器对通一域名请求的并发连接数是有限制的,Chrome和Firefox限制数为6个,ie8则为10个。
总结:永远只有JS引擎线程在执行JS脚本程序,其他三个线程只负责将满足触发条件的处理函数推进事件队列,等待JS引擎线程执行。
ajax由js编写,使用XMLHttpRequest 对象对数据收发进行封装,
发送ajax请求时,ajax会要求调用方(浏览器)开启新线程向服务器进行请求,并对响应的结果进行反复读取,当读取到特定状态后,停止反复读取。读取过程中会把需要运行的js(回调函数)添加入js的执行队列(js执行引擎为单线程),js执行引擎按队列顺序执行。
3. ajax关键属性和代码
1.原生ajax代码步骤
1.创建XMLHttpRequest对象
(核心对象,描述了异步请求线程的处理方式,即将数据使用Http协议封装成XML格式发送到服务器,并且以XML格式返回回来,再自动拆分成文本。)
2.设置回调函数
3.初始化XMLHttpRequest组件(设置)
4.发送请求
2. XMLHttpRequest对象的属性和方法
onreadystatechange,拿到响应时处理响应函数的代码。配一个匿名函数,里面写怎么处理响应函数的代码。配合readyState去看。
readyState,使用的还是http协议,底层还是tcp连接方式。只要状态有改变,对应的onreadystatechange函数代码会自动触发。但一般只处理状态4,故onreadystatechange函数一般加逻辑判断,状态4时才会处理。
status是由服务端返回的响应代码。
最后两个属性是获取服务端响应时,可以以文本状态读,也可以以XML形式读。
open方法,设置发送的请求参数。method请求的方式使用get还是post,若为get,会将参数直接拼在URL上,若为post,会将参数放在请求体,若放在请求体里,会将参数用URL拼接的格式放在send方法里。
send方法,发送请求用的。若method为get,send里什么都不用写,参数已在url里拼好。若为post方式,将参数放在send参数里,最后会放在请求体里。
setRequestHeader方法,自己设置请求头,即发送请求时自己可以写入。
getRequestHeader方法,获取服务端返回的响应头,即响应回来后会从响应报文里把指定的想要看的响应头取出来。
后两个是扩展功能,很多时候用不到。
综上:使用AJAX时:
1.创建异步请求核心对象XMLHttpRequest
2.通过open设置要发送的方式,请求的地址和是否使用异步请求(默认true,即使用异步)
3.然后点send就可以发送了(点send之前一定要设置属性onreadystatechange,因为要通过它定义处理响应的函数,之后会在这个函数里取到响应的文本,即responseTest或者responseXML)
4.发送之前可用setRequestHeader方法设置请求头,响应回来可用getRequestHeader方法获取响应头。
3.相关代码
①.新建项目day5_ajax
ajax纯粹用javascript实现,故可写在jsp里,也可写在html里。
新建web.ajaxDemo1.html
<body> <input type="text" id="mykey"> <input type="button" value="发送ajax异步请求" οnclick="sendAjax()"> </body> <script> function sendAjax() { var xhr = new XMLHttpRequest(); //定义异步请求对象 //指定关键属性 xhr.onreadystatechange = function () { //每当readystate发生改变,此函数都会触发 //readystate与服务器建立连接的过程状态,主要考虑状态4,表示请求结束 //同时,只有服务端正常响应才会处理,状态码200表示响应正确返回 if (xhr.readyState == 4 && xhr.status == 200){ //请求和响应都是成功的 //处理返回数据的代码 } } //设置Ajax的请求地址,请求方式,是否使用异步 open(method:string,url:string,async?:boolean,user?:...) xhr.open("GET","/day5/ajax",true); //设置请求头,用来设置发送的数据的格式。 //之前响应里要给一个contentType="text/html;charset=UTF-8",指定返回的数据格式。 //请求发送也有格式,也叫Content-type。第二个参数,默认的url参数拼接格式,?key=value&key=value格式的名字叫x-www-form-urlencoded xhr.setRequestHeader("Content-type","application/x-www-form-urlencoded"); //发送请求.根据请求方式的不同决定参数的位置 xhr.send(); } </script>
发送请求.根据请求方式的不同决定参数的位置.如果用get方式,一般在url后去拼。如下:
<body> <input type="text" id="mykey"> <input type="button" value="发送ajax异步请求" οnclick="sendAjax()"> </body> <script> function sendAjax() { var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function () { if (xhr.readyState == 4 && xhr.status == 200){ } } //使用get方式,会在url后去拼 xhr.open("GET","/day5/ajax?mykey="+document.getElementById("mykey").value,true); //获取文本框的value值,和url拼接 xhr.setRequestHeader("Content-type","application/x-www-form-urlencoded"); xhr.send(); } </script>
②.测试
虽然没有服务器,但Ajax是浏览器发请求的一个技术。故没有服务器,仍可发送请求。
进入Run/Debug Configurations,在Deployment将Application context改为/day5
重新部署,运行,访问http://localhost:8080/day5/ajaxDemo1.html
页面出现:
通过network可检查是否有请求发送出去。
在文本框输入abc123,在network中可看到有一次请求的发送。
之前发的同步请求type都是document,发送的是要查看整个文档。发送ajax异步请求时类型标记为xhr,就是XMLHttpRequest的缩写。
③.使用post的方式发送请求:
参数直接写在send里:
<script> function sendAjax() { var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function () { if (xhr.readyState == 4 && xhr.status == 200){ } } //使用get方式,会在url后去拼 xhr.open("POST","/day5/ajax",true); //获取文本框的value值,和url拼接 xhr.setRequestHeader("Content-type","application/x-www-form-urlencoded"); xhr.send("mykey="+document.getElementById("mykey").value); } </script>
之后使用ajax时,测试ajax请求不需要有后台,在开发者工具里就可检查请求的地址,请求的方式,拼接的参数等。这些都正确再去弄后台。
④.后台接收:
新建src.com.javasm.controller.AjaxServletDemo
servlet里还是接收请求,调用业务逻辑,返回响应(返回部分数据)。
转发和重定向在这里用不上。转发是转到jsp里拼完整的页面,现在不需要发送完整页面给浏览器,浏览器没有在等完整的页面,发过去也是自己的js去处理。使用异步请求方式,一般只回传部分数据,不需要用jsp拼页面。但转发到其他servlet还是可以的。重定向直接失效,加了也没用,自己的js在等响应,一般不会读重定向的响应头。(同步请求的浏览器在等待响应,而且会优先读取重定向的响应头,所以会做重定向)
@WebServlet("/ajax") public class AjaxServletDemo extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("utf-8"); String mykey = req.getParameter("mykey"); //接收参数 System.out.println(mykey); //调用业务逻辑 String returnStr = "my Dear!!!" + mykey; //往回发数据 resp.setContentType("text/html;charset=utf-8"); PrintWriter writer = resp.getWriter(); writer.print(returnStr); writer.flush(); writer.close(); } }
⑤.返回到ajaxDemo1.html,这就是自己写的js,用来接收响应,处理返回的数据
<script> function sendAjax() { var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function () { if (xhr.readyState == 4 && xhr.status == 200){ //处理返回数据的代码 console.log(xhr.responseText); //给的文本格式,故用文本格式去取。 } } xhr.open("POST","/day5/ajax",true); xhr.setRequestHeader("Content-type","application/x-www-form-urlencoded"); xhr.send("mykey="+document.getElementById("mykey").value); } </script>
⑥.测试
重新部署,运行,访问http://localhost:8080/day5/ajaxDemo1.html,文本框输入jack,点击发送异步请求按钮。控制台输出my Dear!!! jack
当前页面没有跳转。Ajax的异步请求在页面没有跳转的前提下,把请求发出去,实际上是调用了异步请求线程,该线程由浏览器自动控制。
⑦.可以加一些效果:
<body> <input type="text" id="mykey"> <input type="button" value="发送ajax异步请求" οnclick="sendAjax()"> <h1 id="mymsg"></h1> </body> <script> function sendAjax() { var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function () { if (xhr.readyState == 4 && xhr.status == 200){ console.log(xhr.responseText); document.getElementById("mymsg").innerHTML = xhr.responseText; } } xhr.open("POST","/day5/ajax",true); xhr.setRequestHeader("Content-type","application/x-www-form-urlencoded"); xhr.send("mykey="+document.getElementById("mykey").value); } </script>
效果如下:
以上就是异步请求一次完整的发送,接收,处理的过程。
若写另一个异步请求功能,⑦.的匿名函数里的代码,请求的方式和地址,发送的参数都会变动。
正式使用时,用的各种javascrirt扩展库都会对ajax进行重新封装,从而对语法结构简化。接下来看jquery如何对ajax进行简化。
4.使用jquery简化的ajax
新建web.js包,引入jquery-3.6.0.js
新建web.2jqueryAjaxDemo.html
把需要改变的关键属性通过json对象设置好,传给ajax即可。
<head> <meta charset="UTF-8"> <title>Title</title> <script src="js/jquery-3.6.0.js" type="text/javascript" charset="utf-8"></script> </head> <body> <input type="text" id="mykey"> <input id="sendAjaxBtn" type="button" value="发送ajax异步请求"> <h1 id="mymsg"></h1> </body> <script> $("#sendAjaxBtn").click(function () { $.ajax({ url:"/day5/ajax", //请求地址 type:"post", //请求的类型 data:{"mykey":$("#mykey").val()}, //要发送的参数,允许两种格式:json格式或者url拼接格式 dataType:"text", //返回的数据类型,这个参数不配也可以,默认文本形式 success:function (data) { //success表示响应成功后执行的代码,并且读响应回来的参数 console.log(data); } }) }) </script> </html>
重新部署,运行,访问http://localhost:8080/day5/2jqueryAjaxDemo.html,效果和之前一样。
想将效果输出到页面:
<script> $("#sendAjaxBtn").click(function(){ $.ajax({ url:"/day5/ajax", //请求地址 type:"post", //请求的类型 data:{"mykey":$("#mykey").val()}, //要发送的参数,允许两种格式:json格式或者url拼接格式 dataType:"text", //返回的数据类型,这个参数不配也可以,默认文本形式 success:function (data) { //success表示响应成功后执行的代码,并且读响应回来的参数 console.log(data); $("#mymsg").html(data); } }) }) </script>
success函数是由异步请求对象自动触发,由js里一些机制触发的函数叫回调函数。异步请求里回调函数专指配给ajax的这部分。(可将回调理解为响应回来调用函数)
注:ajax方法里允许配置的属性不止这些。
$("#sendAjaxBtn").click(function(){ $.ajax({ url:"/day5/ajax", //请求地址 type:"post", //请求的类型 data:{"mykey":$("#mykey").val()}, //要发送的参数,允许两种格式:json格式或者url拼接格式 dataType:"text", //返回的数据类型,这个参数不配也可以,默认文本形式 success:function (data) { //success表示响应成功后执行的代码,并且读响应回来的参数 console.log(data); $("#mymsg").html(data); }, error:function(){ //出现异常的处理 } }) })
还有更简化的方式:
$.get(请求地址,发送参数,匿名函数(回调),解析响应数据的格式) $.post(请求地址,发送参数,匿名函数(回调),解析响应数据的格式)
当然也可传json对象,和上面一样用。
$.get("/day5/ajax", {"mykey":$("#mykey").val()}, function (data) { console.log(data); $("#mymsg").html(data); },"text");
注意1:使用ajax时,需要自己拼接参数,可以使用$("#myform").serialize(),把form序列化成键值对字符串格式。
注意2:后台可以借助fastJSON工具,给前台返回json格式字符串。
5.验证用户名是否重复
①.新建web.3checkUsernameDemo.html
用户输入一个用户名,想让用户知道是否可用,应该在后面输出信息。并且应该是用户名输完之后做校验。(一般不发请求可以一边输一边效验,这里若一边敲,浏览器一边发请求,服务器要蹦)用change事件。
之后发请求到后台效验。
<head> <meta charset="UTF-8"> <title>Title</title> <script src="js/jquery-3.6.0.js" type="text/javascript" charset="utf-8"></script> </head> <body> <form> <input type="text" placeholder="用户名" id="username"><br/> <input type="text" placeholder="邮箱" ><br/> <input type="text" placeholder="手机号" ><br/> <input type="button" value="提交表单" > </form> </body> <script> $("#username").change(function(){ $.post("/day5/checkUserName",{"username":$("#username").val()},function (data){ //形参data表示返回的数据 console.log(data) },"text") }) </script> </html>
重新部署,运行,访问:http://localhost:8080/day5/3checkUsernameDemo.html
检查ajax请求,前面已说过,用network就可以看。
输入小明, 点击提交表单。打开network,请求发送的地址,请求方式,发送的数据都没问题。
②.添加后台
新建src.com.javasm.controller.CheckServlet
应该连数据库做判断,这里用if替换。
@WebServlet("/checkUserName") public class CheckServlet extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("utf-8"); String username = req.getParameter("username"); String returnStr = ""; if ("jack".equals(username)){ returnStr = "用户名重复"; }else { returnStr = "用户名可用"; } //往回发数据 resp.setContentType("text/html;charset=utf-8"); PrintWriter writer = resp.getWriter(); writer.print(returnStr); writer.flush(); writer.close(); } }
③.后台返回响应后,自己写的js开始处理:
<body> <form> <input type="text" placeholder="用户名" id="username"><span id="checkNameRes"></span><br/> <input type="text" placeholder="邮箱" ><br/> <input type="text" placeholder="手机号" ><br/> <input type="button" value="提交表单" > </form> </body> <script> $("#username").change(function(){ $.post("/day5/checkUserName",{"username":$("#username").val()},function (data){ //形参data表示返回的数据 console.log(data) $("#checkNameRes").html(data); },"text") }) </script>
重新部署,运行,访问:http://localhost:8080/day5/3checkUsernameDemo.html
输入jack,文本框后面出现用户名重复;输入jack123,文本框后面出现用户名可用
④.美化。若用户名可用,绿色;否则红色。
页面里显示多组值,最简单的是使用json格式。一般往前端返回数据,最合理的做法就是后台拼好前端要用的json格式。
在CheckServlet类:
@Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("utf-8"); String username = req.getParameter("username"); String returnStr = ""; if ("jack".equals(username)){ returnStr = "{\"msg\":\"用户名重复\",\"msgcolor\":\"red\"}"; //拼成json字符串格式发回去 }else { returnStr = "{\"msg\":\"用户名可用\",\"msgcolor\":\"green\"}"; } //往回发数据 resp.setContentType("text/html;charset=utf-8"); PrintWriter writer = resp.getWriter(); writer.print(returnStr); writer.flush(); writer.close(); }
在3checkUsernameDemo.html:
需要json格式字符串转json对象
<script> $.post("/day5/checkUserName",{"username":$("#username").val()},function (data){ //形参data表示返回的数据 console.log(data) /*{"msg":"用户名重复","msgcolor":"red"}*/ var myobj = JSON.parse(data) $("#checkNameRes").html(myobj.msg); $("#checkNameRes").css("color",myobj.msgcolor); },"json") }) </script>
最后一个参数表示返回的数据类型,这里text也可改为json,返回的数据data就会按照json格式尝试解析。但json格式有错误,解析不出来,匿名函数里的代码一行都不会执行。遇到解析不出来的检查json格式是否有问题。
此时不需要再转换成text格式,直接就是json对象。
使用get方式时还有一个更简化的方法,$.getJson(请求地址,发送参数,匿名函数(回调))。配合get方式取json数据:
<script> $("#username").change(function(){ $.getJSON("/day5/checkUserName",{"username":$("#username").val()},function(data){ console.log(data) $("#checkNameRes").html(data.msg); $("#checkNameRes").css("color",data.msgcolor); }) }) </script>
⑤.后台规范
回到后台,若结构复杂,自己拼json格式比较麻烦,故一般通过工具生成json对象。
新建web.WEB-INF.lib包,将fastjson-1.2.7.jar包引入。
在当前项目配置。Project Structure,Modules,右边的加号,引入该jar包。
fastjson-1.2.7.jar让对象和json字符串进行转换。用的比较多的是把对象转json字符串,JSON.toJSONString()。
其实,java对象和字符串转换的工具非常多。比较有名的几个:GSON(谷歌提供),jackson,fastjson(阿里巴巴提供,特点:转换效率比较高)。
使用fastjson:
新建src.com.javasm.entity.MyPojo:
@NoArgsConstructor @AllArgsConstructor @Getter @Setter @ToString public class MyPojo { private Integer myId; private String myName; private Integer myAge; private String myAddr; }
新建src.com.javasm.test.MyTest:
public class MyTest { public static void main(String[] args) { //让对象和json字符串进行转换。用的比较多的是把对象转json字符串 MyPojo myPojo = new MyPojo(1,"jack",16,"taitannike"); String s = JSON.toJSONString(myPojo); System.out.println(s); } }
测试,控制台输出:{"myAddr":"taitannike","myAge":16,"myId":1,"myName":"jack"}
json对象通常和集合套在一起使用,比如一个列表数据放了好几个学生信息:
public class MyTest { public static void main(String[] args) { MyPojo myPojo1 = new MyPojo(1,"jack",16,"taitannike"); MyPojo myPojo2 = new MyPojo(2,"jack2",17,"taitannike"); MyPojo myPojo3 = new MyPojo(3,"jack3",18,"taitannike"); List<MyPojo> listStu = new ArrayList<MyPojo>(); listStu.add(myPojo1); listStu.add(myPojo2); listStu.add(myPojo3); String s = JSON.toJSONString(listStu); System.out.println(s); } }
在网上打开JSON在线解析及格式化验证,将控制台输出的结果粘贴到这里,就可方便地看到结构。
[{"myAddr":"taitannike","myAge":16,"myId":1,"myName":"jack"},{"myAddr":"taitannike","myAge":17,"myId":2,"myName":"jack2"},{"myAddr":"taitannike","myAge":18,"myId":3,"myName":"jack3"}]
我们经常使用的状态是json对象套数组,数组里套json对象。在java里一般用Map表示:
public class MyTest { public static void main(String[] args) { Map<String,Object> myMap = new HashMap<String,Object>(); MyPojo myPojo1 = new MyPojo(1,"jack",16,"taitannike"); MyPojo myPojo2 = new MyPojo(2,"jack2",17,"taitannike"); MyPojo myPojo3 = new MyPojo(3,"jack3",18,"taitannike"); List<MyPojo> listStu = new ArrayList<MyPojo>(); listStu.add(myPojo1); listStu.add(myPojo2); listStu.add(myPojo3); myMap.put("stuNum",listStu.size()); myMap.put("students",listStu); String s = JSON.toJSONString(myMap); System.out.println(s); } }
结果:
{ "stuNum":3, "students":[ { "myAddr":"taitannike", "myAge":16, "myId":1, "myName":"jack" }, { "myAddr":"taitannike", "myAge":17, "myId":2, "myName":"jack2" }, { "myAddr":"taitannike", "myAge":18, "myId":3, "myName":"jack3" } ] }
回到CheckServlet类:
@Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("utf-8"); String username = req.getParameter("username"); Map<String,Object> returnMap = new HashMap<String,Object>(); if ("jack".equals(username)){ returnMap.put("msg","用户名重复"); returnMap.put("msgcolor","red"); //returnStr = "{\"msg\":\"用户名重复\",\"msgcolor\":\"red\"}"; //拼成json字符串格式发回去 }else { returnMap.put("msg","用户名可用"); returnMap.put("msgcolor","green"); //returnStr = "{\"msg\":\"用户名可用\",\"msgcolor\":\"green\"}"; } //往回发数据 resp.setContentType("text/html;charset=utf-8"); PrintWriter writer = resp.getWriter(); String returnStr = JSON.toJSONString(returnMap); writer.print(returnStr); writer.flush(); writer.close(); }
换成服务器环境(右上角文本框),重新部署,运行,访问:http://localhost:8080/day5/3checkUsernameDemo.html,功能一样。
还有问题:公司使用json对象时,原则上使用Map放键值对。但每个人都用这样的方式,起的key不一样,还要一个个去查。故,公司提出认为规范:最基本 的要返回三类数据:1.操作状态码(由公司自己定,不是http状态码,比如10000表示成功,成功就是操作状态信息) 2.操作状态信息 3.返回的附加数据
@Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("utf-8"); String username = req.getParameter("username"); Map<String,Object> returnMap = new HashMap<String,Object>(); Map<String,Object> subMap = new HashMap<String,Object>(); if ("jack".equals(username)){ returnMap.put("retCode","2001"); returnMap.put("retMsg","用户名重复"); subMap.put("msg","用户名可用"); subMap.put("msgcolor","green"); returnMap.put("retData",subMap); }else { returnMap.put("retCode","2000"); returnMap.put("retMsg","用户名可用"); subMap.put("msg","用户名可用"); subMap.put("msgcolor","green"); returnMap.put("retData",subMap); } resp.setContentType("text/html;charset=utf-8"); PrintWriter writer = resp.getWriter(); String returnStr = JSON.toJSONString(returnMap); writer.print(returnStr); writer.flush(); writer.close(); }
但作为公司统一的规范,还有可能写错字,比如key部分的retCode可能写成retCoda。故,公司又加一个规范,防止写错字。根据实体类去读key,不用自己写即可。
entity包下新建ReturnEntity类,该实体类没有任何意义,就是用来封装返回的数据对象,规范代码的编写。
@NoArgsConstructor @AllArgsConstructor @Getter @Setter @ToString public class ReturnEntity { private Integer retCode; private String retMsg; private Object retData; }
回到CheckServlet类:
@Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("utf-8"); String username = req.getParameter("username"); //Map<String,Object> returnMap = new HashMap<String,Object>(); ReturnEntity re = new ReturnEntity(); //用来规范返回的key Map<String,Object> subMap = new HashMap<String,Object>(); if ("jack".equals(username)){ re.setRetCode(2001); //returnMap.put("retCode","2001"); re.setRetMsg("用户名重复"); //returnMap.put("retMsg","用户名重复"); subMap.put("msg","用户名可用"); subMap.put("msgcolor","green"); re.setRetData(subMap); //returnMap.put("retData",subMap); }else { re.setRetCode(2000); re.setRetMsg("用户名可用"); subMap.put("msg","用户名可用"); subMap.put("msgcolor","green"); re.setRetData(subMap); } resp.setContentType("text/html;charset=utf-8"); PrintWriter writer = resp.getWriter(); String returnStr = JSON.toJSONString(re); writer.print(returnStr); writer.flush(); writer.close(); } }
做了这样的规范后,公司还是觉得不保险,对应关系还可能写错。
故还会有个枚举类,在entity包下新建ReturnCode:
public enum ReturnCode { OPT_SUCCESS(10000,"操作成功"), OPT_FAILED(10005,"操作失败"), NAME_DUP(20001,"用户名重复"), NAME_OK(20000,"用户名可用"); private Integer code; private String msg; private ReturnCode(Integer code,String msg){ this.code = code; this.msg = msg; } public Integer getCode() { return code; } public String getMsg() { return msg; } }
回到CheckServlet类:
@Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("utf-8"); String username = req.getParameter("username"); ReturnEntity re = new ReturnEntity(); Map<String,Object> subMap = new HashMap<String,Object>(); if ("jack".equals(username)){ re.setRetCode(ReturnCode.NAME_DUP.getCode()); re.setRetMsg(ReturnCode.NAME_DUP.getMsg()); subMap.put("msg","用户名可用"); subMap.put("msgcolor","green"); re.setRetData(subMap); }else { re.setRetCode(ReturnCode.NAME_OK.getCode()); re.setRetMsg(ReturnCode.NAME_OK.getMsg()); subMap.put("msg","用户名可用"); subMap.put("msgcolor","green"); re.setRetData(subMap); } resp.setContentType("text/html;charset=utf-8"); PrintWriter writer = resp.getWriter(); String returnStr = JSON.toJSONString(re); writer.print(returnStr); writer.flush(); writer.close(); }
此servlet类叫服务接口。(不是接口,按照功能区分的)传入一些指定的参数,根据代码执行,返回一些固定的结果,和定义一个方法很类似。
服务接口返回的一般都是json格式,故也叫http接口。这便是服务主流的编写方式。
服务接口比较关键的是接口文档,即写清楚服务调用的地址,发送的参数(往服务接口发送的参数),支持的请求方式,返回的数据格式(页面里要根据返回的数据格式来解析数据并处理页面),字段说明(比如retCode是什么意思)。
接口文档
/* * 查询地区服务接口 * 请求地址 /day5/getArea * 请求参数 areaid 地区编号 * 请求方式 get/post * 响应数据格式 json * 响应数据实例 * {"retCode":10000, * "retData":[{"areaId":"0010101","areaName":"金水区"}, * {"areaId":"0010102","areaName":"高新区"}], * "retMsg":"操作成功"} * * */
如果准备好这样一个文档,写页面的人可按照这样的规范解析页面数据,我们就可按照这样的规范写代码。这种服务接口的形式,会非常方便的让前端和后端根据接口文档作为唯一的标准,来进行分别的编写。这种模式也叫前后端分离的模式。分离的是人,比如用jsp,最大的弊端又要会jsp,又要会java,又要会前端,这样就可使前端程序员分离出来。前端程序员就是写html,jss,javascript,且重点是javascript部分,因为在这前边还有UI设计师,主要写html和css,一般不写javascript。后端程序员写服务接口,可以不会写前端。
有些网站专门做接口服务,百度聚合数据。
从今天开始,代码都依照这样的格式编写。
6.ajax实现省市县级联菜单
①.数据库建表
省和市的关系:一对多,多的一方映射一的一方的id。
市和县区的关系:一对多。
若每个实体相似程度比较高,都是编号和名称,也没有特有字段,一般会合并起来。
地区id比较简单的设计方式就是:1,2,3这样。但查看编号时,会觉得很乱,没有特征表示出来当前是省还是市。故采取类似商品id的形式:前三位表示省(100省级,表示河南省。200表示河北),接下来两位表示市(10001表示河南郑州),最后两位表示县区级(1000101表示河南省郑州市金水区)。
查所有的省:
select ta.area_id,ta.area_name from tb_area ta where ta.parent_id = '0'
查河南所有的市:
select ta.area_id,ta.area_name from tb_area ta where ta.parent_id = '001'
查郑州所有县区:
select ta.area_id,ta.area_name from tb_area ta where ta.parent_id = '00101'
②.在idea
引入jar包(mysql-connector-java-5.1.46.jar和lombok.jar和fastjson-1.2.7.jar)
新建实体类entity.MyArea
@NoArgsConstructor @AllArgsConstructor @Getter @Setter @ToString public class MyArea { private String areaId; private String areaName; }
新建src.com.javasm.dao.AreaDao
public interface AreaDao { List<MyArea> getAreaById(String areaId); }
新建jdbc.properties
jdbc.user=root jdbc.pass=root jdbc.url=jdbc:mysql://localhost:3306/test?useSSL=true&characterEncoding=utf-8 jdbc.driver=com.mysql.jdbc.Driver
新建com.javasm.util.DBHelper
public class DBHelper { static String username; static String pwd; static String url; static String drivername; static { Properties p = new Properties(); try { //程序运行时 不一定在你的工程目录的编译目录下 p.load(DBHelper.class.getResourceAsStream("/jdbc.properties")); username = p.getProperty("jdbc.user"); pwd = p.getProperty("jdbc.pass"); url = p.getProperty("jdbc.url"); drivername = p.getProperty("jdbc.driver"); Class.forName(drivername); } catch (Exception e) { e.printStackTrace(); } } public static Connection getConn() { Connection con = null; try { con = DriverManager.getConnection(url, username, pwd); } catch (SQLException e) { e.printStackTrace(); } return con; } public static void CloseConn(Connection conn,Statement stat,PreparedStatement psta,ResultSet rs){ try { if(stat!=null)stat.close(); if(psta!=null)psta.close(); if(rs!=null)rs.close(); if(conn!=null)conn.close(); } catch (SQLException e) { e.printStackTrace(); } } }
新建dao.impl.AreaDaoImpl
public class AreaDaoImpl implements AreaDao { @Override public List<MyArea> getAreaById(String insertAreaId) { Connection conn = DBHelper.getConn(); String sql = "select ta.area_id,ta.area_name from tb_area ta where ta.parent_id = ?"; PreparedStatement preparedStatement = null; ResultSet resultSet = null; List<MyArea> listArea = new ArrayList<MyArea>(); try { //防止注入攻击 preparedStatement = conn.prepareStatement(sql); preparedStatement.setString(1,insertAreaId); resultSet = preparedStatement.executeQuery(); while(resultSet.next()){ String areaId = resultSet.getString("area_id"); String areaName = resultSet.getString("area_name"); MyArea returnProd = new MyArea(areaId,areaName); listArea.add(returnProd); } } catch (SQLException e) { e.printStackTrace(); }finally { DBHelper.CloseConn(conn,null,preparedStatement,resultSet); } return listArea; } }
新建javasm.text.MyTest
public class MyTest { public static void main(String[] args) { AreaDao ad = new AreaDaoImpl(); List<MyArea> areaById = ad.getAreaById("0"); System.out.println(areaById); } }
结果:[MyArea(areaId=001, areaName=河南), MyArea(areaId=002, areaName=河北)]
新建javasm.service.AreaService接口:
public interface AreaService { List<MyArea> getAreaById(String areaId); }
新建service.impl.AreaServiceImpl:
public class AreaServiceImpl implements AreaService { @Override public List<MyArea> getAreaById(String areaId) { AreaDao ad = new AreaDaoImpl(); return ad.getAreaById(areaId); } }
新建entity.ReturnEntity
@NoArgsConstructor @AllArgsConstructor @Getter @Setter @ToString public class ReturnEntity { private Integer retCode; private String retMsg; private Object retData; }
新建entity.ReturnCode
public enum ReturnCode { OPT_SUCCESS(10000,"操作成功"), OPT_FAILED(10005,"操作失败"), NAME_DUP(20001,"用户名重复"), NAME_OK(20000,"用户名可用"); private Integer code; private String msg; private ReturnCode(Integer code,String msg){ this.code = code; this.msg = msg; } public Integer getCode() { return code; } public String getMsg() { return msg; } }
新建javasm.controller.AreaServlet(查询地区的服务接口)
@WebServlet("/getArea") public class AreaServlet extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String areaid = req.getParameter("areaid"); AreaService as = new AreaServiceImpl(); List<MyArea> areaById = as.getAreaById(areaid); //往前台返回json对象 ReturnEntity re = new ReturnEntity(); //根据处理结果返回不同的响应数据 if(areaById.size()>0){ re.setRetCode(ReturnCode.OPT_SUCCESS.getCode()); re.setRetMsg(ReturnCode.OPT_SUCCESS.getMsg()); re.setRetData(areaById); }else { re.setRetCode(ReturnCode.OPT_FAILED.getCode()); re.setRetMsg(ReturnCode.OPT_FAILED.getMsg()); } resp.setContentType("text/html;charset=utf-8"); PrintWriter writer = resp.getWriter(); writer.print(JSON.toJSONString(re)); writer.flush(); writer.close(); } }
测试服务接口,不需要页面。
重新部署,运行,访问:http://localhost:8080/day5/getArea?areaid=0
显示结果:{"retCode":10000,"retData":[{"areaId":"001","areaName":"河南"},{"areaId":"002","areaName":"河北"}],"retMsg":"操作成功"}
访问:http://localhost:8080/day5/getArea?areaid=001
显示:{"retCode":10000,"retData":[{"areaId":"00101","areaName":"郑州"},{"areaId":"00102","areaName":"开封"},{"areaId":"00103","areaName":"洛阳"}],"retMsg":"操作成功"}
写接口文档:
@WebServlet("/getArea") public class AreaServlet extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { /* *查询地区服务接口 *请求地址 /day5/getArea *请求参数 areaid地区编号 *请求方式 get/post *响应数据格式 json *响应数据实例 * {"retCode":10000, * "retData":[{"areaId":"00101","areaName":"郑州"}, * {"areaId":"00102","areaName":"开封"}, * {"areaId":"00103","areaName":"洛阳"}], * "retMsg":"操作成功"} * */ String areaid = req.getParameter("areaid"); AreaService as = new AreaServiceImpl(); List<MyArea> areaById = as.getAreaById(areaid); //往前台返回json对象 ReturnEntity re = new ReturnEntity(); //根据处理结果返回不同的响应数据 if(areaById.size()>0){ re.setRetCode(ReturnCode.OPT_SUCCESS.getCode()); re.setRetMsg(ReturnCode.OPT_SUCCESS.getMsg()); re.setRetData(areaById); }else { re.setRetCode(ReturnCode.OPT_FAILED.getCode()); re.setRetMsg(ReturnCode.OPT_FAILED.getMsg()); } resp.setContentType("text/html;charset=utf-8"); PrintWriter writer = resp.getWriter(); writer.print(JSON.toJSONString(re)); writer.flush(); writer.close(); } }
③.前端
新建web.4areaPage.html
引入jquery
<html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="js/jquery-3.6.0.js" type="text/javascript" charset="utf-8"></script> </head> <body> <select id="prov"> <option disabled selected>------------请选择-------------</option> </select>省 <select id="city"> <option disabled selected>------------请选择-------------</option> </select>市 <select id="coun"> <option disabled selected>------------请选择-------------</option> </select>县/区 </body> <script> /* *查询地区服务接口 *请求地址 /day5/getArea *请求参数 areaid地区编号 *请求方式 get/post *响应数据格式 json *响应数据实例 * {"retCode":10000, * "retData":[{"areaId":"00101","areaName":"郑州"}, * {"areaId":"00102","areaName":"开封"}, * {"areaId":"00103","areaName":"洛阳"}], * "retMsg":"操作成功"} * */ //页面加载结束后 发送ajax请求 取省数据 $.getJSON("/day5/getArea","areaid=0",function(data){ //返回的json对象 console.log(data); //做页面的处理 //遍历返回的省数据 $.each(data.retData,function(i,d){ //把数组中的json数据 拼接成option标签 var myContent = '<option value="'+d.areaId+'" >'+d.areaName+'</option>'; //填入到省下拉列表中 $("#prov").append(myContent); }) }) //省选项改变 $("#prov").change(function(){ //获取到省编号 var provid = $(this).val(); //清空市县数据 $("#city").html('<option disabled selected>------------请选择-------------</option>'); $("#coun").html('<option disabled selected>------------请选择-------------</option>'); //发送ajax请求 取市数据 $.getJSON("/day5/getArea","areaid="+provid,function(data){ console.log(data); //遍历返回的市数据 $.each(data.retData,function(i,d){ //把数组中的json数据 拼接成option标签 var myContent = '<option value="'+d.areaId+'" >'+d.areaName+'</option>'; //填入到市下拉列表中 $("#city").append(myContent); }) }) }) //市选项改变 $("#city").change(function(){ //获取到省编号 var cityid = $(this).val(); //清空市县数据 $("#coun").html('<option disabled selected>------------请选择-------------</option>'); //发送ajax请求 取县区数据 $.getJSON("/day5/getArea",{"areaid":cityid},function(data){ //遍历返回的县区数据 $.each(data.retData,function(i,d){ //把数组中的json数据 拼接成option标签 var myContent = '<option value="'+d.areaId+'" >'+d.areaName+'</option>'; //填入到县区下拉列表中 $("#coun").append(myContent); }) }) }) </script> </html>
重新部署,运行,访问:http://localhost:8080/day5/4areaPage.html
省市县联动效果出来了。
这周重点:servlet加ajax,jsp可放掉。但三四线城市找工作还是要知道。
7.公司运作流程
一个公司上线一套系统:设计,开发,测试,部署实施,运维。
立项后,专门的设计人员设计功能,以前叫需求人员,现在叫产品。领头的叫产品经理。
设计好后,出一个原型图,通常是动态的,用专门的软件画出来的,不涉及具体的功能。
然后ui设计人员接手,将原型图用到的功能写成真正的页面,原型图的页面没法用,不是页面真正的元素。(将原型图具体成图,切成散图)
ui设计人员或者前端程序员写初版html页面,只有html和css
前端程序员编写javascript代码,写页面效果,过程中和后端程序员互动
后端程序员写服务接口
前后端程序员对着产品功能,一个接口一个接口的过。
一套接口过好,提交测试人员,测试人员找bug,然后一边测一边改。
同组所有人的bug都改完,这个版本就可以正式上线了。
上线的时候有专门的部署人员,把已经打好包的程序在正式环境,比如公司的服务器或租的云服务器里,把代码放上去,把服务器启动。可能会让测试人员在正式开启前再走一遍。若功能有问题,版本屏蔽,不上线。
正式上线后,用户使用还会出现意外的bug,这时运维人员出现。主要看服务器有没有运行,代码有没有运行,小的bug改改数据库数据之类的去修复,之后新的版本会改代码。运维人员也称人肉运维。
一般公司更在乎产品岗,保证产品有竞争力,会提出很多功能让程序员做。但很多产品没干过程序员。
有些初创公司,老板提需求,剩下的都自己做,程序员创业就是这样。
后端首先做好自测,测试人员大多不懂代码。测试人员分黑盒和白盒,黑盒测试是纯功能测试,看到按钮就点,看到输入框就写,各种搞,看系统能不能崩溃。白盒一般是懂代码的,根据bug找哪段代码出现问题,并出整改方案。这些测试工作自己完全可以做。测试人员有bug提交系统,也叫质量检测系统,bug也分级别,写错字也算。工资是基本工资加绩效工资,bug太多绩效会少。
公司里一般后端程序员多,前端少,可能会干两份工作。
全栈是指前端程序员学后端。后端程序员学前端就叫会写页面的后端程序员。前端程序员学后端学的是node.js,还是js。现在的js代码都集成在浏览器里运行,解析js代码的叫V8引擎。node.js是把V8引擎从浏览器揪出来,专门部署一套自己的程序,解析javascript代码。从浏览器里摘出来,浏览器的限制就没有了,主要是做本地IO的限制,浏览器里不允许写本地文件,比如访问数据库。全栈是指前端的一整套解决方案,做一些中小型应用,页面有了,还是用node.js搭建了服务器,和数据库做好交互,js一个语言全搞定。
想往上提升的两条路:1.做设计,把一个程序从头到尾走一遍,日积月累就成了高级程序员
2.学各种各样的技术,所有技术都玩得门清,可做架构。
进入公司的几条路线:1.特别爱写程序,爱设计程序结构,则初级,中级一直到高级程序员。一直做到阿里巴巴可以改jdk的程度。这是纯程序路线。
2.走架构,公司推出一套功能的解决方案,选用哪些技术写到系统里。选用这些技术就要对当下流行技术和以前的技术都门清。试各种技术,或者搭配组合使用的人员就是架构师。这是技术多广的路线。
3.管理。项目经理pm。管理岗用来维护进度,保证项目的正常上线,维护整个公司的人际关系和资源调配。资源就是人员,不同项目组经常借调一些人员。
4.程序员实在做不下去,也可转测试,转需求。转需求,很高大上。不要往部署人员和运维人员转,很苦。部署人员经常要出差,除了部署项目,还要交客户系统怎么用,还要写操作文档,告诉用户按这个按钮会发生什么事。运维就看公司的技术水平,技术水平不行或租的服务器有问题,就要不停地想办法解决。
8.作业
移动换流量包的小游戏。