pooja pooja - 1 month ago 9
Java Question

How to add custom endpoints for executable jar

I have an executable jar developed using spring 3. It periodically performs some task using @Scheduled annotation and generates the data, mainly counters. Now I want to display these counters for monitoring and analysis purposes, similar to what spring boot provides here. I cannot use spring boot as it needs spring 4 and my jar has dependencies those use spring 3.

Here is my @configuration class:

/**
* Requester - The main entry point for this application.
*
*/
@Configuration
@ComponentScan(basePackages = "com.tpv.req")
@EnableScheduling
@ImportResource({ "classpath:/spring/applicationContext-common.xml" })
@PropertySource(value="file:/opt/requester/requester.properties")
public class Requester implements SchedulingConfigurer {

protected static final Logger logger = LoggerFactory.getLogger(Requester.class);

@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setScheduler(taskExecutor());
}

@Bean(destroyMethod = "shutdown")
public Executor taskExecutor() {
return Executors.newScheduledThreadPool(1);
}

@Bean
public static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() {
PropertySourcesPlaceholderConfigurer pspc = new PropertySourcesPlaceholderConfigurer();
return pspc;
}

@SuppressWarnings({ "unused", "resource" })
public static void main(String args[]) {
AbstractApplicationContext context = new AnnotationConfigApplicationContext(Requester.class);
}

}


@Component class :

@Component
public class CustomRequester {
@Scheduled(initialDelay = 5000, fixedDelayString = "${requester.wait.time}")
public void processRequests() {
//Perform some task
}


Tried with @Controller :

@Controller
@RequestMapping("/status")
public class StatusController {

@Autowired
Status status;


/**
* @return Status object (as json)
*/
@RequestMapping(method=RequestMethod.GET)
public @ResponseBody Status doResponse() {
return status;
}

}


This did not work.

Is there any way I can have similar endpoints without spring boot? Or how can I display these counters? Can using embedded jetty serve the purpose?

Thank you.

Answer

I figured it out. Embedding jetty can easily resolve the issue. Refactored my code a little bit, in terms of separation of config classes from main class and started jetty server from main. Here goes the code:

public class ScannerStartup {

private static Logger logger = LoggerFactory.getLogger(ScannerStartup.class);
private static final int DEFAULT_PORT = 8080;
private static final String CONTEXT_PATH = "/";
// Package of the config class
private static final String CONFIG_LOCATION = "com.tpv.req.config";
private static final String MAPPING_URL = "/*";

public static void main(String args[]) throws Exception {
    startJetty(getPortFromArgs(args));
}

private static int getPortFromArgs(String[] args) {
    if (args.length > 0) {
        try {
            return Integer.valueOf(args[0]);
        } catch (NumberFormatException ignore) {
        }
    }
    logger.debug("No server port configured, falling back to {}", DEFAULT_PORT);
    return DEFAULT_PORT;
}

private static void startJetty(int port) throws Exception {
    Server server = new Server(port);
    server.setHandler(getServletContextHandler(getContext()));
    server.start();
    logger.info("Server started at port {}", port);
    server.join();
}

private static ServletContextHandler getServletContextHandler(WebApplicationContext context) throws IOException {
    ServletContextHandler contextHandler = new ServletContextHandler();
    contextHandler.setErrorHandler(null);
    contextHandler.setContextPath(CONTEXT_PATH);
    contextHandler.addServlet(new ServletHolder(new DispatcherServlet(context)), MAPPING_URL);
    contextHandler.addEventListener(new ContextLoaderListener(context));
    return contextHandler;
}

private static WebApplicationContext getContext() {
    AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
    context.setConfigLocation(CONFIG_LOCATION);
    return context;
}

}

Config classes: I separated them out as AppConfig and WebConfig

@Configuration
@ComponentScan(basePackages = "com.tpv.req")
@EnableScheduling
@PropertySource(value = "file:/opt/scanner-application.properties")
public class AppConfig implements SchedulingConfigurer {

protected static final Logger logger = LoggerFactory.getLogger(AppConfig.class);

@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
    taskRegistrar.setScheduler(taskExecutor());
}

@Bean(destroyMethod = "shutdown")
public Executor taskExecutor() {
    return Executors.newScheduledThreadPool(1);
}

@Bean
public static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() {
    PropertySourcesPlaceholderConfigurer pspc = new PropertySourcesPlaceholderConfigurer();
    return pspc;
}
}

WebMvcConfig class:

@Configuration
@ComponentScan(basePackages = "com.tpv.req.controller")
@EnableWebMvc
public class WebMvcConfig extends WebMvcConfigurerAdapter {

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {`enter code here`
        final MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
        final ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        objectMapper.enable(SerializationFeature.INDENT_OUTPUT);
        converter.setObjectMapper(objectMapper);
        converters.add(converter);
        super.configureMessageConverters(converters);
    }

}

The main method in ScannerStartup class will load application context and config classes and these in turn will load the components specified in your project and it will run jetty server at the port provided through command line. If none provided , it will use default port 8080.

Here is an example of controller class:

@Controller
@RequestMapping("/status")
public class ScannerStatusController {

    @Autowired
    ScannerStatus status;

    /**
     * @return Status object (as json)
     */
    @RequestMapping(method=RequestMethod.GET)
    public @ResponseBody ScannerStatus doResponse()  {
        return status;
    }

}

just start the application with: java -jar {jarname}.jar This controller will display 'status' object in json format when you hit: localhost:8080/status

Comments