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?
fonte
Respostas:
Uma dependência de tempo de compilação geralmente é necessária no tempo de execução. No maven, uma
compile
dependê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.forName
e a classe real carregada pode ser configurada por meio de um arquivo de configuração.fonte
provided
escopo 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).runtime
por outro lado, adiciona uma dependência de tempo de execução sem torná-la uma dependência de tempo de compilação.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.
fonte
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.
fonte
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.
fonte
Normalmente, o gráfico de dependências estáticas é um subgráfico do dinâmico, veja, por exemplo, esta entrada de blog do autor de NDepend .
Dito isso, existem algumas exceções, principalmente dependências que adicionam suporte ao compilador, que se torna invisível em tempo de execução. Por exemplo, para geração de código por meio do Lombok ou verificações adicionais por meio do (tipo conectável-) Checker Framework .
fonte
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. Masservlet-api.jar
também está incluído na minha biblioteca Tomcat.A solução aqui é tornar o
servlet-api.jar
maven 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 oservlet-api.jar
contido em minha biblioteca Tomcat.Espero que isso explique a dependência do tempo de compilação e do tempo de execução.
fonte
compile
eprovided
escopos e não entrecompile
eruntime
.Compile scope
é necessário em tempo de compilação e é empacotado em seu aplicativo.Provided scope
só é 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.compile
eruntime
escopos Maven . Oprovided
escopo é 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.Os conceitos gerais de tempo de compilação e tempo de execução e as dependências específicas
compile
e deruntime
escopo 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 mavencompile
eruntime
escopo 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
/java
wrapper e que em Java você tem um classpath de tempo de compilação que você especifica comjavac -cp ...
e um classpath de tempo de execução que você especifica comjava -cp ...
.Não seria errado considerar o
compile
escopo do Maven como uma forma de adicionar uma dependência na compilação Java e no classppath do runtime (javac
ejava
) enquanto oruntime
escopo Maven pode ser visto como uma forma de adicionar uma dependência apenas no classppath do Java runtime (javac
).O que você descreve não tem nenhuma relação com
runtime
ecompile
escopo.Parece mais para o
provided
escopo 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.
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
compile
dependência (é o padrão), mas você especificará a dependência log4j como umaruntime
dependência: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
compile
tempo, 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:Um uso comum de
runtime
escopo é 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.fonte
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)
fonte
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-service
para 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 emcom.google.auto.service:auto-service
tempo de execução (nem em tempo de compilação, nem em qualquer outro momento) .Isso não é muito comum, mas acontece.
fonte
O
runtime
escopo 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:
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.
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.).
fonte