PDA

View Full Version : [newbie] Problems in loading Spring beans from custom ClassLoader



alzamabar
Jun 21st, 2008, 10:47 AM
Hi, I'm trying to create my only simple container. The idea is to have a jar with a Main class in it which has in its classpath Spring jars and my application. I could then start the container with something like:

java -jar myapp.jar

For this purpose, I created a sort of /lib folder with all Spring jars, including some other service jars such as log4j, commons, etc.

I also created a custom classloader, which I use to load all the above jars from the main class in the JAR file.

This is the main class:



package uk.co.jemos.tests;

import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

/**
* Hello world!
*
*/
public class Main
{

private static final String LIB_PATH = "C:" + File.separatorChar + "temp" +
File.separatorChar + "spring-lib";

public static void main( String[] args )
{

new Main().boot();

}

private void boot() {

Set<URL> urlSet = new HashSet<URL>();

try {

String path = ClassLoaderHelper.getPath(LIB_PATH + File.separatorChar);
urlSet.add(new URL("file", null, path));

path = ClassLoaderHelper.getPath(LIB_PATH + File.separatorChar + "ext" + File.separatorChar);

urlSet.add(new URL("file", null, path));

urlSet.add(new URL(this.getClass().getProtectionDomain().getCodeS ource().getLocation() +
this.getClass().getName().replace('.', '/') + ".class"));


URL[] jars = ClassLoaderHelper.getJarNames(urlSet);

System.out.println(Arrays.toString(jars));

URLClassLoader loader = URLClassLoader.newInstance(jars);

Class cl1 = loader.loadClass("org.springframework.context.support.ClassPathXmlAp plicationContext");

Constructor<?> c1 = cl1.getConstructor(String.class);

c1.newInstance("spring-config.xml");

Class cl2 = loader.loadClass("org.springframework.remoting.rmi.RmiRegistryFactor yBean");

System.out.println(cl2.getName());

} catch (MalformedURLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchMethodException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

}
}

The Classloader helper class and the spring configuration files are attached:


If I run the example, it works and it gives me the following:

[CODE]
C:\Projects\jemos-framework\target>java -jar jemos-framework-1.0-SNAPSHOT.jar
Class in classpath: C:\temp\spring-lib\ext\commons-digester.jar
Class in classpath: C:\temp\spring-lib\ext\commons-pool.jar
Class in classpath: C:\temp\spring-lib\spring-beans.jar
Class in classpath: C:\temp\spring-lib\spring-test.jar
Class in classpath: C:\temp\spring-lib\ext\commons-collections.jar
Class in classpath: C:\temp\spring-lib\ext\commons-lang.jar
Class in classpath: C:\temp\spring-lib\ext\commons-validator.jar
Class in classpath: C:\temp\spring-lib\spring-context-support.jar
Class in classpath: C:\temp\spring-lib\spring-core.jar
Class in classpath: C:\temp\spring-lib\ext\commons-io.jar
Class in classpath: C:\temp\spring-lib\ext\commons-discovery.jar
Class in classpath: C:\temp\spring-lib\spring-webmvc-portlet.jar
Class in classpath: C:\temp\spring-lib\ext\commons-codec.jar
Class in classpath: C:\temp\spring-lib\ext\log4j-1.2.15.jar
Class in classpath: C:\temp\spring-lib\spring-tx.jar
Class in classpath: C:\temp\spring-lib\spring-aop.jar
Class in classpath: C:\temp\spring-lib\ext\commons-httpclient.jar
Class in classpath: C:\temp\spring-lib\spring-sources.jar
Class in classpath: C:\temp\spring-lib\ext\commons-attributes-api.jar
Class in classpath: C:\temp\spring-lib\spring.jar
Class in classpath: C:\temp\spring-lib\spring-context.jar
Class in classpath: C:\temp\spring-lib\spring-jdbc.jar
Class in classpath: C:\temp\spring-lib\spring-orm.jar
Class in classpath: C:\temp\spring-lib\ext\commons-fileupload.jar
Class in classpath: C:\temp\spring-lib\ext\commons-beanutils.jar
Class in classpath: C:\temp\spring-lib\spring-webmvc-struts.jar
Class in classpath: C:\temp\spring-lib\spring-web.jar
Class in classpath: C:\temp\spring-lib\spring-webmvc.jar
Class in classpath: C:\temp\spring-lib\ext\commons-dbcp.jar
Class in classpath: C:\temp\spring-lib\spring-jms.jar
Class in classpath: C:\temp\spring-lib\ext\commons-logging.jar
Class in classpath: C:\temp\spring-lib\ext\commons-attributes-compiler.jar
[main] INFO org.springframework.context.support.ClassPathXmlAp plicationContext - Refreshing org.springf
[main] INFO org.springframework.beans.factory.xml.XmlBeanDefin itionReader - Loading XML bean definition
[main] INFO org.springframework.context.support.ClassPathXmlAp plicationContext - Bean factory for appli
[main] INFO org.springframework.beans.factory.support.DefaultL istableBeanFactory - Pre-instantiating si
RMI registry loaded from custom classloader: org.springframework.remoting.rmi.RmiRegistryFactor yBean


Please note the latest line, which tells us that the RmiRegistryFactoryBean has been loaded by the customised classloader.

However if I add the following bean declaration in the configuration file:

<bean class="org.springframework.remoting.rmi.RmiRegistryFactor yBean">
<property name="port" value="8889"></property>
</bean>

It gives this exception:



Caused by: java.lang.ClassNotFoundException: org.springframework.remoting.rmi.RmiRegistryFactor yBean
at java.net.URLClassLoader$1.run(Unknown Source)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
at org.springframework.util.ClassUtils.forName(ClassU tils.java:249)
at org.springframework.beans.factory.support.Abstract BeanDefinition.resolveBeanClass(AbstractBeanDefini tion.java:381)
at org.springframework.beans.factory.support.Abstract BeanFactory.resolveBeanClass(AbstractBeanFactory.j ava:1135)


Notice that between before and now I didn't change the code. I just added a bean configuration. It's like Spring uses its own classloader to load the beans. Is there a way of telling Spring to use my classloader?

Thanks.

M.

alzamabar
Jun 23rd, 2008, 01:57 PM
Hi, I was finally able to solve the problem using another approach to startup the Spring container, applying what in the documentation is stated as the following:



GenericApplicationContext ctx = new GenericApplicationContext();
XmlBeanDefinitionReader xmlReader = new XmlBeanDefinitionReader(ctx);
xmlReader.loadBeanDefinitions(new ClassPathResource("applicationContext.xml"));
PropertiesBeanDefinitionReader propReader = new PropertiesBeanDefinitionReader(ctx);
propReader.loadBeanDefinitions(new ClassPathResource("otherBeans.properties"));


I used introspection in my class to execute exactly the code as above. The difference with the normal approach is that with the above I was able to tell Spring to use my own classloader when loading beans from the configuration file.

The solution is attached.

Basically with that I created a Spring 'microcontainer' (which I named JESPICO [JEmos SPrIng COntainer] BTW :-) which can be used as a basis for Spring applications, without the need of deploying in another container (say an application server).

Obviously this is just the skeleton. I'm thinking of creating an open source project for this and share it with the community!

M.

oleg.zhurakousky
Jun 23rd, 2008, 07:12 PM
There are many different ways of creating Spring enabled container and many vendors have already done that (i.e., GridGain, Apache ServiceMix just a few).
So I am not sure of the value (unless I am missing something).
Also check out OSGi (Spring-DM as well as SpringSource Application Platform), basically a light way platform/container to host JARs including Spring JARs.

Don't take it the wrong way, but I hate to see people going through the hustle of creating something that was already created.
Cheers

alzamabar
Jun 24th, 2008, 01:05 AM
Thank you. I had a look at OSGi and it doesn't seem so 'simple' to use, although it would ultimately give me what I wanted. The problem is that it would give me much more, and for the idea I had about a lightweight container OSGi seemed too much.

As regards the other two projects, I'll have a look at those. Thank you for pointing them out. Don't get me wrong...I also hate having to reinventing the weel, but I also hate having to use a F1 car when all I needed was a mini-car to go from A to B.

M.