關注我Github的小夥伴應該瞭解,之前我開源了一款快速開發腳手架mall-tiny
,該腳手架繼承了mall專案的技術棧,擁有完整的許可權管理功能。最近抽空把該專案支援了Spring Boot 2.7.0
,今天再和大家聊聊這個腳手架,同時聊聊升級專案到Spring Boot 2.7.0
的一些注意點,希望對大家有所幫助!
SpringBoot實戰電商專案mall(50k+star)地址:https://github.com/macrozheng/mall
聊聊mall-tiny專案
可能有些小夥伴還不瞭解這個腳手架,我們先來聊聊它!
專案簡介
mall-tiny是一款基於SpringBoot+MyBatis-Plus的快速開發腳手架,目前在Github上已有1100+Star
。它擁有完整的許可權管理功能,支援使用MyBatis-Plus程式碼生成器生成程式碼,可對接mall專案的Vue前端,開箱即用。
專案地址:https://github.com/macrozheng...
專案演示
mall-tiny專案可無縫對接mall-admin-web
前端專案,秒變前後端分離腳手架,由於mall-tiny專案僅實現了基礎的許可權管理功能,所以前端對接後只會展示許可權管理相關功能。
前端專案地址:https://github.com/macrozheng...
技術選型
這次升級不僅支援了Spring Boot 2.7.0,其他依賴版本也升級到了最新版本。
技術 | 版本 | 說明 |
---|---|---|
SpringBoot | 2.7.0 | 容器+MVC框架 |
SpringSecurity | 5.7.1 | 認證和授權框架 |
MyBatis | 3.5.9 | ORM框架 |
MyBatis-Plus | 3.5.1 | MyBatis增強工具 |
MyBatis-Plus Generator | 3.5.1 | 資料層程式碼生成器 |
Swagger-UI | 3.0.0 | 文件生產工具 |
Redis | 5.0 | 分散式快取 |
Docker | 18.09.0 | 應用容器引擎 |
Druid | 1.2.9 | 資料庫連線池 |
Hutool | 5.8.0 | Java工具類庫 |
JWT | 0.9.1 | JWT登入支援 |
Lombok | 1.18.24 | 簡化物件封裝工具 |
資料庫表結構
化繁為簡,僅保留了許可權管理功能相關的9張表,業務簡單更加方便定製開發,覺得mall專案學習太複雜的小夥伴可以先學習下mall-tiny。
介面文件
由於升級了Swagger版本,原來的介面文件訪問路徑已經改變,最新訪問路徑:http://localhost:8080/swagger...
使用流程
升級版本基本不影響之前的使用方式,具體使用流程可以參考最新版README
檔案:https://github.com/macrozheng...
升級過程
接下來我們再來聊聊專案升級Spring Boot 2.7.0版本遇到的問題,這些應該是升級該版本的通用問題,你如果想升級2.7.0版本的話,瞭解下會很有幫助!
Swagger升級
- 在升級Spring Boot 2.6.x版本的時候,其實Swagger就有一定的相容性問題,需要在配置中新增
BeanPostProcessor
這個Bean,具體可以參考升級 SpringBoot 2.6.x 版本後,Swagger 沒法用了! ;
/**
* Swagger API文件相關配置
* 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);
}
}
};
}
}
- 之前我們通過
@Api
註解的description
屬性來配置介面描述的方法已經被棄用了;
- 我們可以使用
@Tag
註解來配置介面說明,並使用@Api
註解中的tags
屬性來指定。
Spring Security升級
升級Spring Boot 2.7.0版本後,原來通過繼承WebSecurityConfigurerAdapter
來配置的方法已經被棄用了,僅需配置SecurityFilterChain
Bean即可,具體參考Spring Security最新用法。
/**
* SpringSecurity 5.4.x以上新用法配置
* 為避免迴圈依賴,僅用於配置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();
//不需要保護的資源路徑允許訪問
for (String url : ignoreUrlsConfig.getUrls()) {
registry.antMatchers(url).permitAll();
}
//允許跨域請求的OPTIONS請求
registry.antMatchers(HttpMethod.OPTIONS)
.permitAll();
// 任何請求需要身份認證
registry.and()
.authorizeRequests()
.anyRequest()
.authenticated()
// 關閉跨站請求防護及不使用session
.and()
.csrf()
.disable()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
// 自定義許可權拒絕處理類
.and()
.exceptionHandling()
.accessDeniedHandler(restfulAccessDeniedHandler)
.authenticationEntryPoint(restAuthenticationEntryPoint)
// 自定義許可權攔截器JWT過濾器
.and()
.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
//有動態許可權配置時新增動態許可權校驗過濾器
if(dynamicSecurityService!=null){
registry.and().addFilterBefore(dynamicSecurityFilter, FilterSecurityInterceptor.class);
}
return httpSecurity.build();
}
}
MyBatis-Plus升級
MyBatis-Plus從之前的版本升級到了3.5.1版本,用法沒有大的改變,感覺最大的區別就是程式碼生成器的用法改了。 在之前的用法中我們是通過new物件然後set各種屬性來配置的,具體參考如下程式碼:
/**
* 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程式碼生成器已經改成使用建造者模式來配置了,具體可以參考MyBatisPlusGenerator
類中的程式碼。
/**
* 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();
}
}
解決迴圈依賴問題
- 其實Spring Boot從2.6.x版本已經開始不推薦使用迴圈依賴了,如果你的專案中使用的迴圈依賴比較多的話,可以使用如下配置開啟;
spring:
main:
allow-circular-references: true
- 不過既然官方都不推薦使用了,我們最好還是避免迴圈依賴的好,這裡分享下我解決迴圈依賴問題的一點思路。
如果一個類裡有多個依賴項,這個類非必要的Bean就不要配置了,可以使用單獨的類來配置Bean
。比如SecurityConfig
這個配置類中,我只宣告瞭必要的SecurityFilterChain
配置;
/**
* SpringSecurity 5.4.x以上新用法配置
* 為避免迴圈依賴,僅用於配置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();
}
}
- 其他配置都被我移動到了
CommonSecurityConfig
配置類中,這樣就避免了之前的迴圈依賴;
/**
* SpringSecurity通用配置
* 包括通用Bean、Security通用Bean及動態許可權通用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();
}
}
- 還有一個典型的迴圈依賴問題,
UmsAdminServiceImpl
和UmsAdminCacheServiceImpl
相互依賴了;
/**
* 後臺管理員管理Service實現類
* Created by macro on 2018/4/26.
*/
@Service
public class UmsAdminServiceImpl extends ServiceImpl<UmsAdminMapper,UmsAdmin> implements UmsAdminService {
@Autowired
private UmsAdminCacheService adminCacheService;
}
/**
* 後臺使用者快取管理Service實現類
* Created by macro on 2020/3/13.
*/
@Service
public class UmsAdminCacheServiceImpl implements UmsAdminCacheService {
@Autowired
private UmsAdminService adminService;
}
- 我們可以建立一個用於獲取Spring容器中的Bean的工具類來實現;
/**
* Spring工具類
* 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;
}
}
// 通過name獲取Bean
public static Object getBean(String name) {
return getApplicationContext().getBean(name);
}
// 通過class獲取Bean
public static <T> T getBean(Class<T> clazz) {
return getApplicationContext().getBean(clazz);
}
// 通過name,以及Clazz返回指定的Bean
public static <T> T getBean(String name, Class<T> clazz) {
return getApplicationContext().getBean(name, clazz);
}
}
- 然後在
UmsAdminServiceImpl
中使用該工具類獲取Bean來解決迴圈依賴。
/**
* 後臺管理員管理Service實現類
* 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);
}
}
解決跨域問題
在使用Spring Boot 2.7.0版本時,如果不修改之前的跨域配置,通過前端訪問會出現跨域問題,後端報錯如下。
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
已經不再支援萬用字元*
的配置了,改為需要使用allowedOriginPatterns
來設定,具體配置修改如下。
/**
* 全域性跨域配置
* Created by macro on 2019/7/27.
*/
@Configuration
public class GlobalCorsConfig {
/**
* 允許跨域呼叫的過濾器
*/
@Bean
public CorsFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
//允許所有域名進行跨域呼叫
config.addAllowedOriginPattern("*");
//該用法在SpringBoot 2.7.0中已不再支援
//config.addAllowedOrigin("*");
//允許跨越傳送cookie
config.setAllowCredentials(true);
//放行全部原始頭資訊
config.addAllowedHeader("*");
//允許所有請求方法跨域呼叫
config.addAllowedMethod("*");
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
}
總結
今天分享了下我的開源專案腳手架mall-tiny
,以及它升級SpringBoot 2.7.0的過程。我們在寫程式碼的時候,如果有些用法已經廢棄,應該儘量去尋找新的用法來使用,這樣才能保證我們的程式碼足夠優雅!
專案地址
開源不易,覺得專案有幫助的小夥伴點個Star
支援下吧!