¿Puede un programa depender de una biblioteca durante la compilación pero no en tiempo de ejecución?


110

Entiendo la diferencia entre tiempo de ejecución y tiempo de compilación y cómo diferenciar entre los dos, pero no veo la necesidad de hacer una distinción entre dependencias de tiempo de compilación y tiempo de ejecución .

Con lo que me estoy asfixiando es esto: ¿cómo puede un programa no depender de algo en tiempo de ejecución de lo que dependía durante la compilación? Si mi aplicación Java usa log4j, entonces necesita el archivo log4j.jar para compilar (mi código se integra e invoca métodos de miembros desde dentro de log4j) así como el tiempo de ejecución (mi código no tiene absolutamente ningún control sobre lo que sucede una vez que el código dentro de log4j .jar se ejecuta).

Estoy leyendo sobre herramientas de resolución de dependencias como Ivy y Maven, y estas herramientas claramente hacen la distinción entre estos dos tipos de dependencias. Simplemente no entiendo la necesidad de hacerlo.

¿Alguien puede dar una explicación simple del tipo "King's English", preferiblemente con un ejemplo real que incluso un pobre idiota como yo podría entender?


2
Puede usar la reflexión y usar clases que no estaban disponibles en el momento de la compilación. Piense en "complemento".
Per Alexandersson

Respuestas:


64

Generalmente se requiere una dependencia en tiempo de compilación en tiempo de ejecución. En maven, compilese agregará una dependencia de ámbito a la ruta de clase en tiempo de ejecución (por ejemplo, en guerras, se copiarán en WEB-INF / lib).

Sin embargo, no es estrictamente necesario; por ejemplo, podemos compilar con una determinada API, convirtiéndola en una dependencia en tiempo de compilación, pero luego, en tiempo de ejecución, incluir una implementación que también incluya la API.

Puede haber casos marginales en los que el proyecto requiera cierta dependencia para compilarse, pero luego el código correspondiente no es realmente necesario, pero estos serán raros.

Por otro lado, es muy común incluir dependencias en tiempo de ejecución que no son necesarias en tiempo de compilación. Por ejemplo, si está escribiendo una aplicación Java EE 6, compila con la API Java EE 6, pero en tiempo de ejecución, se puede utilizar cualquier contenedor Java EE; es este contenedor el que proporciona la implementación.

Las dependencias en tiempo de compilación se pueden evitar mediante la reflexión. Por ejemplo, un controlador JDBC se puede cargar con a Class.forNamey la clase real cargada se puede configurar a través de un archivo de configuración.


17
Acerca de la API de Java EE: ¿no es para eso el alcance de dependencia "proporcionado"?
Kevin

15
un ejemplo en el que se necesita una dependencia para compilar pero no en tiempo de ejecución es lombok (www.projectlombok.org). El jar se utiliza para transformar el código Java en tiempo de compilación, pero no es necesario en absoluto en tiempo de ejecución. Especificar el alcance "proporcionado" hace que el jar no se incluya en war / jar.
Kevin

2
@Kevin Sí, buen punto, el providedalcance agrega una dependencia de tiempo de compilación sin agregar una dependencia de tiempo de ejecución con la expectativa de que la dependencia se proporcionará en tiempo de ejecución por otros medios (por ejemplo, una biblioteca compartida en el contenedor). runtimepor otro lado, agrega una dependencia en tiempo de ejecución sin convertirla en una dependencia en tiempo de compilación.
Artefacto

Entonces, ¿es seguro decir que generalmente existe una correlación 1: 1 entre una "configuración de módulo" (usando términos de Ivy) y el directorio principal en la raíz de su proyecto? Por ejemplo, todas mis pruebas JUnit que dependen del JUnit JAR estarán bajo la prueba / raíz, etc. Simplemente no veo cómo las mismas clases, empaquetadas bajo la misma raíz de origen, podrían "configurarse" para depender de diferentes JAR en cualquier momento. Si necesita log4j, entonces necesita log4j; no hay forma de decirle al mismo código que invoque las llamadas log4j bajo 1 configuración, pero ignorar las llamadas log4j bajo alguna configuración "sin registro", ¿verdad?
IAmYourFaja

30

Cada dependencia de Maven tiene un alcance que define en qué ruta de clase está disponible esa dependencia.

Cuando crea un JAR para un proyecto, las dependencias no se empaquetan con el artefacto generado; se utilizan solo para la compilación. (Sin embargo, aún puede hacer que maven incluya las dependencias en el jar construido, consulte: Incluir dependencias en un jar con Maven )

Cuando usa Maven para crear un archivo WAR o EAR, puede configurar Maven para agrupar dependencias con el artefacto generado, y también puede configurarlo para excluir ciertas dependencias del archivo WAR utilizando el alcance proporcionado.

El alcance más común - Alcance de compilación - indica que la dependencia está disponible para su proyecto en la ruta de clases de compilación, las rutas de clases de ejecución y compilación de pruebas unitarias y la ruta de clases en tiempo de ejecución eventual cuando ejecuta su aplicación. En una aplicación web Java EE, esto significa que la dependencia se copia en su aplicación implementada. Sin embargo, en un archivo .jar, las dependencias no se incluirán en el ámbito de compilación.

Runtime Scope indica que la dependencia está disponible para su proyecto en la ejecución de la prueba unitaria y en las rutas de clases de ejecución en tiempo de ejecución, pero a diferencia del alcance de compilación, no está disponible cuando compila su aplicación o sus pruebas unitarias. Una dependencia de tiempo de ejecución se copia en su aplicación implementada, pero no está disponible durante la compilación. Esto es bueno para asegurarse de no depender por error de una biblioteca específica.

Finalmente, Provided Scope indica que el contenedor en el que se ejecuta su aplicación proporciona la dependencia en su nombre. En una aplicación Java EE, esto significa que la dependencia ya está en la ruta de clase del servidor de aplicaciones o del contenedor Servlet y no se copia en la aplicación implementada. También significa que necesita esta dependencia para compilar su proyecto.


@Koray Tugay La respuesta es más precisa :) Tengo una pregunta rápida, digo que tengo un frasco de dependencia con el alcance del tiempo de ejecución. ¿El experto buscará el jar en tiempo de compilación?
gks

@gks No, no lo requerirá en tiempo de compilación.
Koray Tugay

9

Necesita en tiempo de compilación dependencias que pueda necesitar en tiempo de ejecución. Sin embargo, muchas bibliotecas se ejecutan sin todas sus posibles dependencias. es decir, una biblioteca que puede usar cuatro bibliotecas XML diferentes, pero solo necesita una para funcionar.

Muchas bibliotecas, a su vez, necesitan otras bibliotecas. Estas bibliotecas no son necesarias en tiempo de compilación, pero sí en tiempo de ejecución. es decir, cuando el código se ejecuta realmente.


¿Podría darnos ejemplos de tales bibliotecas que no serán necesarias en la compilación pero sí en el tiempo de ejecución?
Cristiano

1
@Cristiano todas las bibliotecas JDBC son así. También bibliotecas que implementan una API estándar.
Peter Lawrey

4

Generalmente tiene razón y probablemente es la situación ideal si las dependencias de tiempo de ejecución y tiempo de compilación son idénticas.

Le daré 2 ejemplos cuando esta regla es incorrecta.

Si la clase A depende de la clase B, que depende de la clase C, que depende de la clase D, donde A es su clase y B, C y D son clases de diferentes bibliotecas de terceros, solo necesita B y C en tiempo de compilación y también necesita D en tiempo de ejecución. A menudo, los programas utilizan la carga de clases dinámica. En este caso, no necesita clases cargadas dinámicamente por la biblioteca que está utilizando en tiempo de compilación. Además, a menudo, la biblioteca elige qué implementación usar en tiempo de ejecución. Por ejemplo, SLF4J o Commons Logging pueden cambiar la implementación del registro de destino en tiempo de ejecución. Solo necesita SSL4J en el momento de la compilación.

Ejemplo opuesto cuando necesita más dependencias en tiempo de compilación que en tiempo de ejecución. Piense que está desarrollando una aplicación que debe funcionar en diferentes entornos o sistemas operativos. Necesita todas las bibliotecas específicas de la plataforma en tiempo de compilación y solo las bibliotecas necesarias para el entorno actual en tiempo de ejecución.

Espero que mis explicaciones ayuden.


¿Puede explicar por qué se necesita C en tiempo de compilación en su ejemplo? Tengo la impresión (de stackoverflow.com/a/7257518/6095334 ) de que si se necesita o no C en el momento de la compilación depende de los métodos y campos (de B) a los que hace referencia A.
Hervian


2

Me encontré con un problema que responde a su pregunta. servlet-api.jares una dependencia transitoria en mi proyecto web y se necesita tanto en tiempo de compilación como en tiempo de ejecución. Pero servlet-api.jartambién está incluido en mi biblioteca Tomcat.

La solución aquí es hacer que servlet-api.jaren maven esté disponible solo en el momento de la compilación y no empaquetado en mi archivo war para que no entre en conflicto con el servlet-api.jarcontenido en mi biblioteca Tomcat.

Espero que esto explique el tiempo de compilación y la dependencia del tiempo de ejecución.


3
Su ejemplo es realmente incorrecto para la pregunta dada, porque explica la diferencia entre compiley providedalcances y no entre compiley runtime. Compile scopese necesita en tiempo de compilación y está empaquetado en su aplicación. Provided scopesolo se necesita en el momento de la compilación, pero no está empaquetado en su aplicación porque lo proporciona otro medio, por ejemplo, ya está en el servidor Tomcat.
MJar

1
Bueno, creo que esto es más bien un buen ejemplo, porque la pregunta era cuanto a tiempo de compilación en tiempo de ejecución y las dependencias y no acerca compiley runtime alcances Maven . El providedalcance es la forma en que maven maneja el caso en el que una dependencia en tiempo de compilación no debe incluirse en el paquete de tiempo de ejecución.
Christian Gawron

1

Entiendo la diferencia entre tiempo de ejecución y tiempo de compilación y cómo diferenciar entre los dos, pero no veo la necesidad de hacer una distinción entre dependencias de tiempo de compilación y tiempo de ejecución.

Los conceptos generales de tiempo de compilación y tiempo de ejecución y las dependencias específicas compiley de runtimealcance de Maven son dos cosas muy diferentes. No puede compararlos directamente ya que estos no tienen el mismo marco: los conceptos generales de compilación y tiempo de ejecución son amplios, mientras que los conceptos de maven compiley runtimealcance tratan específicamente sobre la disponibilidad / visibilidad de las dependencias según el tiempo: compilación o ejecución.
No olvides que Maven es sobre todo un javac/ javawrapper y que en Java tienes una ruta de clase de tiempo de compilación que especificas javac -cp ... y una ruta de clase de tiempo de ejecución que especificas java -cp ....
No sería incorrecto considerar el compilealcance de Maven como una forma de agregar una dependencia tanto en la compilación de Java como en la classppath en tiempo de ejecución (javacy java) mientras que el runtimealcance de Maven puede verse como una forma de agregar una dependencia solo en el tiempo de ejecución de Java classppath ( javac).

Con lo que me estoy asfixiando es esto: ¿cómo puede un programa no depender de algo en tiempo de ejecución de lo que dependía durante la compilación?

Lo que usted describe no tiene ninguna relación runtimeni compilealcance.
Se parece más al providedalcance que especifica para que una dependencia dependa de eso en el tiempo de compilación pero no en el tiempo de ejecución.
Lo usa porque necesita la dependencia para compilar, pero no desea incluirlo en el componente empaquetado (JAR, WAR o cualquier otro) porque la dependencia ya la proporciona el entorno: se puede incluir en el servidor o en cualquier Se inicia la ruta de la ruta de clases especificada cuando se inicia la aplicación Java.

Si mi aplicación Java usa log4j, entonces necesita el archivo log4j.jar para compilar (mi código se integra e invoca métodos de miembros desde dentro de log4j) así como el tiempo de ejecución (mi código no tiene absolutamente ningún control sobre lo que sucede una vez que el código dentro de log4j .jar se ejecuta).

En este caso sí. Pero suponga que necesita escribir un código portátil que se base en slf4j como fachada frente a log4j para poder cambiar a otra implementación de registro más adelante (log4J 2, logback o cualquier otro).
En este caso, en su pom, debe especificar slf4j como una compiledependencia (es el valor predeterminado) pero especificará la dependencia log4j como una runtimedependencia:

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>...</version>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>...</version>
    <scope>runtime</scope>
</dependency>

De esta manera, las clases log4j no podrían ser referenciadas en el código compilado pero aún podrá referirse a clases slf4j.
Si especificó las dos dependencias con el compiletiempo, nada le impedirá hacer referencia a las clases log4j en el código compilado y podría crear un acoplamiento no deseado con la implementación de registro:

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>...</version>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>...</version>
</dependency>

Un uso común del runtimealcance es la declaración de dependencia de JDBC. Para escribir código portátil, no desea que el código del cliente pueda referirse a clases de la dependencia DBMS específica (por ejemplo: dependencia PostgreSQL JDBC) pero desea que de todos modos lo incluya en su aplicación, ya que en tiempo de ejecución las clases son necesarias para hacer la API de JDBC funciona con este DBMS.


0

En el momento de la compilación, habilita los contratos / api que se espera de sus dependencias. (por ejemplo: aquí simplemente firma un contrato con el proveedor de Internet de banda ancha) En el tiempo de ejecución, en realidad está utilizando las dependencias. (por ejemplo: aquí realmente está utilizando Internet de banda ancha)


0

Para responder a la pregunta "¿cómo puede un programa no depender de algo en tiempo de ejecución de lo que dependía durante la compilación?", Veamos el ejemplo de un procesador de anotaciones.

Suponga que ha escrito su propio procesador de anotaciones, y suponga que tiene una dependencia en tiempo de compilación com.google.auto.service:auto-servicepara poder usar @AutoService. Esta dependencia solo es necesaria para compilar el procesador de anotaciones, pero no es necesaria en tiempo de ejecución: todos los demás proyectos que dependen de su procesador de anotaciones para procesar anotaciones no requieren la dependencia de com.google.auto.service:auto-serviceen tiempo de ejecución (ni en tiempo de compilación ni en ningún otro momento) .

Esto no es muy común, pero sucede.


0

El runtimealcance está ahí para evitar que los programadores agreguen dependencias directas a las bibliotecas de implementación en el código en lugar de usar abstracciones o fachadas.

En otras palabras, obliga a utilizar interfaces.

Ejemplos concretos:

1) Su equipo está usando SLF4J sobre Log4j. Quiere que sus programadores usen la API de SLF4J, no la de Log4j. SLF4J debe utilizar Log4j solo internamente. Solución:

  • Defina SLF4J como una dependencia regular en tiempo de compilación
  • Defina log4j-core y log4j-api como dependencias del tiempo de ejecución.

2) Su aplicación está accediendo a MySQL usando JDBC. Quiere que sus programadores codifiquen contra la abstracción estándar de JDBC, no directamente contra la implementación del controlador MySQL.

  • Defina mysql-connector-java(controlador MySQL JDBC) como una dependencia de tiempo de ejecución.

Las dependencias en tiempo de ejecución se ocultan durante la compilación (arrojando errores en tiempo de compilación si su código tiene una dependencia "directa" de ellas) pero se incluyen durante el tiempo de ejecución y al crear artefactos desplegables (archivos WAR, archivos jar SHADED, etc.).

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.