前后端分离解决什么问题,痛点在哪里?
在不使用前后端分离的年代,前端人员写好静态的HTML页面后给到后端开发人员(所谓的静态页面是指数据都是固定死的页面),后端开发人员需要把静态的HTML页面转为动态的页面。怎么转为动态的页面呢?方式有很多种,比如通过模板技术转为JSP、Thymeleaf或者Freemarker。因为这些模板技术可以对静态的HTML进行改造,先从数据库查询数据,利用模板技术把查询后的数据和静态的html进行合并,然后再返回给客户端。
服务端开发人员需要使用模板技术对静态的HTML页面进行改造,会向HTML页面里面插入很多标签(模板技术的标签),如果遇到问题,就需要找前端人员来一起排查解决,但此时前端看到的代码已经不是他之前写的HTML了,是混合了一大堆标签的HTML代码,而前端也不懂这些模板技术。类似这样的场景:
后端:你写的页面有问题啊,不显示数据。
前端:不可能,我这边都是好的。
后端:你自己来看啊。
前端:你写的这是什么玩意?我给你的代码不是这样的。
后端:我得把数据加到你的HTML页面里面呀。
前端:我又不懂这些标签,你再把标签摘出来吧,我帮你看看问题。
后端:……
这样直接导致开发效率非常低,遇到问题也不好排查,需要双方一起协商一起查问题,并且还非常浪费用户流量,每次请求都会返回整个HTML页面,很多HTML标签如div是不需要返回的,只需要返回div所需要的数据就可以了。为了解决这些问题前后端分离的模式就诞生了。
什么是前后端分离
顾名思义,前后端分离,就是把一个应用的前端代码和后端代码分离开来,拆分成两个应用,一个纯前端应用,只负责数据展示和用户交互,一个后端应用,只负责提供接口返回数据(如JSON)给前端应用。前端应用使用静态HTML页面,通过异步请求(如Ajax)调用后端接口,返回给客户端(浏览器)数据后,浏览器解析服务端返回的数据,动态生成HTML标签展示到用户界面上。最终的结构是这样的
如上图,其实就是拆分成两个应用,前端应用和后端应用,部署的时候也分开部署。前端应用使用Vue、React、Angular等开发,后端应用使用Spring boot框架进行开发。最流行的方案就是Spring boot + Vue啦。
项目案例
先让静态页面动起来
我们使用Spring boot搭建后端项目,因为它能减少很多配置。前端使用Vue编写(不用害怕,只是非常简单的页面),为了演示我们不搞复杂的东西。项目如下图所示
是的,我们仍然把静态HTML页面放到后端项目里面。HTML页面大致如下
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
</div>
<!-- 本地引入Vue库 -->
<script src="js/vue.js"></script>
<!-- 引入axios发送网络请求 -->
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<template id='temp-test'>
<div>这是一个简单的HTML页面</div>
<button @click="fetchData">获取服务端数据,进行展示</button>
<ul>
<li v-for="item in listData" :key="index">
姓名: {{item.name}} 年龄:{{item.age}}
</li>
</ul>
</template>
<script>
const xxx = {
template: "#temp-test",
data() {
return {
listData:[]
}
},
methods: {
fetchData(){
axios.get("http://localhost:8080/getPersons").then(response => (
this.listData = response.data
))
}
},
}
const app = Vue.createApp(xxx);
app.mount("#app")
</script>
</body>
</html>
我们启动后端应用,访问http://localhost:8080/test.html。如下图所示
点击获取服务数据按钮,如下所示
此时此刻静态的HTML页面真的变成动态的了。这种结构如下图
这种模式还是存在问题,前端和后端代码还是放在同一个工程中,前端人员还需要下载IDEA进行开发吗?这样做肯定不行,前端人员肯定要用前端工具进行开发,比如vscode、webstorm等,所以我们需要创建两个项目,一个后端项目,一个前端项目。
彻底前后端分离
后端项目保持不变,只是把资源文件里面的内容放到了前端项目里面,如下图所示
启动后端项目,服务地址为:http://localhost:8080/getPersons
前端可以使用vscode或者webstorm等,这里我们使用vscode
如下图所示
前端项目,本地服务的地址为:http://127.0.0.1:5501/test.html。此时的结构图如下所示
当点击获取服务端数据按钮时,发现控制台报错
这就是传说的跨域问题,这里我们先刹车讲解一下跨域问题。
同源策略的限制
关于CORS的理解,阮一峰这篇文章感觉不错
- 同源策略要求源相同才能正常进行通信,即协议、域名、端口号都完全一致,默认情况下Ajax请求只能发送给同源的URL
这一点MDN上做了一个例子比较好
有些HTML标签是不受同源策略影响的,比如以下标签
<script src="..."></script>
<link rel="stylesheet" href="...">
<img>
<video>
<audio>
<iframe>
跨域只存在于浏览器端,但是像iOS、Android等移动端因为会有webview加载HTML页面的情况,也会存在跨域,这时客户端和服务器都需要做处理,我们目前只讨论PC浏览器以及Spring boot的解决方案。
- 跨域请求能发出去,服务端能收到请求并正常返回结果,只是结果被浏览器拦截了。
如何解决跨域访问
使用CORS跨域请求,CORS是一个W3C标准,全称是”跨域资源共享”(Cross-origin resource sharing),允许浏览器向跨源服务器,发出请求。客户端支持
默认所有浏览器都支持,如果是IE浏览器至少IE10
服务端Spring Boot配置
服务端处理有很多种方式,我们这里先用简单粗暴的方式直接在Controller上或者Controller里面的方法上加注解即可
@RestController
@CrossOrigin({"http://127.0.0.1:5501"}) //允许哪些源跨域,传一个数组即可
public class TestController {
@RequestMapping("/getPersons")
public List<Person> getUser(){
List<Person> list = new ArrayList<>(10);
for (int i = 0; i < 10; i++) {
list.add(new Person("jack"+i,i));
}
return list;
}
}
点击获取服务端数据按钮
到这里前后端分离就讲完了,我们画一个最终的结构图