第二阶段(day14)Ajax

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.作业

移动换流量包的小游戏。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值