プログラムはコンパイル時にライブラリに依存しますが、ランタイムには依存しませんか?


110

ランタイムとコンパイル時の違い、および2つを区別する方法は理解していますが、コンパイル時の依存関係とランタイムの依存関係を区別する必要があるだけではわかりません。

私が窒息しているのはこれです:コンパイル時にプログラムが依存してたプログラムが、実行時に何かに依存ないようにするにはどうすればよいですか?私のJavaアプリがlog4jを使用している場合、コンパイル(log4j内からのメンバーメソッドとの統合およびメンバーメソッドの呼び出し)とランタイム(log4j内のコードが発生すると何が起こるかを完全に制御できないコード)のために、log4j.jarファイルが必要です。 .jarが実行されます)。

私はIvyやMavenなどの依存関係解決ツールについて読んでいます。これらのツールは、これら2つのタイプの依存関係を明確に区別します。私はそれの必要性を理解していません。

誰でも簡単な「キングの英語」タイプの説明を、できれば私のような貧しい樹液でも理解できる実際の例を使って説明できますか?


2
リフレクションを使用でき、コンパイル時に使用できなかったクラスを使用できます。「プラグイン」と考えてください。
Alexandersson氏による

回答:


64

コンパイル時の依存関係は通常、実行時に必要です。Mavenでは、compileスコープされた依存関係が実行時にクラスパスに追加されます(たとえば、warsではWEB-INF / libにコピーされます)。

ただし、厳密には必須ではありません。たとえば、特定のAPIに対してコンパイルして、コンパイル時の依存関係にする場合がありますが、実行時には、APIも含む実装が含まれます。

プロジェクトをコンパイルするために特定の依存関係が必要であるにもかかわらず、対応するコードが実際には必要ではない場合がありますが、これらはまれです。

一方、コンパイル時に不要なランタイム依存関係を含めることは非常に一般的です。たとえば、Java EE 6アプリケーションを作成している場合は、Java EE 6 APIに対してコンパイルしますが、実行時には任意のJava EEコンテナーを使用できます。実装を提供するのはこのコンテナです。

リフレクションを使用すると、コンパイル時の依存関係を回避できます。たとえば、JDBCドライバーはでロードでき、ロードされたClass.forName実際のクラスは構成ファイルを介して構成可能です。


17
Java EE APIについて-「提供された」依存関係スコープの目的は何ですか?
ケビン

15
コンパイルには依存関係が必要ですが、実行時には不要な例は、ロンボク(www.projectlombok.org)です。jarはコンパイル時にJavaコードを変換するために使用されますが、実行時にはまったく必要ありません。スコープを「提供」に指定すると、jarがwar / jarに含まれなくなります。
ケビン

2
@Kevinはい、良い点です。providedスコープは、依存関係が実行時に他の手段(たとえば、コンテナー内の共有ライブラリー)によって提供されることを期待して、実行時依存関係を追加せずにコンパイル時依存関係を追加します。runtime一方、コンパイル時の依存関係にすることなく、ランタイムの依存関係を追加します。
Artefacto 2011

それで、「モジュール構成」(Ivy用語を使用)とプロジェクトルートの下の主要なディレクトリの間に通常 1:1の相関があると言っても安全ですか?たとえば、JUnit JARに依存するすべてのJUnitテストはtest /ルートの下にあります。同じソースルートの下にパッケージ化された同じクラスが、異なるクラスに依存するように「構成」される方法がわかりません。任意の時点でのJAR。log4jが必要な場合は、log4jが必要です。同じコードで1つの構成の下でlog4j呼び出しを呼び出すように指示する方法はありませんが、「非ロギング」構成の下でlog4j呼び出しを無視する方法はありますよね?
IAmYourFaja 2011

30

各Maven依存関係には、その依存関係が利用可能なクラスパスを定義するスコープがあります。

プロジェクトのJARを作成する場合、依存関係は生成されたアーティファクトにバンドルされません。これらはコンパイルにのみ使用されます。(ただし、ビルドされたjarに依存関係をmavenに含めることはできます。Mavenを使用してjarに依存関係を含めるを参照してください)

Mavenを使用してWARまたはEARファイルを作成する場合、生成されたアーティファクトと依存関係をバンドルするようにMavenを構成できます。また、提供されたスコープを使用して、WARファイルから特定の依存関係を除外するように構成できます。

最も一般的なスコープ— コンパイルスコープ —は、コンパイルクラスパス、ユニットテストのコンパイルと実行のクラスパス、およびアプリケーションを実行したときに最終的なランタイムクラスパスでプロジェクトに依存関係が利用できることを示します。Java EE Webアプリケーションでは、これは依存関係がデプロイされたアプリケーションにコピーされることを意味します。ただし、.jarファイルでは、依存関係はコンパイルスコープに含まれません。

ランタイムスコープは、ユニットテストの実行およびランタイム実行のクラスパスでプロジェクトに依存関係が利用できることを示しますが、コンパイルスコープと異なり、アプリケーションまたはそのユニットテストをコンパイルするときには利用できませんランタイム依存関係はデプロイされたアプリケーションにコピーされますが、コンパイル中は使用できません!これは、特定のライブラリに誤って依存しないようにするために役立ちます。

最後に、提供されたスコープは、アプリケーションが実行されるコンテナーがユーザーに代わって依存関係を提供することを示します。Java EEアプリケーションでは、これは依存関係がすでにサーブレットコンテナーまたはアプリケーションサーバーのクラスパス上にあり、デプロイされたアプリケーションにコピーされないことを意味します。また、プロジェクトをコンパイルするにはこの依存関係が必要です。


@Koray Tugay Answerの方が正確です。:)ランタイムの範囲に依存するjarがあるという簡単な質問があります。Mavenはコンパイル時にjarファイルを探しますか?
gks

@gksいいえ、コンパイル時には必要ありません。
Koray Tugay

9

実行時に必要になる可能性のある依存関係をコンパイル時に必要とします。ただし、多くのライブラリは、すべての可能な依存関係なしで実行されます。つまり、4つの異なるXMLライブラリを使用できますが、1つだけで動作するライブラリです。

多くのライブラリは、順番に他のライブラリを必要とします。これらのライブラリはコンパイル時には必要ありませんが、実行時に必要です。つまり、コードが実際に実行されたとき。


コンパイル時に必要ではないが実行時に必要になるライブラリの例を教えていただけませんか?
クリスティアーノ

1
@CristianoすべてのJDBCライブラリは次のようになります。また、標準APIを実装するライブラリ。
Peter Lawrey、

4

一般に、あなたは正しく、おそらく実行時とコンパイル時の依存関係が同じであれば理想的な状況です。

このルールが正しくない場合、2つの例を挙げます。

クラスAがクラスDに依存するクラスCに依存するクラスBに依存する場合、AはクラスBであり、CとDは異なるサードパーティライブラリのクラスであり、コンパイル時に必要なのはBとCだけであり、さらにDも必要です。ランタイム。多くの場合、プログラムは動的クラスローディングを使用します。この場合、コンパイル時に使用するライブラリによって動的にロードされるクラスは必要ありません。さらに、多くの場合、ライブラリは実行時に使用する実装を選択します。たとえば、SLF4JまたはCommons Loggingは、実行時にターゲットログの実装を変更できます。コンパイル時に必要なのはSSL4Jだけです。

実行時よりもコンパイル時に多くの依存関係が必要な場合の反対の例。異なる環境またはオペレーティングシステムで動作する必要があるアプリケーションを開発していると考えてください。コンパイル時にすべてのプラットフォーム固有のライブラリが必要であり、実行時に現在の環境に必要なライブラリのみが必要です。

私の説明が役に立てば幸いです。


あなたの例でコンパイル時にCが必要な理由について詳しく説明できますか?(stackoverflow.com/a/7257518/6095334から)コンパイル時にCが必要かどうかは、(Bからの)Aが参照しているメソッドとフィールドに依存するという印象を受けます。
ヘルヴィアン2017年

3

通常、静的依存関係グラフは動的グラフのサブグラフです。たとえば、NDependの作者によるこのブログエントリを参照してください。

とはいえ、いくつかの例外があり、主にコンパイラサポートを追加する依存関係があります。これは実行時に見えなくなります。たとえば、Lombokを介したコード生成や、(pluggable type-)Checker Frameworkを介した追加チェックなど。


2

あなたの質問に答える問題に出くわしました。servlet-api.jar私のWebプロジェクトの一時的な依存関係であり、コンパイル時と実行時の両方で必要です。しかしservlet-api.jar、私のTomcatライブラリにも含まれています。

ここでの解決策はservlet-api.jar、コンパイル時にのみmavenを使用可能にし、私のwarファイルにパッケージ化しないことです。これによりservlet-api.jar、Tomcatライブラリーに含まれているものと競合しなくなります。

これがコンパイル時間とランタイムの依存関係を説明することを願っています。


3
それが違いを説明原因あなたの例では、与えられた質問には、実際に間違っているcompileprovided、スコープとないの間compileとをruntimeCompile scopeコンパイル時に必要であり、アプリにパッケージ化されています。Provided scopeコンパイル時にのみ必要ですが、アプリにパッケージ化されていません。これは、Tomcatサーバーにすでに存在するなど、他の手段によって提供されているためです。
MJar

1
ええと、これはかなり良い例だと思います。問題はコンパイル時と実行時の依存関係に関するものでcompileあり、runtime Mavenスコープに関するものではなかったからです。providedスコープは、Mavenのハンドルコンパイル時の依存関係がランタイムパッケージに含まれてはならない場合の方法です。
クリスチャンゴーロン

1

ランタイムとコンパイル時の違いと、2つを区別する方法は理解していますが、コンパイル時とランタイム時の依存関係を区別する必要があるだけではわかりません。

コンパイル時および実行時の一般的な概念と、Maven固有compileおよびruntimeスコープの依存関係は、2つの非常に異なるものです。これらは同じフレームを持たないため、直接比較することはできません。一般的なコンパイルおよびランタイムの概念は広範囲ですが、Maven compileおよびruntimeスコープの概念は、時間に応じた依存関係の可用性/可視性(コンパイルまたは実行)に関するものです。
Mavenは何よりもjavac/ javaラッパーであり、Javaではで指定するコンパイル時クラスパスとで指定javac -cp ... するランタイムクラスパスがあることを忘れないでくださいjava -cp ...。Javaコンパイルとランタイムクラスパスの両方に依存関係を追加する方法として
Maven compileスコープを考慮することは間違いありません(クラスパス(javacおよびjava)Maven runtimeスコープは、Javaランタイムクラスパス(javac)にのみ依存関係を追加する方法と見なすことができます。

私が窒息しているのはこれです:プログラムがコンパイル時に依存している実行時にプログラムが依存しないようにするにはどうすればよいですか?

あなたが説明するものはruntimecompileスコープとは何の関係もありません。依存関係をコンパイル時に依存するように指定し、実行時には依存しないように指定
したprovidedスコープのように見えます。
依存関係をコンパイルする必要があるので使用しますが、パッケージ化されたコンポーネント(JAR、WAR、またはその他)に含めたくありません。依存関係はすでに環境によって提供されているためです。サーバーまたはその他に含めることができます。 Javaアプリケーションの起動時に指定されたクラスパスのパス。

私のJavaアプリがlog4jを使用している場合、コンパイル(log4j内からのメンバーメソッドとの統合およびメンバーメソッドの呼び出し)とランタイム(log4j内のコードが発生すると何が起こるかを完全に制御できないコード)のために、log4j.jarファイルが必要です。 .jarが実行されます)。

この場合はい。しかし、後で別のロギング実装(log4J 2、logbackまたはその他)に切り替えることができるように、log4jの前のファサードとしてslf4jに依存するポータブルコードを記述する必要があるとします。
この場合、pomでcompile依存関係としてslf4jを指定する必要があります(これがデフォルトです)が、log4j依存関係を依存関係として指定しますruntime

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

このように、log4jクラスはコンパイルされたコードで参照できませんでしたが、slf4jクラスを参照することはできます。時間
とともに2つの依存関係を指定した場合compile、コンパイルされたコードでlog4jクラスを参照することを妨げるものは何もないため、ロギング実装との望ましくない結合を作成する可能性があります。

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

runtimeスコープの一般的な使用法は、JDBC依存関係宣言です。移植可能なコードを書くために、クライアントコードが特定のDBMS依存関係のクラスを参照することは望ましくありません(たとえば、PostgreSQL JDBC依存関係)。ただし、実行時にクラスを作成するために必要なのと同じように、アプリケーションに含めます。 JDBC APIはこのDBMSで動作します。


0

コンパイル時に、依存関係から予想されるコントラクト/ APIを有効にします。(例:ここでは、ブロードバンドインターネットプロバイダーとの契約にサインするだけです)実行時には、実際に依存関係を使用しています。(例:ここでは実際にブロードバンドインターネットを使用しています)


0

「コンパイル時にプログラムが依存する実行時のプログラムに、どのように依存しないのか」という質問に答えるために、注釈プロセッサの例を見てみましょう。

独自の注釈プロセッサを作成しcom.google.auto.service:auto-service、それを使用できるようにコンパイル時の依存関係があるとします@AutoService。この依存関係は、注釈プロセッサのコンパイルにのみ必要ですが、実行時には必要ありません。注釈を処理するために注釈プロセッサに依存している他のすべてのプロジェクトは、実行時に依存しませcom.google.auto.service:auto-service(コンパイル時でも他の場合でも)。 。

これはあまり一般的ではありませんが、起こります。


0

runtimeスコープは、抽象化またはファサードを使用する代わりに、プログラマがコード内の実装ライブラリに直接の依存関係を追加できないようにするためのものです。

つまり、インターフェースの使用を強制します。

具体的な例:

1)チームはLog4jよりもSLF4Jを使用しています。プログラマーに、Log4j APIではなくSLF4J APIを使用してほしい。Log4jは、SLF4Jで内部的にのみ使用されます。解決:

  • SLF4Jを通常のコンパイル時の依存関係として定義する
  • log4j-coreおよびlog4j-apiをランタイム依存として定義します。

2)アプリケーションがJDBCを使用してMySQLにアクセスしている。プログラマーに、MySQLドライバーの実装に対して直接ではなく、標準のJDBC抽象化に対してコーディングしてもらいたい。

  • mysql-connector-java(MySQL JDBCドライバー)をランタイム依存関係として定義します。

ランタイムの依存関係は、コンパイル中は非表示(コードに「直接」の依存関係がある場合はコンパイル時エラーがスローされます)ですが、実行時およびデプロイ可能なアーティファクト(WARファイル、SHADED jarファイルなど)の作成時に含まれます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.