Spring Boot - CommandLineRunner vs. ApplicationRunner

Si necesitas correr algún código específico al arranque de la aplicación con Spring Boot, puedes implementar las interfaces CommandLineRunner o ApplicationRunner. Ambas funcionan del mismo modo y ambas tienen un método run con una sola diferencia que podemos explotar según sea el caso de lo que necesitamos.

1. Si quieres correr código al arranque tienes que utilizar CommandLineRunner y tiene su propio parámetro que es un arreglo de String.

2. Si quieres correr código al arranque pero también quieres acceder a los argumentos de la aplicación con un objeto ApplicationRunner es la que debes implementar ya que esta recibe un objeto de tipo ApplicationArguments para el manejo de los argumentos de la aplicación.

 

A modo de ejemplo vamos a utilizar una base de datos embebida y JPA para el manejo de la capa de datos. Al final vamos a exponer los datos en un servicio RESTful.

 

Prerequisitos

1. Generar tu proyecto con el Initializr de Spring Boot (o lo puedes generar manualmente).

- Incluye los siguientes starters: Web, Rest Repositories, JPA y H2.

IMPORTANTE: Recuerda generar un jar, vayámonos olvidando de los odiosos archivos war.

 

2. Abre tu IDE favorito y abre el proyecto que acabas de generar.

 

IMPORTANTE

Te puedes descargar el código directamente del repositorio en github o en formato zip.

 

1. Configuración MAVEN (para el momento en que escribí este código los chicos de Pivotal movieron algo y ya no se estaba importando la librería context de Spring, así que la agregué manualmente).

<groupId>com.ledze</groupId>
<artifactId>ledzedev-commandlinerunner-applicationrunner</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>

<name>ledzedev-commandlinerunner-applicationrunner</name>
<description>Demo project for Spring Boot - CommandLineRunner and ApplicationRunner</description>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.4.4.RELEASE</version>
</parent>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

2. Código Java

/**
* Source code generated by Gerardo Pucheta Figueroa
* Twitter: @ledzedev
* http://ledze.mx
* 2/23/2017
*/
@SpringBootApplication
public class LedzedevCommandlinerunnerApplicationrunnerApplication {

private static Logger logger = LoggerFactory.getLogger(LedzedevCommandlinerunnerApplicationrunnerApplication.class);

public static void main(String[] args) {
SpringApplication.run(LedzedevCommandlinerunnerApplicationrunnerApplication.class, args);
}

/**
* Source code generated by Gerardo Pucheta Figueroa
* Twitter: @ledzedev
* http://ledze.mx
* 2/23/2017
*/
@Bean
CommandLineRunner cargaEquiposClasicos(LigaRepository ligaRepository) {

logger.info("Cargando con CommandLineRunner");
return x -> {
logger.info("CommandLineRunner tiene como parámetros un arreglo de Strings: "+ Arrays.toString(x));
Arrays.asList(
new EquipoDeFutbol("América"),
new EquipoDeFutbol("Cruz Azul"),
new EquipoDeFutbol("Chivas"),
new EquipoDeFutbol("Pumas")
).forEach(e -> {
logger.info(e.toString());
ligaRepository.save(e);
});
logger.info("Carga inicial finalizada.");
};
};

/**
* Source code generated by Gerardo Pucheta Figueroa
* Twitter: @ledzedev
* http://ledze.mx
* 2/23/2017
*/
@Bean
ApplicationRunner cargaEquiposDelNorte(LigaRepository ligaRepository){

logger.info("Cargando con ApplicationRunner");
return x -> {
logger.info("ApplicationRunner tiene como parámetro un objeto ApplicationArguments");

logger.info("non option args: "+ Arrays.toString(x.getNonOptionArgs().toArray()) );
logger.info("contiene files? "+x.containsOption("files"));
logger.info("source args: "+ Arrays.toString(x.getSourceArgs()));
logger.info("option values: "+x.getOptionValues("files"));
logger.info("option names: "+x.getOptionNames());

 Arrays.asList(
new EquipoDeFutbol("Monterrey"),
new EquipoDeFutbol("Tigres"),
new EquipoDeFutbol("Xolos")
).forEach(e -> {
logger.info(e.toString());
ligaRepository.save(e);
});
logger.info("Carga equipos del norte finalizada");
};
};
}

/**
* Source code generated by Gerardo Pucheta Figueroa
* Twitter: @ledzedev
* http://ledze.mx
* 2/23/2017
*/
@RepositoryRestResource
interface LigaRepository extends JpaRepository<EquipoDeFutbol, Long> {

Collection<EquipoDeFutbol> findByNombre(@Param("n") String nombre);
}

/**
* Source code generated by Gerardo Pucheta Figueroa
* Twitter: @ledzedev
* http://ledze.mx
* 2/23/2017
*/
@Entity
class EquipoDeFutbol {


@Id
@GeneratedValue
private Long id;


private String nombre;

public EquipoDeFutbol() {
}

public EquipoDeFutbol(String nombre) {
this.nombre = nombre;
}

public Long getId() {
return id;
}

public void setId(Long id) {
this.id = id;
}

public String getNombre() {
return nombre;
}

public void setNombre(String nombre) {
this.nombre = nombre;
}

@Override
public String toString() {

return "EquipoDeFutbol{" +
"id=" + id +
", nombre='" + nombre + '\'' +
'}';
}
}

3. Ejecución de la clase principal

Se observa la carga inicial que programamos y ambas lo hacen sin problema. La única diferencia es que ApplicacionRunner tiene un manejo de los argumentos del programa con un objeto que tiene algunos métodos convenientes para el manejo de los mismos. Se muestra en la imagen lo que regresan los métodos de ApplicationArguments.

 

 

4. Conclusión

A mi parecer CommandLineRunner es la interfaz que por excelencia deberíamos usar, sin embargo está la otra  opción para un manejo conveniente.

Aquí cuando se consulta en web como un servicio RESTful.