alibaba / Sentinel

A powerful flow control component enabling reliability, resilience and monitoring for microservices. (面向云原生微服务的高可用流控防护组件)
https://sentinelguard.io/
Apache License 2.0
22.32k stars 8k forks source link

Quartz job initialize failed with NullpointerException #1156

Closed luhggit closed 4 years ago

luhggit commented 4 years ago

When I import alibaba sentinel into my SpringCloud project and start, My quartz job all fail initialize.How can I solve it? The Exception like this:

org.quartz.SchedulerException: Job instantiation failed
    at org.springframework.scheduling.quartz.AdaptableJobFactory.newJob(AdaptableJobFactory.java:47)
    at org.quartz.core.JobRunShell.initialize(JobRunShell.java:127)
    at org.quartz.core.QuartzSchedulerThread.run(QuartzSchedulerThread.java:392)
Caused by: java.lang.NullPointerException: null
    at java.util.concurrent.ConcurrentHashMap.get(ConcurrentHashMap.java:936)
    at java.util.concurrent.ConcurrentHashMap.containsKey(ConcurrentHashMap.java:964)
    at com.alibaba.cloud.sentinel.custom.SentinelBeanPostProcessor.postProcessAfterInitialization(SentinelBeanPostProcessor.java:169)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsAfterInitialization(AbstractAutowireCapableBeanFactory.java:431)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1703)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:407)
    at org.springframework.boot.autoconfigure.quartz.AutowireCapableBeanJobFactory.createJobInstance(AutowireCapableBeanJobFactory.java:45)
    at org.springframework.scheduling.quartz.AdaptableJobFactory.newJob(AdaptableJobFactory.java:43)
    ... 2 common frames omitted

My enviorment(part of pom.xml):

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.5.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <spring-cloud.version>Finchley.SR1</spring-cloud.version>
    </properties>
   ...

    <dependencies>
        <!-- quartz -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-quartz</artifactId>
        </dependency>

         <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
            <version>2.0.0.RELEASE</version>
        </dependency>
   </dependencies>

Part of my quartz config: QuartzConfig:

@Configuration
public class QuartzConfig {

    @Bean
    public JobDetail xxxTask() {
        return newJob(XXX.class).withIdentity("xxx").storeDurably().build();
    }

    @Bean
    public Trigger xxxTrigger() {
        //cron
        return TriggerBuilder.newTrigger().forJob(xxxTask())
                .withIdentity("xxx")
                .withSchedule(CronScheduleBuilder.cronSchedule("0 0 */1 * * ?"))
                .build();
    }
}

Task:

@Slf4j
@Component
public class XXXTask extends QuartzJobBean {

    private static XXXService xxxService;

    static {
        xxxService = (XXXService) ApplicationUtils.getBean("xxxService");
    }

    @Override
    protected void executeInternal(JobExecutionContext jobExecutionContext) {
        xxxService.xxxTask();
    }
}
sczyh30 commented 4 years ago

It seems like the beanName is absent. @fangjian0423 Could you please take a look at it?

jasonjoo2010 commented 4 years ago

It's a bug in the sentinel module of SCA[1].

For temporary solution don't introduce any bean without bean name(bean id).

In your code you can try:

@Slf4j
@Component("xxxTaskComponent")     <<<----------------
public class XXXTask extends QuartzJobBean {

    private static XXXService xxxService;

    static {
        xxxService = (XXXService) ApplicationUtils.getBean("xxxService");   <<<<------- It's not safe and you'd better replace it with @autowired or @resource or use bean dependency.
    }

    @Override
    protected void executeInternal(JobExecutionContext jobExecutionContext) {
        xxxService.xxxTask();
    }
}

reference: [1] https://github.com/alibaba/spring-cloud-alibaba/blob/34308b0a4961260c4ef4b9e2b5e8e3b3b6f3ad7b/spring-cloud-alibaba-sentinel/src/main/java/com/alibaba/cloud/sentinel/custom/SentinelBeanPostProcessor.java#L183

fangjian0423 commented 4 years ago

Related #1076.

fangjian0423 commented 4 years ago

I add two classes in sentinel-core-example:

image

@Configuration
public class QuartzConfig {

    @Bean
    public JobDetail xxxTask() {
        return newJob(XXXTask.class).withIdentity("xxx").storeDurably().build();
    }

    @Bean
    public Trigger xxxTrigger() {
        //cron
        return TriggerBuilder.newTrigger().forJob(xxxTask())
            .withIdentity("xxx")
            .withSchedule(CronScheduleBuilder.cronSchedule("0 0 */1 * * ?"))
            .build();
    }
}
@Component
public class XXXTask extends QuartzJobBean {

    @Override
    protected void executeInternal(JobExecutionContext jobExecutionContext) {
        System.out.println("test");
    }

}

Every thing is fine!

how to reproduce it?

luhggit commented 4 years ago

@jasonjoo2010 Thank for you advice, but it doesn't work.

Even though I assign a component name to "XXXTask ", the bean name which was pass to SentinelBeanPostProcessor is still null.

I trace this bug, and finally I find the reason, this bug cause by Quartz's AutowireCapableBeanJobFactory.class:

class AutowireCapableBeanJobFactory extends SpringBeanJobFactory {

    private final AutowireCapableBeanFactory beanFactory;

    AutowireCapableBeanJobFactory(AutowireCapableBeanFactory beanFactory) {
        Assert.notNull(beanFactory, "Bean factory must not be null");
        this.beanFactory = beanFactory;
    }

    @Override
    protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
        Object jobInstance = super.createJobInstance(bundle);
        this.beanFactory.autowireBean(jobInstance);
                //  ↓↓↓↓↓↓↓↓ this line
        this.beanFactory.initializeBean(jobInstance, null);  <<<<------ this param of bean name is null
        return jobInstance;
    }

}

Now, what should I do is to rewrite the quartz job factory and replace the AutowireCapableBeanJobFactory.Let't me try....

luhggit commented 4 years ago

@fangjian0423 Oh, thank you , you give me a hint, when I replace the spring-cloud-starter-alibaba-sentinel to sentinel-core, my quartz job run successfully.

If you want to reproduce that bug, may be you should create a SpringCloud project and import the spring-cloud-starter-alibaba-sentinel like this: (pom.xml)

     <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        <version>2.0.0.RELEASE</version>
    </dependency>
sczyh30 commented 4 years ago

😂We might need to handle this scenario (though it's not the right way to register beans with null name).

jasonjoo2010 commented 4 years ago

@jasonjoo2010 Thank for you advice, but it doesn't work.

Even though I assign a component name to "XXXTask ", the bean name which was pass to SentinelBeanPostProcessor is still null.

I trace this bug, and finally I find the reason, this bug cause by Quartz's AutowireCapableBeanJobFactory.class:

class AutowireCapableBeanJobFactory extends SpringBeanJobFactory {

  private final AutowireCapableBeanFactory beanFactory;

  AutowireCapableBeanJobFactory(AutowireCapableBeanFactory beanFactory) {
      Assert.notNull(beanFactory, "Bean factory must not be null");
      this.beanFactory = beanFactory;
  }

  @Override
  protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
      Object jobInstance = super.createJobInstance(bundle);
      this.beanFactory.autowireBean(jobInstance);
                //  ↓↓↓↓↓↓↓↓ this line
      this.beanFactory.initializeBean(jobInstance, null);  <<<<------ this param of bean name is null
      return jobInstance;
  }

}

Now, what should I do is to rewrite the quartz job factory and replace the AutowireCapableBeanJobFactory.Let't me try....

I think making your own patch on SCA is an easier way :)

luhggit commented 4 years ago

@jasonjoo2010 Ok, if I have a time...