How to Upgrade the mall‑tiny Spring Boot Scaffold to 2.7.0: Tips and Code Changes

This article introduces the open‑source mall‑tiny rapid‑development scaffold, explains its core features and integration with the mall‑admin‑web front‑end, and provides a step‑by‑step guide to upgrading it to Spring Boot 2.7.0, covering Swagger, Spring Security, MyBatis‑Plus, circular‑dependency resolution, and CORS configuration.

macrozheng
macrozheng
macrozheng
How to Upgrade the mall‑tiny Spring Boot Scaffold to 2.7.0: Tips and Code Changes

mall‑tiny Project Overview

mall‑tiny is a rapid‑development scaffold based on Spring Boot and MyBatis‑Plus, featuring complete permission management and integration with the mall‑admin‑web Vue front‑end. It has over 1100 stars on GitHub.

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

Project Demo

mall‑tiny can be seamlessly connected to the mall‑admin‑web front‑end, exposing only permission‑related features.

Front‑end project address: https://github.com/macrozheng/mall-admin-web

Technical Stack

The upgrade adds Spring Boot 2.7.0 and updates other dependencies to their latest versions, including Spring Security 5.7.1, MyBatis‑Plus 3.5.1, Swagger‑UI 3.0.0, Redis 5.0, Docker 18.09.0, Druid 1.2.9, Hutool 5.8.0, JWT 0.9.1, Lombok 1.18.24, etc.

Database Schema

Only nine tables related to permission management are retained, simplifying customization.

API Documentation

After upgrading Swagger, the documentation URL changes to http://localhost:8080/swagger-ui/.

Upgrade Process

Common issues when upgrading to Spring Boot 2.7.0 and their solutions.

Swagger Upgrade

When moving from Spring Boot 2.6.x, add a BeanPostProcessor bean to fix compatibility; the old @Api description attribute is deprecated, use @Tag and @Api tags instead.

/**
 * Swagger API documentation configuration
 * 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);
                }
            }
        };
    }
}

Spring Security Upgrade

WebSecurityConfigurerAdapter is deprecated; configure a SecurityFilterChain bean instead.

/**
 * SpringSecurity 5.4.x+ new configuration
 * 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
    public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
        ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = httpSecurity.authorizeRequests();
        // Permit URLs that do not need protection
        for (String url : ignoreUrlsConfig.getUrls()) {
            registry.antMatchers(url).permitAll();
        }
        // Allow OPTIONS for CORS
        registry.antMatchers(HttpMethod.OPTIONS).permitAll();
        // Require authentication for any request
        registry.and()
                .authorizeRequests()
                .anyRequest()
                .authenticated()
                .and()
                .csrf().disable()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .exceptionHandling()
                .accessDeniedHandler(restfulAccessDeniedHandler)
                .authenticationEntryPoint(restAuthenticationEntryPoint)
                .and()
                .addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
        if (dynamicSecurityService != null) {
            registry.and().addFilterBefore(dynamicSecurityFilter, FilterSecurityInterceptor.class);
        }
        return httpSecurity.build();
    }
}

MyBatis‑Plus Upgrade

The generator now uses a builder pattern.

/**
 * MyBatisPlus code generator
 * Created by macro on 2020/8/20.
 */
public class MyBatisPlusGenerator {
    /**
     * Initialize global configuration
     */
    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;
    }
}
/**
 * MyBatisPlus code generator
 * Created by macro on 2020/8/20.
 */
public class MyBatisPlusGenerator {
    /**
     * Initialize global configuration
     */
    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();
    }
}

Circular Dependency Resolution

Enable circular references in Spring Boot 2.6+ via spring.main.allow-circular-references:true.

Extract shared beans into separate configuration classes (e.g., CommonSecurityConfig) to avoid circular dependencies.

Use a SpringUtil helper to obtain beans programmatically.

/**
 * Spring utility class
 * Created by macro on 2020/3/3.
 */
@Component
public class SpringUtil implements ApplicationContextAware {

    private static ApplicationContext applicationContext;

    // Get applicationContext
    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        if (SpringUtil.applicationContext == null) {
            SpringUtil.applicationContext = applicationContext;
        }
    }

    // Get bean by name
    public static Object getBean(String name) {
        return getApplicationContext().getBean(name);
    }

    // Get bean by class
    public static <T> T getBean(Class<T> clazz) {
        return getApplicationContext().getBean(clazz);
    }

    // Get bean by name and class
    public static <T> T getBean(String name, Class<T> clazz) {
        return getApplicationContext().getBean(name, clazz);
    }
}

CORS Issue

In Spring Boot 2.7.0, allowedOrigins no longer accepts "*". Use allowedOriginPatterns instead.

/**
 * Global CORS configuration
 * Created by macro on 2019/7/27.
 */
@Configuration
public class GlobalCorsConfig {

    /**
     * CORS filter allowing all origins
     */
    @Bean
    public CorsFilter corsFilter() {
        CorsConfiguration config = new CorsConfiguration();
        // Allow all domains
        config.addAllowedOriginPattern("*");
        // Allow credentials
        config.setAllowCredentials(true);
        // Allow all headers
        config.addAllowedHeader("*");
        // Allow all methods
        config.addAllowedMethod("*");
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", config);
        return new CorsFilter(source);
    }
}

Conclusion

The mall‑tiny scaffold has been upgraded to Spring Boot 2.7.0, with updated dependencies and configuration changes such as Swagger, Spring Security, MyBatis‑Plus, circular‑dependency handling, and CORS settings. Adopting the new APIs ensures cleaner and more maintainable code.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Backendsecurityspring-boot
macrozheng
Written by

macrozheng

Dedicated to Java tech sharing and dissecting top open-source projects. Topics include Spring Boot, Spring Cloud, Docker, Kubernetes and more. Author’s GitHub project “mall” has 50K+ stars.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.