EJB @Schedule issue

2019-07-13 03:09发布

问题:

I need to schedule a task in my web application. The task needs to use a member field of the Servlet that is initialized during deployment. I have used the EJB @Schedule. However when the task is fired, the member field is null. I guess that the cause lies in the fact that I had to add the @Stateless annotation to the servlet in order to make the @Schedule work, and my Servlet needs to preserve its state indeed?

If yes, how can I fire my task in a simple and efficace way? Using GlassFish 3

Here is a snapshot of my code

@Stateless  // <-- Wrong ??
public class myServlet extends GenericServlet {
    private MemberField myMemberField = new MemberField();

    @Override
    public void init() throws ServletException {
        myMemberField.initialize();
    }
    @Schedule(dayOfWeek = "Mon-Fri", hour = "21", minute = "59", second = "55")
    public void myTask() {
        System.out.println(myMemberField.toString());
    }
    // other stuff
}

EDIT

The Java EE Tutorial says:

The timer service of the enterprise bean container enables you to schedule timed notifications for all types of enterprise beans except for stateful session beans

So my conclusion is that this way is not suitable for being used in a Servlet.


EDIT 2

The servlet is necessary for starting the CometD Bayeux service: see here why. MyMemberField represents the unique instance of a wrapper class that cares of the interaction with the API of the broker (this is a trading application). This wrapper class instance must be unique across all sessions and users. I initialize it on init() of the Servlet. This wrapper class sends requests to the broker and receives asynchronous answers. Maybe it's better to define this class outside the Bayeux configurator, but I don't know how to define it. As a servlet? As a Managed Bean? On top on it, I need to work with a scheduler in order to send scheduled messages to the broker. So, the scheduled task should be aware of the broker's wrapper class instance.

回答1:

When you annotate a servlet @Stateless, you have created two JavaEE components:

  1. A servlet managed by the webcontainer. The webcontainer will most likely create a single instance of the class to service all requests.
  2. A stateless session bean managed by the EJB container. The EJB container will most likely create several instances of the class and pool them to handle EJB requests.

When the servlet handles the initial request, the instance field in the servlet is initialized. When the @Schedule runs, the instance field of the EJB is initialized to something different.

My recommendation for how to resolve the problem depends on what data is stored in the instance field. Is it application-wide initialization data? If so, then I would create a separate @Singleton class with an @PostConstruct that initializes the instance data, and then move the @Schedule to that class. Is it request-dependent data? If so, then I would use TimerService.createCalendarTimer and pass the data to the timer method via the info parameter of the TimerConfig.

As an aside, if you don't need a guarantee that the timer will "catch up" while the application is stopped or if the JVM crashes, then you might want to consider using a non-persistent timer.



回答2:

@bkail hit the nail on the head. You're mixing the Servlet and EJB concepts. Split them into two separate classes.

@Singleton
public class FooTask {

    private Foo foo;

    @PostConstruct
    public void init() {
        foo = new Foo();
        foo.initialize();
    }

    @Schedule(dayOfWeek = "Mon-Fri", hour = "21", minute = "59", second = "55")
    public void run() {
        System.out.println(foo);
        // ...
    }

    public Foo getFoo() {
        return foo;
    }

}

If you want to be able to access foo on every servlet request for some reason, then you should inject it as @EJB in your servlet.

@WebServlet("/foo/*")
public class FooServlet extends HttpServlet {

    @EJB
    private FooTask fooTask;

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println(fooTask.getFoo());
        // ...
    }

}