本来已收录到我写的
10万字Springboot经典学习笔记
中,笔记在持续更新……文末有领取方式
前面的文章中,我主要给大家讲解了 Spring Boot 中常用的一些技术点,这些技术点在实际项目中可能不会全部用得到,因为不同的项目可能使用的技术不同,但是希望大家都能掌握如何使用,并能自己根据实际项目中的需求进行相应的扩展。
不知道大家了不了解单片机,单片机里有个最小系统,这个最小系统搭建好了之后,就可以在此基础上进行人为的扩展。这节课我们要做的就是搭建一个 “Spring Boot 最小系统架构” 。拿着这个架构,可以在此基础上根据实际需求做相应的扩展。
从零开始搭建一个环境,主要要考虑几点:统一封装的数据结构、可调式的接口、json的处理、模板引擎的使用(本文不写该项,因为现在大部分项目都前后端分离了,但是考虑到也还有非前后端分离的项目,所以我在源代码里也加上了 thymeleaf)、持久层的集成、拦截器(这个也是可选的)和全局异常处理。一般包括这些东西的话,基本上一个 Spring Boot 项目环境就差不多了,然后就是根据具体情况来扩展了。
结合前面的课程和以上的这些点,本文手把手带领大家搭建一个实际项目开发中可用的 Spring Boot 架构。整个项目工程如下图所示,学习的时候,可以结合我的源码,这样效果会更好。
工程架构
1. 统一的数据封装
由于封装的 json 数据的类型不确定,所以在定义统一的 json 结构时,我们需要用到泛型。统一的 json 结构中属性包括数据、状态码、提示信息即可,构造方法可以根据实际业务需求做相应的添加即可,一般来说,应该有默认的返回结构,也应该有用户指定的返回结构。如下:
/** * 统一返回对象 * @author shengwu ni * @param */ public class JsonResult <T > { private T data; private String code; private String msg; /** * 若没有数据返回,默认状态码为0,提示信息为:操作成功! */ public JsonResult () { this .code = "0" ; this .msg = "操作成功!" ; } /** * 若没有数据返回,可以人为指定状态码和提示信息 * @param code * @param msg */ public JsonResult (String code, String msg) { this .code = code; this .msg = msg; } /** * 有数据返回时,状态码为0,默认提示信息为:操作成功! * @param data */ public JsonResult (T data) { this .data = data; this .code = "0" ; this .msg = "操作成功!" ; } /** * 有数据返回,状态码为0,人为指定提示信息 * @param data * @param msg */ public JsonResult (T data, String msg) { this .data = data; this .code = "0" ; this .msg = msg; } /** * 使用自定义异常作为参数传递状态码和提示信息 * @param msgEnum */ public JsonResult (BusinessMsgEnum msgEnum) { this .code = msgEnum.code(); this .msg = msgEnum.msg(); } // 省去get和set方法 }
大家可以根据自己项目中所需要的一些东西,合理的修改统一结构中的字段信息。
2. json的处理
Json 处理工具很多,比如阿里巴巴的 fastjson,不过 fastjson 对有些未知类型的 null 无法转成空字符串,这可能是 fastjson 自身的缺陷,可扩展性也不是太好,但是使用起来方便,使用的人也蛮多的。这节课里面我们主要集成 Spring Boot 自带的 jackson。主要是对 jackson 做一下对 null 的配置即可,然后就可以在项目中使用了。
/** * jacksonConfig * @author shengwu ni */ @Configuration public class JacksonConfig { @Bean @Primary @ConditionalOnMissingBean (ObjectMapper.class ) public ObjectMapper jacksonObjectMapper (Jackson2ObjectMapperBuilder builder ) { ObjectMapper objectMapper = builder.createXmlMapper(false ).build(); objectMapper.getSerializerProvider().setNullValueSerializer(new JsonSerializer() { @Override public void serialize (Object o, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { jsonGenerator.writeString("" ); } }); return objectMapper; } }
这里先不测试,等下面 swagger2 配置好了之后,我们一起来测试一下。
3. swagger2在线可调式接口
有了 swagger,开发人员不需要给其他人员提供接口文档,只要告诉他们一个 Swagger 地址,即可展示在线的 API 接口文档,除此之外,调用接口的人员还可以在线测试接口数据,同样地,开发人员在开发接口时,同样也可以利用 Swagger 在线接口文档测试接口数据,这给开发人员提供了便利。使用 swagger 需要对其进行配置:
/** * swagger配置 * @author shengwu ni */ @Configuration @EnableSwagger 2public class SwaggerConfig { @Bean public Docket createRestApi () { return new Docket(DocumentationType.SWAGGER_2) // 指定构建api文档的详细信息的方法:apiInfo() .apiInfo(apiInfo()) .select() // 指定要生成api接口的包路径,这里把controller作为包路径,生成controller中的所有接口 .apis(RequestHandlerSelectors.basePackage("com.itcodai.course18.controller" )) .paths(PathSelectors.any()) .build(); } /** * 构建api文档的详细信息 * @return */ private ApiInfo apiInfo () { return new ApiInfoBuilder() // 设置页面标题 .title("Spring Boot搭建实际项目中开发的架构" ) // 设置接口描述 .description("跟武哥一起学Spring Boot第18课" ) // 设置联系方式 .contact("倪升武," + "微信公众号:程序员私房菜" ) // 设置版本 .version("1.0" ) // 构建 .build(); } }
到这里,可以先测试一下,写一个 Controller,弄一个静态的接口测试一下上面集成的内容。
@RestController @Api (value = "用户信息接口" )public class UserController { @Resource private UserService userService; @GetMapping ("/getUser/{id}" ) @ApiOperation (value = "根据用户唯一标识获取用户信息" ) public JsonResult getUserInfo (@PathVariable @ApiParam(value = "用户唯一标识" ) Long id) { User user = new User(id, "倪升武" , "123456" ); return new JsonResult<>(user); } }
然后启动项目,在浏览器中输入
localhost:8080/swagger-ui.html
即可看到 swagger 接口文档页面,调用一下上面这个接口,即可看到返回的 json 数据。
4. 持久层集成
每个项目中是必须要有持久层的,与数据库交互,这里我们主要来集成 mybatis,集成 mybatis 首先要在 application.yml 中进行配置。
# 服务端口号 server: port: 8080 # 数据库地址 datasource: url: localhost:3306/blog_test spring: datasource: # 数据库配置 driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://${datasource.url}?useSSL=false&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&autoReconnect=true&failOverReadOnly=false&maxReconnects=10 username: root password: 123456 hikari: maximum-pool-size: 10 # 最大连接池数 max-lifetime: 1770000 mybatis: # 指定别名设置的包为所有entity type-aliases-package: com.itcodai.course18.entity configuration: map-underscore-to-camel-case: true # 驼峰命名规范 mapper-locations: # mapper映射文件位置 - classpath:mapper/*.xml
配置好了之后,接下来我们来写一下 dao 层,实际中我们使用注解比较多,因为比较方便,当然也可以使用 xml 的方式,甚至两种同时使用都行,这里我们主要使用注解的方式来集成,关于 xml 的方式,大家可以查看前面课程,实际中根据项目情况来定。
public interface UserMapper { @Select ("select * from user where id = #{id}" ) @Results ({ @Result (property = "username" , column = "user_name" ), @Result (property = "password" , column = "password" ) }) User getUser (Long id) ; @Select ("select * from user where id = #{id} and user_name=#{name}" ) User getUserByIdAndName (@Param("id" ) Long id, @Param ("name" ) String username) ; @Select ("select * from user" ) List getAll () ; }
关于 service 层我就不在文章中写代码了,大家可以结合我的源代码学习,这一节主要带领大家来搭建一个 Spring Boot 空架构。最后别忘了在启动类上添加注解扫描
@MapperScan("com.itcodai.course18.dao")
5. 拦截器
拦截器在项目中使用的是非常多的(但不是绝对的),比如拦截一些置顶的 url,做一些判断和处理等等。除此之外,还需要将常用的静态页面或者 swagger 页面放行,不能将这些静态资源给拦截了。首先先自定义一个拦截器。
public class MyInterceptor implements HandlerInterceptor { private static final Logger logger = LoggerFactory.getLogger(MyInterceptor.class ) ; @Override public boolean preHandle (HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { logger.info("执行方法之前执行(Controller方法调用之前)" ); return true ; } @Override public void postHandle (HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { logger.info("执行完方法之后进执行(Controller方法调用之后),但是此时还没进行视图渲染" ); } @Override public void afterCompletion (HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { logger.info("整个请求都处理完咯,DispatcherServlet也渲染了对应的视图咯,此时我可以做一些清理的工作了" ); } }
然后将自定义的拦截器加入到拦截器配置中。
@Configuration public class MyInterceptorConfig implements WebMvcConfigurer { @Override public void addInterceptors (InterceptorRegistry registry) { // 实现WebMvcConfigurer不会导致静态资源被拦截 registry.addInterceptor(new MyInterceptor()) // 拦截所有url .addPathPatterns("/**" ) // 放行swagger .excludePathPatterns("/swagger-resources/**" ); } }
在 Spring Boot 中,我们通常会在如下目录里存放一些静态资源:
classpath:/static
classpath:/public
classpath:/resources
classpath:/META-INF/resources
上面代码中配置的
/**
是对所有 url 都进行了拦截,但我们实现了 WebMvcConfigurer 接口,不会导致 Spring Boot 对上面这些目录下的静态资源实施拦截。但是我们平时访问的 swagger 会被拦截,所以要将其放行。swagger 页面在 swagger-resources 目录下,放行该目录下所有文件即可。
然后在浏览器中输入一下 swagger 页面,若能正常显示 swagger,说明放行成功。同时可以根据后台打印的日志判断代码执行的顺序。
6. 全局异常处理
全局异常处理是每个项目中必须用到的东西,在具体的异常中,我们可能会做具体的处理,但是对于没有处理的异常,一般会有一个统一的全局异常处理。在异常处理之前,最好维护一个异常提示信息枚举类,专门用来保存异常提示信息的。如下: