Spring不会在原型Bean中调用@Autowired设置器 新类策略工厂 RestController中的更改

问题描述

问题:

问题出现在运行spring-boot,java 8的k8s pod中(下面有更多详细信息)

使用ObjectProvider<>调用*provider.getobject(.....)*时, 在Spring Configuration中定义的原型bean,时不时地(从不 找到使之定期发生的方法)未调用二传手注入方法

更新10月2日:,请参见Spring Issue #25840

在大多数情况下,它可以很好地工作,但有时它会构造一个新对象 但没有调用@Autowired setter方法(已检查日志信息)。

我们还发现90%的时间发生在应用程序启动后,但并非总是如此。

9月21日更新删除(重新启动)de pod解决了该问题。

更新日期为9月22日:一旦发生,它将一直发生,我的意思是不是一次失败,而下次它正常运行时,它将永远无法调用设置器。

9月23日更新:与真实应用程序中的并发问题相关的新证据直到现在才出现,似乎只有一个线程 产生问题。 (请看下面的课程以更好地理解 此说明)

ToipParentClass (这是一个策略实现)已将@Autowired设置为

  • VLocityService
  • OrderManagemenService

InternetParentClass (这是一项策略实现)已将@Autowired设置为

  • VLocityService
  • OrderManagemenService

日志(注释)

[-nio-80-exec-10] GuidTaskController   : Build Strategy using XOM_TOIP_OFFER .                    
[p-nio-80-exec-2] GuidTaskController   : Build Strategy using XOM_INTERNET_OFFER .                    
[-nio-80-exec-10] ToipParentClass      : @Constructing ToipParentClass                                             
[p-nio-80-exec-2] InternetParentClass  : @Constructing InternetParentClass                                         
[-nio-80-exec-10] ToipParentClass      : @Autowiring VLocityServices@7951cd46                                      
[p-nio-80-exec-2] InternetParentClass  : @Autowiring VLocityServices@7951cd46                                      
[-nio-80-exec-10] ToipParentClass      : @Autowiring OrderManagementService@3385326a     
-------------------------------------------------------------
ERROR !!! Missing @Autowiring log  
[p-nio-80-exec-2] InternetParentClass  : @Autowiring OrderManagementService@7951cd46                      
-------------------------------------------------------------
[p-nio-80-exec-2] Controller           : Unexpected Error
2020-09-22T18:56:45.544525916Z
-------------------------------------------------------------
ERROR: NullPointer when using not set OrderManagementService
-------------------------------------------------------------
2020-09-22T18:56:45.544530395Z java.lang.NullPointerException: null
2020-09-22T18:56:45.544534074Z  at InternetParentClass.generateIds(InternetParentClass.java:50) ~[classes!/:BUG001_PrototypeBeanAutowired-8]                                       
2020-09-22T18:56:45.544538568Z  at GuidTaskController.setGUID(GuidTaskController.java:180) ~[classes!/:BUG001_PrototypeBeanAutowired-8]

我在https://github.com/toniocus/strategy-calculator中进行了一个简单的测试,分别运行它,不同的jdk8版本,以及在pod中使用的同一docker映像中(项目中的所有内容),但均未通过失败。

关于在哪里寻找问题的任何想法,关于尝试什么的建议,甚至 一个解决方案:-),将非常欢迎,在此先感谢

在产品版本,类别下面。

有关版本的详细信息:

k8s:

v1.14.9-eks-658790

弹簧靴:

2.1.4.RELEASE

JDK:

  openjdk version "1.8.0_212"
  OpenJDK Runtime Environment (IcedTea 3.12.0) (Alpine 8.212.04-r0)
  OpenJDK 64-Bit Server VM (build 25.212-b04,mixed mode)

课程

所涉及的类的代码段,没有实际的代码,不用担心语法错误。 (可以在https://github.com/toniocus/strategy-calculator中找到真实代码

// ============================================================
public interface Strategy {
   void work(...);
}

// ============================================================
@Service
public class Service {
    public void doSomething() {
       ......
       ......
    }
}

// ============================================================
public class MobileStrategy implements Strategy {
   private Service service;

   @Autowired
   public void setService(Service s) {
       this.service = s;   // setter not called every Now and then
   }



   public void work(...) {
       this.service.doSomething();  // throws NullPointerException every Now an then
   }
}

// ============================================================
public enum StrategyEnum {

    MOBILE("mobileKey",MobileStrategy.class),TV("tvKey",TvStrategy.class),.......

    public Class<Strategy> getImplementationClass() {
       ......
    }

    public StrategyEnum findByKey(String key) {
       .....
    }
}

// ============================================================
@Configuration
public class Configuration {

    @Bean
    @Scope(value = Configurablebeanfactory.ScopE_PROTOTYPE)
    public Strategy getStrategy(final StrategyEnum selectorEnum) {

        try {

            Constructor<? extends Strategy> constructor =
                    selectorEnum.getImplementationClass().getConstructor();

            return constructor.newInstance();
        }
        catch (Exception ex) {

            throw new Exception("Not able to instantiate interface implementation"
                    + " for enum: " + selectorEnum,ex);
        }

    }
}


// ============================================================
@RestController
public class MathOperRestController {

    @Autowired
    ObjectProvider<Strategy> provider;

    @GetMapping("/{operation}/{x}/{y}")
    public BigDecimal add(
            @PathVariable("operation") final String operation,@PathVariable("x") final BigDecimal x,@PathVariable("y") final BigDecimal y
            ) {

        Strategy strategy = this.provider.getobject(StrategyEnum.findByKey(operation));
        strategy.doWork(x,y);

        return BigDecimal.ZERO;
    }

9月21日更新将Service类添加到示例

解决方法

我发现的问题

更新10月1日:请阅读问题中的@Denium评论!!!! (为此)。

在今天(9月23日)更新之后,显然该问题是并发问题,我能够轻松地重现它(请参阅SimpleSpringAppTest.java) 在Simple Spring应用中,没有spring-boot。

使所有Strategy实施都具有相同的@Autowired设置器集 而且错误仍然存​​在。

似乎已经完成了一些缓存,并且@Autowired setters是从缓存中获取的,而不是从新构造的对象中获取的,尽管我试图在这么短的时间内挖掘难以理解的spring源。

问题已解决,避免了并发(下面的更改),所以现在是我的问题:

这是预期的行为还是错误?

我找不到任何有关此问题的文档或在任何地方描述此行为,因此仍然是我的问题。

我在spring-boot版本1.5.22、2.1.4(我们当前使用的版本)和2.3.4中进行了测试,并且在所有情况下都出现了相同的问题,仅在简单的spring应用程序中,不需要RestController或如此。

解决方法1

添加一个中间工厂,以确保“同步”创建策略bean。

更新十月1:从@Deinum注释开始,据我了解,Spring将每次(或几乎每次)扫描类以查找批注,因此我猜想变通方法2可能更好解决方案。

此解决方案更适合我当前的环境。

新类策略工厂

请注意getStrategy(...)方法是同步的,我想这个解决方案 将对性能产生一些影响,但仍然无法衡量。

@Component
public class StrategyFactory {

    @Autowired
    ObjectProvider<Strategy> provider;

    public synchronized Strategy getStrategy(final MathOperEnum operation) {
        return this.provider.getObject(operation);
    }

}

RestController中的更改

现在使用StrategyFactory代替ObjectProvider

@RestController
public class MathOperRestController {

    @Autowired
    StrategyFactory factory;

    @GetMapping("/{operation}/{x}/{y}")
    public BigDecimal add(
            @PathVariable("operation") final String operation,@PathVariable("x") final BigDecimal x,@PathVariable("y") final BigDecimal y
            ) {

        Strategy strategy = this.factory
             .getStrategy(StrategyEnum.findByKey(operation));

        strategy.doWork(x,y);

        return BigDecimal.ZERO;
    }
}

解决方法2

  • 使StrategyFactory ApplicationContextAware
  • 在每个策略实施中添加@ Componente / @ Scope注释
  • 删除@Configuration类
@Component
public class StrategyFactory implements ApplicationContextAware {

    private ApplicationContext ctx;


    public Strategy getStrategy(final StrategyEnum operation) {
        return this.ctx.getBean(
               operation.getImplementationClass()
               );
    }


    @Override
    public void setApplicationContext(
         final ApplicationContext applicationContext) 
    throws BeansException {

        this.ctx = applicationContext;

    }

}
,

这不是在Strategy方法中创建Configuration#getStrategy bean实例的正确方法,因为它不会使用自动装配来调用setter。

Constructor<? extends Strategy> constructor =
                selectorEnum.getImplementationClass().getConstructor();

return constructor.newInstance();

使用@Autowired的方式,看来您想创建一个由Spring处理实例创建的bean。

您可以参考答案https://stackoverflow.com/a/52355649/2614885来使用AutowireCapableBeanFactory,它会在spring容器中为您指定的bean ID在您的情况下似乎是'mobileKey'或'tvKey'的bean ID中创建bean。>

在您的@Configuration类中尝试以下代码

@Configuration
public class Configuration {

    @Autowired private AutowireCapableBeanFactory autowireCapableBeanFactory;

    @Bean
    @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    public Strategy getStrategy(final StrategyEnum selectorEnum) {

        try {

            Constructor<? extends Strategy> constructor =
                    selectorEnum.getImplementationClass().getConstructor();

            Strategy strategyBean = constructor.newInstance();
            autowireCapableBeanFactory.autowireBean(strategyBean);
            return strategyBean;
        }
        catch (Exception ex) {

            throw new Exception("Not able to instantiate interface implementation"
                    + " for enum: " + selectorEnum,ex);
        }

    }
}