文章目录
一 前言
当你开始接触多人合作的大型项目之后,前后端分离这个概念就离你越来越近了。通过这篇文章,我们来了解一下前后端分离究竟是什么,以及实现前后端分离的常用方法。
在聊聊前后端分离是什么之前,我们先来梳理一下前端和后端的概念。
1、前端
在一个web项目中,前端即网站前台部分,运行在PC端,移动端等浏览器上,负责数据展示和用户交互。
语言:HTML、CSS、JavaScript
框架:NG、React、Vue
2、后端
与数据库进行交互以处理相应的过程,需要考虑如何实现功能、数据的存取、平台的稳定性与性能等。
语言:JAVA、Golang 、Python、PHP
框架:Spring、MyBatis、SpringBoot
了解完前后端后,我们来了解一下前后端协作方式。
二 前后端协作方式
前端后端有两种协作方式,一种叫做服务器端渲染,另一种叫做前后端分离。
2.1 服务器端渲染
服务器端渲染是指在服务器端就将网页直接生成,浏览器这里拿到的是一整个网页,CSS和JS的部分是在浏览器端执行的,而网页的内容部分,也就是数据,是由服务器端生成的,例如我们熟知的jsp。
渲染过程
1.请求一个html -> 2. 服务端请求数据( 内网请求快 ) -> 3. 服务器初始渲染(服务端性能好,较快) -> 4. 服务端返回已经有正确内容的页面 -> 5. 客户端请求js/css文件 -> 6. 等待js文件下载完成 -> 7. 等待js加载并初始化完成 -> 8. 客户端把剩下一部分渲染完成( 内容小,渲染快 )
2.2 前后端分离
前后端分离是指的网页是在浏览器端处理,内容是通过接口从后端拿到数据
渲染过程
1. 请求一个html -> 2. 服务端返回一个html -> 3. 浏览器下载html里面的js/css文件 -> 4. 等待js文件下载完成 -> 5. 等待js加载并初始化完成 -> 6. js代码运行,由js代码向后端请求数据( ajax/fetch ) -> 7. 等待后端数据返回 -> 8. 客户端从无到完整地,把数据渲染为响应页面
2.2.1 前后端分离的出现
在传统Java web开发中,以jsp做前端为例子,通常需要前端把HTML静态页面写出来,然后交给后端,后端再把html转成jsp。如果页面出现了问题,由于问题可能会出现在前端或者后端,这时候要找出错误需要前后端沟通,这样会降低其效率。
使用前后端分离,便可以解决这个问题。
前后端分离后,前后端可以基于接口文档独立开发,独立部署,降低前后端的耦合。
接口文档是前后端分离的核心,并不是具体实现前后端的某些技术,而是约定好前后端之间需要传输的数据以及数据类型。后端开发人员应该要尽可能严谨的制定接口文档,避免开发过程中二次更新。
底层实现:前端html页面通过ajax调用后端的restuful api接口并使用json数据进行交互。
2.3 从服务器端渲染到前后端分离,发生了什么变化?
2.3.1 发生的变化
- 计算任务转移:
原本由服务器执行的渲染任务转移给了客户端,这在大量用户访问的时候大大减轻后端的压力。让后端专注做后端应该做的事情,性能将大大提高,因为服务器做的事情确实减小了,而现在随着客户端软硬件的发展,也能处理好大多数的渲染工作了。 - 放弃前端权限:
将整个UI逻辑交给客户端以后,一些有经验有能力的用户可能会劫持UI,使得他们能够看到一些不该看到的界面。这似乎违反了安全的原则。但是“一切在前端谈安全都是耍流氓”,后端不能轻信一切从前端传来的数据,切记一定要做好过滤与验证。只要使用SSL、屏蔽XSS、后端不出漏洞,想伪造身份劫持App还是难以做到的。 - 数据量:
前后端分离中传递数据,所以传输量会小。 服务器端渲染,会传输更大的数据(html网页),而且有很多内容是重复的。 - 解耦:
前后端分离中,传输的是数据,Model,数据怎么展示,全部交给前端来处理,后端只负责提供数据。
(什么叫解耦:耦合度越高,二者的依赖性越强。解耦则是降低二者的耦合度,在软件设计中,解耦可以提高项目的可扩展性)
2.3.3 前后端分离的优点
- 前后端解耦,可以并行开发,提高开发效率
- 减少后端服务器的压力,服务器只需要发送数据给客户端
- 提高代码的可维护性和易读性
三 如何实现前后端分离
数据交互:Ajax+Json
AJAX:即“Asynchronous Javascript And XML”(异步 JavaScript 和 XML),是指一种创建交互式、快速动态网页应用的网页开发技术,无需重新加载整个网页的情况下,能够更新部分网页的技术。ajax最核心的内容是依赖浏览器提供的XMLHttpRequest对象。ajax支持get和post请求
教程:https://www.w3cschool.cn/ajax/ajax-tutorial.html
JSON全称JavaScript Object Notation(js对象标记法),是一种轻量级数据交换格式,json同时也是Ajax请求传递的数据格式。
接口文档:swagger
swagger会自动生成一篇排版优美的API文档,与此同时还能生成一个供前端人员使用的 Mock Server。同时,它还能支持根据 Swagger API Spec 生成客户端和服务端的代码。
前后端分离开发框架
1.Springboot + Vue
当前Java Web实现前后端分离最主流的设计就是Springboot + Vue,使用Springboot 进行后端开发,使用Vue进行前端开发。
Springboot 可以帮助后端人员快速搭建起基于Spring框架的项目。Springboot 专注于开箱即用,减少配置代码,使开发者能够更加专注于业务逻辑。
Vue是一款便于上手以及能极大地提高你的开发效率的前端框架。其官方介绍为“一套用于构建用户界面的渐进式框架。与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用”。
优点
- Springboot和Vue是目前的热门框架,可参考资料多
- Vue使用axios进行网络请求,它对Ajax进行封装,解决了原生ajax许多缺点
2.Springcloud + Vue
Spring Cloud是一整套基于Spring Boot的微服务解决方案 。它为开发者提供了很多工具,用于快速构建分布式系统的一些通用模式,例如:配置管理、注册中心、服务发现、限流、网关、链路追踪等,是一系列框架的集合,包含SpringBoot。
插一句:前后端分离是一种设计思想,前后端分离的本质就是让前端和后端可以独立开发,降低二者的耦合性。因此不管前端还是后端使用什么语言、什么框架、什么架构,二者都能只通过接口传递和接受Json信息,其余互不影响。因此实际上每种框架都能够实现前后端分离,无非是比较容易实现或者是较难实现罢了。选择什么样的框架来开发前后端分离的项目主要取决于框架的实用性以及项目的要求。
3.Node.JS中间件
仅仅是使用node.js搭建中间层,前后端依然需要其他框架支持。
Node.js作为前端服务器,由前端开发人员负责,前端开发人员不需要知道java后台是如何实现的,也不需要知道API接口是如何实现的,只需要关心前端的开发工作,并且管理好Node.js前端服务器,而后台开发人员也不需要考虑如何前端是如何部署的,他只需要做好自己擅长的部分,提供好API接口就可以;
Node.js本身有着独特的异步、非阻塞I/O的特点,这也就意味着他特别适合I/O密集型操作,在处理并发量比较大的请求上能力比较强,因此,可以利用它来充当前端服务器,向客户端提供静态文件以及响应客户端的请求。
基于Node.JS的前后端分离架构设计:
前后端以及中间件负责的部分:
前后端分离接口文档
在这里用swagger(基于Springboot)介绍一下怎么编写接口文档,以及如何根据接口文档调试
1 idea新建maven项目
file-new-project-maven,输入项目名新建,点击next.
groupId:com.southwind
artifactId:aispringboot-1
2 添加依赖&下载swagger插件
pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.southwind</groupId>
<artifactId>aispringboot-1</artifactId>
<version>1.0-SNAPSHOT</version>
<!-- 引入依赖-->
<!-- 继承父包-->
<parent>
<artifactId>spring-boot-starter-parent</artifactId>
<groupId>org.springframework.boot</groupId>
<version>2.3.1.RELEASE</version>
</parent>
<dependencies>
<!-- web启动jar包-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- lombok依赖 若爆红请更新maven-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.6</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.6.1</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.6.1</version>
</dependency>
</dependencies>
</project>
如果导入依赖maven出现红线,请点一下更新maven。
然后在file-settings-Plugins中搜索下载swagger
3 SwaggerConfid类
swagger2配置类
package com.southwind.swagger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
/**
* @program: swagger2-demo
* @description: swagger2配置类
**/
@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Bean
public Docket buildDocket() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())//调用下面apiInfo()方法
.select()
//Controller所在路径
.apis(RequestHandlerSelectors.basePackage("com.southwind.controller"))
.paths(PathSelectors.any())
.build();
}
public ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("springboot结合swagger2构建Restful API")
.description("这是一个swagger2小型demo")
.termsOfServiceUrl("www.baidu.com")
.contact("bacyang")
.version("0.0.1")
.build();
}
}
4 User类
package com.southwind.entity;
import java.io.Serializable;
public class User implements Serializable {
private Long id;
private String name;
private Integer age;
public Long getId() {
return id;
}
public String getName() {
return name;
}
public void setId(Long id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
public void setAge(Integer age) {
this.age = age;
}
public Integer getAge() {
return age;
}
}
5 UserController类
package com.southwind.controller;
import com.southwind.entity.User;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@RestController
@RequestMapping("/users")
public class UserController {
static Map<Long , User> map = new ConcurrentHashMap<>();
//获取用户列表
@ApiOperation(value = "获取用户列表")
public List<User> getList(){
List<User> list = new ArrayList<>();
return list;
}
//根据user创建用户
@ApiOperation(value = "创建用户", notes = "根据user对象创建用户")
@ApiImplicitParam(name = "user",value = "用户详情实体类",required = true,dataType = "User")
@RequestMapping(value = "",method = RequestMethod.POST)
public String postUser(@RequestBody User user){
map.put(user.getId(),user);
return "添加成功";
}
//根据用户id来获取用户基本信息
@ApiOperation(value = "获取用户详情",notes = "根据url的id来获取用户基本信息")
@ApiImplicitParam(name = "id",value = "用户id",required = true,dataType = "Long",paramType = "path")
@RequestMapping(value = "/{id}",method = RequestMethod.GET)
public User getUserById(@PathVariable Long id) {
return map.get(id);
}
// 根据用户id来指定更新对象,进行用户的信息更新
@ApiOperation(value = "更新用户信息",notes = "根据url的id来指定对象,并且根据传过来的user进行用户基本信息更新")
@ApiImplicitParams({
@ApiImplicitParam(name = "id", value = "用户id", required = true, paramType = "path", dataType = "Long"),
@ApiImplicitParam(name = "user", value = "用户详情实体类user", required = true, dataType = "User")
})
@RequestMapping(value = "/{id}",method = RequestMethod.PUT)
public String putUser(@PathVariable Long id,@RequestBody User user) {
User u = map.get(id);
u.setAge(user.getAge());
u.setName(user.getName());
map.put(id,u);
return "用户基本信息已经更新成功~~~";
}
//根据用户id,删除用户
@ApiOperation(value = "删除用户",notes = "根据url的id来指定对象,进行用户信息删除")
@ApiImplicitParam(name = "id",value = "用户id",required = true,dataType = "Long",paramType = "path")
@RequestMapping(value = "/{id}",method = RequestMethod.DELETE)
public String delUser(@PathVariable Long id) {
map.remove(id);
return "用户ID为:"+ id + " 的用户已经被移除系统~~";
}
}
6 启动项目
首先要在与其他包名同级的位置新建Application作为Springboot的启动
@SpringbootApplication表示当前类是Springboot的入口,Application类的存放位置必须是其他相关业务类存放位置的父级
package com.southwind;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
//整个Springboot的启动类
@SpringBootApplication
public class Application {
public static void main(String[] args) {
//启动Spring项目
SpringApplication.run(Application.class,args);
}
}
对这个main程序启动,打开http://localhost:8080/swagger-ui.html能看到下面的页面
如果你按我的代码来,这个页面你应该比我少一个Student,因为我那部分的代码没贴出来.接下来点击Show/Hide可以看到controller层中我们定义的多个方法.
点开创建用户,以下是代码与API文档之间的对应关系
测试功能
在user框内输入user的属性(json),点击try it out
添加成功:
当接口文档确定后,前后端可以根据接口文档开发,前后端数据传输使用Ajax,从而实现前后端分离。
前后端分离具体实现会在我学会Springboot后再次更新。