Talk about the Java lifecycle management mechanism

  • 2020-04-01 04:35:58
  • OfStack

Pull to say again first

Recently, I have been working on a domestic open source MySQL database middleware. After pulling down the latest version of its code into eclipse, I started it up and then did various tests and code tracking. When I wanted to close it, I pulled out the STOP class and tried to run it, only to find the following lines of code written in the class.


  public static void main(String[] args) {
    System.out.println(new Date() + ",server shutdown!");
  }

When the middleware is up and running, it turns on listening, starts many threads running, and has many socket connections. But it didn't find an elegant way to close it. So I had no choice but to click the red dot of eclipse's broken heart and forcibly shut down the VM.

It is important to have a set of lifecycle management mechanisms for well-architected, well-modularized software, especially for the Server class. Not only can you manage the lifecycle of each module, you can also be more elegant when starting and stopping the entire software without missing any resources.

The lifecycle mechanism is simple to implement

Life cycle state

The life cycle states of a module generally have the following:

New - > Initialization -> Initialization complete -> Start - > Startup complete -> In suspension -> Has been suspended -> Recovering -> Has been restored -> Being destroyed -> Has been destroyed
If a transition between any of these states fails, it goes into another state: failure.

To do this, an enumeration class can be used to enumerate the states, as follows:


public enum LifecycleState {

  NEW, //new

  INITIALIZING, INITIALIZED, //Initialize the

  STARTING, STARTED, //Start the

  SUSPENDING, SUSPENDED, //suspended

  RESUMING, RESUMED,//restore

  DESTROYING, DESTROYED,//The destruction

  FAILED;//failure

}

interface

The various behaviors in the life cycle also need an interface to define, as shown below:


public interface ILifecycle {

  
  public void init() throws LifecycleException;

  
  public void start() throws LifecycleException;

  
  public void suspend() throws LifecycleException;

  
  public void resume() throws LifecycleException;

  
  public void destroy() throws LifecycleException;

  
  public void addLifecycleListener(ILifecycleListener listener);

  
  public void removeLifecycleListener(ILifecycleListener listener);

}

When a lifecycle state transition occurs, it may be necessary to trigger listeners interested in a certain type of event, so ILifeCycle also defines two methods to add and remove listeners. Public void addLifecycleListener(ILifecycleListener listener); And public void removeLifecycleListener(ILifecycleListener listener);

The listener is also defined by an interface as follows:


public interface ILifecycleListener {

  
  public void lifecycleEvent(LifecycleEvent event);
}

Lifecycle events are represented by LifecycleEvent as follows:


public final class LifecycleEvent {

  private LifecycleState state;

  public LifecycleEvent(LifecycleState state) {
    this.state = state;
  }

  
  public LifecycleState getState() {
    return state;
  }

}

Skeleton implementation

With the ILifeCycle interface, any class that implements this interface will be treated as a lifecycle management object, which can be a socket listening service, a specific module, and so on. So should we just implement ILifeCycle? So to speak, but considering that the lifecycle management objects have some common behaviors at various stages of the lifecycle, such as:

Set its own lifecycle state
Check that the transition of state is logical
Notifies the listener that the lifecycle state has changed
Therefore, it is important to provide an abstract class AbstractLifeCycle as the skeleton implementation of ILifeCycle, which avoids a lot of repetitive code and makes the architecture clearer. This abstract class implements all the interface methods defined in ILifeCycle and adds the corresponding abstract methods for subclasses to implement. AbstractLifeCycle can be implemented by:


public abstract class AbstractLifecycle implements ILifecycle {

  private List<ILifecycleListener> listeners = new CopyOnWriteArrayList<ILifecycleListener>();

  
  private LifecycleState state = LifecycleState.NEW;

  
  @Override
  public final synchronized void init() throws LifecycleException {
    if (state != LifecycleState.NEW) {
      return;
    }

    setStateAndFireEvent(LifecycleState.INITIALIZING);
    try {
      init0();
    } catch (Throwable t) {
      setStateAndFireEvent(LifecycleState.FAILED);
      if (t instanceof LifecycleException) {
        throw (LifecycleException) t;
      } else {
        throw new LifecycleException(formatString(
            "Failed to initialize {0}, Error Msg: {1}", toString(), t.getMessage()), t);
      }
    }
    setStateAndFireEvent(LifecycleState.INITIALIZED);
  }

  protected abstract void init0() throws LifecycleException;

  
  @Override
  public final synchronized void start() throws LifecycleException {
    if (state == LifecycleState.NEW) {
      init();
    }

    if (state != LifecycleState.INITIALIZED) {
      return;
    }

    setStateAndFireEvent(LifecycleState.STARTING);
    try {
      start0();
    } catch (Throwable t) {
      setStateAndFireEvent(LifecycleState.FAILED);
      if (t instanceof LifecycleException) {
        throw (LifecycleException) t;
      } else {
        throw new LifecycleException(formatString("Failed to start {0}, Error Msg: {1}",
            toString(), t.getMessage()), t);
      }
    }
    setStateAndFireEvent(LifecycleState.STARTED);
  }

  protected abstract void start0() throws LifecycleException;

  
  @Override
  public final synchronized void suspend() throws LifecycleException {
    if (state == LifecycleState.SUSPENDING || state == LifecycleState.SUSPENDED) {
      return;
    }

    if (state != LifecycleState.STARTED) {
      return;
    }

    setStateAndFireEvent(LifecycleState.SUSPENDING);
    try {
      suspend0();
    } catch (Throwable t) {
      setStateAndFireEvent(LifecycleState.FAILED);
      if (t instanceof LifecycleException) {
        throw (LifecycleException) t;
      } else {
        throw new LifecycleException(formatString("Failed to suspend {0}, Error Msg: {1}",
            toString(), t.getMessage()), t);
      }
    }
    setStateAndFireEvent(LifecycleState.SUSPENDED);
  }

  protected abstract void suspend0() throws LifecycleException;

  
  @Override
  public final synchronized void resume() throws LifecycleException {
    if (state != LifecycleState.SUSPENDED) {
      return;
    }

    setStateAndFireEvent(LifecycleState.RESUMING);
    try {
      resume0();
    } catch (Throwable t) {
      setStateAndFireEvent(LifecycleState.FAILED);
      if (t instanceof LifecycleException) {
        throw (LifecycleException) t;
      } else {
        throw new LifecycleException(formatString("Failed to resume {0}, Error Msg: {1}",
            toString(), t.getMessage()), t);
      }
    }
    setStateAndFireEvent(LifecycleState.RESUMED);
  }

  protected abstract void resume0() throws LifecycleException;

  
  @Override
  public final synchronized void destroy() throws LifecycleException {
    if (state == LifecycleState.DESTROYING || state == LifecycleState.DESTROYED) {
      return;
    }

    setStateAndFireEvent(LifecycleState.DESTROYING);
    try {
      destroy0();
    } catch (Throwable t) {
      setStateAndFireEvent(LifecycleState.FAILED);
      if (t instanceof LifecycleException) {
        throw (LifecycleException) t;
      } else {
        throw new LifecycleException(formatString("Failed to destroy {0}, Error Msg: {1}",
            toString(), t.getMessage()), t);
      }
    }
    setStateAndFireEvent(LifecycleState.DESTROYED);
  }

  protected abstract void destroy0() throws LifecycleException;

  
  @Override
  public void addLifecycleListener(ILifecycleListener listener) {
    listeners.add(listener);
  }

  
  @Override
  public void removeLifecycleListener(ILifecycleListener listener) {
    listeners.remove(listener);
  }

  private void fireLifecycleEvent(LifecycleEvent event) {
    for (Iterator<ILifecycleListener> it = listeners.iterator(); it.hasNext();) {
      ILifecycleListener listener = it.next();
      listener.lifecycleEvent(event);
    }
  }

  protected synchronized LifecycleState getState() {
    return state;
  }

  private synchronized void setStateAndFireEvent(LifecycleState newState) throws LifecycleException {
    state = newState;
    fireLifecycleEvent(new LifecycleEvent(state));
  }

  private String formatString(String pattern, Object... arguments) {
    return MessageFormat.format(pattern, arguments);
  }

  
  @Override
  public String toString() {
    return getClass().getSimpleName();
  }
}

As you can see, the skeleton implementation of the abstract class does several things common to lifecycle management, checking whether transitions between states are legal (for example, you must init before start), setting the internal state, and triggering the appropriate listener.

After the abstract class implements the methods defined by ILifeCycle, it sets aside the corresponding abstract methods for its subclasses to implement. As shown in the above code, the following abstract methods are set aside:


protected abstract void init0() throws LifecycleException;
protected abstract void start0() throws LifecycleException;
protected abstract void suspend0() throws LifecycleException;
protected abstract void resume0() throws LifecycleException;
protected abstract void destroy0() throws LifecycleException;

Elegant implementation

So far, we have defined the interface ILifeCycle and its skeleton implementation AbstractLifeCycle, and added a listener mechanism. It looks like we can start writing a class to subclass AbstractLifecycle and override the abstract method it defines, so far so good.

But before we get started, there are a couple of other things we need to think about,

Is our implementation class interested in all abstract methods?
Whether each implementation needs to implement init0, start0, suspend0, resume0, destroy0?
Are there times when our live classes or modules don't support suspend, resume ?
Directly subclassing AbstractLifeCycle means that all of its abstract methods must be implemented.
So we also need a default implementation, DefaultLifeCycle, to subclass AbstractLifeCycle, and to implement all the abstract methods, but it doesn't do anything practical, do nothing. Just let our real implementation class inherit from the default implementation class and override the methods we're interested in.

Thus, our DefaultLifeCycle is born:


public class DefaultLifecycle extends AbstractLifecycle {

  
  @Override
  protected void init0() throws LifecycleException {
    // do nothing
  }

  
  @Override
  protected void start0() throws LifecycleException {
    // do nothing
  }

  
  @Override
  protected void suspendInternal() throws LifecycleException {
    // do nothing
  }

  
  @Override
  protected void resume0() throws LifecycleException {
    // do nothing
  }

  
  @Override
  protected void destroy0() throws LifecycleException {
    // do nothing
  }

}

For DefaultLifeCycle, do nothing is its responsibility.
So next we can write our own implementation class, inherit DefaultLifeCycle, and override the lifecycle methods that interest us.

For example, I have a class that only needs to do some tasks during initialization, startup, and destruction.


import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

public class SocketServer extends DefaultLifecycle {

  private ServerSocket acceptor = null;
  private int port = 9527;
  
  @Override
  protected void init0() throws LifecycleException {
    try {
      acceptor = new ServerSocket(port);
    } catch (IOException e) {
      throw new LifecycleException(e);
    }
  }

  
  @Override
  protected void start0() throws LifecycleException {
    Socket socket = null;
    try {
      socket = acceptor.accept();
      //do something with socket
      
      
    } catch (IOException e) {
      throw new LifecycleException(e);
    } finally {
      if (socket != null) {
        try {
          socket.close();
        } catch (IOException e) {
          // TODO Auto-generated catch block
          e.printStackTrace();
        }
      }
    }
  }

  
  @Override
  protected void destroy0() throws LifecycleException {
    if (acceptor != null) {
      try {
        acceptor.close();
      } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
      }
    }
  }
}

Here in the ServerSocket, init0 initializes the socket listener, start0 begins to obtain the socket connection, and destroy0 destroys the socket listener.
With this lifecycle management mechanism, we will be able to manage resources easily, without resources being closed, and with clearer architecture and modularity.

The end of the

So far, this article has implemented a simple lifecycle management mechanism and provided all the implementation code. All the source code will then be posted to github. Stay tuned for update in this article.


Related articles: