Um programa pode depender de uma biblioteca durante a compilação, mas não durante a execução?

110

Eu entendo a diferença entre tempo de execução e tempo de compilação e como diferenciá-los, mas simplesmente não vejo a necessidade de fazer uma distinção entre dependências de tempo de compilação e tempo de execução .

O que estou engasgando é o seguinte: como um programa pode não depender de algo em tempo de execução de que dependia durante a compilação? Se meu aplicativo Java usa log4j, ele precisa do arquivo log4j.jar para compilar (meu código se integra e invoca métodos de membro de dentro de log4j), bem como o tempo de execução (meu código não tem absolutamente nenhum controle sobre o que acontece quando o código está dentro de log4j .jar é executado).

Estou lendo sobre ferramentas de resolução de dependência, como Ivy e Maven, e essas ferramentas claramente fazem a distinção entre esses dois tipos de dependências. Eu simplesmente não entendo a necessidade disso.

Alguém pode dar uma explicação simples do tipo "King's English", de preferência com um exemplo real que até um pobre coitado como eu poderia entender?

IAmYourFaja
fonte
2
Você pode usar reflexão e classes que não estavam disponíveis em tempo de compilação. Pense em "plugin".
Por Alexandersson

Respostas:

64

Uma dependência de tempo de compilação geralmente é necessária no tempo de execução. No maven, uma compiledependência com escopo será adicionada ao classpath no tempo de execução (por exemplo, no wars, eles serão copiados para WEB-INF / lib).

No entanto, não é estritamente necessário; por exemplo, podemos compilar em relação a uma determinada API, tornando-a uma dependência em tempo de compilação, mas em tempo de execução incluir uma implementação que também inclui a API.

Pode haver casos marginais em que o projeto requer uma certa dependência para compilar, mas o código correspondente não é realmente necessário, mas isso será raro.

Por outro lado, incluir dependências de tempo de execução que não são necessárias em tempo de compilação é muito comum. Por exemplo, se você está escrevendo um aplicativo Java EE 6, compila com a API Java EE 6, mas em tempo de execução, qualquer contêiner Java EE pode ser usado; é esse contêiner que fornece a implementação.

As dependências de tempo de compilação podem ser evitadas usando reflexão. Por exemplo, um driver JDBC pode ser carregado com um Class.forNamee a classe real carregada pode ser configurada por meio de um arquivo de configuração.

Artefacto
fonte
17
Sobre a API Java EE - não é para isso que serve o escopo de dependência "fornecido"?
Kevin
15
um exemplo em que uma dependência é necessária para compilar, mas não é necessária em tempo de execução é lombok (www.projectlombok.org). O jar é usado para transformar o código java em tempo de compilação, mas não é necessário em tempo de execução. Especificar o escopo "fornecido" faz com que o jar não seja incluído no war / jar.
Kevin
2
@Kevin Sim, bom ponto, o providedescopo adiciona uma dependência de tempo de compilação sem adicionar uma dependência de tempo de execução na expectativa de que a dependência será fornecida em tempo de execução por outros meios (por exemplo, uma biblioteca compartilhada no contêiner). runtimepor outro lado, adiciona uma dependência de tempo de execução sem torná-la uma dependência de tempo de compilação.
Artefacto de
Portanto, é seguro dizer que geralmente há uma correlação de 1: 1 entre uma "configuração de módulo" (usando termos Ivy) e o diretório principal na raiz do projeto? Por exemplo, todos os meus testes JUnit que dependem do JUnit JAR estarão no teste / raiz, etc. Eu simplesmente não vejo como as mesmas classes, empacotadas na mesma raiz de origem, podem ser "configuradas" para depender de diferentes JARs a qualquer momento. Se você precisar do log4j, precisará do log4j; não há como dizer ao mesmo código para invocar chamadas log4j sob 1 configuração, mas ignorar chamadas log4j sob alguma configuração "sem registro", certo?
IAmYourFaja de
30

Cada dependência do Maven tem um escopo que define em qual caminho de classe essa dependência está disponível.

Ao criar um JAR para um projeto, as dependências não são empacotadas com o artefato gerado; eles são usados ​​apenas para compilação. (No entanto, você ainda pode fazer o maven incluir as dependências no jar construído, consulte: Incluindo dependências em um jar com o Maven )

Ao usar o Maven para criar um WAR ou um arquivo EAR, você pode configurar o Maven para agrupar dependências com o artefato gerado e também pode configurá-lo para excluir certas dependências do arquivo WAR usando o escopo fornecido.

O escopo mais comum - Compile Scope - indica que a dependência está disponível para seu projeto no classpath de compilação, nos classpaths de compilação e execução de teste de unidade e no eventual runtime classpath quando você executa seu aplicativo. Em um aplicativo da web Java EE, isso significa que a dependência é copiada em seu aplicativo implementado. Em um arquivo .jar, entretanto, as dependências não serão incluídas no escopo de compilação.

Runtime Scope indica que a dependência está disponível para seu projeto na execução do teste de unidade e nos classpaths de execução em tempo de execução, mas ao contrário do escopo de compilação, ele não está disponível quando você compila seu aplicativo ou seus testes de unidade. Uma dependência de tempo de execução é copiada em seu aplicativo implantado, mas não está disponível durante a compilação! Isso é bom para garantir que você não dependa por engano de uma biblioteca específica.

Por fim, o Escopo fornecido indica que o contêiner no qual seu aplicativo é executado fornece a dependência em seu nome. Em um aplicativo Java EE, isso significa que a dependência já está no contêiner do Servlet ou no classpath do servidor de aplicativos e não é copiada em seu aplicativo implementado. Isso também significa que você precisa dessa dependência para compilar seu projeto.

Koray Tugay
fonte
@Koray Tugay A resposta é mais precisa :) Tenho uma pergunta rápida, digamos que tenho um jar de dependência com o escopo do tempo de execução. O maven estará procurando o jar em tempo de compilação?
gks de
@gks Não, não exigirá isso em tempo de compilação.
Koray Tugay
9

Você precisa em tempo de compilação dependências que você pode precisar em tempo de execução. No entanto, muitas bibliotecas são executadas sem todas as suas dependências possíveis. ou seja, uma biblioteca que pode usar quatro bibliotecas XML diferentes, mas só precisa de uma para funcionar.

Muitas bibliotecas, por sua vez, precisam de outras bibliotecas. Essas bibliotecas não são necessárias em tempo de compilação, mas são necessárias em tempo de execução. ou seja, quando o código é realmente executado.

Peter Lawrey
fonte
você poderia nos dar exemplos de tais bibliotecas que não serão necessárias na compilação, mas serão necessárias em tempo de execução?
Cristiano
1
@Cristiano todas as bibliotecas JDBC são assim. Também bibliotecas que implementam uma API padrão.
Peter Lawrey
4

Geralmente você está certo e provavelmente é a situação ideal se as dependências de tempo de execução e de compilação forem idênticas.

Vou te dar 2 exemplos quando esta regra está incorreta.

Se a classe A depende da classe B que depende da classe C que depende da classe D, onde A é sua classe e B, C e D são classes de diferentes bibliotecas de terceiros, você precisa apenas de B e C em tempo de compilação e também de D em tempo de execução. Freqüentemente, os programas usam carregamento de classe dinâmico. Neste caso, você não precisa de classes carregadas dinamicamente pela biblioteca que você está usando em tempo de compilação. Além disso, muitas vezes a biblioteca escolhe qual implementação usar em tempo de execução. Por exemplo, SLF4J ou Commons Logging podem alterar a implementação do log de destino no tempo de execução. Você precisa apenas do próprio SSL4J no momento da compilação.

Exemplo oposto quando você precisa de mais dependências em tempo de compilação do que em tempo de execução. Pense que você está desenvolvendo um aplicativo que deve funcionar em diferentes ambientes ou sistemas operacionais. Você precisa de todas as bibliotecas específicas da plataforma em tempo de compilação e apenas as bibliotecas necessárias para o ambiente atual em tempo de execução.

Espero que minhas explicações ajudem.

AlexR
fonte
Você pode explicar por que C é necessário em tempo de compilação em seu exemplo? Tenho a impressão (de stackoverflow.com/a/7257518/6095334 ) que se C é ou não necessário em tempo de compilação depende de quais métodos e campos (de B) A está fazendo referência.
Hervian
2

Acabei de encontrar um problema que responde à sua pergunta. servlet-api.jaré uma dependência temporária no meu projeto da web e é necessária tanto no tempo de compilação quanto no tempo de execução. Mas servlet-api.jartambém está incluído na minha biblioteca Tomcat.

A solução aqui é tornar o servlet-api.jarmaven disponível apenas em tempo de compilação e não empacotado em meu arquivo war para que ele não entre em conflito com o servlet-api.jarcontido em minha biblioteca Tomcat.

Espero que isso explique a dependência do tempo de compilação e do tempo de execução.

Mayoor
fonte
3
Seu exemplo é realmente incorreta para determinada questão, porque isso explica a diferença entre compilee providedescopos e não entre compilee runtime. Compile scopeé necessário em tempo de compilação e é empacotado em seu aplicativo. Provided scopesó é necessário em tempo de compilação, mas não é empacotado em seu aplicativo porque é fornecido por outro meio, por exemplo, já está no servidor Tomcat.
MJar
1
Bem, eu acho que isso é uma vez bom exemplo, porque a pergunta era sobre tempo de compilação e tempo de execução dependências e não sobre compilee runtime escopos Maven . O providedescopo é a forma como o maven trata o caso em que uma dependência de tempo de compilação não deve ser incluída no pacote de tempo de execução.
Christian Gawron
1

Eu entendo a diferença entre tempo de execução e tempo de compilação e como diferenciá-los, mas simplesmente não vejo a necessidade de fazer uma distinção entre dependências de tempo de compilação e tempo de execução.

Os conceitos gerais de tempo de compilação e tempo de execução e as dependências específicas compilee de runtimeescopo do Maven são duas coisas muito diferentes. Você não pode compará-los diretamente, já que eles não têm o mesmo quadro: os conceitos gerais de compilação e tempo de execução são amplos, enquanto os conceitos de maven compilee runtimeescopo tratam especificamente da disponibilidade / visibilidade das dependências de acordo com o tempo: compilação ou execução.
Não se esqueça de que Maven é acima de tudo um javac/ javawrapper e que em Java você tem um classpath de tempo de compilação que você especifica com javac -cp ... e um classpath de tempo de execução que você especifica com java -cp ....
Não seria errado considerar o compileescopo do Maven como uma forma de adicionar uma dependência na compilação Java e no classppath do runtime (javace java) enquanto o runtimeescopo Maven pode ser visto como uma forma de adicionar uma dependência apenas no classppath do Java runtime ( javac).

O que estou engasgando é o seguinte: como um programa pode não depender de algo em tempo de execução de que dependia durante a compilação?

O que você descreve não tem nenhuma relação com runtimee compileescopo.
Parece mais para o providedescopo que você especifica para uma dependência depender disso em tempo de compilação, mas não em tempo de execução.
Você o usa porque precisa da dependência para compilar, mas não quer incluí-lo no componente empacotado (JAR, WAR ou qualquer outro) porque a dependência já é fornecida pelo ambiente: pode ser incluída no servidor ou em qualquer caminho do caminho de classe especificado quando o aplicativo Java é iniciado.

Se meu aplicativo Java usa log4j, ele precisa do arquivo log4j.jar para compilar (meu código se integra e invoca métodos de membro de dentro de log4j), bem como o tempo de execução (meu código não tem absolutamente nenhum controle sobre o que acontece quando o código está dentro de log4j .jar é executado).

Neste caso sim. Mas suponha que você precise escrever um código portátil que dependa do slf4j como fachada na frente do log4j para poder alternar para outra implementação de registro posteriormente (log4J 2, logback ou qualquer outro).
Neste caso, em seu pom, você precisa especificar slf4j como uma compiledependência (é o padrão), mas você especificará a dependência log4j como uma runtimedependência:

<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>

Dessa forma, as classes log4j não puderam ser referenciadas no código compilado, mas você ainda poderá fazer referência às classes slf4j.
Se você especificou as duas dependências com o compiletempo, nada o impedirá de fazer referência a classes log4j no código compilado e você poderá criar um acoplamento indesejável com a implementação 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>

Um uso comum de runtimeescopo é a declaração de dependência JDBC. Para escrever código portátil, você não quer que o código do cliente se refira a classes da dependência DBMS específica (por exemplo: dependência PostgreSQL JDBC), mas você quer mesmo incluí-lo em seu aplicativo como no tempo de execução as classes são necessárias para fazer a API JDBC funciona com este DBMS.

davidxxx
fonte
0

Em tempo de compilação, você ativa os contratos / api esperados de suas dependências. (por exemplo: aqui você acabou de assinar um contrato com o provedor de internet banda larga) Em tempo de execução, na verdade, você está usando as dependências. (por exemplo: aqui você realmente está usando a internet banda larga)

Nicolae Dascalu
fonte
0

Para responder à pergunta "como um programa pode não depender de algo em tempo de execução de que dependia durante a compilação?", Vamos dar uma olhada no exemplo de um processador de anotação.

Suponha que você escreveu seu próprio processador de anotação e suponha que ele tenha uma dependência de tempo de compilação de com.google.auto.service:auto-servicepara que possa usar @AutoService. Esta dependência é necessária apenas para compilar o processador de anotação, mas não é necessária em tempo de execução: todos os outros projetos que dependem de seu processador de anotação para processar anotações não exigem a dependência em com.google.auto.service:auto-servicetempo de execução (nem em tempo de compilação, nem em qualquer outro momento) .

Isso não é muito comum, mas acontece.

user23288
fonte
0

O runtimeescopo existe para evitar que os programadores adicionem dependências diretas às bibliotecas de implementação no código em vez de usar abstrações ou fachadas.

Em outras palavras, ele força o uso de interfaces.

Exemplos concretos:

1) Sua equipe está usando SLF4J em vez de Log4j. Você deseja que seus programadores usem a API SLF4J, não a Log4j. Log4j deve ser usado por SLF4J apenas internamente. Solução:

  • Defina SLF4J como uma dependência de tempo de compilação regular
  • Defina log4j-core e log4j-api como dependências de tempo de execução.

2) Seu aplicativo está acessando o MySQL usando JDBC. Você deseja que seus programadores codifiquem a abstração JDBC padrão, não diretamente a implementação do driver MySQL.

  • Defina mysql-connector-java(driver JDBC do MySQL) como uma dependência de tempo de execução.

As dependências de tempo de execução são ocultadas durante a compilação (lançando erros de tempo de compilação se seu código tiver uma dependência "direta" deles), mas são incluídas durante o tempo de execução e ao criar artefatos implantáveis ​​(arquivos WAR, arquivos jar SHADED, etc.).

Agustí Sánchez
fonte