从零搭建SpringBoot+MyBatisPlus快速开发脚手架

网友投稿 361 2022-07-21


目录前言聊聊mall-tiny项目项目简介项目演示技术选型数据库表结构接口文档使用流程升级过程Swagger升级Spring Security升级MyBatis-Plus升级解决循环依赖问题解决跨域问题总结

前言

关注我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-tiny项目可无缝对接mall-admin-web前端项目,秒变前后端分离脚手架,由于mall-tiny项目仅实现了基础的权限管理功能,所以前端对接后只会展示权限管理相关功能。

前端项目地址:https://github.com/macrozheng/mall-admin-web

技术选型

这次升级不仅支持了Spring Boot 2.7.0,其他依赖版本也升级到了最新版本。

技术版本说明SpringBoot2.7.0容器+MVC框架SpringSecurity5.7.1认证和授权框架MyBatis3.5.9ORM框架MyBatis-Plus3.5.1MyBatis增强工具MyBatis-Plus Generator3.5.1数据层代码生成器Swagger-UI3.0.0文档生产工具Redis5.0分布式缓存docker18.09.0应用容器引擎Druid1.2.9数据库连接池Hutool5.8.0java工具类库JWT0.9.1JWT登录支持Lombok1.18.24简化对象封装工具

数据库表结构

化繁为简,仅保留了权限管理功能相关的9张表,业务简单更加方便定制开发,觉得mall项目学习太复杂的小伙伴可以先学习下mall-tiny。

接口文档

由于升级了Swagger版本,原来的接口文档访问路径已经改变,最新访问路径:http://localhost:8080/swagger-ui/

使用流程

升级版本基本不影响之前的使用方式,具体使用流程可以参考最新版README文件:

https://github.com/macrozheng/mall-tiny

升级过程

接下来我们再来聊聊项目升级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来配置的方法已经被弃用了,仅需配置SecurityFilterChainBean即可,具体参考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();

PclQW 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 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 getBean(Class clazz) {

return getApplicationContext().getBean(clazz);

}

// 通过name,以及Clazz返回指定的Bean

public static T getBean(String name, Class clazz) {

return getApplicationContext().getBean(name, clazz);

}

}

然后在UmsAdminServiceImpl中使用该工具类获取Bean来解决循环依赖。

/**

* 后台管理员管理Service实现类

* Created by macro on 2018/4/26.

*/

@Service

public class UmsAdminServiceImpl extends ServiceImpl 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-OriPclQWgin" rePclQWsponse 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的过程。我们在写代码的时候,如果有些用法已经废弃,应该尽量去寻找新的用法来使用,这样才能保证我们的代码足够优雅!

项目地址 https://github.com/macrozheng/mall-tiny

以上就是从零搭建SpringBoot+MyBatisPlus快速开发脚手架的详细内容,更多关于SpringBoot+MyBatisPlus从零搭建的资料请关注我们其它相关文章!


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:Java使用Freemarker页面静态化生成的实现
下一篇:Java实现全排列的三种算法详解
相关文章

 发表评论

暂时没有评论,来抢沙发吧~