本文目录
- 前言
- 专栏介绍
- 一、创建SpringBoot项目
- 1.1 添加springboot依赖
- 1.2 创建启动类
- 1.3 创建控制器类
- 1.4 Run 或 Debug
- 二、开发图书管理API
- 2.1 web层
- BookAdminController
- BookVO
- 2.2 service层
- BookService
- BookServiceImpl
- BookBO
- 2.3 dal层
- BookMapper
- BookMapperImpl
- Book
- 2.4 Postman测试
- 三、庖丁解牛API实现
- 3.1 Spring MVC 常用注解
- 3.2 API结果统一封装
- 3.3 各层对象说明
- 3.4 各层包结构规范
- 3.5 Spring IOC
- 3.6 约定优于配置
- 四、支持跨域
- 五、打包
- 最后
前言
在上文,我们基于Maven,已经把三层架构项目搭建起来了,重点掌握的是如何规范的创建Maven项目、如何统一管理依赖版本。还没掌握的同学,可以先看看上文:天狗实战SpringBoot+Vue(二)项目结构搭建(上)
本文,将基于上文搭建的项目结构,开始创建SpringBoot项目,并进行API开发,最终输出给前端两个API:分别基于GET和POST 请求。
从实现层面:会结合实战 解读SpringMVC常用注解的使用,并实现 API结果统一封装、 支持跨域请求,以及 多Jar如何打包。
从规范层面:会结合实战 把 三层架构 都串起来,包括各层的命名规范和对象职责。
这些都是实际项目必须掌握的,所以跟上节奏,Let’s Go!
对应思维导图的红框处:
专栏介绍
本文对应前端博文:基于Vue+Less+axios封装+ElementUI搭建项目底层支撑实战
因为可能还有很多同学还不清楚上下文,所以简单介绍一下这个专栏要做的事:
天罡老哥和狗哥(博客主页)有意
从0到1
带大家搭建一个SpringBoot+SpringCloud+Vue
的前后端分离项目!
打造一个短小精悍、技术主流、架构规范的前后端分离实战项目!我负责后端,狗哥负责前端!
目的就是让大家通过项目实战,学到一些真东西,将所学理论落地,助力有心强大的你更快的成长!开启你的工作之旅,让开发游刃有余!
详细的后端规划和后端大纲思维导图在开篇已经给出,你可以到开篇查收:基于SpringBoot+SpringCloud+Vue前后端分离项目实战 –开篇。
一、创建SpringBoot项目
因为我们已经创建Maven项目:tg-book-web,所以基于此,创建SpringBoot项目,简单来说只需要3小步
:
① 修改pom.xml:加依赖 spring-boot-starter-web
② 创建启动类:加注解 @SpringBootApplication
③ 创建控制器类:加注解 @RestController
1.1 添加springboot依赖
1). web层增加依赖
在tg-book-web的 pom.xml 增加依赖如下:
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> 其它dependency。。。</dependencies>
2). 父项目统一管理依赖
上面的spring-boot-starter-web
没有定义版本号version
,还记得上文我们说的父项目统一管理依赖
吗?
没错,就是父pom.xml的dependencyManagement
节点,用来统一管理依赖版本。
所以,我们需要在dependencyManagement
节点下增加依赖spring-boot-dependencies
,这里包括SpringBoot为我们定义好的所有依赖。
<properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> <spring-boot.version>2.3.12.RELEASE</spring-boot.version></properties><dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>${spring-boot.version}</version> <type>pom</type> <scope>import</scope> </dependency> 其它dependency。。。 </dependencies></dependencyManagement>
另外,这里有一个小知识点:统一管理依赖有两种方式,一种是上面这样,另外还可以把parent定义为spring-boot-starter-parent
,实际内部也是依赖spring-boot-dependencies
。综合考虑Maven是单继承,所以建议使用上面spring-boot-dependencies
的方式。
3).Maven刷新依赖
温馨提示
:添加完依赖,别忘了刷新Maven,上文说过了,我再重复一遍!
- 父项目右键-》Maven-》Reload Project
- 父项目右键-》Run Maven-》Reimport
我最喜欢的是在最右侧的Maven选项卡,选中父项目,点上面的刷新图标,如下图:(或者右键Reload Project)
1.2 创建启动类
创建启动类:org.tg.book.ApplicationRunner
,如下图 java包右键菜单->New->Java Class
输入类的全名,包括包名,如下图:
启动类里面只有一个入口main方法,用于项目的启动。
main方法里调用SpringApplication.run
方法,并配上@SpringBootApplication
注解,这样就齐活了,里面做了啥先不用深挖!
package org.tg.book;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplicationpublic class ApplicationRunner { public static void main(String[] args) { SpringApplication.run(ApplicationRunner.class, args); }}
1.3 创建控制器类
创建控制器类:org.tg.book.web.controller.BookAdminController
,用于向前端提供API,定义如下:
package org.tg.book.web.controller;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.RestController;@RestController@RequestMapping("/admin")public class BookAdminController { @GetMapping("/hello") public String hello(@RequestParam("name") String name){ return "hello:" + name; }}
到这,我们就已经定义好了一个GET请求的API,请求路径为:/admin/hello” />
2.1 web层
BookAdminController
- getBook方法:Get请求
- saveBook方法:Post请求
- web层会调用service层,所以依赖注入了BookService
- 注解会在下面的【3.1 Spring MVC常用注解】里详解!
package org.tg.book.web.controller;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestBody;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.RestController;import org.tg.book.common.dto.TgResult;import org.tg.book.service.BookService;import org.tg.book.service.bo.BookBO;import org.tg.book.web.vo.BookVO;@RestController@RequestMapping("/admin")public class BookAdminController { @Autowired private BookService bookService; @GetMapping("/book") public TgResult<BookBO> getBook(@RequestParam("id") Integer id) { return TgResult.ok(bookService.getBook(id)); } @PostMapping("/book") public TgResult<Integer> saveBook(@RequestBody BookVO bookVO) { return TgResult.ok(bookService.saveBook(bookVO.toBookBO())); }}
BookVO
web层对象,即前端传入的对象!关于各层对象,会在【3.3各层对象说明】详解
package org.tg.book.web.vo;import com.fasterxml.jackson.annotation.JsonFormat;import lombok.Data;import org.springframework.beans.BeanUtils;import org.tg.book.service.bo.BookBO;import java.io.Serializable;import java.util.Date;@Datapublic class BookVO implements Serializable { private Integer id; private String bookName; private String bookNo; private String bookAuthor; private Integer bookType; private String bookDesc; private String publisher; @JsonFormat(pattern = "yyyy-MM-dd") private Date publishDate; public BookBO toBookBO() { BookBO bookBO = new BookBO(); BeanUtils.copyProperties(this, bookBO); return bookBO; }}
2.2 service层
BookService
定义service接口:
package org.tg.book.service;import org.tg.book.service.bo.BookBO;public interface BookService { BookBO getBook(Integer id); Integer saveBook(BookBO bookBO);}
BookServiceImpl
实现service方法:service层会调用dal层,所以依赖注入了BookMapper
package org.tg.book.service.impl;import org.springframework.beans.BeanUtils;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import org.tg.book.dal.mapper.BookMapper;import org.tg.book.dal.po.Book;import org.tg.book.service.BookService;import org.tg.book.service.bo.BookBO;@Servicepublic class BookServiceImpl implements BookService { @Autowired private BookMapper bookMapper; @Override public BookBO getBook(Integer id) { Book book = bookMapper.selectByPrimaryKey(id); if (book == null) { return null; } BookBO bookBO = new BookBO(); BeanUtils.copyProperties(book, bookBO); return bookBO; } @Override public Integer saveBook(BookBO bookBO) { if (bookBO == null) { return null; } Book book = new Book(); BeanUtils.copyProperties(bookBO, book); if (bookBO.getId() == null) { return bookMapper.insert(book); } else { return bookMapper.updateByPrimaryKey(book); } }}
BookBO
service层对象
package org.tg.book.service.bo;import lombok.AllArgsConstructor;import lombok.Builder;import lombok.Data;import lombok.NoArgsConstructor;import java.io.Serializable;import java.util.Date;@Data@NoArgsConstructor@AllArgsConstructor@Builderpublic class BookBO implements Serializable { private Integer id; private String bookName; private String bookNo; private String bookAuthor; private Integer bookType; private String bookDesc; private String publisher; private Date publishDate;}
2.3 dal层
BookMapper
定义数据访问接口:
package org.tg.book.dal.mapper;import org.tg.book.dal.po.Book;public interface BookMapper { Book selectByPrimaryKey(Integer id); Integer insert(Book book); Integer updateByPrimaryKey(Book book);}
BookMapperImpl
数据访问实现类,这里没有保存到数据库,因为还没有讲,所以直接保存在内存List中,当我们集成Mybatis时会改造这块代码。
package org.tg.book.dal.mapper.impl;import org.springframework.beans.BeanUtils;import org.springframework.stereotype.Repository;import org.tg.book.dal.mapper.BookMapper;import org.tg.book.dal.po.Book;import java.util.ArrayList;import java.util.List;import java.util.Optional;@Repositorypublic class BookMapperImpl implements BookMapper { private final List<Book> bookList = new ArrayList<>(); @Override public Book selectByPrimaryKey(Integer id) { return bookList.stream().filter(p -> p.getId().equals(id)).findFirst().orElse(null); } @Override public Integer insert(Book book) { book.setId(bookList.size() + 1); bookList.add(book); return book.getId(); } @Override public Integer updateByPrimaryKey(Book book) { Optional<Book> first = bookList.stream().filter(p -> p.getId().equals(book.getId())).findFirst(); if (first.isPresent()) { BeanUtils.copyProperties(book, first.get()); return book.getId(); } return null; }}
Book
持久化对象,与数据库表字段对应
package org.tg.book.dal.po;import lombok.Data;import java.util.Date;@Datapublic class Book { private Integer id; private String bookName; private String bookNo; private String bookAuthor; private Integer bookType; private String bookDesc; private String publisher; private Date publishDate;}
2.4 Postman测试
最终实现了两个API,可以先对号入座!
三、庖丁解牛API实现
上面只贴了代码,相信大家肯定会有疑问,那下面我们就来一一化解!
3.1 Spring MVC 常用注解
接下来说说如下图中这几个注解的作用。
- 1) @RestController
表示用于处理web请求的控制器
。我们开发前后端分离的Restful风格API,使用@RestController就对了。
关于@RestController经常拿来与@Controller做区别和联系,所以简单说明一下:
@Controller
也表示用于处理web请求的控制器,返回的是视图,配合视图解析器才能返回到指定页面,当需要直接返回数据时需要配合加@ResponseBody注解。
@ResponseBody
表示返回的是数据对象,而不经过视图解析器。
@RestController 等同于@Controller + @ResponseBody,这样我们开发Restful API就方便了,因为我们就是直接返回数据对象,像普通文本、JSON、XML等等对象,所以开发Restful风格的前后端分离项目通常都会用@RestController。
- 2) @RequestMapping
用于处理请求映射
,就是定义请求路径,可以作用于类和方法上。
value:请求的映射地址
method:请求方式,包括 GET, POST, PUT, DELETE 等等
produces:响应类型,像”application/json;charset=UTF-8″代表返回json,”application/xml;charset=UTF-8″代表返回xml,等等
另外,从@RequestMapping派生了4个常用注解:,分别对应常见的4种请求方法,只可以作用于方法上,因为更方便,所以通常 代替@RequestMapping 作用于方法上。
- 3) @GetMapping和@PostMapping
@GetMapping、@PostMapping、@PutMapping、@DeleteMapping 是同类注解,都是从@RequestMapping
派生的,分别对应4类请求方法,所以用法和@RequestMapping 相同,区别是只可以作用于方法上。
所以,上面的API,类路径使用的是@RequestMapping,方法路径使用的@GetMapping和@PostMapping
- 4) @RequestParam
用于获取传入参数
的值,就是在请求url上" />
接口通常都是前后端协商定义的,通常都会定义统一返回JSON格式数据,这样可以返回标准的结构化数据,另外还可以传达更多的信息。
我这里定义success、code、message、data
四个字段,说明:
success:是否成功的标识;
code:返回给前端的状态码;
message: code对应的消息描述;
data: 该接口定义的对象数据,因为API结果对象有多种多样,所以会用到
泛型类
。
我们将这种通用的对象类,定义在tg-book-common
中,命名为org.tg.book.common.dto.TgResult
,就是上面API返回的TgResult
.
package org.tg.book.common.dto;import lombok.Data;import java.io.Serializable;@Datapublic class TgResult<T> implements Serializable { private boolean success; private String code; private String message; private T data; public static <T> TgResult<T> ok(T data) { return ok("200", "成功", data); } public static <T> TgResult<T> ok(String code, String message, T data) { return build(true, code, message, data); } public static <T> TgResult<T> fail(String code, String message) { return fail(code, message, null); } public static <T> TgResult<T> fail(String code, String message, T data) { return build(false, code, message, data); } private static <T> TgResult<T> build(boolean success, String code, String message, T data) { TgResult<T> result = new TgResult<>(); result.setSuccess(success); result.setCode(code); result.setMessage(message); result.setData(data); return result; }}
只要你会定义泛型类和泛型方法,这个代码你就能看懂,只是重载定义了
ok
和fail
两个方法便于外部调用。调用时直接
TgResult.ok
或TgResult.fail
即可!
3.3 各层对象说明
命名规范:
项目做的越大,各层的高内聚
就显的格外重要,职责越单一,代码耦合性就越低!
所以,对于三层(web层、service层、dal层),规范使用各层自定义的对象,对象在各层传输时进行对象转换后使用!
- VO:值对象,在web层使用,对应接口的参数,例如:BookVO
- BO:业务对象,在service层使用,对应web层传入的参数 和 返回给web层的结果 ,例如:BookBO
- PO:持久化对象,在dal层使用,对应数据库的表字段,例如:Book
- DTO:数据传输对象,这里顺带提一下DTO,当我们与外部接口调用时使用。
3.4 各层包结构规范
对于上面代码的各层包结构,都是有讲究的,你发现了吗?
各层的包的前缀都是以项目Maven的groupId+artifactId为根,我们定义的groupId为org.tg
,所以:
tg-book-web:org.tg.book.web
tg-book-service:org.tg.book.service
tg-book-dal:org.tg.book.dal
3.5 Spring IOC
接下来我们说说IOC(Inversion of Control):控制反转
像上面这个bookService,没有new就使用,按正常编码是会报错的,那为什么没报错呢?
是因为它使用了@Autowired
注解,Spring会自动给它new,还有属性赋值等都会由Spring管理,这样我们在调用的地方就不会报错了。
说的更直白点:bookService不由使用它的BookAdminController控制,这样做的好处:如果想替换BookService的实现,不用更改BookAdminController的代码,而是由外部控制,从而达到解耦合的目的!
而相应的在BookService类上也需要加@Service注解,如下图:
常见的组件注解如下:
@Service: 通常放在service层的服务类上
@Repository: 通常放在dal层的数据访问类上
@Controller: 通常放在web层控制器的类上
@Component: 代表通用的组件,从它派生了上面3个注解,用于各个实际的场景.
被加上这些注解的对象,在Spring中称之为Bean
3.6 约定优于配置
我们使用SpringBoot,有必要了解它采用的约定优于配置的思想。
约定优于配置(convention over configuration),也称作按约定编程,是一种软件设计范式。
白话说就是:约定好的就不用配置了!
比如,当我们导入一个 spring-boot-starter-web
后,SpringBoot就知道你要开发API,所以就会自动帮我们引入Spring MVC
的相关依赖和内置的 Tomcat
容器,所以,你就很方便,它很懂你对不对?当然,你不想按约定的来的话,也可以通过配置来修改。
SpringBoot约定以starter
的形式减少依赖,于是相继推出了很多常用的 starter,就像本文我们用到的spring-boot-starter-web
,所以你看到,我们只添加了一个依赖,就可以实现API的定义。
四、支持跨域
下面咱们说说跨域
默认我们通过SpringBoot提供的API,由于浏览器的同源策略
:前端和后端不同源
就会报跨域的CORS
错误。
支持跨域有很多方式,因为很多实现会涉及到很多知识,所以这里不扩展说明,我先使用最简单的方式处理!
在BookAdminController
上增加@CrossOrigin
注解,像这样:
这样,BookAdminController下的所有API就都支持跨域请求了,是不是又简单又容易理解?
五、打包
再说说打包
如何打包多jar包的springboot项目?
可以使用Maven build插件:spring-boot-maven-plugin
,通过它可以打成一个jar包。
在启动类所在的项目tg-book-web
的pom.xml
中,添加build
节点,配置如下:
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>2.3.12.RELEASE</version> <configuration> <executable>true</executable> </configuration> <executions> <execution> <goals> <goal>repackage</goal> </goals> </execution> </executions> </plugin> </plugins> <finalName>tg-book</finalName></build>
这里我们最终会打成一个jar,即通过finalName指定的tg-book.jar
具体操作:
① 对父tg-book
,Maven先clean
,再install
② 对启动类所在的项目tg-book-web
,Maven执行package
最终生成的 tg-book.jar 位置在\tg-book\tg-book-web\target\tg-book.jar
。
最后
通过本文,我们最终输出给前端两个API:分别基于GET和POST 请求。
但还有很多遗留点,像日志、统一异常处理、参数校验、权限认证等等,不要担心没有相关内容,这篇已经很长,我会按照思维导图在后面的博文都讲到,当然,如果你有你的需求,也可以联系我来增加需求!
最后,我希望大家能跟着本文去实现这两个API,并可以举一反三,按照本文的设计实现更多的API,这样,这个项目的后端是不是可以不仅是我一个人?
如果觉得写的不错,订阅起来吧,后面还有更多干货输出… ,让我们一起拉开有趣的程序人生~~~
另外,别忘了关注天哥:天罡gg ,发布新文不容易错过: https://blog.csdn.net/scm_2008
有没有觉得干货太多?
能不能跟上节奏?
请投票告诉我!