Spring Maven Web应用编程式初始化Bean失败的解决方案咨询
我正在开发一个Spring Maven Web应用,使用WebApplicationInitializer和AnnotationConfigWebApplicationContext做配置。我的需求是通过编程方式手动注册自定义Bean,但尝试通过root上下文获取BeanFactory注册后,出现了无法@Autowire指定名称Bean的错误,提示应用上下文中不存在该Bean。
我的代码
WebApplicationInitializer实现类的onStartup方法:
public void onStartup(ServletContext sc) throws ServletException { AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext(); // 手动向root上下文注册Bean registerBeansManually(rootContext, new Class<?>[]{MyEntity1.class, MyEntity2.class}, mapReadFromSomewhere); sc.addListener(new ContextLoaderListener(rootContext)); AnnotationConfigWebApplicationContext webContext = new AnnotationConfigWebApplicationContext(); webContext.setParent(rootContext); webContext.register(SpringWebConfig.class); DispatcherServlet ds = new DispatcherServlet(webContext); ServletRegistration.Dynamic registration = sc.addServlet("dispatcher1", ds); registration.setLoadOnStartup(1); registration.addMapping("/"); }
手动注册Bean的方法:
public void registerBeansManually(AnnotationConfigWebApplicationContext rootContext, Class<?>[] entityClasses, Map<String,String> dbInfoMap) { // 获取Bean工厂 rootContext.refresh(); ConfigurableListableBeanFactory beanFactory = rootContext.getBeanFactory(); // 1. 注册DataSource DataSource ds = new DatabaseBeansProducer().produceDataSource(dbInfoMap); beanFactory.registerSingleton("myProjectDataSource", ds); // 2. 注册SessionFactory LocalSessionFactoryBean sf = new DatabaseBeansProducer().produceSessionFactory(ds, entityClasses); beanFactory.registerSingleton("myProjectSessionFactory", sf); // 3. 注册TransactionManager HibernateTransactionManager txm = new DatabaseBeansProducer().produceTransactionManager(sf); beanFactory.registerSingleton("myProjectTransactionManager", txm); }
报错信息
Could not autowire field: private org.springframework.orm.hibernate5.LocalSessionFactoryBean com.example.MyService.myProjectSessionFactory; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [org.springframework.orm.hibernate5.LocalSessionFactoryBean] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
请问该如何解决这个Bean无法被Autowired的问题?
我帮你分析下问题出在哪,主要是两个核心点导致的Bean无法被Autowired,咱们一步步来解决:
1. 不要提前调用rootContext.refresh()
你在registerBeansManually里先调用了rootContext.refresh(),这是关键错误:Spring的上下文刷新是一次性操作,刷新完成后再注册的Bean不会被上下文识别。而且此时你还没把root上下文交给ContextLoaderListener,提前刷新会打乱Spring的生命周期管理流程。
正确的做法是:把刷新操作交给ContextLoaderListener负责,它会在初始化上下文时自动调用refresh(),你只需要在这之前完成所有Bean的注册即可。
2. LocalSessionFactoryBean是FactoryBean,不能直接注册为Singleton
LocalSessionFactoryBean实现了FactoryBean<SessionFactory>接口,它的作用是生产SessionFactory实例,而非作为Bean本身被注入。你直接把它注册为singleton会导致两个问题:
- Spring会尝试注入
LocalSessionFactoryBean类型(而你实际需要的是它生成的SessionFactory) - 它的初始化方法
afterPropertiesSet()不会被触发,根本生成不了真正的SessionFactory实例
修复后的完整代码
调整registerBeansManually方法:
public void registerBeansManually(AnnotationConfigWebApplicationContext rootContext, Class<?>[] entityClasses, Map<String,String> dbInfoMap) { // 不要提前调用refresh!直接获取未刷新的上下文BeanFactory ConfigurableListableBeanFactory beanFactory = rootContext.getBeanFactory(); // 1. 注册DataSource DataSource ds = new DatabaseBeansProducer().produceDataSource(dbInfoMap); beanFactory.registerSingleton("myProjectDataSource", ds); // 2. 正确处理LocalSessionFactoryBean:触发初始化并注册生成的SessionFactory LocalSessionFactoryBean sfBean = new DatabaseBeansProducer().produceSessionFactory(ds, entityClasses); // 手动触发FactoryBean的初始化逻辑 try { sfBean.afterPropertiesSet(); } catch (IOException e) { throw new RuntimeException("Failed to initialize LocalSessionFactoryBean", e); } // 注册真正的SessionFactory实例,而非LocalSessionFactoryBean本身 beanFactory.registerSingleton("myProjectSessionFactory", sfBean.getObject()); // 3. 注册TransactionManager:传入SessionFactory实例而非LocalSessionFactoryBean HibernateTransactionManager txm = new DatabaseBeansProducer().produceTransactionManager(sfBean.getObject()); beanFactory.registerSingleton("myProjectTransactionManager", txm); }
调整onStartup方法的执行顺序:
确保先完成Bean注册,再交给ContextLoaderListener管理:
public void onStartup(ServletContext sc) throws ServletException { AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext(); // 先完成所有手动Bean注册,此时上下文未刷新 registerBeansManually(rootContext, new Class<?>[]{MyEntity1.class, MyEntity2.class}, mapReadFromSomewhere); // 添加ContextLoaderListener,由它负责触发上下文刷新 sc.addListener(new ContextLoaderListener(rootContext)); AnnotationConfigWebApplicationContext webContext = new AnnotationConfigWebApplicationContext(); webContext.setParent(rootContext); webContext.register(SpringWebConfig.class); DispatcherServlet ds = new DispatcherServlet(webContext); ServletRegistration.Dynamic registration = sc.addServlet("dispatcher1", ds); registration.setLoadOnStartup(1); registration.addMapping("/"); }
额外建议:使用@Configuration类替代手动注册
其实Spring更推荐用配置类的方式声明Bean,这样能更好地利用Spring的生命周期管理,避免手动操作的失误。比如你可以创建一个数据库配置类:
@Configuration public class DatabaseConfig { @Autowired private Map<String, String> dbInfoMap; // 假设该Map已注册到上下文 @Bean(name = "myProjectDataSource") public DataSource dataSource() { return new DatabaseBeansProducer().produceDataSource(dbInfoMap); } @Bean(name = "myProjectSessionFactory") public SessionFactory sessionFactory(DataSource dataSource) throws IOException { LocalSessionFactoryBean sfBean = new DatabaseBeansProducer().produceSessionFactory(dataSource, new Class<?>[]{MyEntity1.class, MyEntity2.class}); sfBean.afterPropertiesSet(); return sfBean.getObject(); } @Bean(name = "myProjectTransactionManager") public HibernateTransactionManager transactionManager(SessionFactory sessionFactory) { return new DatabaseBeansProducer().produceTransactionManager(sessionFactory); } }
然后在onStartup里直接注册这个配置类:
rootContext.register(DatabaseConfig.class);
这种方式更符合Spring的最佳实践,也更易于维护和调试。
内容的提问来源于stack exchange,提问作者Lazaruss

