انواع مختلف Classpath

classpath هر چند دارای تعریف واحدی است اما در فازهای مختلف کامپایل، تست و اجرا میتواند محتوای متفاوتی داشته باشد. بسیاری از برنامهنویسان برداشتی اجمالی از موضوع دارند اما میتوان آن را به دقت بررسی کرده و از رویارویی با برخی مشکلات پیشگیری کرد.
Classpath چیست؟ مجموعهای از کلاسها و فایلهای jar است که برنامه به آنها نیاز دارد. اما در واقع دو یا سه نوع Classpath متفاوت وجود دارد.
- Compile-time Classpath: با این فرض که از یک IDE استفاده میکنید، کلاسهایی است که به IDE اضافه کردهاید تا کد بتواند کامپایل شود. به عبارت دیگر این Classpath همانی است که به عنوان یکی از ورودیهای کامپایلر javac استفاده میشود. (هر چند میتوان از کامپایلر دیگری هم استفاده کرد)
- Runtime Classpath: حاوی کلاسهایی است که نرمافزار هنگام اجرا از آنها استفاده میکند. این Classpath، توسط دستور java استفاده میشود. در مورد وباپلیکیشنها، حاوی همۀ کلاسهایی است که توسط server یا servlet container عرضه شده، به اضافۀ آنهایی که در فولدر lib/ قرار داده شده است.
- Test Classpath: گونهای از Runtime Classpath است. اما تنها هنگامی که تستها را اجرا میکنیم مورد استفاده قرار میگیرد. از آنجا که تستها داخل server/servlet container اجرا نمیشوند؛ Classpath هنگام اجرا و تست با هم مقداری متفاوتاند.
maven هنگام تعریف وابستگیها، مفهومی به نام scope را مورد استفاده قرار میدهد که برای مدیریت Classpathهای مختلف بسیار کاربردی است. در این لینک میتوانید بیشتر بخوانید.
بسیاری از افراد تصور میکنند اگر بتوانند یک اپلیکیشن را کامپایل کرده و یک فایل jar سالم تولید کنند، بهخوبی اجرا خواهد شد. اما واقعیت این طور نیست. باید jarهایی که هنگام کامپایل به آنها نیاز داشتید نیز هنگام اجرا در اختیار شما باشد. البته نه ضرورتا همۀ آنها، و نه فقط آنها، یعنی ممکن است هنگام اجرا به jarهای بیشتری نیاز داشته باشید. بیایید چند مثال را بررسی کنیم:
- می خواهید کدی را کامپایل کنید. برای کامپایل نیاز دارید یک کتابخانه را در Classpath قرار دهید. اگر فراموش کنید همان کتابخانه را در Runtime Classpath قرار دهید، هنگام اجرا JVM یک NoClassDefFoundError پرتاب میکند. این خطا به صراحت فقدان یکی از jarهایی که در Compile‑time Classpath موجود بوده و در Runtime Classpath موجود نیست را بیان میکند.
همچنین خطای فوق در صورتی پیش میآید که شما از کتابخانهای استفاده کنید که به کتابخانۀ دیگری وابسته باشد و شما آن را در اختیار نداشته باشید. به همین دلیل است که هر کتابخانهای باید لیست تمام وابستگیهایش را اعلام کند. - containerها، چه servlet container و چه application serverها، دارای کتابخانههایی هستند که در داخل آنها گنجانده میشود. عموما امکان تغییر این کتابخانههای درونی وجود ندارد. به عنوان مثال میخواهید از Tomcat استفاده کنید، این servlet container بستهای به نام servlet-api.jar را در اختیار شما قرار میدهد. قاعدتا هنگام کامپایل، این بسته را در Compiler Classpath قرار داده و برنامه را کامپایل میکنید. اما نباید آن را در WEB-INF/lib قرار دهید؛ چرا که عرضۀ آن وظیفۀ Tomcat است و آن را در Runtime Classpath قرار خواهد داد. اگر اصرار به قرار دادن بسته در WEB-INF/lib داشته باشید امکان دارد به دلیل اختلال در کار classloader با نتایج غیر منطقی روبرو شوید.
- تصور کنید از فریمورکی مانند spring-mvc استفاده میکنید که به کتابخانۀ دیگری برای ساخت JSON نیاز دارد (معمولا از Jackson برای این کار استفاده میشود). در این حالت نیازی به قرار دادن Jackson در Compile‑time Classpath نیست؛ چرا که نیازی به استفادۀ مستقیم از کلاسهای Jackson نخواهید داشت. اما در عمل، spring برای اجرا نیاز به Jackson دارد. در نتیجه باید آن را در داخل WEB-INF/lib یا Runtime Classpath قرار دهید.
هنگام ساخت، تست و اجرای نرمافزارها ممکن است با شرایط پیچیدهتری نیز مواجه شوید. ناسازگاری نسخههای مختلف وابستگیها، داشتن وابستگیهای تکراری، تعارضها و … دور از تصور نیست. اما نکتۀ مهم این است: باید آگاه باشید Classpathای که هنگام کامپایل و اجرا استفاده میکنید با هم فرق دارند.
مقالهی بسیار مفید و کاربردیای بود. خیلی ممنون
فقط یک سوال مطرح میشه، اینکه دقیقا چطور تشخیص بدیم یک کتاب خانه باید در runtime classpath قرار بگیره یا compile-time classpath. با وجود مثالهایی که زدید ولی من باز متوجه نشدم که چظور تشخیص بدم!