关于java:还在从零开始搭建项目这款升级版快速开发脚手架值得一试

8次阅读

共计 10058 个字符,预计需要花费 26 分钟才能阅读完成。

关注我 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 来配置的办法曾经被弃用了,仅需配置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();
        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();
    }
}
  • 还有一个典型的循环依赖问题,UmsAdminServiceImplUmsAdminCacheServiceImpl 相互依赖了;
/**
 * 后盾管理员治理 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 反对下吧!

https://github.com/macrozheng…

正文完
 0