Dapatkah program bergantung pada pustaka selama kompilasi tetapi tidak pada runtime?

110

Saya memahami perbedaan antara waktu proses dan waktu kompilasi serta cara membedakan keduanya, tetapi saya tidak melihat kebutuhan untuk membuat perbedaan antara dependensi waktu kompilasi dan waktu proses .

Apa yang saya tersedak adalah ini: bagaimana sebuah program tidak bergantung pada sesuatu pada saat runtime yang bergantung padanya selama kompilasi? Jika aplikasi Java saya menggunakan log4j, maka ia membutuhkan file log4j.jar untuk mengkompilasi (kode saya terintegrasi dengan dan memanggil metode anggota dari dalam log4j) serta runtime (kode saya sama sekali tidak memiliki kendali atas apa yang terjadi setelah kode di dalam log4j .jar dijalankan).

Saya sedang membaca tentang alat resolusi ketergantungan seperti Ivy dan Maven, dan alat ini dengan jelas membuat perbedaan antara kedua jenis ketergantungan ini. Saya hanya tidak mengerti perlunya itu.

Adakah yang bisa memberikan penjelasan tipe "King's English" yang sederhana, lebih disukai dengan contoh nyata yang bahkan orang miskin seperti saya pun bisa mengerti?

IAmYourFaja
sumber
2
Anda dapat menggunakan refleksi, dan menggunakan kelas yang tidak tersedia pada waktu kompilasi. Pikirkan "plugin".
Per Alexandersson

Jawaban:

64

Dependensi waktu kompilasi umumnya diperlukan pada waktu proses. Di maven, compiledependensi yang tercakup akan ditambahkan ke classpath saat runtime (misalnya dalam perang , dependensi tersebut akan disalin ke WEB-INF / lib).

Namun, itu tidak benar-benar diperlukan; misalnya, kita dapat mengompilasi terhadap API tertentu, menjadikannya dependensi waktu kompilasi, tetapi kemudian pada waktu proses menyertakan implementasi yang juga menyertakan API.

Mungkin ada kasus pinggiran di mana proyek memerlukan ketergantungan tertentu untuk dikompilasi tetapi kemudian kode yang sesuai sebenarnya tidak diperlukan, tetapi ini akan jarang terjadi.

Di sisi lain, termasuk dependensi waktu proses yang tidak diperlukan pada waktu kompilasi sangat umum terjadi. Misalnya, jika Anda menulis aplikasi Java EE 6, Anda melakukan kompilasi terhadap Java EE 6 API, tetapi saat runtime, container Java EE apa pun dapat digunakan; wadah inilah yang menyediakan implementasi.

Ketergantungan waktu kompilasi dapat dihindari dengan menggunakan refleksi. Misalnya, driver JDBC dapat dimuat dengan a Class.forNamedan kelas yang sebenarnya dimuat dapat dikonfigurasi melalui file konfigurasi.

Artefacto
sumber
17
Tentang Java EE API - bukankah itu kegunaan cakupan dependensi "yang disediakan"?
Kevin
15
contoh di mana dependensi diperlukan untuk dikompilasi tetapi tidak diperlukan saat runtime adalah lombok (www.projectlombok.org). Jar digunakan untuk mengubah kode java pada waktu kompilasi tetapi tidak diperlukan sama sekali saat runtime. Menentukan ruang lingkup "disediakan" menyebabkan tabung tidak dimasukkan ke dalam perang / tabung.
Kevin
2
@Kevin Ya, bagus, providedcakupan menambahkan ketergantungan waktu kompilasi tanpa menambahkan ketergantungan waktu proses dengan harapan bahwa ketergantungan akan disediakan pada waktu proses dengan cara lain (misalnya perpustakaan bersama dalam wadah). runtimedi sisi lain menambahkan ketergantungan waktu proses tanpa menjadikannya sebagai ketergantungan waktu kompilasi.
Artefacto
Jadi aman untuk mengatakan bahwa biasanya ada korelasi 1: 1 antara "konfigurasi modul" (menggunakan istilah Ivy) dan direktori utama di bawah root proyek Anda? Misalnya, semua pengujian JUnit saya yang bergantung pada JUnit JAR akan berada di bawah pengujian / root, dll. Saya hanya tidak melihat bagaimana kelas yang sama, yang dipaketkan di bawah root sumber yang sama, dapat "dikonfigurasi" untuk bergantung pada yang berbeda. JAR pada waktu tertentu. Jika Anda membutuhkan log4j, maka Anda membutuhkan log4j; tidak ada cara untuk memberi tahu kode yang sama untuk memanggil panggilan log4j di bawah 1 konfigurasi, tetapi mengabaikan panggilan log4j di bawah beberapa konfigurasi "non-logging", bukan?
IAmYourFaja
30

Setiap dependensi Maven memiliki cakupan yang menentukan classpath tempat dependensi tersebut tersedia.

Saat Anda membuat JAR untuk sebuah proyek, dependensi tidak digabungkan dengan artefak yang dihasilkan; mereka hanya digunakan untuk kompilasi. (Namun, Anda masih bisa membuat maven menyertakan dependensi di dalam jar bawaan, lihat: Termasuk dependensi di jar dengan Maven )

Saat Anda menggunakan Maven untuk membuat file WAR atau EAR, Anda dapat mengkonfigurasi Maven untuk menggabungkan dependensi dengan artefak yang dihasilkan, dan Anda juga dapat mengkonfigurasinya untuk mengecualikan dependensi tertentu dari file WAR menggunakan cakupan yang disediakan.

Cakupan yang paling umum - Compile Scope - menunjukkan bahwa dependensi tersedia untuk project Anda di classpath kompilasi, classpath dan compile pengujian unit, dan classpath waktu proses akhirnya saat Anda menjalankan aplikasi. Dalam aplikasi web Java EE, ini berarti ketergantungan tersebut disalin ke dalam aplikasi yang Anda terapkan. Namun dalam file .jar, dependensi tidak akan disertakan dengan cakupan kompilasi ..

Runtime Scope menunjukkan bahwa dependensi tersedia untuk project Anda pada classpath eksekusi pengujian unit dan eksekusi runtime, tetapi tidak seperti cakupan kompilasi, dependensi ini tidak tersedia saat Anda mengompilasi aplikasi atau pengujian unitnya. Dependensi Runtime disalin ke dalam aplikasi yang Anda terapkan, tetapi tidak tersedia selama kompilasi! Ini bagus untuk memastikan Anda tidak salah bergantung pada perpustakaan tertentu.

Terakhir, Cakupan yang Diberikan menunjukkan bahwa wadah tempat aplikasi Anda dijalankan menyediakan ketergantungan atas nama Anda. Dalam aplikasi Java EE, ini berarti dependensi sudah ada di classpath penampung Servlet atau server aplikasi dan tidak disalin ke dalam aplikasi yang Anda terapkan. Ini juga berarti Anda memerlukan ketergantungan ini untuk mengompilasi proyek Anda.

Koray Tugay
sumber
@Koray Tugay Answer lebih tepat :) saya punya pertanyaan singkat mengatakan saya memiliki tabung dependeny w / ruang lingkup run time. Apakah pakar akan mencari toples pada saat kompilasi?
gks
@gks Tidak, itu tidak akan membutuhkannya dalam waktu kompilasi.
Koray Tugay
9

Anda membutuhkan dependensi pada waktu kompilasi yang mungkin Anda perlukan pada waktu proses. Namun banyak pustaka yang berjalan tanpa semua kemungkinan dependensinya. yaitu sebuah perpustakaan yang dapat menggunakan empat perpustakaan XML yang berbeda, tetapi hanya membutuhkan satu untuk bekerja.

Banyak perpustakaan, membutuhkan perpustakaan lain pada gilirannya. Pustaka ini tidak diperlukan pada waktu kompilasi tetapi dibutuhkan pada waktu proses. yaitu ketika kode benar-benar dijalankan.

Peter Lawrey
sumber
dapatkah Anda memberi kami contoh pustaka semacam itu yang tidak akan diperlukan saat kompilasi tetapi akan dibutuhkan saat runtime?
Cristiano
1
@Cristiano semua perpustakaan JDBC seperti ini. Juga pustaka yang menerapkan API standar.
Peter Lawrey
4

Secara umum Anda benar dan mungkin itu adalah situasi yang ideal jika dependensi waktu proses dan waktu kompilasi identik.

Saya akan memberi Anda 2 contoh jika aturan ini salah.

Jika kelas A bergantung pada kelas B yang bergantung pada kelas C yang bergantung pada kelas D di mana A adalah kelas Anda dan B, C dan D adalah kelas dari pustaka pihak ketiga yang berbeda, Anda hanya perlu B dan C pada waktu kompilasi dan Anda juga perlu D pada runtime. Seringkali program menggunakan pemuatan kelas dinamis. Dalam hal ini Anda tidak perlu kelas yang dimuat secara dinamis oleh perpustakaan yang Anda gunakan pada waktu kompilasi. Selain itu, sering kali pustaka memilih implementasi mana yang akan digunakan pada waktu proses. Misalnya SLF4J atau Commons Logging dapat mengubah implementasi log target saat runtime. Anda hanya perlu SSL4J itu sendiri pada waktu kompilasi.

Contoh sebaliknya ketika Anda membutuhkan lebih banyak dependensi pada waktu kompilasi daripada saat runtime. Pikirkan bahwa Anda sedang mengembangkan aplikasi yang harus bekerja di lingkungan atau sistem operasi yang berbeda. Anda memerlukan semua pustaka khusus platform pada waktu kompilasi dan hanya pustaka yang diperlukan untuk lingkungan saat ini pada waktu proses.

Saya harap penjelasan saya membantu.

AlexR
sumber
Dapatkah Anda menjelaskan mengapa C diperlukan pada waktu kompilasi dalam contoh Anda? Saya mendapat kesan (dari stackoverflow.com/a/7257518/6095334 ) bahwa apakah C diperlukan atau tidak pada waktu kompilasi tergantung pada metode dan bidang apa (dari B) A yang menjadi acuan.
Hervian
2

Baru saja mengalami masalah yang menjawab pertanyaan Anda. servlet-api.jaradalah ketergantungan sementara dalam proyek web saya dan dibutuhkan baik pada waktu kompilasi maupun waktu proses. Tetapi servlet-api.jarjuga termasuk dalam perpustakaan Tomcat saya.

Solusinya di sini adalah membuat servlet-api.jarmaven hanya tersedia pada waktu kompilasi dan tidak dikemas dalam file perang saya sehingga tidak akan bentrok dengan yang servlet-api.jarterdapat di perpustakaan Tomcat saya.

Saya harap ini menjelaskan waktu kompilasi dan dependensi Runtime.

Mayoor
sumber
3
Contoh Anda sebenarnya salah untuk pertanyaan yang diberikan, karena ini menjelaskan perbedaan antara compiledan providedcakupan dan bukan antara compiledan runtime. Compile scopekeduanya dibutuhkan pada waktu kompilasi dan dikemas dalam aplikasi Anda. Provided scopehanya diperlukan pada waktu kompilasi tetapi tidak dikemas dalam aplikasi Anda karena ini disediakan dengan cara lain, misalnya sudah ada di server Tomcat.
MJar
1
Saya rasa ini adalah contoh yang cukup bagus karena pertanyaannya adalah tentang waktu kompilasi dan dependensi waktu proses , bukan tentang cakupancompile dan runtime maven . The providedlingkup adalah cara maven menangani kasus di mana ketergantungan kompilasi-kali tidak harus disertakan dalam paket runtime.
Christian Gawron
1

Saya memahami perbedaan antara waktu proses dan waktu kompilasi serta cara membedakan keduanya, tetapi saya tidak melihat kebutuhan untuk membuat perbedaan antara dependensi waktu kompilasi dan waktu proses.

Konsep umum waktu kompilasi dan runtime serta dependensi spesifik compiledan runtimecakupan Maven adalah dua hal yang sangat berbeda. Anda tidak dapat membandingkannya secara langsung karena keduanya tidak memiliki bingkai yang sama: konsep umum kompilasi dan runtime luas sedangkan konsep maven compiledan runtimecakupan secara khusus membahas ketersediaan / visibilitas dependensi sesuai dengan waktu: kompilasi atau eksekusi.
Jangan lupa bahwa Maven di atas segalanya adalah a javac/ javawrapper dan bahwa di Java Anda memiliki classpath waktu kompilasi yang Anda tentukan javac -cp ... dan classpath waktu proses yang Anda tentukan java -cp ....
Tidak salah jika mempertimbangkan compilecakupan Maven sebagai cara untuk menambahkan dependensi baik dalam compile Java maupun classppath waktu proses (javacdan java) sementara runtimecakupan Maven bisa dilihat sebagai cara untuk menambahkan dependensi hanya di classppath ( javac) runtime Java .

Apa yang saya tersedak adalah ini: bagaimana sebuah program tidak bergantung pada sesuatu pada saat runtime yang bergantung padanya selama kompilasi?

Apa yang Anda gambarkan tidak memiliki hubungan runtimedan compileruang lingkup.
Sepertinya lebih banyak providedcakupan yang Anda tentukan agar dependensi bergantung padanya pada waktu kompilasi tetapi tidak pada waktu proses.
Anda menggunakannya karena Anda memerlukan dependensi untuk dikompilasi tetapi Anda tidak ingin memasukkannya ke dalam komponen yang dikemas (JAR, WAR, atau lainnya) karena dependensi tersebut sudah disediakan oleh lingkungan: dapat disertakan di server atau yang lainnya. jalur classpath yang ditentukan saat aplikasi Java dimulai.

Jika aplikasi Java saya menggunakan log4j, maka ia membutuhkan file log4j.jar untuk mengkompilasi (kode saya terintegrasi dengan dan memanggil metode anggota dari dalam log4j) serta runtime (kode saya sama sekali tidak memiliki kendali atas apa yang terjadi setelah kode di dalam log4j .jar dijalankan).

Dalam hal ini ya. Tetapi anggaplah Anda perlu menulis kode portabel yang mengandalkan slf4j sebagai fasad di depan log4j untuk dapat beralih ke implementasi logging lain nanti (log4J 2, logback, atau lainnya).
Dalam kasus ini di you pom Anda perlu menentukan slf4j sebagai compiledependensi (ini adalah default) tetapi Anda akan menentukan dependensi log4j sebagai runtimedependensi:

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

Dengan cara ini, kelas log4j tidak dapat dirujuk dalam kode yang dikompilasi tetapi Anda masih dapat merujuk kelas slf4j.
Jika Anda menentukan dua dependensi dengan compilewaktu, tidak ada yang akan menghalangi Anda untuk mereferensikan kelas log4j dalam kode yang dikompilasi dan Anda dapat membuat penggabungan yang tidak diinginkan dengan implementasi logging:

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

Penggunaan umum runtimeruang lingkup adalah deklarasi ketergantungan JDBC. Untuk menulis kode portabel, Anda tidak ingin kode klien dapat merujuk kelas dependensi DBMS tertentu (misalnya: ketergantungan JDBC PostgreSQL) tetapi Anda ingin semua hal yang sama memasukkannya ke dalam aplikasi Anda karena pada waktu proses kelas perlu membuatnya API JDBC bekerja dengan DBMS ini.

davidxxx
sumber
0

Pada waktu kompilasi, Anda mengaktifkan kontrak / api yang diharapkan dari dependensi Anda. (misalnya: di sini Anda baru saja menandatangani kontrak dengan penyedia internet broadband) Pada saat run-time sebenarnya Anda menggunakan dependensi. (mis .: di sini Anda sebenarnya menggunakan internet broadband)

Nicolae Dascalu
sumber
0

Untuk menjawab pertanyaan "bagaimana sebuah program tidak bergantung pada sesuatu pada saat runtime yang bergantung padanya selama kompilasi?", Mari kita lihat contoh pemroses anotasi.

Misalkan Anda telah menulis pemroses anotasi Anda sendiri, dan anggap pemroses tersebut memiliki ketergantungan waktu kompilasi com.google.auto.service:auto-servicesehingga dapat digunakan @AutoService. Ketergantungan ini hanya diperlukan untuk mengompilasi pemroses anotasi, tetapi tidak diperlukan pada waktu proses: semua proyek lain yang bergantung pada pemroses anotasi Anda untuk memproses anotasi tidak memerlukan ketergantungan pada com.google.auto.service:auto-servicesaat runtime (atau pada waktu kompilasi atau pada waktu lain) .

Ini tidak terlalu umum, tetapi itu terjadi.

pengguna23288
sumber
0

The runtimelingkup yang ada untuk mencegah programmer menambahkan dependensi langsung ke implementasi perpustakaan dalam kode daripada menggunakan abstraksi atau fasad.

Dengan kata lain, itu memaksa untuk menggunakan antarmuka.

Contoh konkrit:

1) Tim Anda menggunakan SLF4J melalui Log4j. Anda ingin programmer Anda menggunakan SLF4J API, bukan Log4j. Log4j hanya akan digunakan oleh SLF4J secara internal. Larutan:

  • Tentukan SLF4J sebagai ketergantungan waktu kompilasi biasa
  • Tentukan log4j-core dan log4j-api sebagai ketergantungan waktu proses.

2) Aplikasi Anda mengakses MySQL menggunakan JDBC. Anda ingin programmer Anda membuat kode berdasarkan abstraksi JDBC standar, bukan secara langsung terhadap implementasi driver MySQL.

  • Tentukan mysql-connector-java(driver MySQL JDBC) sebagai dependensi waktu proses.

Dependensi runtime disembunyikan selama kompilasi (memunculkan error waktu kompilasi jika kode Anda memiliki ketergantungan "langsung" padanya) tetapi disertakan selama waktu eksekusi dan saat membuat artefak yang dapat diterapkan (file WAR, file jar SHADED, dll.).

Agustí Sánchez
sumber