Spring Boot 搭建實(shí)際項(xiàng)目開發(fā)中的腳手架
本來已收錄到我寫的10萬字Springboot經(jīng)典學(xué)習(xí)筆記中,筆記在持續(xù)更新……文末有領(lǐng)取方式
前面的文章中,我主要給大家講解了 Spring Boot 中常用的一些技術(shù)點(diǎn),這些技術(shù)點(diǎn)在實(shí)際項(xiàng)目中可能不會全部用得到,因?yàn)椴煌捻?xiàng)目可能使用的技術(shù)不同,但是希望大家都能掌握如何使用,并能自己根據(jù)實(shí)際項(xiàng)目中的需求進(jìn)行相應(yīng)的擴(kuò)展。
不知道大家了不了解單片機(jī),單片機(jī)里有個最小系統(tǒng),這個最小系統(tǒng)搭建好了之后,就可以在此基礎(chǔ)上進(jìn)行人為的擴(kuò)展。這節(jié)課我們要做的就是搭建一個 “Spring Boot 最小系統(tǒng)架構(gòu)” 。拿著這個架構(gòu),可以在此基礎(chǔ)上根據(jù)實(shí)際需求做相應(yīng)的擴(kuò)展。
從零開始搭建一個環(huán)境,主要要考慮幾點(diǎn):統(tǒng)一封裝的數(shù)據(jù)結(jié)構(gòu)、可調(diào)式的接口、json的處理、模板引擎的使用(本文不寫該項(xiàng),因?yàn)楝F(xiàn)在大部分項(xiàng)目都前后端分離了,但是考慮到也還有非前后端分離的項(xiàng)目,所以我在源代碼里也加上了 thymeleaf)、持久層的集成、攔截器(這個也是可選的)和全局異常處理。一般包括這些東西的話,基本上一個 Spring Boot 項(xiàng)目環(huán)境就差不多了,然后就是根據(jù)具體情況來擴(kuò)展了。
結(jié)合前面的課程和以上的這些點(diǎn),本文手把手帶領(lǐng)大家搭建一個實(shí)際項(xiàng)目開發(fā)中可用的 Spring Boot 架構(gòu)。整個項(xiàng)目工程如下圖所示,學(xué)習(xí)的時候,可以結(jié)合我的源碼,這樣效果會更好。

1. 統(tǒng)一的數(shù)據(jù)封裝
由于封裝的 json 數(shù)據(jù)的類型不確定,所以在定義統(tǒng)一的 json 結(jié)構(gòu)時,我們需要用到泛型。統(tǒng)一的 json 結(jié)構(gòu)中屬性包括數(shù)據(jù)、狀態(tài)碼、提示信息即可,構(gòu)造方法可以根據(jù)實(shí)際業(yè)務(wù)需求做相應(yīng)的添加即可,一般來說,應(yīng)該有默認(rèn)的返回結(jié)構(gòu),也應(yīng)該有用戶指定的返回結(jié)構(gòu)。如下:
/**
* 統(tǒng)一返回對象
* @author shengwu ni
* @param <T>
*/
public class JsonResult<T> {
private T data;
private String code;
private String msg;
/**
* 若沒有數(shù)據(jù)返回,默認(rèn)狀態(tài)碼為0,提示信息為:操作成功!
*/
public JsonResult() {
this.code = "0";
this.msg = "操作成功!";
}
/**
* 若沒有數(shù)據(jù)返回,可以人為指定狀態(tài)碼和提示信息
* @param code
* @param msg
*/
public JsonResult(String code, String msg) {
this.code = code;
this.msg = msg;
}
/**
* 有數(shù)據(jù)返回時,狀態(tài)碼為0,默認(rèn)提示信息為:操作成功!
* @param data
*/
public JsonResult(T data) {
this.data = data;
this.code = "0";
this.msg = "操作成功!";
}
/**
* 有數(shù)據(jù)返回,狀態(tài)碼為0,人為指定提示信息
* @param data
* @param msg
*/
public JsonResult(T data, String msg) {
this.data = data;
this.code = "0";
this.msg = msg;
}
/**
* 使用自定義異常作為參數(shù)傳遞狀態(tài)碼和提示信息
* @param msgEnum
*/
public JsonResult(BusinessMsgEnum msgEnum) {
this.code = msgEnum.code();
this.msg = msgEnum.msg();
}
// 省去get和set方法
}
大家可以根據(jù)自己項(xiàng)目中所需要的一些東西,合理的修改統(tǒng)一結(jié)構(gòu)中的字段信息。
2. json的處理
Json 處理工具很多,比如阿里巴巴的 fastjson,不過 fastjson 對有些未知類型的 null 無法轉(zhuǎn)成空字符串,這可能是 fastjson 自身的缺陷,可擴(kuò)展性也不是太好,但是使用起來方便,使用的人也蠻多的。這節(jié)課里面我們主要集成 Spring Boot 自帶的 jackson。主要是對 jackson 做一下對 null 的配置即可,然后就可以在項(xiàng)目中使用了。
/**
* 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<Object>() {
@Override
public void serialize(Object o, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
jsonGenerator.writeString("");
}
});
return objectMapper;
}
}
這里先不測試,等下面 swagger2 配置好了之后,我們一起來測試一下。
3. swagger2在線可調(diào)式接口
有了 swagger,開發(fā)人員不需要給其他人員提供接口文檔,只要告訴他們一個 Swagger 地址,即可展示在線的 API 接口文檔,除此之外,調(diào)用接口的人員還可以在線測試接口數(shù)據(jù),同樣地,開發(fā)人員在開發(fā)接口時,同樣也可以利用 Swagger 在線接口文檔測試接口數(shù)據(jù),這給開發(fā)人員提供了便利。使用 swagger 需要對其進(jìn)行配置:
/**
* swagger配置
* @author shengwu ni
*/
@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2)
// 指定構(gòu)建api文檔的詳細(xì)信息的方法:apiInfo()
.apiInfo(apiInfo())
.select()
// 指定要生成api接口的包路徑,這里把controller作為包路徑,生成controller中的所有接口
.apis(RequestHandlerSelectors.basePackage("com.itcodai.course18.controller"))
.paths(PathSelectors.any())
.build();
}
/**
* 構(gòu)建api文檔的詳細(xì)信息
* @return
*/
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
// 設(shè)置頁面標(biāo)題
.title("Spring Boot搭建實(shí)際項(xiàng)目中開發(fā)的架構(gòu)")
// 設(shè)置接口描述
.description("跟武哥一起學(xué)Spring Boot第18課")
// 設(shè)置聯(lián)系方式
.contact("倪升武," + "微信公眾號:程序員私房菜")
// 設(shè)置版本
.version("1.0")
// 構(gòu)建
.build();
}
}
到這里,可以先測試一下,寫一個 Controller,弄一個靜態(tài)的接口測試一下上面集成的內(nèi)容。
@RestController
@Api(value = "用戶信息接口")
public class UserController {
@Resource
private UserService userService;
@GetMapping("/getUser/{id}")
@ApiOperation(value = "根據(jù)用戶唯一標(biāo)識獲取用戶信息")
public JsonResult<User> getUserInfo(@PathVariable @ApiParam(value = "用戶唯一標(biāo)識") Long id) {
User user = new User(id, "倪升武", "123456");
return new JsonResult<>(user);
}
}
然后啟動項(xiàng)目,在瀏覽器中輸入 localhost:8080/swagger-ui.html 即可看到 swagger 接口文檔頁面,調(diào)用一下上面這個接口,即可看到返回的 json 數(shù)據(jù)。
4. 持久層集成
每個項(xiàng)目中是必須要有持久層的,與數(shù)據(jù)庫交互,這里我們主要來集成 mybatis,集成 mybatis 首先要在 application.yml 中進(jìn)行配置。
# 服務(wù)端口號
server:
port: 8080
# 數(shù)據(jù)庫地址
datasource:
url: localhost:3306/blog_test
spring:
datasource: # 數(shù)據(jù)庫配置
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 # 最大連接池數(shù)
max-lifetime: 1770000
mybatis:
# 指定別名設(shè)置的包為所有entity
type-aliases-package: com.itcodai.course18.entity
configuration:
map-underscore-to-camel-case: true # 駝峰命名規(guī)范
mapper-locations: # mapper映射文件位置
- classpath:mapper/*.xml
配置好了之后,接下來我們來寫一下 dao 層,實(shí)際中我們使用注解比較多,因?yàn)楸容^方便,當(dāng)然也可以使用 xml 的方式,甚至兩種同時使用都行,這里我們主要使用注解的方式來集成,關(guān)于 xml 的方式,大家可以查看前面課程,實(shí)際中根據(jù)項(xiàng)目情況來定。
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<User> getAll();
}
關(guān)于 service 層我就不在文章中寫代碼了,大家可以結(jié)合我的源代碼學(xué)習(xí),這一節(jié)主要帶領(lǐng)大家來搭建一個 Spring Boot 空架構(gòu)。最后別忘了在啟動類上添加注解掃描 @MapperScan("com.itcodai.course18.dao")
5. 攔截器
攔截器在項(xiàng)目中使用的是非常多的(但不是絕對的),比如攔截一些置頂?shù)?url,做一些判斷和處理等等。除此之外,還需要將常用的靜態(tài)頁面或者 swagger 頁面放行,不能將這些靜態(tài)資源給攔截了。首先先自定義一個攔截器。
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("執(zhí)行方法之前執(zhí)行(Controller方法調(diào)用之前)");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
logger.info("執(zhí)行完方法之后進(jìn)執(zhí)行(Controller方法調(diào)用之后),但是此時還沒進(jìn)行視圖渲染");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
logger.info("整個請求都處理完咯,DispatcherServlet也渲染了對應(yīng)的視圖咯,此時我可以做一些清理的工作了");
}
}
然后將自定義的攔截器加入到攔截器配置中。
@Configuration
public class MyInterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 實(shí)現(xiàn)WebMvcConfigurer不會導(dǎo)致靜態(tài)資源被攔截
registry.addInterceptor(new MyInterceptor())
// 攔截所有url
.addPathPatterns("/**")
// 放行swagger
.excludePathPatterns("/swagger-resources/**");
}
}
在 Spring Boot 中,我們通常會在如下目錄里存放一些靜態(tài)資源:
classpath:/static
classpath:/public
classpath:/resources
classpath:/META-INF/resources
上面代碼中配置的 /** 是對所有 url 都進(jìn)行了攔截,但我們實(shí)現(xiàn)了 WebMvcConfigurer 接口,不會導(dǎo)致 Spring Boot 對上面這些目錄下的靜態(tài)資源實(shí)施攔截。但是我們平時訪問的 swagger 會被攔截,所以要將其放行。swagger 頁面在 swagger-resources 目錄下,放行該目錄下所有文件即可。
然后在瀏覽器中輸入一下 swagger 頁面,若能正常顯示 swagger,說明放行成功。同時可以根據(jù)后臺打印的日志判斷代碼執(zhí)行的順序。
6. 全局異常處理
全局異常處理是每個項(xiàng)目中必須用到的東西,在具體的異常中,我們可能會做具體的處理,但是對于沒有處理的異常,一般會有一個統(tǒng)一的全局異常處理。在異常處理之前,最好維護(hù)一個異常提示信息枚舉類,專門用來保存異常提示信息的。如下:
public enum BusinessMsgEnum {
/** 參數(shù)異常 */
PARMETER_EXCEPTION("102", "參數(shù)異常!"),
/** 等待超時 */
SERVICE_TIME_OUT("103", "服務(wù)調(diào)用超時!"),
/** 參數(shù)過大 */
PARMETER_BIG_EXCEPTION("102", "輸入的圖片數(shù)量不能超過50張!"),
/** 500 : 發(fā)生異常 */
UNEXPECTED_EXCEPTION("500", "系統(tǒng)發(fā)生異常,請聯(lián)系管理員!");
/**
* 消息碼
*/
private String code;
/**
* 消息內(nèi)容
*/
private String msg;
private BusinessMsgEnum(String code, String msg) {
this.code = code;
this.msg = msg;
}
public String code() {
return code;
}
public String msg() {
return msg;
}
}
在全局統(tǒng)一異常處理類中,我們一般會對自定義的業(yè)務(wù)異常最先處理,然后去處理一些常見的系統(tǒng)異常,最后會來一個一勞永逸(Exception 異常)。
@ControllerAdvice
@ResponseBody
public class GlobalExceptionHandler {
private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
/**
* 攔截業(yè)務(wù)異常,返回業(yè)務(wù)異常信息
* @param ex
* @return
*/
@ExceptionHandler(BusinessErrorException.class)
@ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
public JsonResult handleBusinessError(BusinessErrorException ex) {
String code = ex.getCode();
String message = ex.getMessage();
return new JsonResult(code, message);
}
/**
* 空指針異常
* @param ex NullPointerException
* @return
*/
@ExceptionHandler(NullPointerException.class)
@ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
public JsonResult handleTypeMismatchException(NullPointerException ex) {
logger.error("空指針異常,{}", ex.getMessage());
return new JsonResult("500", "空指針異常了");
}
/**
* 系統(tǒng)異常 預(yù)期以外異常
* @param ex
* @return
*/
@ExceptionHandler(Exception.class)
@ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
public JsonResult handleUnexpectedServer(Exception ex) {
logger.error("系統(tǒng)異常:", ex);
return new JsonResult(BusinessMsgEnum.UNEXPECTED_EXCEPTION);
}
}
其中,BusinessErrorException 是自定義的業(yè)務(wù)異常,繼承一下 RuntimeException 即可,具體可以看我的源代碼,文章中就不貼代碼了。在 UserController 中有個 testException 方法,用來測試全局異常的,打開 swagger 頁面,調(diào)用一下該接口,可以看出返回用戶提示信息:”系統(tǒng)發(fā)生異常,請聯(lián)系管理員!“。當(dāng)然了,實(shí)際情況中,需要根據(jù)不同的業(yè)務(wù)提示不同的信息。
7. 總結(jié)
本文主要手把手帶領(lǐng)大家快速搭建一個項(xiàng)目中可以使用的 Spring Boot 空架構(gòu),主要從統(tǒng)一封裝的數(shù)據(jù)結(jié)構(gòu)、可調(diào)式的接口、json的處理、模板引擎的使用(代碼中體現(xiàn))、持久層的集成、攔截器和全局異常處理。一般包括這些東西的話,基本上一個 Spring Boot 項(xiàng)目環(huán)境就差不多了,然后就是根據(jù)具體情況來擴(kuò)展了。
該文已收錄到我寫的《10萬字Springboot經(jīng)典學(xué)習(xí)筆記》中,點(diǎn)擊下面小卡片,進(jìn)入【武哥聊編程】,回復(fù):筆記,即可免費(fèi)獲取。
點(diǎn)贊是最大的支持

