Over a million developers have joined DZone.

Correcting the Server Shutdown Process: A Web App Detour (Part 1)

This article makes handles the where and how to of handling the issue of Spring initialization/context, and the servlet context, so your listeners can work for you.

· Java Zone

What every Java engineer should know about microservices: Reactive Microservices Architecture.  Brought to you in partnership with Lightbend.

Introduction

We were well on our way in the previous article toward completing our third round of applications that utilize an in-house component, but we noticed an issue when shutting down the server. We'll get on with the web app completion after we handle this shutdown issue.

Here is what we noticed:

Sep 09, 2016 11:29:43 AM org.apache.catalina.loader.WebappClassLoaderBase clearReferencesJdbc

WARNING: The web application [webapp-p3] registered the JDBC driver [com.mysql.jdbc.Driver] but failed to unregister it when the web application was stopped. To prevent a memory leak, the JDBC Driver has been forcibly unregistered.

Sep 09, 2016 11:29:43 AM org.apache.catalina.loader.WebappClassLoaderBase clearReferencesThreads

WARNING: The web application [webapp-p3] appears to have started a thread named [Abandoned connection cleanup thread] but has failed to stop it. This is very likely to create a memory leak. Stack trace of thread:

java.lang.Object.wait(Native Method)

java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:143)

com.mysql.jdbc.AbandonedConnectionCleanupThread.run(AbandonedConnectionCleanupThread.java:43)


The Shape Calculator itself uses an ExecutorService  thread poolThe idea here is that when shutting down the server, we have not dealt correctly with cleaning up some resources.   I'm thinking of:

  1. And it also has a DataSource.

  2. And we have the MySQL driver.

We need to handle those in that same order.

Before we get to them, though, let's learn some more about how a Spring-based web app starts up and shuts down.

You Will Need The Code

There are two projects, one depends on the other.

Here is the code for the main project is the web app (where we noticed the shutdown issue):

The above project depends on this one, the Shape Calculator component that we've been working with for several articles.

And you'll need to add this to the first (web app) project, as we covered this in the previous article, but I didn't place it in the repository:

package com.eli.calc.shape.mvc.controllers;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;

import com.eli.calc.shape.domain.CalculationRequest;
import com.eli.calc.shape.service.ShapeCalculatorService;

@Controller
public class RequestResponseController {

    @Autowired
    private ShapeCalculatorService calculator;


    @RequestMapping(value="/",method=RequestMethod.GET)
    public ModelAndView welcomeTheSlash() {

        System.err.println("\n\n\nWelcome: The / (slash) ; Index page\n\n\n");

        return new ModelAndView("/index","message","Slash Index Page: dynamic message from Controller");
    }


    @RequestMapping(value="/index",method=RequestMethod.GET)
    public ModelAndView welcomeTheIndex() {

        System.err.println("\n\n\nWelcome2: The /index Index page\n\n\n");

        return new ModelAndView("/index","message","Index Index Page: dynamic message from Controller");
    }


    @RequestMapping(value="/newreq",method=RequestMethod.GET)
    public ModelAndView newreq() {

        System.err.println("\n\n\nNew Request\n\n\n");

        return new ModelAndView("/newreq","message","New Request Page: message from Controller");

    }

    @RequestMapping(value="/pending",method=RequestMethod.GET)
    public ModelAndView pending() {

        System.err.println("\n\n\nPending Requests\n\n\n");
        List<CalculationRequest> calcReqList = calculator.getAllPendingRequests();

        if (null==calcReqList || calcReqList.isEmpty()) {
            return new ModelAndView("/pending","message", "There are NO Pending CalculationRequests");
        }

        return new ModelAndView("/pending","calcReqList",calcReqList);

    }

    @RequestMapping(value="/results",method=RequestMethod.GET)
    public ModelAndView results() {

        System.err.println("\n\n\nCalculated Results\n\n\n");

        return new ModelAndView("/results","message","Calculated ResultsPage: message from Controller");

    }


}


And you'll need to make sure you've added this to the web app's POM; it's what links it to the other project:

<dependency>
<groupId>shape-calc-jpa-hibernate</groupId>
<artifactId>shape-calc-jpa-hibernate</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>


After you've built, deployed, and started the server, shut it down and check the end of the console output to verify you have the above errors. You'll have to have set up and started the MySQL database. (Please see the previous articles for more information on how to do that.)

Learning A Little Of The Startup Process

If you take a look at the code, we had previously added some print statements to the WebAppInitializer and to the WebConfig classes.

Once we start up the server, the console output (briefly) tells us the following:

  1. INFO: Starting Servlet Engine: Apache Tomcat/8.0.0.

  2. ELI: Web AppInitializer onStartup().

  3. ...and everything inside the onStartup() method...

  4. ELI: Web AppInitializer onStartup() done.

  5. INFO: Initializing Spring root WebApplicationContext.

  6. ...and then Spring does a lot of work initialzing the application context... and you'll see output also from Hibernate...

  7. And then nearing the end of startup: 

  8. ELI: Web Config addResourceHandlers.

  9. ELI: Web Config @Bean ViewResolvr.

  10. INFO: Initializing Spring FrameworkServlet 'dispatcherServlet'.

  11. And finally: INFO: Server startup in 37129 ms.

What we need is a "listener." This listener will come into play when the web application is being shut down, and the listener's job is to take care of cleaning up the resources that are causing the above errors.

However, the listener must ALSO be involved at some point near or during the startup, because it must KNOW (be aware of) WHAT resources it needs to take care of. (There are probably other ways to do this, but I chose this one.)

A wrinkle that we have to deal with is that of two contexts: the root application context and the servlet context. There are many good articles and posts on the web about this topic, so I won't go into the "why" here;  in this article, we'll just tackle our particular problem. Hopefully, you will gain some understanding and apply it to another situation.

Add a Listener

I added a "ShapeCalcServletContextListener" to a new package in the webapp project (NOT the shape-calc component or service project). The package is:

com.eli.calc.shape.listeners.impl.

This listener implements the "ServletContextListener" interface, which has two methods.

package com.eli.calc.shape.listeners.impl;

public class ShapeCalcServletContextListener implements ServletContextListener {

    @Override
    public void contextDestroyed(ServletContextEvent event) {

        System.err.println("\n\n\nContext is being Destroyed.....SHUTTING DOWN!!!!\n\n\n");

    }

    @Override
    public void contextInitialized(ServletContextEvent event) {

        System.err.println("\n\n\nELI:Context is Initializing...STARTING UP!!!!\n\n\n");
    }

}


The question now is: How do we get this listener involved in the startup/shutdown process?

One thing we could try is to annotate it as a @WebListener.

javax.servlet.annotation Annotation Type WebListener

@Target(value=TYPE)
@Retention(value=RUNTIME)
@Documented
public @interface WebListener

This annotation is used to declare a WebListener. Any class annotated with WebListener must implement one or more of the ServletContextListener, ServletContextAttributeListener, ServletRequestListener, ServletRequestAttributeListener, HttpSessionListener, or HttpSessionAttributeListener interfaces.


Once we add it, and startup up the server, the console output now (briefly) tells us the following:

  1. INFO: Starting Servlet Engine: Apache Tomcat/8.0.0.

  2. ELI: Web AppInitializer onStartup().

  3. ...and everything inside the onStartup() method..

  4. ELI: Web AppInitializer onStartup() done.

  5. (new after our listener added): ELI:Context is Initializing ... STARTING UP!

  6. INFO: Initializing Spring root WebApplicationContext.

  7. ...and then Spring does a lot of work initializing the application context, and you'll see output also from Hibernate...

  8. And then nearing the end of startup.

  9. ELI: Web Config addResourceHandlers.

  10. ELI: Web Config @Bean ViewResolvr.

  11. INFO: Initializing Spring FrameworkServlet 'dispatcherServlet'.

  12. And finally: INFO: Server startup in 37129 ms.

The above sequence is a problem for us. Why?

Because our new listener runs BEFORE Spring gets to do its thing. We will not be able to easily be informed of the two resources we want to handle inside the listener.

So, let's drop the @WebListener annotation.

What else could we do?

One easy thing we could try is to annotate it so that Spring will manage it. This might be good, as we are going to want to add two resources (the ExecutorService and the DataSource), which are already being managed by Spring. So let's try it.

Let's annotate our listener with @Component.

Let's  Build.Deploy.Start (BDS) (don't judge me...).

After the startup, I did a search in the console output log for "STARTING UP," but I didn't see it.

To cut to the chase, Spring DOES know about this listener. We could inject our two resources very easily into it.

But that's not the issue.

The issue is that the operations will never be fired...  the Listener will not come into play during startup and shutdown. Yes, it will know about our resources, but it won't have a chance to do anything about them.

We have a conundrum.  If we use:

  • @WebListener, we get the listener involved like we want. But it is useless to us.

  • @Component, we have a listener that is useful. But it will not be involved.

I think we're saying we can not use annotations for the listener.  We're going to have to do some something manually.

Let's open the WebAppInitializer, and add the following:

System.err.println("\n\n\n\n"
                   +"ELI: Web AppInitializer add shape calc servlet context"
                   +"listener to servletcontext..... "
                   +"\n\n\n\n");

ShapeCalcServletContextListener myServletContextListener = 
                  new ShapeCalcServletContextListener(rootCtx);

servletContext.addListener(myServletContextListener);

We have informed the web initialization of our listener, AND we have passed our listener the root context.

Here is the complete, updated code for WebAppInitializer:

package com.eli.calc.shape.config;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;

import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;

import com.eli.calc.shape.listeners.impl.ShapeCalcServletContextListener;

public class WebAppInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {

        System.err.println("\n\n\n\nELI: Web AppInitializer onStartup() \n\n\n\n");

        AnnotationConfigWebApplicationContext rootCtx =
                                new AnnotationConfigWebApplicationContext();
        rootCtx.register(WebConfig.class);

        System.err.println("\n\n\n\nELI: Web AppInitializer root context loader listener.. \n\n\n\n");
        ContextLoaderListener loaderListener = new ContextLoaderListener(rootCtx);

        System.err.println("\n\n\n\nELI: Web AppInitializer add root context loader listener to servletcontext..... \n\n\n\n");
        servletContext.addListener(loaderListener);

        System.err.println("\n\n\n\nELI: Web AppInitializer add shape calc servlet context listener to servletcontext..... \n\n\n\n");
        ShapeCalcServletContextListener myServletContextListener = new ShapeCalcServletContextListener(rootCtx);
        servletContext.addListener(myServletContextListener);

        System.err.println("\n\n\n\nELI: Web AppInitializer created dispatcher servlet passing root ctx..... \n\n\n\n");
        DispatcherServlet dispatcherServlet = new DispatcherServlet(rootCtx);

        System.err.println("\n\n\n\nELI: Web AppInitializer register dispatcher servlet with servlet context..... \n\n\n\n");
        ServletRegistration.Dynamic registration = servletContext.addServlet("dispatcherServlet",dispatcherServlet);

        registration.setLoadOnStartup(1);
        registration.addMapping("/");

        System.err.println("\n\n\n\nELI: Web AppInitializer onStartup() done\n\n\n\n");

    }

}

Eclipse complains because it can not find an appropriate constructor inside the listener.

Open the ShapeCalcServletContextListener, add the constructor, and let's add some internal members:

  • private ApplicationContext ctx;

  • private DataSource dataSource;

  • private ExecutorService executor;

Remember, we were not able to use annotations and make the listener automatically managed by Spring, so we need to load those resources ourselves.

Here is the updated listener code:

package com.eli.calc.shape.listeners.impl;

import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Enumeration;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
import javax.sql.DataSource;

import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;

//@WebListener
@Component
public class ShapeCalcServletContextListener implements ServletContextListener {

    private ApplicationContext ctx;

    private DataSource dataSource;

    private ExecutorService executor;

    public ShapeCalcServletContextListener(ApplicationContext ctx) { this.ctx = ctx; }


    @Override
    public void contextDestroyed(ServletContextEvent event) {

        System.err.println("\n\n\nContext is being Destroyed.....SHUTTING DOWN!!!!\n\n\n");

    }

    @Override
    public void contextInitialized(ServletContextEvent event) {

        System.err.println("\n\n\nELI:Context is Initializing...STARTING UP!!!!\n\n\n");

        dataSource = ctx.getBean(DataSource.class);
        executor = ctx.getBean(ExecutorService.class);

    }

}


OK, let's BDS.

This time, the console output now (briefly) tells us the following:

  1. INFO: Starting Servlet Engine: Apache Tomcat/8.0.0.

  2. ELI: Web AppInitializer onStartup().

  3. ...and everything inside the onStartup() method...

  4. ELI: Web AppInitializer onStartup() done.
  5. INFO: Initializing Spring root WebApplicationContext.

  6. ... and then Spring does a lot of work initializing the application context, and you'll see output also from Hibernate...

  7. And then nearing the end of startup: 

  8. ELI: Web Config addResourceHandlers.

  9. ELI: Web Config @Bean ViewResolvr.
  10. ELI:Context is Initializing ... STARTING UP!

  11. INFO: Initializing Spring FrameworkServlet 'dispatcherServlet'.

  12. And finally: INFO: Server startup in 37129 ms.

This is GOOD. Notice that the listener was involved. Notice that it was involved AFTER Spring had done its work.

Next...

We ran on quite a bit. This article makes the point of where/how to handle the issue of Spring initialization/context, and the servlet context.

We still haven't taken care of cleaning up our resources during server shutdown, though. We will cover that in the next article and then continue where we left off with our web application.

See you next time!

Microservices for Java, explained. Revitalize your legacy systems (and your career) with Reactive Microservices Architecture, a free O'Reilly book. Brought to you in partnership with Lightbend.

Topics:
java 8 ,j2ee ,spring ,datasource ,server ,memory leaks ,shutdown ,servlet container ,annotations ,context and dependency injection

Opinions expressed by DZone contributors are their own.

The best of DZone straight to your inbox.

SEE AN EXAMPLE
Please provide a valid email address.

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.
Subscribe

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}