Một chương trình có thể phụ thuộc vào thư viện trong quá trình biên dịch nhưng không phụ thuộc vào thời gian chạy không?


110

Tôi hiểu sự khác biệt giữa thời gian chạy và thời gian biên dịch cũng như cách phân biệt giữa hai loại này, nhưng tôi không thấy cần phải phân biệt giữa thời gian biên dịch và thời gian chạy phụ thuộc .

Điều tôi đang mắc kẹt là: làm thế nào một chương trình có thể không phụ thuộc vào một thứ gì đó trong thời gian chạy mà nó phụ thuộc vào trong quá trình biên dịch? Nếu ứng dụng Java của tôi sử dụng log4j, thì nó cần tệp log4j.jar để biên dịch (mã của tôi tích hợp và gọi các phương thức thành viên từ bên trong log4j) cũng như thời gian chạy (mã của tôi hoàn toàn không có quyền kiểm soát những gì xảy ra khi mã bên trong log4j .jar được chạy).

Tôi đang đọc về các công cụ giải quyết sự phụ thuộc như Ivy và Maven, và những công cụ này rõ ràng phân biệt hai loại phụ thuộc này. Tôi chỉ không hiểu sự cần thiết của nó.

Có ai có thể đưa ra một lời giải thích đơn giản, kiểu "tiếng Anh của Vua", tốt nhất là với một ví dụ thực tế mà ngay cả một người nghèo như tôi cũng có thể hiểu được không?


2
Bạn có thể sử dụng phản chiếu và sử dụng các lớp không có sẵn tại thời điểm biên dịch. Hãy nghĩ đến "plugin".
Per Alexandersson

Câu trả lời:


64

Thường yêu cầu phụ thuộc thời gian biên dịch trong thời gian chạy. Trong maven, một compilephụ thuộc theo phạm vi sẽ được thêm vào classpath trong thời gian chạy (ví dụ: trong chiến tranh, chúng sẽ được sao chép sang WEB-INF / lib).

Tuy nhiên, nó không được yêu cầu nghiêm ngặt; ví dụ: chúng tôi có thể biên dịch dựa trên một API nhất định, làm cho nó trở thành phụ thuộc vào thời gian biên dịch, nhưng sau đó trong thời gian chạy sẽ bao gồm một triển khai cũng bao gồm API.

Có thể có những trường hợp rìa trong đó dự án yêu cầu một phụ thuộc nhất định để biên dịch nhưng sau đó mã tương ứng không thực sự cần thiết, nhưng những trường hợp này sẽ rất hiếm.

Mặt khác, bao gồm các phụ thuộc thời gian chạy không cần thiết vào thời gian biên dịch là rất phổ biến. Ví dụ: nếu bạn đang viết một ứng dụng Java EE 6, bạn biên dịch dựa trên API Java EE 6, nhưng trong thời gian chạy, bất kỳ vùng chứa Java EE nào cũng có thể được sử dụng; chính vùng chứa này cung cấp việc triển khai.

Có thể tránh được sự phụ thuộc vào thời gian biên dịch bằng cách sử dụng phản xạ. Ví dụ, một trình điều khiển JDBC có thể được tải bằng một Class.forNamevà lớp thực tế được tải có thể được định cấu hình thông qua một tệp cấu hình.


17
Giới thiệu về Java EE API - đó không phải là phạm vi phụ thuộc "được cung cấp" dùng để làm gì?
Kevin

15
một ví dụ trong đó một phụ thuộc cần thiết để biên dịch nhưng không cần thiết trong thời gian chạy là lombok (www.projectlombok.org). Jar được sử dụng để chuyển đổi mã java tại thời điểm biên dịch nhưng không cần thiết trong thời gian chạy. Chỉ định phạm vi "được cung cấp" khiến bình không được đưa vào chiến tranh / bình.
Kevin,

2
@Kevin Có, điểm tốt, providedphạm vi thêm phụ thuộc thời gian biên dịch mà không thêm phụ thuộc thời gian chạy với kỳ vọng rằng phụ thuộc sẽ được cung cấp trong thời gian chạy bằng các phương tiện khác (ví dụ: thư viện được chia sẻ trong vùng chứa). runtimemặt khác thêm phụ thuộc thời gian chạy mà không làm cho nó trở thành phụ thuộc thời gian biên dịch.
Artefacto

Vì vậy, có an toàn không khi nói rằng thường có mối tương quan 1: 1 giữa "cấu hình mô-đun" (sử dụng thuật ngữ Ivy) và thư mục chính dưới gốc dự án của bạn? Ví dụ: tất cả các bài kiểm tra JUnit của tôi phụ thuộc vào JUnit JAR sẽ nằm dưới bài kiểm tra / gốc, v.v. Tôi không thấy làm thế nào mà các lớp giống nhau, được đóng gói dưới cùng một gốc nguồn, có thể được "cấu hình" để phụ thuộc vào JAR tại bất kỳ thời điểm nào. Nếu bạn cần log4j, thì bạn cần log4j; không có cách nào để nói cùng một mã để gọi các cuộc gọi log4j trong 1 cấu hình, nhưng để bỏ qua các lệnh gọi log4j trong một số cấu hình "không ghi nhật ký", phải không?
IAmYourFaja

30

Mỗi phụ thuộc Maven có một phạm vi xác định classpath mà phụ thuộc đó có sẵn.

Khi bạn tạo JAR cho một dự án, các phần phụ thuộc không được đóng gói cùng với tạo tác được tạo; chúng chỉ được sử dụng để biên dịch. (Tuy nhiên, bạn vẫn có thể làm cho maven bao gồm các phụ thuộc trong jar đã xây dựng, hãy xem: Bao gồm các phụ thuộc trong một jar với Maven )

Khi bạn sử dụng Maven để tạo tệp WAR hoặc tệp EAR, bạn có thể định cấu hình Maven để nhóm các phần phụ thuộc với phần mềm được tạo và bạn cũng có thể định cấu hình nó để loại trừ các phần phụ thuộc nhất định khỏi tệp WAR bằng phạm vi được cung cấp.

Phạm vi phổ biến nhất - Phạm vi biên dịch - chỉ ra rằng sự phụ thuộc có sẵn cho dự án của bạn trên đường dẫn biên dịch, các classpath biên dịch và thực thi đơn vị và cuối cùng là classpath thời gian chạy khi bạn thực thi ứng dụng của mình. Trong ứng dụng web Java EE, điều này có nghĩa là phần phụ thuộc được sao chép vào ứng dụng đã triển khai của bạn. Tuy nhiên, trong tệp .jar, các phần phụ thuộc sẽ không được bao gồm trong phạm vi biên dịch ..

Phạm vi thời gian chạy chỉ ra rằng sự phụ thuộc có sẵn cho dự án của bạn trên các đường dẫn thực thi đơn vị và thực thi thời gian chạy, nhưng không giống như phạm vi biên dịch, nó không khả dụng khi bạn biên dịch ứng dụng của mình hoặc các kiểm thử đơn vị của nó. Phụ thuộc thời gian chạy được sao chép vào ứng dụng đã triển khai của bạn, nhưng nó không khả dụng trong quá trình biên dịch! Điều này rất tốt để đảm bảo rằng bạn không nhầm lẫn phụ thuộc vào một thư viện cụ thể.

Cuối cùng, Phạm vi được cung cấp chỉ ra rằng vùng chứa mà ứng dụng của bạn thực thi cung cấp phần phụ thuộc thay mặt bạn. Trong ứng dụng Java EE, điều này có nghĩa là phần phụ thuộc đã có trên đường dẫn liên kết của vùng chứa Servlet hoặc máy chủ ứng dụng và không được sao chép vào ứng dụng đã triển khai của bạn. Điều đó cũng có nghĩa là bạn cần sự phụ thuộc này để biên dịch dự án của mình.


@Koray Tugay Câu trả lời chính xác hơn :) tôi có câu hỏi nhanh nói rằng tôi có một cái lọ phụ thuộc w / phạm vi thời gian chạy. Maven sẽ tìm kiếm cái lọ tại thời điểm biên dịch?
gks

@gks Không, nó sẽ không yêu cầu nó trong thời gian biên dịch.
Koray Tugay

9

Bạn cần các phụ thuộc thời gian biên dịch mà bạn có thể cần trong thời gian chạy. Tuy nhiên, nhiều thư viện chạy mà không có tất cả các phụ thuộc có thể có của nó. tức là một thư viện có thể sử dụng bốn thư viện XML khác nhau, nhưng chỉ cần một thư viện để hoạt động.

Nhiều thư viện, lần lượt cần các thư viện khác. Các thư viện này không cần thiết vào thời gian biên dịch nhưng cần thiết trong thời gian chạy. tức là khi mã thực sự được chạy.


bạn có thể cho chúng tôi ví dụ về các thư viện như vậy sẽ không cần thiết khi biên dịch nhưng sẽ cần trong thời gian chạy không?
Cristiano

1
@Cristiano tất cả các thư viện JDBC đều như thế này. Ngoài ra các thư viện triển khai một API tiêu chuẩn.
Peter Lawrey

4

Nói chung, bạn đã đúng và có lẽ đó là tình huống lý tưởng nếu thời gian chạy và thời gian biên dịch phụ thuộc giống hệt nhau.

Tôi sẽ cung cấp cho bạn 2 ví dụ khi quy tắc này không chính xác.

Nếu lớp A phụ thuộc vào lớp B phụ thuộc vào lớp C phụ thuộc vào lớp D trong đó A là lớp của bạn và B, C và D là các lớp từ các thư viện bên thứ ba khác nhau, bạn chỉ cần B và C tại thời điểm biên dịch và bạn cũng cần D tại thời gian chạy. Thông thường các chương trình sử dụng tải lớp động. Trong trường hợp này, bạn không cần các lớp được tải động bởi thư viện mà bạn đang sử dụng tại thời điểm biên dịch. Hơn nữa, thư viện thường chọn cài đặt để sử dụng trong thời gian chạy. Ví dụ SLF4J hoặc Commons Logging có thể thay đổi việc triển khai nhật ký mục tiêu trong thời gian chạy. Bạn chỉ cần bản thân SSL4J tại thời điểm biên dịch.

Ví dụ ngược lại khi bạn cần nhiều phụ thuộc vào lúc biên dịch hơn là lúc chạy. Hãy nghĩ rằng bạn đang phát triển ứng dụng phải hoạt động ở các môi trường hoặc hệ điều hành khác nhau. Bạn cần tất cả các thư viện dành riêng cho nền tảng tại thời điểm biên dịch và chỉ các thư viện cần thiết cho môi trường hiện tại trong thời gian chạy.

Tôi hy vọng những lời giải thích của tôi sẽ giúp ích.


Bạn có thể giải thích tại sao C lại cần thiết tại thời điểm biên dịch trong ví dụ của bạn không? Tôi có ấn tượng (từ stackoverflow.com/a/7257518/6095334 ) rằng việc có cần C hay không tại thời điểm biên dịch là tùy thuộc vào phương thức và trường nào (từ B) A đang tham chiếu.
Hervian

3

Thông thường, biểu đồ phụ thuộc tĩnh là một biểu đồ phụ của biểu đồ động, hãy xem ví dụ: mục nhập blog này của tác giả NDepend .

Điều đó nói rằng, có một số ngoại lệ, chủ yếu là các phụ thuộc bổ sung hỗ trợ trình biên dịch, sẽ trở nên vô hình trong thời gian chạy. Ví dụ: để tạo mã thông qua Lombok hoặc kiểm tra bổ sung thông qua Khung công cụ kiểm tra (loại có thể cắm được-) .


2

Vừa gặp phải một vấn đề trả lời câu hỏi của bạn. servlet-api.jarlà một sự phụ thuộc nhất thời trong dự án web của tôi và cần thiết cả ở thời gian biên dịch và thời gian chạy. Nhưng servlet-api.jarcũng được bao gồm trong thư viện Tomcat của tôi.

Giải pháp ở đây là tạo servlet-api.jartrong maven chỉ có sẵn tại thời điểm biên dịch và không được đóng gói trong tệp war của tôi để nó không đụng độ với tệp servlet-api.jarcó trong thư viện Tomcat của tôi.

Tôi hy vọng điều này giải thích sự phụ thuộc vào thời gian Biên dịch và Thời gian chạy.


3
Ví dụ của bạn thực sự không chính xác cho câu hỏi đã cho, vì nó giải thích sự khác biệt giữa compileprovidedphạm vi chứ không phải giữa compileruntime. Compile scopecả hai đều cần thiết tại thời điểm biên dịch và được đóng gói trong ứng dụng của bạn. Provided scopechỉ cần thiết tại thời điểm biên dịch nhưng không được đóng gói trong ứng dụng của bạn vì nó được cung cấp bởi phương tiện khác, ví dụ như nó đã có trong máy chủ Tomcat.
MJar

1
Chà, tôi nghĩ đây là một ví dụ khá hay vì câu hỏi liên quan đến sự phụ thuộc thời gian biên dịch và thời gian chạy chứ không phải phạm vi about compileruntime maven . Các providedphạm vi là cách maven xử lý trường hợp một phụ thuộc thời gian biên dịch không nên được bao gồm trong gói thời gian chạy.
Christian Gawron

1

Tôi hiểu sự khác biệt giữa thời gian chạy và thời gian biên dịch cũng như cách phân biệt giữa hai loại này, nhưng tôi không thấy cần phải phân biệt giữa thời gian biên dịch và thời gian chạy phụ thuộc.

Các khái niệm thời gian biên dịch và thời gian chạy chung và các phụ thuộc phạm vi compilevà cụ thể của Maven runtimelà hai điều rất khác nhau. Bạn không thể so sánh trực tiếp chúng vì chúng không có cùng khung: khái niệm thời gian chạy và biên dịch chung là rộng trong khi khái niệm maven compileruntimephạm vi nói về cụ thể tính khả dụng / khả năng hiển thị theo thời gian: biên dịch hoặc thực thi.
Đừng quên rằng Maven là trên hết a javac/ javawrapper và trong Java, bạn có một đường dẫn thời gian biên dịch mà bạn chỉ định javac -cp ... và một đường dẫn thời gian chạy mà bạn chỉ định java -cp ....
Sẽ không sai nếu coi compilephạm vi Maven như một cách để thêm phần phụ thuộc vào cả trình biên dịch Java và đường dẫn lớp thời gian chạy (javacjava) trong khi runtimephạm vi Maven có thể được coi là một cách để thêm một phần phụ thuộc chỉ trong lớp chạy thời gian chạy Java classppath ( javac).

Điều tôi đang mắc kẹt là: làm thế nào một chương trình có thể không phụ thuộc vào một thứ gì đó trong thời gian chạy mà nó phụ thuộc vào trong quá trình biên dịch?

Những gì bạn mô tả không có bất kỳ mối quan hệ runtimecompilephạm vi nào.
Có vẻ như nhiều hơn đối với providedphạm vi mà bạn chỉ định cho một phụ thuộc để phụ thuộc vào đó tại thời điểm biên dịch nhưng không phải trong thời gian chạy.
Bạn sử dụng nó khi bạn cần phụ thuộc để biên dịch nhưng bạn không muốn đưa nó vào thành phần được đóng gói (JAR, WAR hoặc bất kỳ thành phần nào khác) bởi vì phụ thuộc đã được cung cấp bởi môi trường: nó có thể được đưa vào máy chủ hoặc bất kỳ đường dẫn của classpath được chỉ định khi ứng dụng Java được khởi động.

Nếu ứng dụng Java của tôi sử dụng log4j, thì nó cần tệp log4j.jar để biên dịch (mã của tôi tích hợp và gọi các phương thức thành viên từ bên trong log4j) cũng như thời gian chạy (mã của tôi hoàn toàn không có quyền kiểm soát những gì xảy ra khi mã bên trong log4j .jar được chạy).

Trong trường hợp này có. Nhưng giả sử rằng bạn cần viết một mã di động dựa vào slf4j làm mặt tiền trước log4j để có thể chuyển sang triển khai ghi nhật ký khác sau này (log4J 2, logback hoặc bất kỳ thứ gì khác).
Trong trường hợp này, bạn cần chỉ định slf4j làm compilephụ thuộc (đó là mặc định) nhưng bạn sẽ chỉ định phụ thuộc log4j làm runtimephụ thuộc:

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

Bằng cách này, các lớp log4j không thể được tham chiếu trong mã đã biên dịch nhưng bạn vẫn có thể tham chiếu đến các lớp slf4j.
Nếu bạn đã chỉ định hai phần phụ thuộc cùng với compilethời gian, thì không có gì ngăn cản bạn tham chiếu đến các lớp log4j trong mã đã biên dịch và do đó bạn có thể tạo một khớp nối không mong muốn với việc triển khai ghi nhật ký:

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

Cách sử dụng phổ biến của runtimephạm vi là khai báo phụ thuộc JDBC. Để viết mã di động, bạn không muốn mã máy khách có thể tham chiếu đến các lớp của phụ thuộc DBMS cụ thể (ví dụ: phụ thuộc PostgreSQL JDBC) nhưng bạn muốn tất cả những thứ tương tự bao gồm nó trong ứng dụng của mình vì trong thời gian chạy các lớp cần thiết để tạo API JDBC hoạt động với DBMS này.


0

Tại thời điểm biên dịch, bạn bật các hợp đồng / api mà bạn mong đợi từ các phụ thuộc của mình. (ví dụ: ở đây bạn chỉ cần ký hợp đồng với nhà cung cấp internet băng thông rộng) Tại thời điểm hoạt động thực sự bạn đang sử dụng các phụ thuộc. (ví dụ: ở đây bạn thực sự đang sử dụng internet băng thông rộng)


0

Để trả lời câu hỏi "làm thế nào một chương trình có thể không phụ thuộc vào thứ gì đó trong thời gian chạy mà nó phụ thuộc vào trong quá trình biên dịch?", Hãy xem ví dụ về bộ xử lý chú thích.

Giả sử bạn đã viết bộ xử lý chú thích của riêng mình và giả sử nó có phụ thuộc vào thời gian biên dịch com.google.auto.service:auto-serviceđể nó có thể sử dụng @AutoService. Sự phụ thuộc này chỉ được yêu cầu để biên dịch bộ xử lý chú thích, nhưng không bắt buộc trong thời gian chạy: tất cả các dự án khác tùy thuộc vào bộ xử lý chú thích của bạn để xử lý chú thích không yêu cầu phụ thuộc vào com.google.auto.service:auto-servicetrong thời gian chạy (không phải lúc biên dịch cũng như bất kỳ lúc nào khác) .

Điều này không phải là rất phổ biến, nhưng nó xảy ra.


0

Các runtimephạm vi là có để ngăn chặn các lập trình viên từ việc thêm phụ thuộc trực tiếp vào thư viện thực hiện trong các mã thay vì sử dụng trừu tượng hoặc mặt tiền.

Nói cách khác, nó bắt buộc sử dụng các giao diện.

Ví dụ cụ thể:

1) Nhóm của bạn đang sử dụng SLF4J qua Log4j. Bạn muốn các lập trình viên của mình sử dụng API SLF4J, không phải API Log4j. Log4j chỉ được sử dụng bởi SLF4J trong nội bộ. Giải pháp:

  • Xác định SLF4J là phụ thuộc thời gian biên dịch thông thường
  • Xác định log4j-core và log4j-api là các phần phụ thuộc thời gian chạy.

2) Ứng dụng của bạn đang truy cập MySQL bằng JDBC. Bạn muốn các lập trình viên của mình viết mã theo tiêu chuẩn trừu tượng JDBC chứ không phải trực tiếp chống lại việc triển khai trình điều khiển MySQL.

  • Xác định mysql-connector-java(trình điều khiển MySQL JDBC) làm phụ thuộc thời gian chạy.

Các phụ thuộc thời gian chạy được ẩn trong quá trình biên dịch (gây ra lỗi thời gian biên dịch nếu mã của bạn có phụ thuộc "trực tiếp" vào chúng) nhưng được bao gồm trong thời gian thực thi và khi tạo các tạo tác có thể triển khai (tệp WAR, tệp jar SHADED, v.v.).

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.