SpringMVC前后端分离交互传参详细教程
温故而知新,本文为一时兴起写出,如有错误还请指正
本文后台基于SpringBoot2。5。6编写,前端基于Vue2axios和微信小程序JS版分别编写进行联调测试,用于理解前后端分离式开发的交互流程,如果没用过axios可以点我看之前的帖子
如果你没有学过SpringBoot也不要紧,把他看做成SpringMVC即可,写法完全一致(其实我不说你也发现不了)
本文主要讲前后端交互流程,力求帮助新人快速入门前后端分离式开发,不会讲关于环境搭建部分的内容SpringMVC接收参数的方式
在文章开头快速的过一遍SpringMVC接收参数的几种方式,一定要记住这几种方式,看不懂或不理解都没关系,后续会结合前端代码过一遍,这里就不过多解释了,直接上代码
1。【正常接收参数】正常接收参数注意:本Controller为了演示同时写了多个路径相同的GetMapping,不要直接复制,启动会报错RestControllerpublicclassIndexController{通过变量接收参数GetMapping(index)publicStringindex(Stringusername,Stringpassword){System。out。println(username);System。out。println(password);returnindex;}通过实体类接收参数GetMapping(index)publicStringindex(UserEntityuserEntity){System。out。println(userEntity。getUsername());System。out。println(userEntity。getPassword());returnindex;}通过Map集合接收参数GetMapping(index)publicStringindex(MapString,Objectparam){System。out。println(param。get(username));System。out。println(param。get(password));returnindex;}通过基于HTTP协议的Servlet请求对象中获取参数GetMapping(index)publicStringindex(HttpServletRequestreq){System。out。println(req。getParameter(username));System。out。println(req。getParameter(password));returnindex;}变量接收参数还可以使用RequestParam完成额外操作GetMapping(index)publicStringindex(RequestParam(valueusername,requiredtrue,defaultValuezhang)Stringusername){System。out。println(username);returnindex;}}
2。【路径占位接收参数】路径占位接收参数,参数作为请求路径的一部分,使用{}作为占位符RestControllerpublicclassIndexController{路径占位接收参数,名称相同GetMapping(user{id})publicStringindex(PathVariableIntegerid){System。out。println(id);returnindex;}路径占位接收参数,名称不同GetMapping(user{id})publicStringindex(PathVariable(id)LonguserId){System。out。println(userId);returnindex;}}
3。【请求体接收参数】如果请求参数在请求体中,需要使用RequestBody取出请求体中的值RestControllerpublicclassIndexController{使用实体类接收参数GetMapping(index)publicStringindex(RequestBodyUserEntityuserEntity){System。out。println(userEntity。getUsername());System。out。println(userEntity。getPassword());returnindex;}使用Map集合接收参数GetMapping(index)publicStringindex(RequestBodyMapString,Objectparam){System。out。println(param。get(username));System。out。println(param。get(password));returnindex;}变量接收参数GetMapping(index)publicStringindex(RequestBodyStringusername){System。out。println(username);returnindex;}}
细心的人应该留意到了,最后使用变量接收参数的时候只接收了username这一个值,并没有接收password,作为扩展在这里解释一下,不看也可以,看了不理解也没关系,知道这个事儿就够了,以后接触多了就理解了
如果请求参数放在了请求体中,只有参数列表第一个变量能接收到值,这里需要站在Servlet的角度来看:通过基于HTTP协议的Servlet请求对象获取请求体内容GetMapping(index)publicStringindex(HttpServletRequestreq){ServletInputStreaminputStreamreq。getInputStream();returnindex;}
可以看到请求体内容是存到了InputStream输入流对象中,想要知道请求体中的内容是什么必须读流中的数据,读取到数据后会将值给第一个变量,而流中的数据读取一次之后就没了,当第二个变量读流时发现流已经被关闭了,自然就接收不到前后端分离式交互流程
SpringMVC回顾到此为止,只需要记住那三种方式即可,在前后端交互之前先在Controller中写个测试接口RestControllerpublicclassIndexController{GetMapping(index)publicMapString,Objectindex(){创建map集合对象,添加一些假数据并返回给前端HashMapString,ObjectresultnewHashMap();result。put(user,zhang);result。put(name,hanzhe);result。put(arr,newint〔〕{1,2,3,4,5,6});返回数据给前端returnresult;}}
这个接口对应的是GET类型的请求,这里直接在浏览器地址栏访问测试一下:
这里推荐一个Chrome浏览器的插件JSONView,它可以对浏览器显示的JSON数据进行格式化显示,推荐的同时也提个醒,安装需谨慎,如果JSON数据量太大的话页面会很卡
跨域请求
之前已经写好一个GET请求的测试接口了,这里就在前端写代码访问一下试试看
VUE请求代码template!我这里为了看着好看(心情好点),引用了ElementUIelbuttongroupelbuttontypeprimarysizesmallclickrequest1发起普通请求elbuttonelbuttongrouptemplate
代码已经写完了,接下来打开页面试一下能不能调通:
可以看到请求代码报错了,查看报错信息找到重点关键词CORS,表示该请求属于跨域请求
认识跨域请求
什么是跨域请求?跨域请求主要体现在跨域两个字上,当发起请求的客户端和接收请求的服务端他们的【协议、域名、端口号】有任意一项不一致的情况都属于跨域请求,拿刚刚访问的地址举例,VUE页面运行在9000端口上,后台接口运行在8080端口上,端口号没有对上所以该请求为跨域请求
处理跨域请求
如果在调试的时候仔细一点就会发现,虽然前端提示请求报错了,但是后端还是接收到请求了,那为什么会报错呢?是因为后端返回数据后,浏览器接收到响应结果发现该请求跨域,然后给我们提示错误信息,也就是说问题在浏览器这里
怎样才能让浏览器允许该请求呢?我们需要在后端动点手脚,在返回结果的时候设置允许前端访问即可
首先配置一个过滤器,配置过滤器有很多种实现的方法,我这里是实现Filter接口ComponentpublicclassCorsFilterimplementsFilter{OverridepublicvoiddoFilter(ServletRequestservletRequest,ServletResponseservletResponse,FilterChainfilterChain)throwsIOException,ServletException{将response响应转换为基于HTTP协议的响应对象HttpServletResponseresp(HttpServletResponse)servletResponse;这个方法是必须调用的,不做解释filterChain。doFilter(servletRequest,resp);}Overridepublicvoidinit(FilterConfigfilterConfig)throwsServletException{}Overridepublicvoiddestroy(){}}
过滤器创建完成了,回来看前端提示的报错信息为AccessControlAllowOrigin,意思是允许访问的地址中并不包含当前VUE的地址,那么我们就在响应结果时将VUE的地址追加上OverridepublicvoiddoFilter(ServletRequestservletRequest,ServletResponseservletResponse,FilterChainfilterChain)throwsIOException,ServletException{将response响应转换为基于HTTP协议的响应对象HttpServletResponseresp(HttpServletResponse)servletResponse;在允许请求的地址列表中添加VUE的地址resp。addHeader(AccessControlAllowOrigin,http:localhost:9000);这个方法是必须调用的,不做解释filterChain。doFilter(servletRequest,resp);}
添加完成后重启项目后台就会发现请求已经成功并且拿到了返回值
再次进行测试,将后台的GetMapping修改为PostMapping,修改前端请求代码后重新发起请求进行测试
可以看到POST请求还是提示跨域请求,对应的错误信息则是AccessControlAllowHeaders,也就是说请求头中包含了不被允许的信息,这里图省事儿用通配符把所有请求头都放行OverridepublicvoiddoFilter(ServletRequestservletRequest,ServletResponseservletResponse,FilterChainfilterChain)throwsIOException,ServletException{将response响应转换为基于HTTP协议的响应对象HttpServletResponseresp(HttpServletResponse)servletResponse;后台接口除了VUE访问之外微信小程序也会访问,这里使用通配符替换resp。addHeader(AccessControlAllowOrigin,);这里图省事也允许所有请求头访问resp。addHeader(AccessControlAllowHeaders,);这个方法是必须调用的,不做解释filterChain。doFilter(servletRequest,resp);}
这样处理之后,请求就可以正常访问啦
传参路径占位参数
路径占位参数,就是将参数作为请求路径的一部分,例如你现在正在看的这篇博客使用的就是路径占位传参
这种传参方法很简单,就不细讲了,可以效仿他这种方法写个测试案例
后台接口的编写RestControllerpublicclassIndexController{路径中包含user和blogId两个占位参数GetMapping({user}p{blogId}。html)publicMapString,Objectindex(PathVariableStringuser,PathVariableLongblogId){将接收的参数返回给前端HashMapString,ObjectresultnewHashMap();result。put(user,user);result。put(blogId,blogId);returnresult;}}
VUE请求代码request1(){this。axios。get(http:localhost:8080hanzhep11223344。html,this。config)。then(res{console。log(res,res);});}
小程序请求代码request1(){wx。request({url:请求的目标地址url:http:localhost:8080hanzhep223344。html,success:请求成功后执行的方法success:res{console。log(res);}})}
传参路径参数
这里需要注意区分【路径占位传参】和【路径传参】两个概念,不要记混
什么是路径传参?发起一个请求http:localhost:8080index?a1b2,在路径?后面的都属于路径传参,路径传参就是将参数以明文方式拼接在请求地址后面
路径传参使用【正常接收参数】中的实例代码即可接收到值
后台接口的编写RestControllerpublicclassIndexController{GetMapping(index)publicMapString,Objectindex(Stringuser,Stringname){将接收的参数返回给前端HashMapString,ObjectresultnewHashMap();result。put(user,user);result。put(name,name);returnresult;}}
VUE代码
除了自己手动拼接请求参数之外,axios在config中提供了params属性,也可以实现该功能正常拼接request1(){this。axios。get(http:localhost:8080index?userzhangnamehanzhe)。then(res{console。log(res,res);});},使用config中的params属性进行路径传参request2(){letconfig{params:{user:zhang,name:hanzhe}}this。axios。get(http:localhost:8080index,config)。then(res{console。log(res,res);});}
小程序代码正常拼接request1(){wx。request({url:http:localhost:8080index?userzhangnamehanzhe,success:res{console。log(res);}})},将请求类型设置为GET,wx识别后会将data转换为路径传参request2(){wx。request({url:http:localhost:8080index,method:GET,data:{user:zhang,name:hanzhe},success:res{console。log(res);}})}
传参表单类型参数
表单类型参数,就是通过form表单提交的参数,通常用在例如HTML、JSP页面的form标签上,但如果是前后端分离的话就不能使用form表单提交了,这里可以手动创建表单对象进行传值
需要注意,GET请求一般只用于路径传参,其他类型传参需要使用POST或其他类型的请求
表单类型参数也是【正常接收参数】中的实例代码接收值
后台接口的编写RestControllerpublicclassIndexController{PostMapping(index)publicMapString,Objectindex(Stringusername,Stringpassword){将接收的参数返回给前端HashMapString,ObjectresultnewHashMap();result。put(username,username);result。put(password,password);returnresult;}}
VUE代码request1(){构建表单对象,向表单中追加参数letdatanewFormData();data。append(username,123);data。append(password,456);发起请求this。axios。post(http:localhost:8080index,data)。then(res{console。log(res,res);});},
小程序代码
小程序删除了FormData对象,不能发起表单类型参数的请求,如果非要写的话可以试着使用wx。uploadFile实现,这里就不尝试了传参请求体参数
请求体传参,是在发起请求时将参数放在请求体中
表单类型参数需要使用上面【请求体接收参数】中的实例代码接收值
后台接口的编写RestControllerpublicclassIndexController{PostMapping(index)publicMapString,Objectindex(RequestBodyUserEntityentity){将接收的参数返回给前端HashMapString,ObjectresultnewHashMap();result。put(username,entity。getUsername());result。put(password,entity。getPassword());returnresult;}}
VUE代码
axios如果发起的为POST类型请求,默认会将参数放在请求体中,这里直接写即可request1(){创建date对象存储参数letdata{username:哈哈哈哈,password:嘿嘿嘿嘿}发起请求this。axios。post(http:localhost:8080index,data)。then(res{console。log(res,res);});},
小程序代码
小程序代码也是一样的,当发起的时POST类型的请求时,默认会把参数放在请求体中request1(){构建表单对象,向表单中追加参数letdata{username:哈哈哈哈哈哈,password:aabbccdd}发起请求wx。request({url:http:localhost:8080index,method:POST,data:data,success:res{console。log(res。data);}})},
小技巧:如何区分传参类型
在实际开发中大概率不用写前端代码,只负责编写后台接口,但怎样才能知道前端请求是什么类型参数?
关于这点可以通过浏览器开发者工具的【网络】面板可以看出来,网络面板打开时会录制网页发起的所有请求
路径占位传参就不解释了,没啥好说的,这里介绍一下路径传参、表单传参和请求体传参
路径传参
编写好路径传参的请求代码后切换到网络面板,点击发起请求:
请求体传参
编写好请求体传参的请求代码后切换到网络面板,点击发起请求:
表单类型传参
编写好表单类型传参的请求代码后切换到网络面板,点击发起请求:
封装统一响应工具类
掌握了前后端交互的流程就可以正常开发网站了,这里推荐后端返回一套规定好的模板数据,否则某些情况可能会比较难处理,例如这个查询用户列表的接口:RestControllerpublicclassIndexController{RequestMapping(index)publicListHashMapString,Stringindex(){查询用户列表ListHashMapString,StringuserListthis。selectList();将用户列表数据返回给前端returnuserList;}模拟dao层的查询代码,返回一个集合列表,集合中每个元素对应一条用户信息publicListHashMapString,StringselectList(){ArrayListHashMapString,StringlistnewArrayList();for(inti1;i5;i){HashMapString,StringmapnewHashMap();map。put(id,UUID。randomUUID()。toString());map。put(username,游客i);map。put(gender,i21?男:女);list。add(map);}returnlist;}}
该接口乍一看没毛病,拿到用户列表数据后返回给前端用于渲染,合情合理,可是如果后端业务逻辑有BUG可能会导致前端接收到的结果为空,这种情况下前端就需要判断,如果接收到的值为空,就提示请求出错,问题貌似已经解决,但是如果表中本来就没有任何数据的话有应该怎么处理
上述的就是最常见的一种比较头疼的情况,所以针对这种情况最好指定一套标准的返回模板进行处理
制定响应工具类
根据刚刚的举例来看,返回结果中应该有一个标识来判断该请求是否执行成功,如果执行失败的话还应该返回失败原因,响应给前端的数据会被转换为JSON数据,使用Map集合来返回最合适不过了importjava。util。HashMap;importjava。util。Map;publicclassResultextendsHashMapString,Object{私有化构造方法,不让外界直接创建对象paramstatustrue为请求成功,false为请求失败parammsg返回给前端的消息privateResult(booleanstatus,Stringmsg){规定无论请求成功还是失败,这两个参数都必须携带super。put(status,status);super。put(msg,msg);}静态方法,如果请求成功就调用okpublicstaticResultok(){returnnewResult(true,请求成功);}静态方法,如果请求失败就调用fail,需要提供失败信息publicstaticResultfail(Stringmsg){returnnewResult(false,msg);}规定所有返回前端的数据都放在data中paramname对象名paramobj返回的对象publicResultput(Stringname,Objectobj){如果集合中不包含data,就创建个Map集合添加进去if(!this。containsKey(data)){super。put(data,newHashMapString,Object());}获取data对应的map集合,往里面添加数据MapString,Objectdata(MapString,Object)this。get(data);data。put(name,obj);returnthis;}}
扩展:ApiPost接口调试工具
在后台接口编写完成后,一般情况下我们都需要进行测试,GET请求还好,浏览器直接就访问呢了,如果是POST请求还要去写前端代码就很烦,这里介绍一款接口调试工具ApiPost
你可能没听过ApiPost,但是你大概率听说过Postman,他们的用法几乎一致,且ApiPost是国人开发的免费的接口调试工具,界面中文很友好
这里也可以看出来,form表单传参其实也算在了请求体里面,只不过使用的是multipartformdata类型的参数而已,而之前提到的请求体传参对应的就是applicationjson
原文地址:https:www。cnblogs。comhanzhep16037322。html
轻松看漫画,快乐学成语,阅读一套书,成语大王就是你众人皆说,成之于语,故成语。成语是中华文化中一颗璀璨的明珠。成语是我们中华民族的优秀传统文化之一,是汉语词汇系统中的精华。成语读起来朗朗上口,极富语言之美。成语的意思精辟……
辽宁广东5队球员注册变化,9位新人16人换队,12位球星未公文水清清昨天,CBA官网公示辽宁、广东、广厦、山西和青岛五支球队46人,相较于上赛季,出现的变化为:9位新人登陆联赛,16人换队,还有12位球星未公示。一、辽宁队,……
最平常的土豆,有哪些特色的做法?哈哈!这五个土豆好诱人啊!喜欢!我干脆拿去用油炸了得了!吃到嘴里喷香喷香喷喷香!不过你这几个土豆看似白瓤的,白瓤的土豆口感欠佳。我们家种的黄瓤的土豆样子长的长长的,上的豆……
你对年夜饭有哪些难忘的回忆?鞭炮一响,一盘黄豆芽,几瓣腊八蒜,老陈醋几滴香油,热腾腾饺子,来上半斤粮食酒是最难忘年夜饭!说起年夜饭最难忘的回忆,莫过于父母在的时候,我的父母是特别热爱生活的人,小时候……
你们家乡特产有什么,家乡特色是什么?谢邀请回答!我们家乡的主要特产是武昌鱼!伟大领袖毛主席在畅游长江后留下的才饮长沙水,又食武昌鱼。的著名诗句,让武昌鱼驰名中外。武昌鱼不是生长在武昌,而是产于武汉毗邻……
原来20年前,没有平坦小腹也能穿低腰RomComCore,我的春日浪漫补充剂每年圣诞节就要重温《真爱至上》,为爱情焦虑时就想看《BJ单身日记》,工作不顺就得翻翻《律政俏佳人》吸吸好运,精神ValleyGir……
周杰从红极一时到被迫退出娱乐圈,如今靠种地成为亿万富翁他是火遍大江南北的《还珠格格》中尔康的饰演者,也是热播剧中《少年包青天》中包青天的饰演者。可以说他出道即巅峰,演技可圈可点。性格较真的他被剧组的人称为戏疯子。而林心如的一……
柿子你喜欢吃软的还是脆的?柿子当然是软的好吃,苹果当然是硬的好吃,品种不同,吃法不一样。不是有句话说的好,柿子专拣软的捏吗。软柿子好吃,记住了啊。柿子还是脆的好吃,脆中带劲道懒柿子吃脆的。懒……
你觉得好酒和好喝的酒有什么区别?好酒和好喝的酒的区别主要表现在香味、颜色、味道、酒后反应这四个方面。闻香:优质酒复合香突出,主要比较高级的香气有陈香,窖香,粮香;等级较差的酒中一般凸显糟香,香精香;……
北大仓和汾酒有什么区别?哪个更好?北大仓酒,是黑龙江北大仓集团有限公司出品的一款白酒,企业标准50度,这款酒是酱香型白酒,酒质微黄,有北国茅台之称,口感有麸曲的酱香,高梁酒的特殊味道,回味有香甜,不那么醇厚,略……
米糕怎么做最好吃?米糕是健康营养的美食,至今已经有上千年的历史,说明米糕还是很深受欢迎的。而且特别适合小孩子吃,软糯香甜。当早餐也是不错的选择,制作简单易上手,省时间。米糕的做法多样化,传……
2场独造2球!国米左路新飞翼横空出世,小因扎吉喜迎救星暂保帅北京时间10月5日凌晨,欧冠联赛战火重燃,在本轮小组赛中,迎来多场强强对话,其中意甲豪门国际米兰主场迎战西甲豪门巴塞罗那的比赛颇受球迷关注,凭借中场大将恰尔汗奥卢在第45分钟的……