مدیریت Dependency ها در جاوا - قسمت دوم: Apache Maven

« پست بروز رسانی شده »همانطور که دیدیم مدیریت کتابخانه های مورد نیاز (Dependency ها) در فرآیند توسعه نرم افزارهای مبتنی بر Java می تواند به کار پیچیده ای بدل شود. با استفاده از ابزارهایی نظیر Apache Maven این امر و بطور کلی مدیریت چرخه تولید و انتشار پروژه های Java ساده تر می شود.در این پست قصد داریم ابتدا مروری بر ساختار و نحوه کار Maven داشته باشیم و سپس مراحل ایجاد پروژه string-to-excel را با استفاده از Apache Maven به انجام برسانیم و از مزیت این ابزار در مدیریت Dependency ها و Build پروژه استفاده کنیم.داستان Mavenابزار Maven که خود قبلا بخشی از پروژه Jakarta Turbine بوده با هدف ساده سازی فرآیند Build این پروژه بوجود آمده و نسل قبل از آن را می توان Apache Ant دانست که پیش از Maven با امکان تعریف Task های مختلف فرایند Build را میسر می کرد و هنوز هم در برخی پروژه های Java و حتی زبان های دیگر از آن استفاده می شود. اما مدلی که Maven در مدیریت Build و Dependency ها ارائه داد به مراتب ساده تر بود و بنابراین باعث شد دیگر نیازی به ذخیره dependency های مورد نیاز پروژه (مثل jar file ها) در Version Control ها (CVS، SVN و GIT) نباشد و فقط یک یا چند فایل XML (بنام pom.xml) برای مدیریت چرخه Build در هر پروژه نگهداری شود. با اینکار علاوه بر کاهش چشمگیر حجم پروژه ها در Version Control ها، امکان استفاده مشترک از dependency های موجود در Maven Repository در چند پروژه هم به آسانی فراهم می شود. ساختار Mavenابزار Maven بطور کلی دارای سه جزء اصلی است:پوشه نصب maven: شامل فایل های اجرایی و تنظیمات اصلی Maven (فایل settings.xml)پوشه repository : پوشه Repository شامل تمامی dependency های دریافت شده از Remote Repository های Maven بر روی اینترنت و یا شبکه داخلی. (مسیر پیش فرض Home Directory کاربر. مثل: C:\Users\Ramin Mehrani\.m2\repository). در زمان اجرای فرآیند build، ابزار Maven بصورت خودکار dependency های مورد نیاز پروژه را از سرورهای اینترنتی و یا Mirror های تعریف شده دریافت و برای استفاده پروژه در حال build و سایر پروژه ها در Local Repository ذخیره می کند.فایل pom.xml یا Project Object Model: به ازای هر پروژه Java که با استفاده از Maven آن را مدیریت می کنیم یک فایل pom.xml در شاخه اصلی پروژه وجود دارد. فایل pom.xml شامل مشخصات پروژه و dependency ها، فرآیند build و artifact خروجی پروژه (مثلا خروجی jar یا war پروژه) و همه چیزهایی که در زمان توسعه پروژه به آنها نیاز داریم می شود.مشخص کردن مسیر Local Repository در فایل settings.xmlگفتیم Maven تمامی Dependency های دریافت شده را برای استفاده پروژه های مختلف درون Local Repository ذخیره می کند. با توجه به اینکه استفاده مستمر از maven باعث افزایش حجم فایل های ذخیره شده در Local Repository می شود این امکان وجود دارد که مسیر پیش فرض Local Repo را تغییر و آن را در جایی که فضای بیشتری برای ذخیره سازی دارد قرار داد. نکته: مسیر پیش فرض Local Repository در Maven، شاخه m2. در Home Directory کاربر می باشد. مثال:C:\Users\Ramin Mehrani\.m2مسیر مورد نظر Local Repository را در فایل settings.xml مشخص کنیم:<settings xmlns=&quothttp://maven.apache.org/SETTINGS/1.0.0&quot xmlns:xsi=&quothttp://www.w3.org/2001/XMLSchema-instance&quot xsi:schemaLocation=&quothttp://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd&quot> <!-- localRepository The path to the local repository maven will use to store artifacts. Default: ${user.home}/.m2/repository <localRepository>/path/to/local/repo</localRepository> --> ....همانطوری که مشاهده می کنیم مسیر پیش فرض در شاخه home کاربر می باشد. برای تغییر آن تنها کاری که باید انجام شود این است که خط پر رنگ مشخص شده را از حال comment خارج کنیم و مسیر مورد نظرمان را در آن قرار دهیم:<settings xmlns=&quothttp://maven.apache.org/SETTINGS/1.0.0&quot xmlns:xsi=&quothttp://www.w3.org/2001/XMLSchema-instance&quot xsi:schemaLocation=&quothttp://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd&quot> ... <localRepository>D:/Development/maven-repo</localRepository> .....ساختار پروژه های مبتنی بر Maven:ابزار Maven بصورت پیش فرض برای ساختار پروژه ها از الگوهایی پیروی می کند که بر اساس آنها source file ها را شناسایی و پروژه را compile و test و package می کند. عملا وجود این الگوها باعث استاندارد شدن ساختار پروژه ها می شود و صرفه جویی در زمان و هزینه توسعه پروژه ها را به همراه دارد.بطور معمول ساختار پروژه های Java ایجاد شده با Maven چیزی شبیه این ساختار می باشد:my-app |-- pom.xml `-- src |-- main | `-- java | `-- com | `-- mycompany | `-- app | `-- App.java `-- test `-- java `-- com `-- mycompany `-- app `-- AppTest.javaهمانطور که در بالا می بینیم در شاخه اصلی پروژه فایل pom.xml و در کنار آن، شاخه src قرار دارد که شامل دو شاخه java برای کد های اصلی و شاخه test برای کد های test پروژه می باشد. درون هر یک از این شاخه ها بر اساس package های پروژه فایل های .java قرار دارند. (در مثال بالا فایل App.java در com/mycompany/app قرار دارد.فایل pom.xmlفایل pom.xml (که برای استفاده از Maven باید در شاخه اصلی هر پروژه Java وجود داشته باشد) دارای بخش هایی برای معرفی پروژه (شامل نام، نسخه و ... پروژه) و همچنین مشخص کردن dependency های مورد استفاده در پروژه و نسخه آنها (مثلا jar file مربوط به Apache POI نسخه 4.1.2) و تنظیمات مربوط به compile، آماده سازی artifact خروجی و اجرای پروژه می باشد.نکته: با توجه به ماهیت pluggable بودن Maven، اکثر امکانات و قابلیت های این ابزار بصورت plugin قابل استفاده است و این plugin ها در فایل pom.xml تعریف می شوند.ساختار فایل pom.xmlفایل pom.xml بطور کلی دارای ساختاری بشکل زیر است:<project xmlns=&quothttp://maven.apache.org/POM/4.0.0&quot xmlns:xsi=&quothttp://www.w3.org/2001/XMLSchema-instance&quot xsi:schemaLocation=&quothttp://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd&quot> <modelVersion>4.0.0</modelVersion> <groupId>com.mycompany.app</groupId> <artifactId>string-to-excel</artifactId> <version>1.0-SNAPSHOT</version> <name>String to Excel Converter</name> <url>http://www.myprojectwebsite.com</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <my-junit-version>4.11</my-junit-version> </properties> <dependencies> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi</artifactId> <version>4.1.2</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>${my-junit-version}</version> <scope>test</scope> </dependency> </dependencies> <build> <pluginManagement> <plugins> <plugin> <artifactId>maven-clean-plugin</artifactId> <version>3.1.0</version> </plugin> <plugin> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.0</version> </plugin> <plugin> <artifactId>maven-jar-plugin</artifactId> <version>3.0.2</version> </plugin> <plugin> <artifactId>maven-install-plugin</artifactId> <version>2.5.2</version> </plugin> <!-- Other Maven Plugins ... --> </plugins> </pluginManagement> </build> </project>نسخه POM: فایل pom.xml خود دارای نسخه می باشد که حتما باید در تگ modelVersion مشخص شود و به ندرت تغییر می کند.آیتم groupId: برای تعریف نام شخص/سازمان و یا قسمتی که پروژه را ایجاد کرده استفاده می شود و معمولا بر اساس نام domain ایجاد کننده پروژه می باشد. (مثال: io.virgool برای پروژه های پلتفرم virool.io). لازم به ذکر است groupId به عنوان یک شناسه یکتا برای تمامی artifact های مشترک از یک گروه استفاده می شود.آیتم artifactId: نام پروژه ما خواهد بود! (مثل weblog).آیتم version: نسخه ای از نرم افزار است که قرار است تولید و package شود. (این ویژگی امکان اختصاص نسخه به پروژه را فراهم می کند.)آیتم URL: همانطور که از اسم آن پیداست آدرس وب سایت پروژه می باشد. (مثال: https://www.virgool.io)قسمت properties: شامل تعریف property های مختلفی از جمله encoding فایل های پروژه، نسخه جاوا مورد استفاده در پروژه و امکانی برای تعریف property های جدید (با نام دلخواه) می باشد.مثال: در pom.xml بالا برای تعریف نسخه junit از property جدید <my-junit-version> استفاده شده و در قسمت dependency از property تعریف شده با فرمت {my-junit-version}$ استفاده شده است.قسمت dependencies: شامل تعریف تمامی dependency های پروژه می باشد. این قسمت که ساختاری مشابه قسمت معرفی پروژه دارد، این امکان را فراهم می کند که با مشخص کردن groupId و artifactId و version کتابخانه مورد نظر را در پروژه استفاده کنیم. (با اجرای دستورات Maven نظیر maven compile، ابتدا dependency های تعریف شده در این قسمت در Local Repository دانلود تا در compile و package نمودن پروژه استفاده می شود.)به عنوان مثال اگر بخواهیم کتابخانه apache-poi نسخه 4.1.2 را که در پست قبلی از آن استفاده کردیم به پروژه خود اضافه کنیم می بایست در قسمت dependencies آن را اضافه کنیم:<dependencies> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi</artifactId> <version>4.1.2</version> </dependency> <!-- Other Dependencies -->نکته ای که در این اینجا باید به آن اشاره کنیم، scope مربوط به dependency ها است. بصورت پیش فرض (و در صورت عدم درج) scope هر dependency برابر مقدار compile می باشد که معنای آن این است که dependency مورد نظر در زمان compile و test و package و runtime وجود دارد.چنانچه مثل فایل pom.xml بالا، scope را برابر test قرار دهیم، dependency مورد نظر تنها در زمان test استفاده شده و در زمان package نمودن پروژه همراه آن نخواهد بود.همچنین اگر scope را برابر provided قرار دهیم، انتظار داریم در زمان runtime (اجرای برنامه) dependency مورد نظر توسط محیط اجرا برای application در دسترس باشد. (مثلا در Application Server وجود داشته باشد.)قسمت build: شامل پیکربندی فرآیند build و همچنین تعریف plugin های Maven مورد استفاده در پروژه برای انجام فعالیت هایی نظیر compile و test و package می باشد.برای هر plugin هم همانند dependency ها می بایست groupId و artifactId و version تعریف شود که البته groupId بصورت پیش فرض و در صورت عدم درج org.apache.maven.plugins خواهد بود.نکته: تعریف version برای plugin ها باعث می شود که همواره build پروژه بر اساس روند یکسانی انجام شود و در صورتی که رفتار plugin ها در نسخه های بعدی تغییر کرد بر روی build پروژه ما تاثیری نداشته باشد.ایجاد پروژه string-to-excel با استفاده از Mavenحالا که با ساختار کلی Maven آشنا شدیم می توانیم نحوه ایجاد پروژه string-to-excel را که در پست قبلی بدون استفاده از Maven ایجاد کردیم این بار با استفاده از این ابزار ایجاد کنیم.قبل از اینکه پروژه را ایجاد کنیم اگر هنوز Maven را نصب نکرده اید این پست را مشاهده کنید.قدم اول: ایجاد ساختار پروژه و فایل pom.xmlفرض کنید قصد داریم برای شرکت فرضی mycompany که Domain آن company.com است اپلیکیشن String to Excel Converter را با استفاده از ابزار Maven ایجاد کنیم.شاید به نظر این طور بیاید که ایجاد پروژه مبتنی بر Maven کمی سخت باشد. خوشبختانه Maven با استفاده از مکانیزم archetype، این کار را هم برای ما ساده کرده است. در واقع archetype امکان تولید پروژه بر اساس Template های از پیش تعریف شده را فراهم می کند.نکته: این امکان وجود دارد که template های جدیدی را نیز به عنوان archetype تعریف و استفاده کنیم. (البته در اکثر پروژه ها archetype های موجود در Maven جوابگوی نیاز ما برای ایجاد پروژه می باشد و نیازی به ایجاد archetype جدیدی نیست.)در اینجا قصد داریم پروژه string-to-excel را با استفاده از Maven ایجاد و توسعه دهیم، بنابراین از maven-archetype-quickstart استفاده می کنیم. برای اینکار دستور زیر را در command prompt اجرا می کنیم:mvn archetype:generate -DgroupId=com.mycompany.app -DartifactId=string-to-excel -DarchetypeArtifactId=maven-archetype-quickstart -DarchetypeVersion=1.4 -DinteractiveMode=falseاین دستور ضمن ایجاد شاخه پروژه (هم نام artifactId)، فایل pom.xml و شاخه های مربوط به کد اصلی پروژه و test را نیز تولید می کند:└───string-to-excel └───src ├───main │ └───java │ └───com │ └───mycompany └───test └───java └───com └───mycompanyقدم دوم: تنظیمات Maven در Eclipse IDEدر حال حاضر Eclipse IDE برای Java با پلاگین Maven منتشر می شود. اما در اینجا قصد داریم نحوه استفاده از Maven که قبلا آن را نصب و پیکربندی کردیم را در Eclipse IDE مرور کنیم. در واقع می خواهیم در Eclipse IDE از Maven بصورت External استفاده کنیم!برای دسترسی به تنظیمات Eclipse IDE می بایست از Window « Preferences را انتخاب و سپس در قسمت جستجو maven را وارد کنیم:و با انتخاب گزینه Installation به قسمت تنظیمات نصب Maven در Eclipse وارد شویم:تنظیمات نصب Maven در Eclipse IDEهمانطور که می بینیم Maven بصورت Embedded (داخلی) همراه Eclipse IDE وجود دارد و به عنوان گزینه پیش فرض نیز انتخاب شده است. و اگر گزینه User Settings را از قسمت سمت چپ انتخاب کنیم، مسیر فایل settings.xml مربوط به Embedded Maven و Repository تعریف شده در فایل settings.xml را خواهیم یافت.تنظیمات مرتبط با Maven در Eclipse IDEدر این قسمت امکان تعریف یک فایل settings.xml بصورت Global (سراسری) و هم بصورت اختصاصی کاربر (User Settings) قابل تنظیم است و همانطور که قبلا دیدم Local Repository هم بر اساس تنظیمات Global و User مشخص می شود.معرفی Maven نصب شده به Eclipse IDEدر قسمت Installations باید با استفاده از گزینه Add، مسیر نصب maven را مشخص کنیم:معرفی مسیر نصب maven در تنظمیات Eclipse IDEسپس در لیست نمایش داده شده آن را انتخاب می کنیم:انتخاب Maven نصب شده در Eclipse IDEو در قسمت User Settings (منو سمت چپ)، مسیر فایل Settings.xml را در قسمت User Settings مشخص می کنیم:مشخص کردن مسیر Settings.xml در قسمت User Settingsبا این کار Local Repository به مسیری که قبلا در فایل settings.xml مشخص کرده ایم تغییر می یابد.قدم سوم: وارد نمودن پروژه در Eclipse IDEبعد از انجام تنظیمات مربوط به Maven در Eclipse، می توانیم پروژه string-to-excel را که با استفاده از Maven ایجاد کرده ایم را از طریق File » Import » Existing Maven Projects به Eclipse وارد (Import) نماییم::با انتخاب شاخه پروژه (string-to-excel)، پروژه توسط Eclipse بعنوان یک پروژه Maven شناسایی می شود:قدم سوم: تعریف property ها و dependency های لازم در pom.xmlقبل از اینکه شروع به نوشتن کد برنامه کنیم لازم است pom.xml پروژه را برای معرفی dependency های مورد نیاز و تعریف property ها تغییر دهیم.تغییر نسخه JDK:<properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> </properties>با توجه به اینکه قصد داریم از Apache POI نسخه 3.1.4 استفاده کنیم لازم است این dependency در فایل pom.xml تعریف کنیم. پس همانطور که در بالا دیدیم این dependency ها را بصورت زیر به قسمت dependencies اضافه می کنیم:<dependencies> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi</artifactId> <version>4.1.2</version> </dependency> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> <version>4.1.2</version> </dependency> </dependencies>با این کار عملا Dependency مربوط به Apache POI با استفاده از Maven به پروژه ما اضافه شده و در زمان build پروژه اگر این Dependency در Local Repository وجود نداشته باشد، به آن اضافه می شود.قدم چهارم: نوشتن کد و اجرای برنامهکد برنامه را مشابه پست قبلی در کلاس App که این بار توسط Maven در package com.mycompany.app ایجاد شده می نویسیم:import java.io.FileOutputStream; import java.io.IOException; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.xssf.streaming.SXSSFWorkbook;public class App { public static void main(String[] args) throws IOException { FileOutputStream out = new FileOutputStream(&quotd:\\workbook.xlsx&quot); SXSSFWorkbook workbook = new SXSSFWorkbook(); Sheet sh = workbook.createSheet(); Row row = sh.createRow(0); Cell cell = row.createCell(0); cell.setCellValue(&quotViva Excel&quot); workbook.write(out); out.close(); System.out.println(&quotDONE !&quot); } }برای اجرای پروژه روش های مختلفی وجود دارد:اجرای main method برنامه با استفاده از گزینه Run در Eclipse: بر روی فایل App.java، کلیک راست و سپس گزینه Run As « Java Application را انتخاب می کنیم:اجرای برنامه در Eclipse IDEاجرای برنامه توسط Maven:برای این کار باید ابتدا پلاگین اجرای Java را به pom.xml اضافه کنیم:<plugins> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>exec-maven-plugin</artifactId> <version>1.4.0</version> <configuration> <mainClass>com.mycompany.app.App</mainClass> </configuration> </plugin> .....و با استفاده از Run Configuration در Eclipse، اجرای برنامه توسط maven را پیکربندی کنیم:در قسمت Name، نام مورد نظر را وارد می کنیم: (مثلا Run String to Excel).در قسمت Base Directory، با استفاده از گزینه Workspace پروژه را انتخاب می کنیم و در قسمت Goals هم به maven می گوییم چه کارهایی را به چه ترتیبی انجام دهد:ابتدا clean (همه فایل ها و پوشه های خروجی را پاک کند)، سپس compile کند و در نهایت برنامه را اجرا کند: (exec:java)تنظمیمات Run Configuration برای اجرای برنامه توسط Maven در Eclipseو در نهایت با انتخاب گزینه Run، برنامه اجرا خواهد شد:اجرای برنامه توسط Maven در Eclipse IDEایجاد Executable JAR file (فایل JAR قابل اجرا) توسط Maven:برای اینکه بتوانیم پروژه را بصورت مستقل از IDE و Maven و با صرفا با دستوری مثل دستور زیر اجرا کنیم لازم است دو پیش نیاز وجود دارد: java -jar string-to-excel.jar ایجاد فایل Executable JAR که برای این امکان می بایست با استفاده از Maven فایل JAR قابل تولید کنیم. (لازم است کلاس جاوایی که حاوی متد main برنامه هست مشخص شود)قرار دادن همه Dependency های پروژه (مثل JAR فایل Apache POI مورد استفاده در این پروژه) در فایل JAR تولید شده در مرحله اول. البته ذکر این نکته لازم است که بدون قرار دادن Dependency ها نیز امکان ایجاد و اجرای Executable JAR وجود دارد، اما نیاز است Dependency های پروژه در classpath در زمان اجرا قابل دسترسی باشند. (مثل زمان اجرای پروژه های تحت وب در Application Server ها و یا مشخص نمودن مسیر dependency ها در زمان اجرا با استفده از سوییچ classpath.)با استفاده از پلاگین Assembly در Maven، امکان تولید انواع artifact ها نظیر (JAR، WAR، EAR و ...) وجود دارد که ما در حال حاضر برای تولید Executable JAR with Dependencies از آن استفاده می کنیم.برای این کار لازم است این پلاگین را به pom.xml اضافه کنیم و Configuration لازم برای ایجاد Executable JAR و قرار دادن Dependency ها را انجام دهیم:<plugin> <artifactId>maven-assembly-plugin</artifactId> <executions> <execution> <phase>package</phase> <goals> <goal>single</goal> </goals> </execution> </executions> <configuration> <descriptorRefs> <descriptorRef>jar-with-dependencies</descriptorRef> </descriptorRefs> <archive> <manifest> <mainClass>com.mycompany.app.App</mainClass> </manifest> </archive> </configuration> </plugin>همانطور که مشاهده می شود، در این پلاگین در قسمت configuration، برای قرار دادن Dependency ها از jar-with-dependencies و برای مشخص نمودن کلاس جاوایی که حاوی متد main می باشد از mainClass در manifest استفاده شده است. در واقع فایل MANIFEST.MF در مسیر META-INF در فایل JAR نهایی ایجاد می شود که مسیر mainClass تعیین شده در قسمت configuration را در خود دارد:Manifest-Version: 1.0 Archiver-Version: Plexus Archiver Created-By: Apache Maven Built-By: Ramin Mehrani Build-Jdk: 14.0.1 Main-Class: com.mycompany.app.Appنکته دیگری که وجود دارد این است که پلاگین assembly فرآیند ایجاد JAR را در فاز package انجام می دهد. (مطابق تعریفی که در قسمت execution انجام دادیم.) پس اگر این phase را اجرا کنیم می توانیم به خروجی مورد نظر (Executable JAR file with dependencies) دست پیدا کنیم: mvn clean packageو یا در Eclipse IDE همانند ایجاد Run Configuration برای compile و اجرای برنامه این بار یک Configuration جدید برای Package نمودن پروژه ایجاد می کنیم:تعریف Run Configuration برای Package نمودن JAR در Eclipse IDEو اگر پوشه target پروژه را مشاهده کنیم دو فایل JAR قابل اجرا وجود دارد:string-to-excel-1.0-SNAPSHOT.jar string-to-excel-1.0-SNAPSHOT-jar-with-dependencies.jarکه فایل اول JAR قابل اجرا اما بدون dependency ها و فایل دوم همانطور که از نام آن مشخص است فایل JAR قابل اجرا شامل dependency های پروژه می باشد که می توان با دستور زیر آن را اجرا کرد: java -jar string-to-excel-1.0-SNAPSHOT-jar-with-dependencies.jarسورس بروز رسانی شده پروژه در GitHub.