PropertySourcesPlaceholderConfigurer导致Spring启动失败问题排查
我来帮你理清楚这个问题的根因和解决办法:
首先,你的MyConfiguration是被@Configuration标记的配置类,而且里面包含了@Bean方法。Spring在处理这类配置类时,默认会通过CGLIB动态代理生成一个子类来增强它(就是报错日志里的MyConfiguration$$EnhancerBySpringCGLIB$$d0d9c3b2)——这个增强是为了拦截@Bean方法的调用,确保每次返回的都是同一个Bean实例(单例特性)。
但CGLIB生成子类有个硬性要求:父类必须提供无参的默认构造函数,否则子类无法完成实例化。而你的MyConfiguration只定义了一个带ApplicationContext参数的构造函数,没有显式声明无参构造函数,所以就抛出了NoSuchMethodException。
至于你说其他Bean没问题,是因为普通的@Component类(非@Configuration)的代理逻辑和配置类不一样:普通Bean即使被CGLIB增强,Spring也可以直接通过构造函数注入来实例化父类,不需要依赖无参构造函数;但配置类的代理子类必须先调用父类的无参构造函数,这就是核心差异所在。
下面给你几种可行的解决方法:
方法一:添加无参构造函数
最直接的方式就是给MyConfiguration添加一个无参构造函数,这样CGLIB就能正常生成子类了:
@Configuration @ComponentScan( useDefaultFilters = false, basePackages = "...", includeFilters = @Filter( type = FilterType.REGEX, pattern = "..." ) ) class MyConfiguration { private final ApplicationContext context; // 添加无参构造函数 MyConfiguration() { // 这里可以留空,Spring会通过带参构造函数完成实际注入 this.context = null; } MyConfiguration(final ApplicationContext context) { this.context = context; } @Bean PropertySourcesPlaceholderConfigurer configurer() { return new PropertySourcesPlaceholderConfigurer(); } }
方法二:改用ApplicationContextAware接口注入
如果你不想添加无参构造函数,可以让配置类实现ApplicationContextAware接口,Spring会自动把ApplicationContext注入进来,不需要构造函数:
@Configuration @ComponentScan(...) class MyConfiguration implements ApplicationContextAware { private ApplicationContext context; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.context = applicationContext; } @Bean PropertySourcesPlaceholderConfigurer configurer() { return new PropertySourcesPlaceholderConfigurer(); } }
方法三:关闭配置类的代理(仅适用于Spring Boot 2.2+ / Spring 5.2+)
如果你使用的是Spring Boot 2.2及以上版本(对应Spring 5.2+),可以给@Configuration添加proxyBeanMethods = false属性,将配置类设置为Lite模式——这种模式下Spring不会生成CGLIB代理,自然也就不需要无参构造函数了:
@Configuration(proxyBeanMethods = false) @ComponentScan(...) class MyConfiguration { private final ApplicationContext context; MyConfiguration(final ApplicationContext context) { this.context = context; } @Bean PropertySourcesPlaceholderConfigurer configurer() { return new PropertySourcesPlaceholderConfigurer(); } }
不过注意,Lite模式下,配置类里的@Bean方法如果互相调用,不会被拦截,会直接创建新的实例,所以如果你的配置类里有Bean之间的依赖调用,需要谨慎使用这个选项。
内容的提问来源于stack exchange,提问作者LppEdd

