0

I am trying to run some job in my Spring MVC application periodically. Based on tutorials online I set up the Scheduled Job as follows:

Here is the AppInitializer (I have no setup in the XMLs):

public class AppInitializer implements WebApplicationInitializer {
    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {

        AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();  
        rootContext.register(JPAConfiguration.class);

        servletContext.addListener(new ContextLoaderListener(rootContext));

        AnnotationConfigWebApplicationContext dispatcherServlet = new AnnotationConfigWebApplicationContext();
        dispatcherServlet.register(MvcConfig.class);

        ServletRegistration.Dynamic dispatcher = servletContext.addServlet("dispatcher", new DispatcherServlet(dispatcherServlet));
        dispatcher.setLoadOnStartup(1);
        dispatcher.addMapping("/");

    }
}

Here is the configuration file:

@Configuration
@EnableScheduling
@EnableTransactionManagement
@ComponentScan(basePackages = {"com.prime.tutorials"})
@EnableJpaRepositories(basePackages = {"com.prime.tutorials.model", "com.prime.tutorials.repository"})
public class JPAConfiguration {

    @Value("${jdbcURL}")
    private transient String jdbcURL;

    @Value("${dbPassword}")
    private transient String dbPassword;

    /* The usual stuff here, let me know if you want me post that as well */


}

This is the class handling the Scheduled job:

@Service
public class ScheduledJobService {

    @Autowired
    private PrimeRepository primeRepository

    @Scheduled(fixedDelayString = "${fixedDelay.in.milliseconds:10000}")
    public void run() {
        System.out.println("Current time is :: " + Calendar.getInstance().getTime());
    }

}

As you can see the fixed delay is set to 10 secs but my job is running every 5 seconds. I am not able to understand why this is happening. I went through the questions posted in regards to this topic before but I havent been able to find a matching solution.

Other posts suggest that in such cases the beans might be getting initialized twice but based on my config I am not sure how that is happening.

Similar Question This question above seems like an exact duplicate of the what I am asking but the OP hasnt posted his/her configuration or setup. Most of the answers suggest double initialization of the components which I am not sure is the case with my application.

Current time is :: Sun Jun 10 22:53:16 EDT 2018
Current time is :: Sun Jun 10 22:53:22 EDT 2018
Current time is :: Sun Jun 10 22:53:26 EDT 2018
Current time is :: Sun Jun 10 22:53:32 EDT 2018
Current time is :: Sun Jun 10 22:53:36 EDT 2018

EDIT-1

Based on suggestion from Jennifer I do see that two instances are calling the run method.

EDIT-2

M.Deinum's guess was absolutely right, my MvcConfig.java was annotated by @ComponentScan which made the Schedule Job run twice. But after removing that annotation from the MvcConfig.java my end-points stopped working. What am I missing here..

Nick Div
  • 5,338
  • 12
  • 65
  • 127
  • declare `fixedDelay.in.milliseconds` in application.properties – John Joe Jun 11 '18 at 03:21
  • @JohnJoe Its already in properties. 10000 is a default value. – Nick Div Jun 11 '18 at 03:36
  • Let me guess... The `MvcConfig` also has a `@ComponentScan(basePackages = {"com.prime.tutorials"})`. – M. Deinum Jun 11 '18 at 06:14
  • @M.Deinum My bad, that was absolutely right. Removing that annotation actually made the scheduled job execute at the right frequency but my end-points stopped working. Is there a way out and should I post another question or edit the existing one? – Nick Div Jun 11 '18 at 07:00
  • Your `MvcConfig` should only detect web related components. Currently it detects everything and you are basically loading your whole application twice (I suspect it will also detect the `JPAConfiguration`). You should disable the default filters and add an include filter for `@Controller` and `@ControllerAdvice` and in your root context you should exclude the `@Controller` and `@ControllerAdvice`. – M. Deinum Jun 11 '18 at 07:17
  • @M.Deinum I am not sure what you mean by 'disable the default filters' But if I understand correctly, should I just scan 'com.prime.tutorials.controller' package in the MvcConfig.java and rest of the packages in JPAConfiguration.java – Nick Div Jun 11 '18 at 07:24
  • No... You want to disable the detection of certain components... (Also technical separation is a wrong way to split things imho). You can set `useDefaultFilters` to `false` on `@ComponentScan` and then also configure `includeFilters` and `excludeFilters`. – M. Deinum Jun 11 '18 at 07:38
  • @M.Deinum Now I get it. What you said makes perfect sense. Thanks a lot for your patience and your explanation. Appreciate it. – Nick Div Jun 11 '18 at 07:48

2 Answers2

2

This typically happens when you create more than one instance of the Scheduled class (ScheduledJobService). A common cause is that the Spring context is created more than once.

Add this to the run method to see if you have more than one instance:

public void run() {
   System.out.println(this + " Current time is :: " + Calendar.getInstance().getTime());
}

You will be able to see in the output if its more than one instance.

To make sure its not the property value, try without a hard coded value of 10000.

@Scheduled(fixedDelay = 10000)
Jennifer P.
  • 377
  • 1
  • 3
  • 17
2

The problem is that you, probably, have the same component scan in both your JPAConfiguration and MvcConfig. The result is that you basically are loading your whole application twice (so unless you want to have memory-issues, weird transactional issues etc. that is the way to go).

What you want is that your ContextLoaderListener (the root context) loads everything but web related beans and the DispatcherServlet should load only web related beans (views, controllers, advices etc.).

The @ComponentScan has properties with which you can control this. In the JPAConfiguration add some excludeFilters on the MvcConfig disable the defaults and add some includeFilters.

Your @ComponentScan for the JPAConfiguration should contain the following:

@ComponentScan(
        basePackages = {"com.prime.tutorials"},
        excludeFilters = {
            @ComponentScan.Filter( { Controller.class, ControllerAdvice.class })
})

Your MvcConfig you should use the same but as an includeFilters instead and disable the default filters.

@ComponentScan(
        basePackages = {"com.prime.tutorials"},
        useDefaultFilters = false,
        includeFilters = {
            @ComponentScan.Filter( { Controller.class, ControllerAdvice.class })
})

You don't want to do different base packages as that would be quite cumbersome. It is also IMHO that you shouldn't use technical separation as a way of creating packages (see also https://softwareengineering.stackexchange.com/questions/258196/java-application-structure-horizontal-vs-vertical-split/258197#258197) .

M. Deinum
  • 115,695
  • 22
  • 220
  • 224