Hello,
I have been developing a stand-alone Java application packaged as an executable JAR file.
The application had,
- Database interaction using iBatis APIs.
- Log the various activities in a log file using log4j API.
I had packaged the “my-db.properties” file that feeds the driver/username/password information to “SqlMapConfig.xml(iBatis configuration file)” within the JAR itself.Also the “log4j-myapp.properties” file was placed inside the JAR.
This introduced strong coupling/dependency between these files and JAR. Whenever I need to change database or log4j configuration, I had to re-package the JAR.
I was searching for a solution for this and based on google searches and interpretation of Java documentation for Class.getResourceAsStream() addressed this problem in the following way.
Solution:
- Consider that we are generating this executable jar (myapp.jar) in “myapp/dist” directory.
- To resolve dependencies on iBatis and log4j, put respective JARs in the same i.e “myapp/dist” directory.
- Add Class-Path attribute in “Manifest.mf” file that lists these JAR files.
- Create a directory named “config” in “myapp/dist” where we will keep properties file. Of course this can be any name you like.
- Add the entry “config/” to the Class-Path attribute in “Manifest.mf” file. This brings “config” directory in the classpath while executing the application.
- Put the “log4j-myapp.properties” and “my-db.properties” in “config” directory.
- Your application(may be constructor of your main class) can load the log4j configuration as shown below –
- “SqlMapConfig.xml” can point to the resource properties file as shown below. This does not change much as compared to log4j.
- Now when you run your application (java -jar myapp.jar), the properties files will be picked up from the config directory.
This can be done by providing the custom manifest file while generating the JAR file. #2 and #3 are widely used in various enterprise as well as standalone applications so doing this is not an issue.
/*Initialize log4j */ Properties props = new Properties(); props.load(this.getClass().getResourceAsStream("/log4j-myapp.properties")); props.list(System.out); PropertyConfigurator.configure(props);
<sqlMapConfig> <!-- This is where we specify the properties file where DB configuration is specified --> <properties resource="my-db.properties"/> <transactionManager type="JDBC" commitRequired="false"> <dataSource type="SIMPLE"> <property name="JDBC.Driver" value="${driver}"/> <property name="JDBC.ConnectionURL" value="${url}"/> <property name="JDBC.Username" value="${username}"/> <property name="JDBC.Password" value="${password}"/> </dataSource> </transactionManager> <sqlMap resource="com/amit/test/xyz.xml"/> </sqlMapConfig>
This ensures you do not need to change the JAR file for making Database and log4j configuration changes.
The Directory structure for “dist” looks as shown below –
Directory Strcture for "dist": myapp |----dist | |----myapp.jar | |----ibatis-2.3.4.726.jar |----log4j-1.2.14.jar |----ojdbc14_g.jar |----config | |---log4j-myapp.properties |---my-db.properties
Manifest.mf classpath entry as below –
Why this works?
If you see the line that loads log4j configuration file, it has “/” at the start of filename.
props.load(this.getClass().getResourceAsStream("/log4j-myapp.properties"));
As per the Java documentation(see below) for the method, we have to give “/” at the start and then only the resource will be located using absolute name calculated as mentioned.
I was facing issues earlier because,
- I was only giving “log4j-myapp.properties” while loading the log4j configuration in my program.
- In the Manifest.mf, I was not appending “/” at the end of “config” directory. Without this the resource was not getting located.
Of course this is not just limited to iBatis and/or log4j, but any properties file that needs to be controlled outside the realm of the JAR file especially in stand-alone application can be configured this way.
Java API documentation to the rescue once again. Many thanks to them.
Cheers !!
Amit