Maven is a very flexible and powerful technology. Anyone in Java software industry is suggested to make use of it.
This document will describe multi module maven project. Assembly plugin is used to merge outputs of modules into one jar.
Provided project is a full fledged Java project which uses spring framework for dependency injection, hibernate for ORM. The project comes with several basic classes to make use of the project architecture and show use of some usefull patterns. All project files are attached to the end of this document.
1. Parent Pom
Lists all submodules and uses maven-assembly-module.
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.mygroup</groupId>
<artifactId>myproject</artifactId>
<packaging>pom</packaging>
<version>1.0</version>
<build>
<directory>target</directory>
<outputDirectory>target/myproject/classes</outputDirectory>
<testOutputDirectory>target/test-classes</testOutputDirectory>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.2</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
</plugins>
<pluginManagement>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.4</version>
<configuration>
<descriptors>
<descriptor>src/assemble/bin.xml</descriptor>
</descriptors>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
<modules>
<module>myproject-assembly</module>
<module>myproject-module1</module>
<module>myproject-module2</module>
</modules>
<properties>
<spring.version>3.2.3.RELEASE</spring.version>
<hibernate.version>3.2.0.ga</hibernate.version>
<hibernate.annotations.version>3.2.0.ga</hibernate.annotations.version>
<hibernate.validator.version>4.0.2.GA</hibernate.validator.version>
<logback.version>1.0.13</logback.version>
<slf4j.version>1.7.5</slf4j.version>
<junit.version>4.11</junit.version>
<spring.security.version>3.2.0.RELEASE</spring.security.version>
<jsf.version>2.2.9</jsf.version>
</properties>
<repositories>
<repository>
<id>spring</id>
<name>Spring repository</name>
<url>http://s3.amazonaws.com/maven.springframework.org/release</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>mvnrepository</id>
<name>Spring repository</name>
<url>http://mvnrepository.com/artifact/</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
</project>
2. Module1
Module1 comes with basic constructs. First of all project wise dependencies are listed in module1 pom.
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>myproject</artifactId>
<groupId>com.mygroup</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>myproject-module1</artifactId>
<packaging>jar</packaging>
<version>1.0</version>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- data source -->
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
<!-- persistence -->
<dependency>
<groupId>javax.persistence</groupId>
<artifactId>persistence-api</artifactId>
<version>1.0</version>
</dependency>
<!-- hibernate jars-->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-annotations</artifactId>
<version>${hibernate.annotations.version}</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate</artifactId>
<version>${hibernate.version}</version>
<exclusions>
<exclusion>
<artifactId>commons-logging</artifactId>
<groupId>commons-logging</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>javassist</groupId>
<artifactId>javassist</artifactId>
<scope>compile</scope>
<version>3.3</version>
</dependency>
<!-- end of hibernate jars-->
<!-- google generic hibernate dao-->
<dependency>
<groupId>com.googlecode.genericdao</groupId>
<artifactId>dao-hibernate</artifactId>
<version>1.1.0</version>
<scope>compile</scope>
<exclusions>
<exclusion>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- end of persistence -->
<!-- jdbc connectors -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.15</version>
</dependency>
<!-- Logging with SLF4J & LogBack -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
<scope>runtime</scope>
</dependency>
<!-- for log4j support -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>log4j-over-slf4j</artifactId>
<version>${slf4j.version}</version>
</dependency>
<!-- Unit Testing -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
Module1 comes with three spring configuration files:
· applicationContext-core.xml : Loads property file and other config files.
· applicationContext-jmx.xml : Contains JMX configuration for remote monitoring.
· persistence-config-core.xml : Contains spring transaction management declerations, database connection pool definitions and hibernate configurations.
2.A. applicationContext-core.xml
applicationPropertiesDir should be passed to application as a JVM argument.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
<!-- Load Property File -->
<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="ignoreResourceNotFound" value="true"/>
<property name="locations">
<list>
<!-- set at startup as -DapplicationPropertiesDir=dir-to-props-folder -->
<value>file:${applicationPropertiesDir}application.properties</value>
</list>
</property>
</bean>
<import resource="persistence-config-core.xml"/>
<import resource="applicationContext-jmx.xml"/>
</beans>
2.B. persistenceContext-core.xml
Placeholders are replaced with values from properties file. packagesToScan should be containing list of packages that may contain Entity classes. Make sure hibernate.hbm2ddl.auto property is set to be empty (in properties file) for production environments. Similarly jdbc:initialize-database should be disabled for production environments. jdbc:initialize-database can be used for test environments to populate db tables with test data.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd http://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd">
<!-- configure spring managed transactions -->
<tx:annotation-driven mode="proxy" proxy-target-class="true" transaction-manager="transactionManager"/>
<!-- Data Source Definition-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
destroy-method="close">
<property name="driverClass" value="${database.connection.driver}"/>
<property name="jdbcUrl" value="${database.connection.url}"/>
<property name="user" value="${database.connection.username}"/>
<property name="password" value="${database.connection.password}"/>
<property name="acquireIncrement" value="${database.connection.acquireIncrement}"/>
<property name="initialPoolSize" value="${database.connection.initialPoolSize}"/>
<property name="maxIdleTime" value="${database.connection.maxIdleTime}"/>
<property name="maxPoolSize" value="${database.connection.maxPoolSize}"/>
<property name="maxStatements" value="${database.connection.maxStatements}"/>
<property name="minPoolSize" value="${database.connection.minPoolSize}"/>
<property name="idleConnectionTestPeriod"
value="${database.connection.idleConnectionTestPeriod}"/>
<property name="testConnectionOnCheckout" value="true"/>
<property name="preferredTestQuery" value="${database.connection.preferredTestQuery}"/>
<property name="maxAdministrativeTaskTime"
value="${database.connection.maxAdministrativeTaskTime}"/>
<property name="unreturnedConnectionTimeout"
value="${database.connection.unreturnedConnectionTimeout}"/>
<property name="debugUnreturnedConnectionStackTraces"
value="${database.connection.debugUnreturnedConnectionStackTraces}"/>
</bean>
<!-- Hibernate SessionFactory Definition -->
<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="packagesToScan">
<list>
<value>com.mygroup.myproject.module1.model</value>
</list>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">${hibernate.dialect}</prop>
<prop key="hibernate.hbm2ddl.auto">${hibernate.hbm2ddl.auto}</prop>
<prop key="hibernate.show_sql">${hibernate.show_sql}</prop>
<prop key="hibernate.id.new_generator_mappings">true</prop>
<prop key="hibernate.cglib.use_reflection_optimizer">true</prop>
<prop key="hibernate.cache.provider_class">org.hibernate.cache.HashtableCacheProvider</prop>
<!-- important to avoid connection leaks -->
<prop key="hibernate.connection.release_mode">after_transaction</prop>
<!-- encoding-->
<prop key="hibernate.connection.CharSet">utf8</prop>
<prop key="hibernate.connection.characterEncoding">utf8</prop>
<prop key="hibernate.connection.useUnicode">true</prop>
<!-- tracking -->
<prop key="generate_statistics">${hibernate.generate.statistics}</prop>
</props>
</property>
</bean>
<!-- Spring Data Access Exception Translator Defintion -->
<bean id="jdbcExceptionTranslator"
class="org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- Hibernate Template Definition -->
<bean id="hibernateTemplate" class="org.springframework.orm.hibernate3.HibernateTemplate">
<property name="sessionFactory" ref="sessionFactory"/>
<property name="jdbcExceptionTranslator" ref="jdbcExceptionTranslator"/>
</bean>
<!-- Hibernate Transaction Manager Definition -->
<bean id="transactionManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
<jdbc:initialize-database enabled="${db.init.enabled}" data-source="dataSource">
<jdbc:script location="file:${applicationPropertiesDir}${db.init.file}"/>
<!-- encoding="UTF-8" -->
</jdbc:initialize-database>
</beans>
2.C. applicationContext-jmx.xml
Contains JMX configuration. Make sure JVM is started with relevant parameters (listed below) for remote monitoring process.
-Dcom.sun.management.jmxremote=true -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.port=3309 -Djava.rmi.server.hostname=HOST_IP
HOST_IP shold be replaced with IP value of host system. Jconsole, JvisualVM or JMC can be used to remote monitor java processes. For remote monitoring a java process details, loot at this page.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- spring based jmx export -->
<bean id="jmxExporter" class="org.springframework.jmx.export.MBeanExporter" lazy-init="false">
<property name="registrationPolicy" value="FAIL_ON_EXISTING"/>
<property name="beans">
<map>
<entry key="org.hibernate.jmx:type=Statistics,name=hibernateStatistics" value-ref="hibernateStatisticsMBean"/>
<!-- additional/Custom JMX beans goes here -->
</map>
</property>
</bean>
<bean id="hibernateStatisticsMBean" class="org.hibernate.jmx.StatisticsService">
<property name="statisticsEnabled" value="${hibernate.generate.statistics}"/>
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
</beans>
3. Module2
Module contains real bussiness. Service beans which contains bussiness tasks are defined here. Also application entry point (main method) is found in this module. Typically depends on first module.
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>myproject</artifactId>
<groupId>com.mygroup</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>myproject-module2</artifactId>
<packaging>jar</packaging>
<version>1.0</version>
<dependencies>
<dependency>
<groupId>com.mygroup</groupId>
<artifactId>myproject-module1</artifactId>
<version>1.0</version>
<type>jar</type>
<scope>compile</scope>
</dependency>
</dependencies>
</project>
Logback configration file is provided. It basically outputs to two files. One file contains all log information, other file only collects warning or error level logs. Configuration file is scanned at every 60 seconds, if modified changes are loaded. jmxConfigurator , as name implies exports JMX beans for remote monitoring. Log files are rotated daily. Also rotated if max file size is reached. Rotated log files are compressed. As history, records of last 60 days are kept.
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds">
<jmxConfigurator/>
<property name="log.dir" value="logs"/>
<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
<Target>System.out</Target>
<encoder>
<pattern>%date{HH:mm:ss.SSS} [%thread] %-5level %logger{35} - %msg%n</pattern>
</encoder>
</appender>
<appender name="defaultLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
<File>${log.dir}/catchAll.log</File>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- daily rollover -->
<fileNamePattern>${log.dir}/%d{yyyy-MM,aux}/catchAll.%d{yyyy-MM-dd}.%i.zip</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<!-- or whenever the file size reaches 500MB -->
<maxFileSize>500MB</maxFileSize>
<maxIndex>100</maxIndex>
</timeBasedFileNamingAndTriggeringPolicy>
<!-- keep 30 days' worth of history -->
<maxHistory>360</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%date{HH:mm:ss.SSS} [%thread] %-5level %logger{35} - %msg%n</pattern>
</encoder>
</appender>
<appender name="errorLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
<File>${log.dir}/error.log</File>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- daily rollover -->
<fileNamePattern>${log.dir}/%d{yyyy-MM,aux}/error.%d{yyyy-MM-dd}.%i.zip</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<!-- or whenever the file size reaches 500MB -->
<maxFileSize>500MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<!-- keep 30 days' worth of history -->
<maxHistory>360</maxHistory>
</rollingPolicy>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>WARN</level>
</filter>
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{35} - %msg%n</pattern>
</encoder>
</appender>
<root level="DEBUG">
<appender-ref ref="defaultLog"/>
<appender-ref ref="errorLog"/>
<!--appender-ref ref="stdout"/-->
</root>
<!-- suppress c3p0 logs -->
<logger name="com.mchange.v2.log.MLog" level="info"/>
</configuration>
4. Module Assembly
Assembly module, assembles other modules into one jar file. All dependent jars are copied to target/lib dir. A bat file is provied to run the application. One thing to note with bat file is, it requires the existence of properties-directory as applicationPropertiesDir in project root.
<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2 http://maven.apache.org/xsd/assembly-1.1.2.xsd">
<id>myproject-assembly</id>
<formats>
<format>jar</format>
</formats>
<includeBaseDirectory>false</includeBaseDirectory>
<dependencySets>
<dependencySet>
<!-- Enable access to all projects in the current multimodule build! -->
<useProjectArtifact>true</useProjectArtifact>
<!-- Now, select which projects to include in this module-set. -->
<includes>
<include>com.mygroup:*</include>
</includes>
<outputDirectory>/</outputDirectory>
<unpack>true</unpack>
</dependencySet>
</dependencySets>
</assembly>
Assembly module pom executes assembling process and copies all dependencies to target/lib directory.
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>myproject</artifactId>
<groupId>com.mygroup</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>myproject-assembly</artifactId>
<packaging>pom</packaging>
<dependencies>
<dependency>
<artifactId>myproject-module2</artifactId>
<groupId>com.mygroup</groupId>
<!-- Before first build 'LATEST' may cause errors, replace it with a valid version number and build.
After a successful build, LATEST is good to go again. -->
<version>LATEST</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
<id>make-bundles</id>
<goals>
<goal>single</goal>
</goals>
<phase>package</phase>
<configuration>
<finalName>${project.build.finalName}</finalName>
<appendAssemblyId>false</appendAssemblyId>
<descriptors>
<descriptor>src/assembly/bin.xml</descriptor>
</descriptors>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<phase>install</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/lib</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
And finally run.bat for launching the application.
java -cp .;target/myproject-assembly-1.0.jar;target/lib/* -DapplicationPropertiesDir=../properties-directory/ com.mygroup.myproject.module2.Main
JMX parameters can be added to make remote monitoring possible. See section 2.C for details.
java -cp .;target/myproject-assembly-1.0.jar;target/lib/* -DapplicationPropertiesDir=../properties-directory/ -Dcom.sun.management.jmxremote=true -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.port=3309 -Djava.rmi.server.hostname=HOST_IP com.mygroup.myproject.module2.Main
Download whole project from this link.
Comments
Post a Comment