为啥在 Spring Boot 中超类上的 @Bean 泛型创建方法比子类上的方法调用晚?

Posted

技术标签:

【中文标题】为啥在 Spring Boot 中超类上的 @Bean 泛型创建方法比子类上的方法调用晚?【英文标题】:Why are @Bean Generics creation methods on a superclass called later than ones on the child-class in Spring Boot?为什么在 Spring Boot 中超类上的 @Bean 泛型创建方法比子类上的方法调用晚? 【发布时间】:2018-07-10 00:03:04 【问题描述】:

我有一个创建 bean 的 Spring Boot 基础抽象配置类。如果我从它继承,bean 将在我的控制器之后创建(它需要自动连接它并因此失败)。 注意:它确实是在控制器之后创建的。所以它不能自动连接,但必须通过appContext.getBean( BeanType.class )

找到

如果我改为覆盖子类中的 bean 方法,那么它会在 控制器之前创建,并且可以自动连接。

我该如何解决这个问题并让超类 bean 定义与子类同时加载?

@SpringBootApplication
public class ChildConfig extends ParentConfig<PCTestBean>

    public ChildConfig()
    
        super();
    

    @Override
    public PCTestBean getT()
    
        return new PCTestBean();
    


public abstract class ParentConfig<T>

    public ParentConfig() 

    @Bean
    public T createTestBean()
    
        return getT();
    

    public abstract T getT();


public class PCTestBean



@RestController
@RequestMapping( "/client" )
public class MyController

    @Autowired
    private PCTestBean pcTestBean;

    @RequestMapping( "/get" )
    @ResponseBody
    public String getClient(HttpServletRequest request) throws Exception
    
        return pcTestBean.toString();
    


@RunWith( SpringJUnit4ClassRunner.class )
@SpringBootTest(
    webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT
)
@ContextConfiguration(
    classes = 
        ChildConfig.class
    
)
public class TestConfigs

    @LocalServerPort
    private String port;

    private MockMvc mockMvc;

    @Autowired
    private WebApplicationContext context;

    @Before
    public void setUp() throws Exception
    
        mockMvc = MockMvcBuilders
            .webAppContextSetup( context )
            .build();
    

    @Test
    public void testValidCall() throws Exception
    
        MvcResult result = mockMvc.perform(
            MockMvcRequestBuilders.get( new URI( "http://localhost:" + port + "/client/get" ) )
        )
            .andExpect( MockMvcResultMatchers.status().isOk() ).andReturn();

        System.out.println( result.getResponse().getContentAsString() );
    

【问题讨论】:

请提供一个minimal reproducible example 表明Spring 未能将TestBean bean 注入@Autowired 注释字段。 @SotiriosDelimanolis 我现在正在做,谢谢,它表明它不仅仅是一个超类,超类方法使用泛型。这就是导致问题的原因(它确实创建它,只是直到控制器之后)。但奇怪的是,覆盖它消除了这个问题。 @SotiriosDelimanolis 我更新了示例 【参考方案1】:

当 Spring 扫描您的配置类 ChildConfig 时,它会发现这个继承方法

@Bean
public T createTestBean() 
    return getT();

并为其注册一个bean定义。该 bean 定义包含有关 bean 类型的元数据。该类型是从方法的返回类型推断出来的。在这种情况下,它被解析为Object,因为类型变量T 在其声明中没有边界,并且因为Spring 不会尝试根据ChildConfigextends ParentConfig&lt;PCTestBean&gt; 子句中提供的类型参数来解析它。

当 Spring 然后尝试处理

@Autowired
private PCTestBean pcTestBean;

注入目标,它寻找PCTestBean bean,它认为它没有,因为缺少元数据。 如果 bean 没有通过其他一些强制命令被初始化,那么 Spring 没有其他信息可以继续并认为 bean 不存在。

当您将代码更改为

改为覆盖子类中的bean方法

方法的返回类型是PCTestBean,然后Spring可以匹配@Autowired注入需求,找到(并初始化)bean,然后注入它。

当您使用ApplicationContext#getBean(Class) 时,PCTestBean 已被初始化。因此,Spring 可以依赖于实例的实际类型。它会或多或少地循环遍历所有 bean 并检查 beanClass.isInstance(eachBean) 是否返回匹配的那个(或者如果超过一个则失败)。

Pankaj,in their answer,建议使用@DependsOn(在您编辑问题之前,他们提出的建议是错误的)。这有助于建立我之前提到的顺序。


我不知道你的配置类有多么广泛,你认为你需要泛型来抽象一些行为,但我建议只删除泛型行为并在每个类中明确。

【讨论】:

您的解释 (it looks for a PCTestBean bean, which it doesn't think it has, because the metadata is lacking.) 似乎不正确,因为它确实找到了它。我可以通过输入 appContext.getBean( BeanType.class ) 搜索 bean,它确实找到了它,但要等到以后。 您提到可以通过某种强制方式初始化bean。我该怎么做? @DonRhummy @DependsOn 是一种方式。我暂时想不到其他人。您还可以使用 bean 名称限定您的 @Autowired,即。用@Resource(name = "createTestBean")注释它。【参考方案2】:

试试DependsOn注解,它保证子bean应该在父bean之后创建

@Configuration
public class ChildConfig extends ParentConfig

    public ChildConfig()
    
        super();
    


    @DependsOn("parentConfig")
    @Override
    public TestBean createTestBean()
    
        return super.createTestBean();
    */


public abstract class ParentConfig

    public ParentConfig() 

    @Bean (name ="parentConfig")
    public TestBean createTestBean()
    
        return new TestBean();
    

【讨论】:

使用@Autowired,已经有一个隐含的依赖关系。 Spring 必须先初始化 bean,然后才能注入它。 好吧,你必须删除 @Bean 注释,同时在子类中覆盖它。

以上是关于为啥在 Spring Boot 中超类上的 @Bean 泛型创建方法比子类上的方法调用晚?的主要内容,如果未能解决你的问题,请参考以下文章

在 Spring Boot 测试类上使用 @WebMvcTest 注释时出错

如何在 Spring Boot 应用程序的同一个域类上同时使用 Spring Data JPA 和 Spring Data Elasticsearch 存储库?

为啥 Spring Social 插件偶尔会在 User 类上返回空邮件?

spring boot 注解记录

spring boot返回Josn的两种方式

Spring Boot中@Async的作用