從零搭建SpringBoot+MyBatisPlus快速開(kāi)發(fā)腳手架
前言

關(guān)注我Github的小伙伴應(yīng)該了解,之前我開(kāi)源了一款快速開(kāi)發(fā)腳手架mall-tiny,該腳手架繼承了mall項(xiàng)目的技術(shù)棧,擁有完整的權(quán)限管理功能。最近抽空把該項(xiàng)目支持了Spring Boot 2.7.0,今天再和大家聊聊這個(gè)腳手架,同時(shí)聊聊升級(jí)項(xiàng)目到Spring Boot 2.7.0的一些注意點(diǎn),希望對(duì)大家有所幫助!
SpringBoot實(shí)戰(zhàn)電商項(xiàng)目mall(50k+star)地址:https://github.com/macrozheng/mall
聊聊mall-tiny項(xiàng)目
可能有些小伙伴還不了解這個(gè)腳手架,我們先來(lái)聊聊它!
項(xiàng)目簡(jiǎn)介
mall-tiny是一款基于SpringBoot+MyBatis-Plus的快速開(kāi)發(fā)腳手架,目前在Github上已有1100+Star。它擁有完整的權(quán)限管理功能,支持使用MyBatis-Plus代碼生成器生成代碼,可對(duì)接mall項(xiàng)目的Vue前端,開(kāi)箱即用。

項(xiàng)目地址:https://github.com/macrozheng/mall-tiny
項(xiàng)目演示
mall-tiny項(xiàng)目可無(wú)縫對(duì)接mall-admin-web前端項(xiàng)目,秒變前后端分離腳手架,由于mall-tiny項(xiàng)目?jī)H實(shí)現(xiàn)了基礎(chǔ)的權(quán)限管理功能,所以前端對(duì)接后只會(huì)展示權(quán)限管理相關(guān)功能。

前端項(xiàng)目地址:https://github.com/macrozheng/mall-admin-web
技術(shù)選型
這次升級(jí)不僅支持了Spring Boot 2.7.0,其他依賴(lài)版本也升級(jí)到了最新版本。
| 技術(shù) | 版本 | 說(shuō)明 |
|---|---|---|
| SpringBoot | 2.7.0 | 容器+MVC框架 |
| SpringSecurity | 5.7.1 | 認(rèn)證和授權(quán)框架 |
| MyBatis | 3.5.9 | ORM框架 |
| MyBatis-Plus | 3.5.1 | MyBatis增強(qiáng)工具 |
| MyBatis-Plus Generator | 3.5.1 | 數(shù)據(jù)層代碼生成器 |
| Swagger-UI | 3.0.0 | 文檔生產(chǎn)工具 |
| Redis | 5.0 | 分布式緩存 |
| Docker | 18.09.0 | 應(yīng)用容器引擎 |
| Druid | 1.2.9 | 數(shù)據(jù)庫(kù)連接池 |
| Hutool | 5.8.0 | Java工具類(lèi)庫(kù) |
| JWT | 0.9.1 | JWT登錄支持 |
| Lombok | 1.18.24 | 簡(jiǎn)化對(duì)象封裝工具 |
數(shù)據(jù)庫(kù)表結(jié)構(gòu)
化繁為簡(jiǎn),僅保留了權(quán)限管理功能相關(guān)的9張表,業(yè)務(wù)簡(jiǎn)單更加方便定制開(kāi)發(fā),覺(jué)得mall項(xiàng)目學(xué)習(xí)太復(fù)雜的小伙伴可以先學(xué)習(xí)下mall-tiny。

接口文檔
由于升級(jí)了Swagger版本,原來(lái)的接口文檔訪(fǎng)問(wèn)路徑已經(jīng)改變,最新訪(fǎng)問(wèn)路徑:http://localhost:8080/swagger-ui/

使用流程
升級(jí)版本基本不影響之前的使用方式,具體使用流程可以參考最新版README文件:
https://github.com/macrozheng/mall-tiny

升級(jí)過(guò)程
接下來(lái)我們?cè)賮?lái)聊聊項(xiàng)目升級(jí)Spring Boot 2.7.0版本遇到的問(wèn)題,這些應(yīng)該是升級(jí)該版本的通用問(wèn)題,你如果想升級(jí)2.7.0版本的話(huà),了解下會(huì)很有幫助!
Swagger升級(jí)
- 在升級(jí)Spring Boot 2.6.x版本的時(shí)候,其實(shí)Swagger就有一定的兼容性問(wèn)題,需要在配置中添加BeanPostProcessor這個(gè)Bean,具體可以參考升級(jí) SpringBoot 2.6.x 版本后,Swagger 沒(méi)法用了! ;
/**
* Swagger API文檔相關(guān)配置
* Created by macro on 2018/4/26.
*/
@Configuration
@EnableSwagger2
public class SwaggerConfig extends BaseSwaggerConfig {
@Bean
public static BeanPostProcessor springfoxHandlerProviderBeanPostProcessor() {
return new BeanPostProcessor() {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof WebMvcRequestHandlerProvider || bean instanceof WebFluxRequestHandlerProvider) {
customizeSpringfoxHandlerMappings(getHandlerMappings(bean));
}
return bean;
}
private <T extends RequestMappingInfoHandlerMapping> void customizeSpringfoxHandlerMappings(List<T> mappings) {
List<T> copy = mappings.stream()
.filter(mapping -> mapping.getPatternParser() == null)
.collect(Collectors.toList());
mappings.clear();
mappings.addAll(copy);
}
@SuppressWarnings("unchecked")
private List<RequestMappingInfoHandlerMapping> getHandlerMappings(Object bean) {
try {
Field field = ReflectionUtils.findField(bean.getClass(), "handlerMappings");
field.setAccessible(true);
return (List<RequestMappingInfoHandlerMapping>) field.get(bean);
} catch (IllegalArgumentException | IllegalAccessException e) {
throw new IllegalStateException(e);
}
}
};
}
}
- 之前我們通過(guò)@Api注解的description屬性來(lái)配置接口描述的方法已經(jīng)被棄用了;

- 我們可以使用@Tag注解來(lái)配置接口說(shuō)明,并使用@Api注解中的tags屬性來(lái)指定。

Spring Security升級(jí)
升級(jí)Spring Boot 2.7.0版本后,原來(lái)通過(guò)繼承WebSecurityConfigurerAdapter來(lái)配置的方法已經(jīng)被棄用了,僅需配置SecurityFilterChainBean即可,具體參考Spring Security最新用法。
/**
* SpringSecurity 5.4.x以上新用法配置
* 為避免循環(huán)依賴(lài),僅用于配置HttpSecurity
* Created by macro on 2019/11/5.
*/
@Configuration
public class SecurityConfig {
@Autowired
private IgnoreUrlsConfig ignoreUrlsConfig;
@Autowired
private RestfulAccessDeniedHandler restfulAccessDeniedHandler;
@Autowired
private RestAuthenticationEntryPoint restAuthenticationEntryPoint;
@Autowired
private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
@Autowired
private DynamicSecurityService dynamicSecurityService;
@Autowired
private DynamicSecurityFilter dynamicSecurityFilter;
@Bean
SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = httpSecurity
.authorizeRequests();
//不需要保護(hù)的資源路徑允許訪(fǎng)問(wèn)
for (String url : ignoreUrlsConfig.getUrls()) {
registry.antMatchers(url).permitAll();
}
//允許跨域請(qǐng)求的OPTIONS請(qǐng)求
registry.antMatchers(HttpMethod.OPTIONS)
.permitAll();
// 任何請(qǐng)求需要身份認(rèn)證
registry.and()
.authorizeRequests()
.anyRequest()
.authenticated()
// 關(guān)閉跨站請(qǐng)求防護(hù)及不使用session
.and()
.csrf()
.disable()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
// 自定義權(quán)限拒絕處理類(lèi)
.and()
.exceptionHandling()
.accessDeniedHandler(restfulAccessDeniedHandler)
.authenticationEntryPoint(restAuthenticationEntryPoint)
// 自定義權(quán)限攔截器JWT過(guò)濾器
.and()
.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
//有動(dòng)態(tài)權(quán)限配置時(shí)添加動(dòng)態(tài)權(quán)限校驗(yàn)過(guò)濾器
if(dynamicSecurityService!=null){
registry.and().addFilterBefore(dynamicSecurityFilter, FilterSecurityInterceptor.class);
}
return httpSecurity.build();
}
}
MyBatis-Plus升級(jí)
MyBatis-Plus從之前的版本升級(jí)到了3.5.1版本,用法沒(méi)有大的改變,感覺(jué)最大的區(qū)別就是代碼生成器的用法改了。 在之前的用法中我們是通過(guò)new對(duì)象然后set各種屬性來(lái)配置的,具體參考如下代碼:
/**
* MyBatisPlus代碼生成器
* Created by macro on 2020/8/20.
*/
public class MyBatisPlusGenerator {
/**
* 初始化全局配置
*/
private static GlobalConfig initGlobalConfig(String projectPath) {
GlobalConfig globalConfig = new GlobalConfig();
globalConfig.setOutputDir(projectPath + "/src/main/java");
globalConfig.setAuthor("macro");
globalConfig.setOpen(false);
globalConfig.setSwagger2(true);
globalConfig.setBaseResultMap(true);
globalConfig.setFileOverride(true);
globalConfig.setDateType(DateType.ONLY_DATE);
globalConfig.setEntityName("%s");
globalConfig.setMapperName("%sMapper");
globalConfig.setXmlName("%sMapper");
globalConfig.setServiceName("%sService");
globalConfig.setServiceImplName("%sServiceImpl");
globalConfig.setControllerName("%sController");
return globalConfig;
}
}
而新版的MyBatis-Plus代碼生成器已經(jīng)改成使用建造者模式來(lái)配置了,具體可以參考MyBatisPlusGenerator類(lèi)中的代碼。
/**
* MyBatisPlus代碼生成器
* Created by macro on 2020/8/20.
*/
public class MyBatisPlusGenerator {
/**
* 初始化全局配置
*/
private static GlobalConfig initGlobalConfig(String projectPath) {
return new GlobalConfig.Builder()
.outputDir(projectPath + "/src/main/java")
.author("macro")
.disableOpenDir()
.enableSwagger()
.fileOverride()
.dateType(DateType.ONLY_DATE)
.build();
}
}
解決循環(huán)依賴(lài)問(wèn)題
- 其實(shí)Spring Boot從2.6.x版本已經(jīng)開(kāi)始不推薦使用循環(huán)依賴(lài)了,如果你的項(xiàng)目中使用的循環(huán)依賴(lài)比較多的話(huà),可以使用如下配置開(kāi)啟;
spring:
main:
allow-circular-references: true
- 不過(guò)既然官方都不推薦使用了,我們最好還是避免循環(huán)依賴(lài)的好,這里分享下我解決循環(huán)依賴(lài)問(wèn)題的一點(diǎn)思路。如果一個(gè)類(lèi)里有多個(gè)依賴(lài)項(xiàng),這個(gè)類(lèi)非必要的Bean就不要配置了,可以使用單獨(dú)的類(lèi)來(lái)配置Bean。比如SecurityConfig這個(gè)配置類(lèi)中,我只聲明了必要的SecurityFilterChain配置;
/**
* SpringSecurity 5.4.x以上新用法配置
* 為避免循環(huán)依賴(lài),僅用于配置HttpSecurity
* Created by macro on 2019/11/5.
*/
@Configuration
public class SecurityConfig {
@Autowired
private IgnoreUrlsConfig ignoreUrlsConfig;
@Autowired
private RestfulAccessDeniedHandler restfulAccessDeniedHandler;
@Autowired
private RestAuthenticationEntryPoint restAuthenticationEntryPoint;
@Autowired
private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
@Autowired
private DynamicSecurityService dynamicSecurityService;
@Autowired
private DynamicSecurityFilter dynamicSecurityFilter;
@Bean
SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
//省略若干代碼...
return httpSecurity.build();
}
}
- 其他配置都被我移動(dòng)到了CommonSecurityConfig配置類(lèi)中,這樣就避免了之前的循環(huán)依賴(lài);
/**
* SpringSecurity通用配置
* 包括通用Bean、Security通用Bean及動(dòng)態(tài)權(quán)限通用Bean
* Created by macro on 2022/5/20.
*/
@Configuration
public class CommonSecurityConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public IgnoreUrlsConfig ignoreUrlsConfig() {
return new IgnoreUrlsConfig();
}
@Bean
public JwtTokenUtil jwtTokenUtil() {
return new JwtTokenUtil();
}
@Bean
public RestfulAccessDeniedHandler restfulAccessDeniedHandler() {
return new RestfulAccessDeniedHandler();
}
@Bean
public RestAuthenticationEntryPoint restAuthenticationEntryPoint() {
return new RestAuthenticationEntryPoint();
}
@Bean
public JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter(){
return new JwtAuthenticationTokenFilter();
}
@Bean
public DynamicAccessDecisionManager dynamicAccessDecisionManager() {
return new DynamicAccessDecisionManager();
}
@Bean
public DynamicSecurityMetadataSource dynamicSecurityMetadataSource() {
return new DynamicSecurityMetadataSource();
}
@Bean
public DynamicSecurityFilter dynamicSecurityFilter(){
return new DynamicSecurityFilter();
}
}
- 還有一個(gè)典型的循環(huán)依賴(lài)問(wèn)題,UmsAdminServiceImpl和UmsAdminCacheServiceImpl相互依賴(lài)了;
/**
* 后臺(tái)管理員管理Service實(shí)現(xiàn)類(lèi)
* Created by macro on 2018/4/26.
*/
@Service
public class UmsAdminServiceImpl extends ServiceImpl<UmsAdminMapper,UmsAdmin> implements UmsAdminService {
@Autowired
private UmsAdminCacheService adminCacheService;
}
/**
* 后臺(tái)用戶(hù)緩存管理Service實(shí)現(xiàn)類(lèi)
* Created by macro on 2020/3/13.
*/
@Service
public class UmsAdminCacheServiceImpl implements UmsAdminCacheService {
@Autowired
private UmsAdminService adminService;
}
- 我們可以創(chuàng)建一個(gè)用于獲取Spring容器中的Bean的工具類(lèi)來(lái)實(shí)現(xiàn);
/**
* Spring工具類(lèi)
* Created by macro on 2020/3/3.
*/
@Component
public class SpringUtil implements ApplicationContextAware {
private static ApplicationContext applicationContext;
// 獲取applicationContext
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
if (SpringUtil.applicationContext == null) {
SpringUtil.applicationContext = applicationContext;
}
}
// 通過(guò)name獲取Bean
public static Object getBean(String name) {
return getApplicationContext().getBean(name);
}
// 通過(guò)class獲取Bean
public static <T> T getBean(Class<T> clazz) {
return getApplicationContext().getBean(clazz);
}
// 通過(guò)name,以及Clazz返回指定的Bean
public static <T> T getBean(String name, Class<T> clazz) {
return getApplicationContext().getBean(name, clazz);
}
}
- 然后在UmsAdminServiceImpl中使用該工具類(lèi)獲取Bean來(lái)解決循環(huán)依賴(lài)。
/**
* 后臺(tái)管理員管理Service實(shí)現(xiàn)類(lèi)
* Created by macro on 2018/4/26.
*/
@Service
public class UmsAdminServiceImpl extends ServiceImpl<UmsAdminMapper,UmsAdmin> implements UmsAdminService {
@Override
public UmsAdminCacheService getCacheService() {
return SpringUtil.getBean(UmsAdminCacheService.class);
}
}
解決跨域問(wèn)題
在使用Spring Boot 2.7.0版本時(shí),如果不修改之前的跨域配置,通過(guò)前端訪(fǎng)問(wèn)會(huì)出現(xiàn)跨域問(wèn)題,后端報(bào)錯(cuò)如下。
java.lang.IllegalArgumentException: When allowCredentials is true, allowedOrigins cannot contain the special value "*" since that cannot be set on the "Access-Control-Allow-Origin" response header.
To allow credentials to a set of origins, list them explicitly or consider using "allowedOriginPatterns" instead.
具體的意思就是allowedOrigins已經(jīng)不再支持通配符*的配置了,改為需要使用allowedOriginPatterns來(lái)設(shè)置,具體配置修改如下。
/**
* 全局跨域配置
* Created by macro on 2019/7/27.
*/
@Configuration
public class GlobalCorsConfig {
/**
* 允許跨域調(diào)用的過(guò)濾器
*/
@Bean
public CorsFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
//允許所有域名進(jìn)行跨域調(diào)用
config.addAllowedOriginPattern("*");
//該用法在SpringBoot 2.7.0中已不再支持
//config.addAllowedOrigin("*");
//允許跨越發(fā)送cookie
config.setAllowCredentials(true);
//放行全部原始頭信息
config.addAllowedHeader("*");
//允許所有請(qǐng)求方法跨域調(diào)用
config.addAllowedMethod("*");
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
}
總結(jié)
今天分享了下我的開(kāi)源項(xiàng)目腳手架mall-tiny,以及它升級(jí)SpringBoot 2.7.0的過(guò)程。我們?cè)趯?xiě)代碼的時(shí)候,如果有些用法已經(jīng)廢棄,應(yīng)該盡量去尋找新的用法來(lái)使用,這樣才能保證我們的代碼足夠優(yōu)雅!
項(xiàng)目地址 https://github.com/macrozheng/mall-tiny
以上就是從零搭建SpringBoot+MyBatisPlus快速開(kāi)發(fā)腳手架的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot+MyBatisPlus從零搭建的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java中多線(xiàn)程同步類(lèi) CountDownLatch
本篇文章主要介紹了Java中多線(xiàn)程同步類(lèi) CountDownLatch的相關(guān)知識(shí),具有很好的參考價(jià)值。下面跟著小編一起來(lái)看下吧2017-05-05
Java獲取當(dāng)?shù)氐娜粘鋈章鋾r(shí)間代碼分享
這篇文章主要介紹了Java獲取當(dāng)?shù)氐娜粘鋈章鋾r(shí)間代碼分享,國(guó)外猿友寫(xiě)的一個(gè)類(lèi),需要的朋友可以參考下2014-06-06
Java如何使用正則表達(dá)式從字符串中提取數(shù)字
這篇文章主要介紹了Java如何使用正則表達(dá)式從字符串中提取數(shù)字問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-12-12
一文帶你掌握J(rèn)ava?ReentrantLock加解鎖原理
這篇文章將為大家詳細(xì)介紹一下Java中ReentrantLock?加鎖和釋放鎖的原理,以及和?Synchronized?的對(duì)比。文中的示例代碼講解詳細(xì),希望對(duì)大家有所幫助2022-12-12
詳解java開(kāi)發(fā)webservice的幾種方式
webservice的應(yīng)用已經(jīng)越來(lái)越廣泛了,下面介紹幾種在Java體系中開(kāi)發(fā)webservice的方式,有興趣的可以了解一下。2016-11-11
淺談一下RabbitMQ、Kafka和RocketMQ消息中間件對(duì)比
這篇文章主要介紹了淺談一下RabbitMQ、Kafka和RocketMQ消息中間件對(duì)比,消息中間件屬于分布式系統(tǒng)中一個(gè)字系統(tǒng),關(guān)注于數(shù)據(jù)的發(fā)送和接收,利用高效可靠的異步信息傳遞機(jī)制對(duì)分布式系統(tǒng)中的其余各個(gè)子系統(tǒng)進(jìn)行集成,需要的朋友可以參考下2023-05-05
java導(dǎo)出大批量(百萬(wàn)以上)數(shù)據(jù)的excel文件
這篇文章主要為大家詳細(xì) 介紹了java導(dǎo)出大批量即百萬(wàn)以上數(shù)據(jù)的excel文件,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-04-04
Java業(yè)務(wù)校驗(yàn)工具實(shí)現(xiàn)方法
這篇文章主要介紹了Java業(yè)務(wù)校驗(yàn)工具實(shí)現(xiàn)方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-06-06

