对于后端开发人员来说,干过最多的事无非就是增删改查,没事修修BUG,重构下代码,看似简单的工作,其实做好也不简单,这都是后话了,后面会讲到;
如果你还在用Spring+SpringMVC+Hibernate/Mybatis这一套框架的话,我建议你换SpringBoot用一段时间试试,如果限于公司技术栈的约束,可以私底下做个知识扩展,因为这家伙开发Web项目真是太快、太便捷了,一堆的Starter包,引进来即可用,无需繁琐的Maven配置,使用的时候简单的注解一加,所需的Bean就注进IOC容器了,切实的达到了开箱即用的效果;
IOC(Inversion of Control 控制反转,又称DI -- 依赖注入)并不是一项技术,而是一种设计思想,其是Spring框架的核心之一,Spring框架通过IOC容器管理Bean的生命周期,而IOC容器也不仅仅只由Spring这一项开源技术所独有,千万不要以为市面上只有Spring一家独大,只不过用的人多了,感觉好像一提起做Web项目,就自动联想到Spring框架了;说直白点,还是好用;
SpringBoot是基于Spring框架的扩展(由Pivotal团队提供的全新框架),它并不是完全的独立于Spring体系的,毕竟也是Spring字辈的,它的存在,是为了简化Spring框架中WebApp应用程序的搭建和配置,其内置Tomcat、Jetty等容器,无需配置,只需Run一下Main函数即可实现项目的启动,其核心思想就是:约定大于配置,一切自动完成!
所有的技术文字描述都是苍白且无力的,不结合实际开发编码的话,是很难被消化和记忆犹新的,因此,博主结合切身的项目开发经验,做了一下总结,花了一天半的时间(工作中挤出的时间)搭建了一个通用的Web后端脚手架,之所以称作是脚手架,是因为,整个项目从最基本的POM依赖、数据源配置、结果分页、模块划分、异常处理、包的命名和层级上等都做了大量的细致工作,里面也不缺乏一些实用代码的编写,可以说,用它作为一个简单的增删改查web项目模板来说,是再合适不过的了;
你可以通过博文末尾提供的GitHub项目的仓库地址,clone到本地或者fork到自己的仓库中,细细品味一下,如果有不对或者不懂的地方,欢迎留言一起沟通,如果你觉得本项目值得参考,或者在你实际的项目或产品开发中帮助很大的话,我的目的就到达了!
说明:
1、典型的父子Maven工程
2、根据不同功能细分模块
3、sample-core:核心组件,为整个项目提供所需的业务模型(内存对象)、统一规范接口(定义通用数据操作方法),抽离出共性的业务模型对象的字段属性(提取基类或定义抽象类)、编写通用的帮助/工具/字典/注解/常量类等(如果想细分的话,可以再分出一个common模块来存在这些通用的东西)
4、sample-dao:数据持久层组件,为整个项目提供所需的数据实体类、其使用tk.mybatis构建通用mapper接口,通过使用单表操作的example条件实例对象或者sql注解的形式或者编写原生态的sql语句(基于xml文件配置sql语句)来实现数据的增删改查
5、sample-service:业务处理组件,为整个项目提供所需的数据操作接口的实现,其将核心组件中的业务模型转化为数据实体模型(业务模型 映射 数据实体,即内存对象转换为持久层的数据对象),并调用dao层组件中对应的mapper实现数据的增删改查,必要的增删改查规则都放在service模块中完成,一些字段和参数的校验可放在Controller层进行统一拦截和处理
6、sample-web:Web应用组件,为整个项目提供所需的配置(配置多环境、配置数据源、配置拦截器、配置全局异常处理等)及接口的请求控制转发,该组件提供main入口,启动后,通过注解@SpringBootApplication自动完成Bean的装配,并对外暴露API接口地址,一些测试单元也可以放在该组件中进行
SpringBoot 2.1.4.RELEASE + 通用MyBatis Mapper 4.0.4 + PageHelper-Starter 1.2.10
数据库支持:PostgreSql + MySql(通过多环境配置实现数据源的切换)
数据库连接池:使用2.0自带的Hikari(高性能、并发,号称最快,是不是有待考证)
其中JackSon、PostgreSql/MySql驱动包的Pom依赖版本不是最新的就是使用比较多的
Maven 仓库地址:http://mvnrepository.com/
面向对象三大技术的体现:抽象、继承、多态
(1)如抽象出一个服务类,用来注册子类的实例
(2)如定义统一的数据管理接口,实现接口的多态
(3)如定义基类,抽离出对象共有的的Id和Name属性,不管你是什么,总归是对象吧,继承基类就对了
(1)统一定义请求接口的地址映射
如定义业务系统接口请求的根地址、如常用的create、update、query等,如上传下载download等,都是可以固定下来的,没必要每个controller都要写一遍,除非你controller的接口请求地址定义的很随性,否则,建议你还是统一用一个地址映射常量类来保存这些MappingValue吧
(2)统一定义Http请求响应结果(body体中的内容)
我们可以使用Spring框架中自带的package org.springframework.http.ResponseEntity类的实例来实现controller中接口响应结果的统一返回
如果感觉它不够用的话(body对象还是统一封装一下比较好),我们最好还是自定义一套body体的结果类。
status状态码并不是http的状态码,而是我们业务系统内部自定义的码值;
message字段多用于接收系统中业务处理或者数据持久化过程中发生的异常或者错误的消息,一般都是我们可以预知和捕获的,如果是系统异常的话,会有统一的异常拦截器进行处理;
调用方最关心的就是请求的数据了,data字段是一个object类型的,它泛指Java所有的类型,什么集合(List,Set,Map...)、字符串、整型啊..etc,都可以"填充"它;
当然,如果我们能把响应的时间也返回给调用者,会不会看起来让人更加舒服呢?timeStamp字段就是干这件事的,唯一注意的一个点就是Date类型的字段如何进行String类型的正反序列化,一个@JsonFormat即可搞定,别忘了规定下日期的格式和选择一个时区。
(3)定义枚举类型时,别忘了JSON正反序列化的事
标准的定义枚举类,其字段就两个,一个code,一个name,你可以理解为code是key,name是value(别忘了加一些注释,养成好的习惯),如果要实现类中枚举对象的JSON正反序列化,就要考虑是根据枚举对象的code(码)反啊还是根据name(值)反啊,这个要看个人习惯、需求或者前后端的约定,反正,二者只能选一个
(4)能封装的方法一定要用一个类封装起来,如分页结果的包装
写代码这件事,绝对不是能交差就够了!!! 如果你觉得你写出来的代码没有BUG就很牛逼了,那我只能断定,你肯定是刚毕业没多久的coder;代码首先要保证功能,其次要优雅、精简与巧妙;牛逼点或者有经验的开发者二者是兼备的,如果只有前者没有后者,你开发出来的代码,别人将难以进行解读和维护;或者你写的代码一团糟,虽然功能有,但是逻辑很绕,调试起来都费老大劲了,谁还愿意和你"合作"啊;但如果你既能完成功能,又能将代码当做是一项艺术来写的话,相信我,不仅领导喜欢你,你身边的同事也会对你投来膜拜的小眼神;
说这么多就一句话:看到重复的或让人难受的代码块,一定要记得提取、封装、精简,养成好的习惯!
(5)如果想自己定义异常类,自产自销,那就继承Exception类吧,干!
(6)对于业务系统来说,少不了异常(自定义异常和系统异常),有了异常,结果该怎么返回? 不如定义一个异常拦截类吧!
(7)小结一下吧
啰嗦一下,写代码这件事虽然跟经验的多少有关系,但还真不是鉴定代码和个人能力好坏的唯一标准,主要还是要勤奋和开窍外加你真的喜欢技术,热爱写代码,把代码当回事,话又说回来,我们不可能一直都干写代码的工作吧,人总得往前看、往上走,代码写好了,就该考虑转型了,比如博主我,现在就在带领一个小团队,虽然大部分的研发工作是在组员之间完成的,但是框架的搭建、代码和功能的审查以及接口文档我是一项也不能落下的,做不到最好,但是要要求自己做到比之前更好!
(1)目录树
一棵树只有一个根(root),但是有多个枝干(文件夹),每个枝干可以再生枝干(文件夹下放文件夹)或者只有叶子(文件夹下放文件),注意叶子不能再分支了,因为其是一棵树的最小不可分对象
如果想实现一个基于数据库的用户组的管理,我们可以通过创建目录/目录节点构建一棵用户组的目录树如下:
其中PostgreSql用户组是这课树的根节点,其parentid(父目录)和rootid(根目录)均为0
其中普通、管理员、超级管理员用户组是目录(文件夹),他们的parentid和rootid均是PostgreSql用户组这个目录对象的id
其中appleyk是一个用户对象,其是这棵树的一个叶子节点,业务模型中我定义为目录节点,注意目录和节点对象是分开来的
业务模型这里我就不分析了,我们来看一下数据库是怎么存这棵树的
首先是目录表:
然后是目录节点表
注意,目录节点关联的不仅仅有目录(通过catalogid关联),还有一个对象,这个对象是真实存在于业务系统中的,比如超级管理员appleyk是在user表中存在的:
那么,后台接口是如何展开目录的呢,我们看下目录查询接口的测试结果,以查询超级管理员用户组这个目录为准:
我们换一个目录ID查一下,看一下,根目录下面是不是只有三个子目录:
基于目录树接口的调用,前端可以构建出任意一棵炫酷的业务目录树,而且框架中的目录树业务模型和数据实体类可作为通用的目录树组件来使用,不过框架中的例子只是一个参考,主要是为了说明如何快速的搭框架、分模块,如何基于模块的划分写代码,如何构建利于团队协同开发的标准代码模板...etc
(2)用户令牌生成和验证
JSON Web Token (JWT)是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于作为JSON对象在各方之间安全地传输信息。该信息可以被验证和信任,因为它是数字签名的。
具体JWT的应用场景和结构可以参考:认知JWT
测试单元走一波,效果如下:
用法如下:
1> 对应的请求方法上加Token注解
2> 看一下拦截器是如何拦截带有GToken注解的方法的
3> 走进UserHelper类,我们揭秘下,如何使用自定义的Bean实例管理容器(其实还是借助了Spring的手)来实现token的验证
4> 上面看不懂? 什么,先别急,我们再看一下ServiceContainer是个什么鬼
5> 一脸懵逼? AbstractService又是个什么东西呢,哪冒出来的啊?,我们再来看一下
6> 循序渐进,到目前为止,我们还没有看到,checkToken这个方法究竟是在哪定义的?
7> 我们看一下AbstractService这个抽象类实现了ITokenManager接口的什么方法
8> 是不是很简单? 写代码的最高境界就是随意穿插于抽象类和接口之间,熟练应用继承和实现,另外说一句,Java可不能多继承的,只能多实现,还要啰嗦一句,Java 23种设计模式中,大量用到了抽象类和接口,所以想要写一手实用又好看的代码,面向对象的思想一定要根深蒂固才行!
9> 我们看下是哪个倒霉孩子(绕来绕去,谁来实例化AbstractService)具体实现了checkToken的功能
不看不知道,一看吓一跳?还能这么玩吗? 又是继承又是实现的,没错,就是这么"酷",这就叫分身术,User业务处理不仅要实现用户的增删改查(UserService接口继承了数据管理接口IDataManager),还要实现用户token的验证;
注意,User服务的实现类上一定要加@Service注解,告诉Spring,这是一个Bean,你启动的时候,记得实例化一下它;一旦实例化,首先IOC容器拿到了这个Bean,其次会调用抽象类AbstractService的构造器,将User接口实现类的实例放进全局服务实例容器中(实现自主可控),这样我们就可以通过调用类的静态方法取出对应的服务实例,从而调用服务实例的指定方法了,而不需要再手动注入(@Autowired)bean的实例了。
再来回顾一下,这种方式是如何实现checkToken方法的调用的:
10> 说了这么多,你倒是给我看看这种方式能否调通呢? Ok,测试走一下
首先Postman调用相应接口,并在headers中,带上token参数
然后我们去后台看一下,看下是否进了checkToken方法
(3)小结一下
如果上面你完全没有心思看下来,没关系,文末提供的有整个项目的代码,不仅有代码,还有Postman测试的JSON用例,建议有时间的同学,可以好好看看,学习和交流一下,当然项目中还是有很多亮点的,虽然不是什么复杂的web应用,但基本上是可以很快的搭建出一个平时项目开发中所需要的数据持久层框架的。
(4)再插一句
如果自己开发组件,需要被别人引用,那自己组件里面的bean实例是怎么完成自动化装配的呢? 两种方式
A:把bean所在的包告诉需要引入它的web应用,在web应用中,通过手动添加注解完成bean的扫描,如下:
B:在组件模块里的resources资源文件夹下建一个META-INF文件夹,里面配置一个spring.factories文件:
其内容如下(多个配置包可以用逗号隔开,但最好是定义一个主配置Bean的类,由这个主的配置类发起子Bean的扫描):
org.springframework.boot.autoconfigure.EnableAutoConfiguration=xxx.xxx.你的bean所在的包
这种方式,就是现在很多starter包所干的事,也就是你引入一个starter包,基本上不用配置,拿来就可以用的原因所在,因为包里已经帮你自动完成了Bean实例的注入。
(1)本地、正式、测试
多环境的好处不用说吧,如果一个项目或者一个产品只有一个正式环境的话,那....嗯....,数据岂不是很乱,测试人员用正式环境测试项目刚刚发布的版本是否存在bug,开发人员也在用正式环境迭代产品功能、用户呢,啊....用户就比较郁闷了
"这都什么垃圾数据啊?"
"我去,他们的网站该不会被黑了吧?"
"卧槽,我昨天辛辛苦苦创建的任务工序怎么没了,是谁给我删了!!!!?"...
当然,你也可以在项目中配置一个application.properties,这样的话,无非就是累点,麻烦点,但是记住,有捷径不走,除非你有会飞的能力!
(2)正式环境配置数据源连接参数 : PostgreSql
#hikari
spring.datasource.hikari.driver-class-name=org.postgresql.Driver
spring.datasource.hikari.jdbc-url=jdbc:postgresql://localhost:5432/my_db_test?stringtype=unspecified
spring.datasource.hikari.username=postgres
spring.datasource.hikari.password=postgres
spring.datasource.type=com.zaxxer.hikari.HikariDataSource
spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.maximum-pool-size=15
spring.datasource.hikari.auto-commit=true
spring.datasource.hikari.idle-timeout=30000
spring.datasource.hikari.pool-name=DatebookHikariCP
spring.datasource.hikari.max-lifetime=1800000
spring.datasource.hikari.connection-timeout=30000
spring.datasource.hikari.connection-test-query=SELECT 1
(3)测试环境配置数据源连接参数 :MySql
#hikari
spring.datasource.hikari.driver-class-name=com.mysql.cj.jdbc.Driver
#数据源url后面需要指定默认的系统时间参考,否则会报错
spring.datasource.hikari.jdbc-url=jdbc\:mysql\://localhost\:3306/my_db_test?useUnicode\=true&autoReconnect=true&useSSL=false&characterEncoding\=utf-8&useSSL=true&allowMultiQueries=true&serverTimezone=UTC
spring.datasource.hikari.username=root
spring.datasource.hikari.password=root
spring.datasource.type=com.zaxxer.hikari.HikariDataSource
spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.maximum-pool-size=15
spring.datasource.hikari.auto-commit=true
spring.datasource.hikari.idle-timeout=30000
spring.datasource.hikari.pool-name=DatebookHikariCP
spring.datasource.hikari.max-lifetime=1800000
spring.datasource.hikari.connection-timeout=30000
spring.datasource.hikari.connection-test-query=SELECT 1
(4)数据源注入配置,扫描Mapper包,关联Mapper对应的xml文件
注入主数据源、sql会话工厂、事务管理、sql会话模板Bean实例,扫描mapper包并指定引用的sql会话模板
注意,数据源配置这部分的代码是通用的
package com.appleyk.config;
import com.zaxxer.hikari.HikariDataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import tk.mybatis.spring.annotation.MapperScan;
import javax.sql.DataSource;
/**
* 数据源配置 -- 支持多数据源配置,本项目中只配置一个主数据源
*
* @author appleyk
* @version V.1.0.0
* @blob https://blog.csdn.net/appleyk
* @date created on 上午 10:32 2019-4-27
*/
@EnableTransactionManagement
@MapperScan(basePackages = "com.appleyk.dao.mapper", sqlSessionTemplateRef = "sqlSessionTemplate")
@Configuration
public class DataSourceConfig {
@Primary
@Bean(name = "masterDatasource")
@ConfigurationProperties(prefix = "spring.datasource.hikari") //需要导入配置
public HikariDataSource dataSource(){
return DataSourceBuilder.create().type(HikariDataSource.class).build();
}
@Bean(name = "sqlSessionFactory")
public SqlSessionFactory sqlSessionFactory(@Qualifier("masterDatasource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:mapper/*/*.xml"));
return bean.getObject();
}
@Bean(name = "transactionManager")
public DataSourceTransactionManager transactionManager(@Qualifier("masterDatasource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
@Bean(name = "sqlSessionTemplate")
public SqlSessionTemplate sqlSessionTemplate(@Qualifier("sqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
(1)PostgreSql
关系型数据库,支持空间查询,默认端口号:5432,开源
具体可参考:https://baike.baidu.com/item/PostgreSQL/530240?fr=aladdin
(2)MySql
关系型数据库,默认端口号:5432
(3)数据库建表语句及自增序列的SQL脚本存放在项目中的静态资源文件夹下
(1)例子中总过涉及四个Controller
(2)主要测下Hello接口
A:自定义通用异常CommonException拦截测试结果:
B:自定义异常MyException拦截测试结果:
需要基于SpringBoot2.0+快速搭建一个数据持久层框架的话,建议将项目clone下来,作为参考,如果想在项目原有的模块上直接进行开发的话,建议将不用的类或者代码移除掉,以免对你造成干扰!
框架比较简陋,想要高深莫测的功能,请自行扩展,该引包引包,该添加模块添加模块,该吃吃,该睡睡....