Backend Development 14 min read

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.

<code>/**
 * 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);
                }
            }
        };
    }
}
</code>

Spring Security Upgrade

WebSecurityConfigurerAdapter is deprecated; configure a

SecurityFilterChain

bean instead.

<code>/**
 * 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();
    }
}
</code>

MyBatis‑Plus Upgrade

The generator now uses a builder pattern.

<code>/**
 * 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;
    }
}
</code>
<code>/**
 * 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();
    }
}
</code>

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.

<code>/**
 * 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);
    }
}
</code>

CORS Issue

In Spring Boot 2.7.0,

allowedOrigins

no longer accepts "*". Use

allowedOriginPatterns

instead.

<code>/**
 * 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);
    }
}
</code>

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.

backendJavaSpring BootSecurityMyBatis-PlusSwagger
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

login 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.