前言
首先在这里恭祝大家新年快乐,兔年大吉。本来是想在年前发布这篇博文的,奈何过年期间走街串巷,实在无心学术,所以不得不放在近日写下这篇Spring Boot的博文。在还没开始写之前,我已经预见到,这恐怕将是我从业以来写过最长的博文了。前一篇Java开发 - Mybatis框架初体验2.7w的字数我觉得已经是最长了,但在整理Spring Boot的知识点时我才知道,是我小瞧了它,所以在这里先给大家做个预告,有个心理准备。为什么不拆分成几段来写呢?技术这东西,最好是一蹴而就,一次学完,好有个整体的框架感。敢拆分成三五篇,要是好几天才能看完,前面的东西也就忘了,来来回回看又浪费时间,不如花几个小时,一次学完来的痛快。再根据目录去针对性的学习不熟悉的内容,此为最佳,这也是博主平时学习的一个方式,个人觉得很高效。废话不多说,接下来,就跟着博主一起来学习Spring Boot吧。
Spring Boot是什么
什么是Spring Boot
对于这个问题,不用多说,先上百度词条:Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置。通过这种方式,Spring Boot致力于在蓬勃发展的快速应用开发领域(rapid application development)成为领导者。
Spring Boot的特点
SpringBoot基于Spring4.0设计,不仅继承了Spring框架原有的优秀特性,而且还通过简化配置来进一步简化了Spring应用的整个搭建和开发过程。另外SpringBoot通过集成大量的框架使得依赖包的版本冲突,以及引用的不稳定性等问题得到了很好的解决。
SpringBoot所具备的特征有:
(1)可以创建独立的Spring应用程序,并且基于其Maven或Gradle插件,可以创建可执行的JARs和WARs;
(2)内嵌Tomcat或Jetty等Servlet容器;
(3)提供自动配置的“starter”项目对象模型(POMS)以简化Maven配置;
(4)尽可能自动配置Spring容器;
(5)提供准备好的特性,如指标、健康检查和外部化配置;
(6)绝对没有代码生成,不需要XML配置。
Spring Boot工程
创建Spring Boot工程
创建Spring Boot项目时,我们选择Spring Initializr,注意Group和Artifact,这两个值直接决定了Package name,有些特殊字符在这里体现不一,比如‘-’将会直接消失。
我们看到Java处也圈了起来,这个学Java的同学都知道,高版本可能会存在一些意想不到的问题,我们还是以项目的稳定性为主,所以选择低版本使用。
下一步之后会看到此界面:
Spring Boot的版本我们先不管,在项目内进行统一修改,因为官方更新的会频率比较高,所以我们一般不会选择太高的版本,下面的依赖,我们在学习的时候先不勾选,在项目中统一手动添加。
在此图中,pom.xml文件内修改Spring Boot的版本为2.5.9,这个版本博主目前使用中没遇到问题。修改完后记得右上角的刷新按钮刷新一下,这个在SSM框架中我们已经比较熟悉了。
前面我们看到Spring Boot项目创建过程中是可以直接勾选依赖的,这就很方便了,这在我们先前的SSM框架的项目中是没有的,在大家熟悉之后,可以根据自己的需要在这里直接勾选需要的依赖。
Spring Boot工程结构
Spring Boot创建时我们看到有Maven字样,所以其本质上也是一个Maven工程,所以和我们的SSM框架的项目区别并不大。展开文件目录,发现还是有些不一样的。
可以看到src/main/java和src/test/java下默认都已存在指定的package,且此package是这个工程中的执行组件扫描的根包,所以,后续创建的所有的类和包都需要位于此包中。
在src/main/java下的此包中,默认已经创建了启动类,启动类默认添加了@SpringBootApplication
注解,此注解的元注解中包含@SpringBootConfiguration
,而@SpringBootConfiguration
的元注解中包含@Configuration
,所以,启动类本身也是配置类!将允许将@Bean
方法写在此类中,或者某些与配置相关的注解也可以添加在此类上!但为了项目整体的整洁和统一管理,我们并不会将大量的代码塞入启动类,这都是后话了。
在src/test/java下的此包中,默认存在测试类,其类名是在启动类的基础上命名的,我们就认为这是给启动类创建的测试类,在后续创建测试类时也要遵守这一规则,事不大,但有助于帮助我们区分类名。测试类一般不会是public权限,这也是创建时默认的,所以其内的方法也遵从这一原则。此测试类上添加了@SpringBootTest
注解,其元注解中包含@ExtendWith(SpringExtension.class)
,与使用spring-test
时的@SpringJUnitTest
注解中的元注解相同,所以,@SpringBootTest
注解也会使得当前测试类在执行测试方法之前是加载了Spring环境的,在实际编写测试时,可以通过自动装配得到任何已存在于Spring容器中的对象,在各测试方法中只需要关注被测试的目标即可。这一点就很好,帮我们整合了Spring框架,免去了手动添加的麻烦,甚至还有可能导致错误。
spring-boot-starter
和spring-boot-starter-test是Spring Boot的基础依赖项和测试依赖项。
src\main\resources
文件夹下默认就已经存在application.properties
文件,在SSM中我们用于编写配置,在Spring Boot项目中,则会自动读取此文件,这主要是依赖了@PropertySource
注解,相较于SSM框架要手动获取方便了很多。
还有一个比较重要的东西就是:创建项目时勾选依赖项选中了Web
项,在src\main\resources
下默认就已经创建了static
和templates
文件夹,如果没有勾选Web
则没有这2个文件夹,则需要自行创建。但在我们目前的市场下,前后端分离是主流,所以这个可有可无,可能有些地方会使用这个功能来创建一些错误页面,比如我们常见的404。
其实到这里为止,Spring Boot项目可以说已经给大家介绍完了,但Spring Boot的应用还远没有结束,接下来就是Spring Boot中常用的一些依赖和配置讲解。前面的比较简单,后面的大家就要打起精神认真看了。
Spring Boot怎么连接数据库
连接数据库我们在SSM框架中学过,是通过Mybatis框架来实现了,所以在这里,我们要引入Mybatis框架,是不是很神奇?SSM框架和Spring Boot不分家。
要引入Mybatis框架,我们需要添加两个依赖项:
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope></dependency><dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.2.2</version></dependency>
这里有两个问题需要注意:
Spring Boot下依赖项的版本不需要显示指定,这是因为父项目将对这些依赖项的版本进行统一管理,若有必要,可通过上面的方式<version>来指定版本号如果你注意到<scope>中的runtime,那我正好说一下,它的意思是此依赖项是在项目运行过程中需要使用的,在编译期并不参与编译。到这里 ,有些同学比较着急,会去运行项目,但此时,项目还是不能够跑起来的,虽然数据库依赖已经添加完毕,但是数据库的信息还没有配置,将无法连接到数据库。这些配置信息我们先前都是写在application.properties中的,不要急,我们继续往下看。
数据库配置信息和原来相比很相似,但却有明显的区别,Spring Boot对属性名有严格的要求,符合要求,则自动进行读取,并创建数据源对象,否则无法读取,我们下面来看看怎么写吧:
# 连接数据库的URLspring.datasource.url=jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai# 登录数据库的用户名spring.datasource.username=root# 登录数据库的密码spring.datasource.password=123456
此处密码,大家要写成自己的数据库密码,否则将无法连接数据库。这里还有个坑,虽然配置写完了,但是无法验证配置的正确与否,因为Spring Boot只是加载配置,并不会实际连接数据库,所以,即使配置错误,我们也无从得知。
为了验证配置的正确性,这里我们将采用测试类来进行测试,我们就在系统默认的测试类中进行测试,我们在Mybatis中创建的数据库为mybatis,所以这里就以此数据库为例,查看完整代码:
package com.codingfire.springboot;import org.junit.jupiter.api.Test;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;import javax.sql.DataSource;@SpringBootTestclass SpringbootApplicationTests { @Autowired DataSource dataSource; @Test void testGetConnection() throws Exception { System.out.println(dataSource.getConnection()); }}
运行测试方法后,查看输出如下:
很好,控制台已经输出了dataSource连接对象,说明数据库连接成功,否则,连接数据库失败。这里测试时,要格外注意几点:
数据库一定是已经存在的数据库数据库密码一定不要写错,要写成自己的数据库密码,不要写成博主的数据库服务一定要启动以上缺一不可,否则将无法测试成功。
Profile配置文件
在移动端我们对这种配置叫做多target环境,是为了区分测试环境,开发环境,生产环境三个环境,因为不同的环境所对应的域名是不一样的,为了方便测试,而不是每次都手动修改,这种配置就出现了。
在Java中也存在这样的配置问题,比如数据库密码,在Spring Boot下,对Profile配置也是支持的。我们可以通过在resources下创建多个配置文件的方式来进行配置,创建的配置文件大概命名如下:
application-test.properties 测试还款
application-dev.properties 开发环境
application-prod.properties 生产环境
创建完成后,Spring Boot并不会主动使用这些文件,还需要我们来手动在application.properties文件中指定需要运行的环境配置,通过如下方式:
# 激活Profile配置spring.profiles.active=dev
此时 application.properties文件中只有这一句配置即可,原来的url等信息都需要放在dev所对应的配置文件中。接着我们重新运行上面的测试代码,发现运行成功,依然可以获取到配置中所包含的数据库信息。
如需使用test配置,则改为test,抑或是prod。这在实际开发中还是很有用的,早年间大家对这种配置使用的还不多,现在如果谁项目中还不是这么使用的,那一定会遭到嘲笑了,话不多说,我们进入下一个内容。
YAML配置
yaml配置听起来费解,其实就是将后缀名.properties更换为.yml,这种方式目前使用的也比较多。YAML配置原来并不是Spring系列框架内置的配置语法,如果在项目中需要使用这种语法进行配置,解析这类文件需要添加相关依赖,而Spring Boot默认已添加。
.properties配置中使用的是以.隔开的名字,如:
spring.profiles.active=dev
各部分用.连接,在YAML配置下,则有很大的区别,以换行+缩紧2个空格的形式,换行前使用冒号表示值类型,属性名和值之间使用冒号+一个空格的形式进行连接,两者缺一不可,否则都会导致报错。
YAML配置还有个有点,在.properties文件中,我们会看到重复的前缀,在YAML中则不会出现重复的前缀,否则会报错。
说了这么多,可能代价并不明白实际要怎么写,下面带大家一起来写一下,然后再结合文字看一遍,就懂了,这种方式我们应该都在其他地方见过,并不会十分陌生。
properties文件中我们这么写:
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghaispring.datasource.username=root
yml文件中我们这么写:
spring: datasource: url: jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai username: root
怎么知道自己写的对不对呢?其实很好分辨,如果写的不对,颜色就不会变,看下图:
用户名和密码颜色不一致,那是因为密码没有在冒号后面加空格,这点格外注意。
还有个小快捷方式,缩进两个空格我们可以使用tab键,IDE会将其转换为两个字符。虽然YAML配置和properties写起来不太一样,但并不影响项目本身,在Spring Boot眼里,他们最终的展现方式并无任何不同。
数据库连接池
在Mybatis中,我们使用的数据库连接池如下:
<!--数据库连接池依赖--><dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-dbcp2</artifactId> <version>2.8.0</version></dependency>
在这里,我们要说的不是这个,而是阿里巴巴团队研发的Durid,在Spring Boot项目中,如果需要明确指定使用此连接池,需要在项目中添加此连接池依赖:
<dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.20</version></dependency>
添加玩依赖之后还不行,我们还需要在指定spring.datasource.type属性,在yml文件中表示如下:
spring: datasource: url: jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai username: root password: 123456 type: com.alibaba.druid.pool.DruidDataSourc
常用的配合框架
开发中,我们经常会使用一些配合的工具框架来提高我们的开发效率,在Mybatis中也提到过一些,比如简化setter&getter方法的框架,检查请求参数格式的框架,现在,他们来了。
Lombok
POJO在前文中已经给大家做过说明,它包含了实体类,VO,DTO等,说白了,他们都是数据模型,所以他们也都有相同的数据格式,比如:
setter&getter方法属性私有化实现Serializable接口重写hashCode方法,equals方法,一般我们还会重toString方法由于这些操作非常固定,却占用了较大篇幅的代码,以至于要修改,删除或新增一个属性时比较麻烦,所以这时候Lombok就应运而生了。
Lombok框架可以极大的简化这些操作,可以通过添加注解的方式在编译期生成Setters & Getters、equals()
、hashCode()
、toString()
,甚至生成构造方法等,所以,使用了此框架,开发人员只需要声明属性并添加注解即可,即使如此,Serializable接口也是需要我们自己实现的。
要添加Lombok框架有两种方式,一种是在创建项目时直接勾选:
另一种是通过手动在pom文件中添加依赖的方式,添加格式如下:
<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional></dependency>
完成后,在各POJO类上则不需要再额外添加Setters & Getters、equals()
、hashCode()
、toString()
这些方法,只需在类上添加@Data注解即可。这里有个小问题,为了避免IntelliJ IDEA判断失误而提示了警告和错误,推荐安装Lombok插件,此插件是否安装,都不影响代码的编写和运行 ,安装可查看下图:
我们以用户信息的VO类为例:
package com.codingfire.springboot.pojo.vo;import lombok.Data;import java.io.Serializable;@Datapublic class UserInfoVO implements Serializable { private String username; private String password;}
这就是一个使用Lombok的类,大家可参考使用。
Slf4j
在移动端开发的时候,我们输出数据常用NSLog(),Java里面输出用System.out.println(),但在实际开发中,这两种系统级输出我们都是不用的,这是因为,无论是在开发环境,还是测试环境,还是生产环境中,这些输出语句都将输出相关信息,而删除或添加这些输出语句的操作成本比较高,操作可行性低。虽然这么说,但如果你要是用,还是可以使用的,只是一般我们不建议,甚至有些公司直接禁止这么用。
所以,大家应该猜到Slf4j的作用是什么了,没错,这是一个日志输出框架,但此框架并不需要单独额外添加,当添加了Lombok框架后,Slf4j框架就可以使用了,需要使用时,在类上添加@Slf4j
注解,然后,在类的任意未知,均可使用名为log
的变量,且调用其方法来输出日志。
既然控制了日志的输出,那么日志一定是有优先级的,没错,Slf4j提供了这一能力,从低到高有仙界分别为:
trace:跟踪信息debug:调试信息info:一般信息,通常不涉及关键流程和敏感数据warn:警告信息,通常代码可以运行,但不够完美,或不规范error:错误信息根据开发环境,我们也可以在配置文件中配置不同环境日志的级别,这样就可以控制日志的输出,设置方法如下:
logging.level.cn.codingfire.springboot.xxxxx: info
xxx部分可具体到对应的类名或者包名。
设置完成后,仅显示设置级别高于其级别的日志,此例中设置显示级别为info
,则只显示info
、warn
、error的日志信息
,debug和
trace
级别的日志则不会显示!
关键数据和敏感数据我们通常推荐使用trace或debug级别这样可以方便在开发时直观的看到输出的日志信息,帮助开发者快速定位问题,减少项目交付过程中出现的问题。交付后,这些信息也还是需要的,我们推荐info级别,这样,一些关键性数据的日志还是可以输出的。
忘记说了,日志的默认显示级别为info,即使不设置,info及以上优先级的日志也会自动输出。
在开发实践中,属性名称通常配置为logging.level.项目根包
,例如:
logging.level.cn.codingfire.springboot: trace
各级别对应的方法如下:
public void trace(String format, Object... arguments);public void debug(String format, Object... arguments);public void info(String format, Object... arguments);public void warn(String format, Object... arguments);public void error(String format, Object... arguments);
以上方法第1个参数是将要输出的字符串的模式(模版),在此字符串中,如果需要包含某个变量值,则使用{}
表示,如果有多个变量值,均是如此,然后,再通过第2个参数(是可变参数)依次表示各{}
对应的值,例如:
log.debug("用户名:{},用户昵称:{}", username, nickname);
它的好处是可以避免频繁的拼接字符串,日志框架也会将第一个参数缓存,以提高后续每一次的执行效率。
但是怎么添加日志可能很多同学不清楚,这里做简要说明:
程序可能会抛出异常的时候程序执行一些增删改查的时候一些关键的核心数据改变的时候,做前后对比判断代码执行到的位置的时候在Java中还有其他的一些日志框架,如log4j、logback等,但由于其实现功能并不统一,所以才有了Slf4j的这种标准,Slf4j提供了对主流日志框架的兼容,在Spring Boot工程中,spring-boot-starter
就已经依赖了spring-boot-starter-logging
,而在此依赖下,我们通常可以看到Slf4j,还有一些具体的日志框架,以及Slf4j对具体日志框架的兼容,这也是Spring Boot框架很神奇的一点,包罗万象!
Validation
Validation和Slf4j一样,作用于POJO类,不同的是,Slf4j是简化代码,而Validation的作用是请求时检查属性参数是否符合我们的要求。比如为null,字符串长度是否在规定的长度,其他的格式问题。当检查到这种问题时,可直接响应错误,而不需要直接响应到Service。
需要注意的是,这里指检查格式问题,而不验证参数的正确与否,涉及到这方面的一般都是要和数据库中的数据做比较的,这部分都交由Service来完成,在此基础延伸出了不同的业务分层,这些我们稍后会讲。
要添加Validation依赖,需要在pom文件中引入其依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId></dependency>
我们以刚刚创建的UserInfoVO类为例,来讲讲怎么添加@Validation注解,在此之前,我们要先知道Validation框架有哪些注解:
@NotEmpty
:只能添加在String
类型上,不许为空字符串,例如""
即视为空字符串@NotBlank
:只能添加在String
类型上,不允许为空白,例如普通的空格可视为空白,使用TAB键输入的内容也是空白,换行产生的空白区域也是空白@Size
:限制长短@Min
:限制最小值@Max
:限制最大值@Range
:可以配置min
和max
属性,同时限制最小值和最大值@Pattern
:只能添加在String
类型上,自行指定正则表达式进行验证其它以上注解允许叠加使用,其还有一个属性message,用于指定验证失败的提示信息。这里着重说明几个注解:
对于必须提交的属性,会添加@NotNull
对于数值类型的,需要考虑是否添加@Range
(则不需要使用@Min
和@Max
),比如密码长度对于字符串类型,都添加@Pattern
注解进行验证,比如手机号,身份证等使用此注解分两步进行:
第一步,在模型类上添加限制注解:
package com.codingfire.springboot.pojo.vo;import lombok.Data;import org.hibernate.validator.constraints.Range;import javax.validation.constraints.NotNull;import java.io.Serializable;@Datapublic class UserInfoVO implements Serializable { @NotNull(message = "用户名不能为空") private String username; @NotNull @Range(min = 12,max = 30,message = "密码长度不符合要求,请重新输入") private String password;}
第二步,在控制器中,对需要检查格式的参数添加@Valid
或@Validated
注解(这2个注解没有区别),比如:
@RequestMapping("/add") public JsonResult<Void> addNew(@Validated UserInfoVO userInfoVO) { adminService.addNew(userInfoVO); return JsonResult.ok(); }
如果是明确的某个参数,则只检查此参数,如果添加注解的是模型对象类,则检查此类中所有属性的格式。
记得曾经刚学Java时看后台的代码,明明没有看到后台的返回,但是我提交的参数就是无法通过,问题就是在这里了,注解在移动端是不存在这种用法的,但确实很好用,极大的简化了开发流程,节省了代码,若是不知道这些东西,直接看Java代码,真是一件让人头疼的事情呢。
跨域问题
跨域问题专用来解决部署在不同服务器上的项目,比如我们现在的项目都是前后端分离的,很多时候会分别独立部署在不同的服务器上,这时候要想成功访问,就需要先解决跨域问题。
我们基于前面学过Spring MVC框架来解决这个问题,需要先有一个Spring MVC的配置类,这在前面是有使用过的,这个配置类还需要实现WebMvcConfigurer接口,重写addCorsMappings方法,以允许指定条件的跨域访问,我们来看看这个类里写了什么:
package com.codingfire.springboot.config;import org.springframework.context.annotation.Configuration;import org.springframework.web.servlet.config.annotation.CorsRegistry;import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;@Configurationpublic class SpringMvcConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOriginPatterns("*") .allowedMethods("*") .allowedHeaders("*") .allowCredentials(true) .maxAge(3600); }}
这里博主采用了通配符配置,所以任何类型的请求都可以进入,若是出于安全考虑,可增加前缀来进行使用。最早我记得在客户端里也做过类似的处理针对的也是这几个参数,不过已经记不太清了,大家使用时可按照实际需求再做处理。
这里要说明下,此类需要添加一个依赖:
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>6.0.2</version></dependency>
版本可按照项目需要进行调整。这是Spring MVC的一个依赖,不加此依赖,将会报错。
参数的请求格式
不学Java不知道,一学吓一跳,原来参数的请求格式有两种,真实大跌眼镜,我后面才知道当年为什么和后端的同事在调试接口的时候怎么也成功不了,我说是json,他说是json字符串,在客户端的角度,json字符串是json格式转义成字符串格式,一般都是带很多斜杠的‘/’,但在后端来说,这就是字符串,但我看,却不是json的,不过要说是json格式,好像也有道理,个人理解不同,表达就会不同,导致出现一些问题,耽误了时间。所以前后端都懂是多么的重要。
下面我们来看看这两种格式的差别:
第一种,以&拼接的字符串格式:
//username=codingfire&password=123456let data = 'username=' + this.userInfo.username + '&password=' + this.userInfo.password;
第二种,我认为是标准的json格式,也是我做移动端多年常用的一种格式:
let data = { 'username': this.userInfo.username, // 'codingfire' 'password': this.userInfo.password, // '123456'};
使用哪种方法要看服务端的设计:
如果在处理请求的方法中,在参数前添加@RequestBody
,则允许使用第2种做法(JSON数据),将不允许使用第1种做法(使用&
拼接),否则,两者皆可。所以会发现,第1种做法多出现在前端,第2种做法多出现在客户端,这可能是受限于两种语言的特点,因为还有可能要配合设置header和cookie等参数的原因。
业务分层
在写业务的时候,我们都会对业务进行分层,以解耦整体的功能,达到可能复用的目的,一般分层来说,在客户端,我们常见的就是MVC,MVP,MVVM,具体是什么就不多做解释了,在Java这边并不完全适用,在Java这边,我们经常会大致分围持久层,业务逻辑层,控制器层,可能有一些公司会分的更详细,但大致这三层就够我们用了,下面来说说这几层分别都写些什么,因为他们直接决定了我们项目的结构。
持久层
持久层,我们有时候也叫数据访问层,作用是数据的持久化。为什么要做数据持久化呢?通常认为:正在执行或处理的数据,这些数据都是在内存中的。而内存(RAM)的特征包含“一旦断电,数据将全部丢失”,为了让数据永久保存下来,通常会将数据存储到能够永久存储数据的介质中,通常是计算机的硬盘,硬盘上的数据都是以文件的形式存在的,所以,当需要永久保存数据时,可以将数据存储到文本文件中,或存储到XML文件中,或存储到数据库中,这些保存的做法就是数据持久化,而文本文件、XML文件都不利于实现增删改查中的所有数据访问操作,而数据库是实现增删改查这4种操作都比较便利的一种方式。所以,一般在讨论数据持久化时,默认指的都是使用数据库存储数据。
我们划分这些层就是为了解决不同的问题,持久层解决的就是数据持久化的问题。作用于代码,我们可以认为,持久层就是编写数据库相关的文件或代码。
我们前面使用的是Mybatis框架来处理数据库相关的东西,所以使用Mybatis技术实现持久层编码,有几个注意事项:
使用@MapperScan
指定接口所在的Base Package,配置SQL语句所在的XML文件的位置在接口中添加必要的抽象方法,在XML文件配置抽象方法对应的映射SQL关于具体的操作,此处不再详细写明,忘记的可以前往Mybatis框架初体验在此细品,这里的目的是让大家了解代码的分层和项目的结构。
业务逻辑层
这里要明确一点,业务逻辑层一定是被Controller直接调用的层,因为我们默认Controller不能直接调用持久层,这将会导致一些可怕的后果。
业务逻辑层的目的是为了保证数据的完整性和安全性,让代码按照我们设定的方式执行并产生一些列的变化。
业务逻辑层的代码由接口和实现类组件沟通,其中,接口是必须存在的。所以很多时候推荐采用基于接口的编程方式,有些功能会使用基于接口的代理模式,例如Spring JDBC框架在处理事务时。但接口本身不处理编程逻辑,而是由接口的实现类完成业务逻辑的处理。如果整个过程发生问题,则通过抛出异常的方式来解决。
关于异常,通常我们会自定义异常,并统一处理,自定义异常通常是RuntimeException
的子类,这是因为可以不用显示的抛出或捕获,因为业务逻辑层的异常永远是抛出的,而控制器层会调用业务逻辑层,在控制器层的Controller中其实也是永远抛出异常的,这些异常会通过Spring MVC统一处理异常的机制进行处理,关于异常的整个过程都是固定流程,所以,没有必要显式抛出或捕获。而前面说过的Spring JDBC框架在处理事务时,默认只对RuntimeException
的子孙类进行识别并处理。这就导致我们自定义异常通常是RuntimeException的子类,可以避免漏下异常。
实际编码中,我们需要先规划异常,比如创建一个统一的异常
类,并在类中捕获我们提前规定好的要抛出的异常做统一处理,免去了在不同的类中单独处理异常的情况,此方式我记得在前文中也有说明,大家可回过头去再细看。
控制器层
控制器层很好理解,我们认为控制器里除了接口,不会再有其他的东西了,控制器一般以controller结尾,代表是某某控制器。
Spring MVC是用于处理控制器层开发的,在使用Spring Boot时,在pom.xml
中添加spring-boot-starter-web
即可整合Spring MVC框架及相关的常用依赖项(包含jackson-databind
),可以将已存在的spring-boot-starter
直接改为spring-boot-starter-web
,因为在spring-boot-starter-web
中已经包含了spring-boot-starter
。
使用控制器层,我们需要先在跟包下创建controller子包,并在此包下创建具体的controller,此类应该添加@RestController
和@RequestMapping(value = "/xxxxxx", produces = "application/json; charset=utf-8")
注解,看如下代码格式:
package com.codingfire.springboot.controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;@RestController@RequestMapping(value = "/admins", produces = "application/json; charset=utf-8")public class AdminController { }
服务器响应数据类型一般都是确定的,这也需要我们自定义数据的格式,但必须是json的,此前学习的Spring MVC框架中我们已经学习过相关的部分,通过自定义数据返回类型的方式返回数据:
package cn.codingfire.springmvc.controller;import cn.codingfire.springmvc.vo.UserVO;import cn.codingfire.springmvc.web.JsonResult;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.*;@Controller@RequestMapping(value = "/user", produces = "application/json; charset=utf-8")@ResponseBodypublic class UserController { @GetMapping("/codingFireInfo.page") public JsonResult<UserVO> codingFireInfo() { UserVO userVO = new UserVO(); userVO.setUsername("codingfire"); userVO.setPassword("123456"); JsonResult jsonResult = new JsonResult(); jsonResult.setState(200); jsonResult.setMessage("请求成功"); jsonResult.setData(userVO); return jsonResult; }}
记不清的可以前往查看这篇博客:Spring MVC初体验
项目结构
前面已经介绍了项目分层后各层的作用,那么结合这些,我们就知道了项目整体的结构,其实可以参考Spring MVC那篇的项目结构,看下图:
只是实际开发中vo包我们会单独放在pojo包下,大格局基本不变,只是会调整相应的位置和层级。这个要根据各公司情况去处理,毕竟还有为服务的存在,项目的结构可能还会再变,但万变不离其宗,我们只要知道项目该怎么分层,各层所负责的功能,你基本上就掌握了项目的结构,这也是成为架构师的第一步,或者说,连第一步都算不上,只是个开端。
结语
这篇Spring Boot知识点的博客到这里就算是写完了,我本来预期要写2w+字的,最终还是收了一下,一些代码相关的东西考虑还是放在后面的实战中结合起来说明,但紧写慢写也写了1.5w的字,从过年前开始写,横跨20多天,这篇博客才出来跟大家见面,实在是过年期间有太多的诱惑,容我捂脸笑一会儿。当写到这段话的时候,我内心os:终于写完了。真的,以后再也不想写这么长的博客了,我写着困难,列为看着也愁。但是不写这么长不手把手教,又失去了教程的意义,唉!两难啊。
到这篇为止,前期的知识点部分就先到此为止了,后面是继续知识点结合代码合适增加实战内容我需要再考虑下,希望前面的这几部分入门的东西大家喜欢,觉得不错就点个赞再走吧!