关注我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,其余依赖版本也降级到了最新版本。

技术版本阐明
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...

应用流程

降级版本根本不影响之前的应用形式,具体应用流程能够参考最新版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@EnableSwagger2public 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. */@Configurationpublic 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. */@Configurationpublic 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. */@Configurationpublic 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();    }}
  • 还有一个典型的循环依赖问题,UmsAdminServiceImplUmsAdminCacheServiceImpl相互依赖了;
/** * 后盾管理员治理Service实现类 * Created by macro on 2018/4/26. */@Servicepublic class UmsAdminServiceImpl extends ServiceImpl<UmsAdminMapper,UmsAdmin> implements UmsAdminService {    @Autowired    private UmsAdminCacheService adminCacheService;}/** * 后盾用户缓存治理Service实现类 * Created by macro on 2020/3/13. */@Servicepublic class UmsAdminCacheServiceImpl implements UmsAdminCacheService {    @Autowired    private UmsAdminService adminService;}
  • 咱们能够创立一个用于获取Spring容器中的Bean的工具类来实现;
/** * Spring工具类 * Created by macro on 2020/3/3. */@Componentpublic 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. */@Servicepublic 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. */@Configurationpublic 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反对下吧!

https://github.com/macrozheng...