BeanFactoryPostProcessor的依赖注入限制
首先,我需要感谢wenzhuo4657给我的项目提的pr:
其中数据库文件的生成确实是我自己考虑的不够周到,没有意识到flyway有可能会在数据库文件生成之前进行数据库的操作。
我在这条pr里,也学到了一些东西。
其一,是BeanFactoryPostProcessor,pr里DatabaseConfig实现了BeanFactoryPostProcessor接口,这样,DatabaseConfig就能在所有Bean初始化之前执行了,能够避免flyway过早操作数据库。
其二,就是依赖注入。
wenzhuo4657还为DatabaseConfig实现了一个EnvironmentAware接口,我看了看逻辑,是为了获取到spring.datasource.url这个属性的值的,我之前的实现是直接用@Value直接依赖注入,没有接触过用environment.getProperty来获取属性值的,所以一开始,我认为这是多此一举,想要提一个修改请求,但是转念一想,应该不至于特地实现这么一个接口而不用@Value吧?抱着怀疑的态度,我去问了问Gemini,果然,实现这个接口是有道理的,不如说,在BeanFactoryPostProcessor里要获取属性值,就必须实现EnvironmentAware接口。
先说如果用@Value会怎么样:
@Value("${spring.datasource.url}")  
private String databaseUrl;
@Override  
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {  
    // 在所有Bean创建之前执行,确保在Flyway初始化之前创建数据库目录  
    if (databaseUrl != null) {  
        // 从数据库URL中提取路径  
        String dbPath = databaseUrl.replace("jdbc:sqlite:", "");  
        File dbFile = new File(dbPath);  
        File parentDir = dbFile.getParentFile();  
  
        if (parentDir != null && !parentDir.exists()) {  
            boolean created = parentDir.mkdirs();  
            if (created) {  
                log.info("数据库父目录创建成功: {}", parentDir.getAbsolutePath());  
            } else {  
                log.error("数据库父目录创建失败: {}", parentDir.getAbsolutePath());  
            }  
        } else if (parentDir != null) {  
            log.info("数据库父目录已存在: {}", parentDir.getAbsolutePath());  
        }  
    }else{  
        throw new RuntimeException("数据库URL未配置");  
    }  
}这时,启动程序,控制台会报错:
2025-08-27 10:55:21.008 [restartedMain] WARN  o.s.b.w.s.c.AnnotationConfigServletWebServerApplicationContext - Exception encountered during context initialization - cancelling refresh attempt: java.lang.RuntimeException: 数据库URL未配置这是因为,BeanFactoryPostProcessor是在一切Bean创建之前执行的,也就是说,@Value的处理器也还没有开始工作,导致databaseUrl的值为空。
如果用上了EnvironmentAware呢?
private Environment environment;  
  
@Override  
public void setEnvironment(Environment environment) {  
    this.environment = environment;  
}  
  
@Override  
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {  
    // 在所有Bean创建之前执行,确保在Flyway初始化之前创建数据库目录  
    String databaseUrl = environment.getProperty("spring.datasource.url");  
    if (databaseUrl != null) {  
        // 从数据库URL中提取路径  
        String dbPath = databaseUrl.replace("jdbc:sqlite:", "");  
        File dbFile = new File(dbPath);  
        File parentDir = dbFile.getParentFile();  
  
        if (parentDir != null && !parentDir.exists()) {  
            boolean created = parentDir.mkdirs();  
            if (created) {  
                log.info("数据库父目录创建成功: {}", parentDir.getAbsolutePath());  
            } else {  
                log.error("数据库父目录创建失败: {}", parentDir.getAbsolutePath());  
            }  
        } else if (parentDir != null) {  
            log.info("数据库父目录已存在: {}", parentDir.getAbsolutePath());  
        }  
    }else{  
        throw new RuntimeException("数据库URL未配置");  
    }  
}这时,程序就能正常启动了,这是因为,EnvironmentAware是一个更底层的“特权”接口,实现它,就相当于有了Environment,而Environment对象是Spring容器最早准备好的东西之一,它包含了所有的配置信息,包括spring.datasource.url。这样,使用environment.getProperty("spring.datasource.url")就能获取到spring.datasource.url的值了。
同样的,在实现了BeanFactoryPostProcessor的类里,@Autowired也会失效,因为这时还没到Bean的实例化阶段。
不过,我在想:如果非要另一个类,另一个Bean,怎么办?
Gemini给出了两个方法,第一个,使用beanFactory对象:
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
    // 这是一个非常危险且不推荐的操作喵!
    MyNeededService myService = beanFactory.getBean(MyNeededService.class); 
    myService.doSomething();
}不过这个操作很危险,因为会打乱这个Bean的生命周期,也会导致它的AOP失效(早产儿说是)。
第二个,那就是不用Bean,不把这个类交给Spring容器统一管理,只需要在实现了BeanFactoryPostProcessor的Bean里创建这个类的对象,直接使用它就行了。