13
votes

I'm developing a desktop application in java using spring and hibernate. I want to package it as a executable jar but I'm having problems loading the context configuration XML from within the jar file.

I package the application as a runnable jar file and when I run the jar file it tells me that the file doesn't exist. I know that from within a jar file I should load an InputStream but there's no ApplicationContext implementation that supports it.

I believe that I'll have to code my own InputStreamXmlApplicationContext and I already tried doing it. I did some research and a little coding:


import java.io.InputStream;

import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.context.support.AbstractXmlApplicationContext;
import org.springframework.core.io.InputStreamResource;
import org.springframework.core.io.Resource;

public class InputStreamXmlApplicationContext extends AbstractXmlApplicationContext {

    private Resource[] configResources;

    public InputStreamXmlApplicationContext(InputStream in) {
        InputStreamResource resource = new InputStreamResource(in);
        configResources = new InputStreamResource[] {resource};
        setConfigResources(configResources);
        refresh();
    }

    public Resource[] getConfigResources() {
        return configResources;
    }

    public void setConfigResources(Resource[] configResources) {
        this.configResources = configResources;
    }

    protected void initBeanDefinitionReader(XmlBeanDefinitionReader beanDefinitionReader) {
        beanDefinitionReader.setValidationMode(XmlBeanDefinitionReader.VALIDATION_XSD);
    }
}

But I can't get it to work. Could someone help me?

4

4 Answers

10
votes

Try with an ClassPathXmlApplicationContext

It is a standalone XML application context, taking the context definition files from the class path, interpreting plain paths as class path resource names that include the package path (e.g. "mypackage/myresource.txt").

Useful for test harnesses as well as for application contexts embedded within JARs.

Here is how you might do it:

Create yourself a Test class with the following content in it:

package com.test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
  public static void main(String[] args) {
    ClassPathXmlApplicationContext context = new 
           ClassPathXmlApplicationContext("/com/test/appConfig.xml");
    Integer someIntBean = (Integer) context.getBean("testBean");
    System.out.println(someIntBean.intValue()); // prints 100, as you will see later
  }
}

Now create the beans application config file named appConfig.xml:

<?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-2.0.xsd">
  <bean id="testBean" class="java.lang.Integer">
    <constructor-arg type="int" value="100" />
  </bean>
</beans>

You create these files in a package called com.test, next to each other. Add classpath references to your spring jars or pack them together into your own single jar file wich should look like this:

test.jar --- com
          |   |--- test
          |          |--- appConfig.xml
          |          |--- Test.class
          |
          |-- META-INF
          |        |--- MANIFEST.MF
          |
          |-- org
          |     |--- springframework 
          |               |--- ...
          |               |--- ...
          |-- ....

In your manifest file you will have this (use with a trailing blank line):

Main-Class: com.test.Test

And that is it.

When your run your file (double click or java -jar test.jar) you should see 100 printed on the screen. Here is what I get from executing it (notice the 100 I was talking about above - on the last row):

Feb 23, 2011 11:29:18 PM org.springframework.context.support.AbstractApplicationContext prepareRefresh
INFO: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@ca2dce: 
display name [org.springframework.context.support.ClassPathXmlApplicationContext@ca2dce]; 
startup date [Wed Feb 23 23:29:18 PST 2011]; root of context hierarchy
Feb 23, 2011 11:29:18 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [com/test/appConfig.xml]
Feb 23, 2011 11:29:20 PM org.springframework.context.support.AbstractApplicationContext obtainFreshBeanFactory
INFO: Bean factory for application context [org.springframework.context.support.ClassPathXmlApplicationContext@ca2dce]: 
org.springframework.beans.factory.support.DefaultListableBeanFactory@199f91c
Feb 23, 2011 11:29:20 PM org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
INFO: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@199f91c: 
defining beans [testBean]; root of factory hierarchy
100

P.S. You don't absolutely have to include the Spring jars content into your own jar. You can have them available on the classpath when you run your app. I placed them like that since you mentioned one jar. Basically this is what you need:

test.jar --- com
          |   |--- test
          |          |--- appConfig.xml
          |          |--- Test.class
          |
          |-- META-INF
                   |--- MANIFEST.MF
10
votes

Provided your jar is in classpath; you can import bean definitions from jars using import

<import resource="classpath:/path-to.xml"/>
2
votes

Why not use ClasspathXmlApplicationContext and load them using the classpath relative path?

1
votes

I suppose you want to run your application as java -jar myapp.jar

In this case, use class ClassPathXmlApplicationContext within your class with the method main.

public static void main( String[] args ) {
    String config[] = { "spring-beans.xml" };
    ApplicationContext ctx = new ClassPathXmlApplicationContext(config);
    DataSource ds = (DataSource) ctx.getBean("dataSource", DataSource.class);
}

It's a terrible idea to try to implement your own ApplicationContext. That's too much work and too much room for errors.