Загрузка классов с использованием Spring внутри контейнера eclipse OSGi, похоже, не работает. Фикс?
Я несколько озадачен поведением Spring по загрузке классов в контейнере OSGi eclipse (который является основой для среды выполнения TIBCO ActiveMatrix, которую я пытаюсь запустить) и свел это к следующему простому примеру.
В нем (объект TestComponent создается и вызывается внутри класса Activator комплекта OSGi, но я не думаю, что это важно здесь) Сначала я создаю объект непосредственно, просто чтобы дважды проверить, что его класс существует и может быть создан, Это, конечно, работает, как и следовало ожидать... Во-вторых, я хочу создать второй объект того же класса, используя Spring (как я изначально предполагал), но это не удается с ClassNotFoundException.
Spring утверждает, что он не может найти этот класс (даже если имя пакета и имя класса точно совпадают), поэтому я даже добавил вызов.setClassLoader(...), чтобы передать ему тот же самый загрузчик классов, который только что успешно загрузил тот же самый класс, но Spring по-прежнему не может найти этот класс. Любая подсказка, почему это так? У меня кончились идеи. Что мне не хватает?
Позже редактирование: мне просто нужно было понять, что это не вызов метода...getBean(...)- сбой, а скорее уже конструктор ClassPathXmlApplicationContext(). Т.е. объект уже создан в этом конструкторе, а не только позже при последующем вызове метода getBean (...). Таким образом, моя попытка передать загрузчик классов тщетна, так как это уже поздно. Таким образом, вопрос, скорее, такой: как я могу передать загрузчик класса контекста этому конструктору (или фабрике, или тому, что Spring использует внутри для создания объекта ClassPathXmlApplicationContext)?
Мой пример:
Сначала я определил интерфейс для класса, который будет создан через Spring:
package com.example.some_package_0;
public interface SomeInterface
{
public String getSomeString();
}
... и класс, реализующий этот интерфейс:
package com.example.some_package_1;
import com.example.some_package_0.SomeInterface;
public class SomeClassA implements SomeInterface
{
private String someProperty;
public void setSomeProperty(String someProperty) {
this.someProperty = someProperty;
}
public String getSomeString() {
return this.someProperty;
}
}
Моя тестовая программа читает
public class TestComponent
{
import com.example.some_package_0.SomeInterface;
import com.example.some_package_1.SomeClassA;
public void test() {
SomeClassA obj1 = new SomeClassA();
obj1.setSomeProperty("SomeClassA-object (directly created)");
System.out.println("@@ message=\"" + obj1.getSomeString() + "\"");
ClassPathXmlApplicationContext applicationContext;
applicationContext = new ClassPathXmlApplicationContext("/META-INF/package1_beans.xml");
applicationContext.setClassLoader(Thread.currentThread().getContextClassLoader());
SomeInterface obj2 = (SomeInterface) applicationContext.getBean("bean1");
System.out.println("@@ message=\"" + obj2.getSomeString() + "\"");
}
}
Используемый файл /META-INF/package1_beans.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 classpath:/org/springframework/beans/factory/xml/spring-beans-2.5.xsd">
<bean id="bean1" class="com.example.some_package_1.SomeClassA">
<property name="someProperty"><value>SomeClassA-object (created via Spring)</value></property>
</bean>
</beans>
Исключение гласит:
org.springframework.beans.factory.CannotLoadBeanClassException: Cannot find class [com.example.some_package_1.SomeClassA] for bean with name 'bean1' defined in class path resource [META-INF/package1_beans.xml]; nested exception is java.lang.ClassNotFoundException: com.example.some_package_1.SomeClassA
at org.springframework.beans.factory.support.AbstractBeanFactory.resolveBeanClass(AbstractBeanFactory.java:1141)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.predictBeanType(AbstractAutowireCapableBeanFactory.java:524)
at org.springframework.beans.factory.support.AbstractBeanFactory.isFactoryBean(AbstractBeanFactory.java:1177)
at org.springframework.beans.factory.support.AbstractBeanFactory.isFactoryBean(AbstractBeanFactory.java:758)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:422)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:728)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:380)
at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:139)
at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:83)
at com.example.test_spring_example.TestComponent.testOperation(TestComponent.java:71)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at com.tibco.amf.platform.runtime.componentframework.internal.proxies.operation.OperationHandler.invokeMethodWithThreadContext(OperationHandler.java:667)
at com.tibco.amf.platform.runtime.componentframework.internal.proxies.operation.AsyncToSyncOperationHandler.invoke(AsyncToSyncOperationHandler.java:98)
at com.tibco.amf.platform.runtime.componentframework.internal.proxies.ProxyInvocationHandlerRegistry$ProxyInvocationContext.invoke(ProxyInvocationHandlerRegistry.java:411)
at $Proxy67.invoke(Unknown Source)
at com.tibco.amf.binding.soap.runtime.transport.http.SoapHttpInboundEndpoint.processHttpPost(SoapHttpInboundEndpoint.java:565)
at com.tibco.amf.binding.soap.runtime.transport.http.SoapHttpServer.doPost(SoapHttpServer.java:195)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:710)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:803)
at org.mortbay.jetty.servlet.ServletHolder.handle(ServletHolder.java:502)
at org.mortbay.jetty.servlet.ServletHandler$Chain.doFilter(ServletHandler.java:1213)
at com.tibco.governance.pa.amxcomponent.pep.http.HttpPepFilter.doFilter(HttpPepFilter.java:126)
at org.mortbay.jetty.servlet.ServletHandler$Chain.doFilter(ServletHandler.java:1205)
at com.tibco.amf.implementation.common.httpfilter.GenericComponentFilter.doFilter(GenericComponentFilter.java:65)
at org.mortbay.jetty.servlet.ServletHandler$Chain.doFilter(ServletHandler.java:1205)
at com.tibco.amf.hpa.tibcohost.jetty.internal.ConnectorFilter.doFilter(ConnectorFilter.java:49)
at org.mortbay.jetty.servlet.ServletHandler$Chain.doFilter(ServletHandler.java:1205)
at org.mortbay.jetty.servlet.ServletHandler.handle(ServletHandler.java:388)
at org.mortbay.jetty.security.SecurityHandler.handle(SecurityHandler.java:216)
at org.mortbay.jetty.servlet.SessionHandler.handle(SessionHandler.java:182)
at org.mortbay.jetty.handler.ContextHandler.handle(ContextHandler.java:765)
at org.mortbay.jetty.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:230)
at org.mortbay.jetty.handler.HandlerCollection.handle(HandlerCollection.java:114)
at org.mortbay.jetty.handler.HandlerWrapper.handle(HandlerWrapper.java:152)
at org.mortbay.jetty.Server.handle(Server.java:326)
at org.mortbay.jetty.HttpConnection.handleRequest(HttpConnection.java:536)
at org.mortbay.jetty.HttpConnection$RequestHandler.content(HttpConnection.java:928)
at org.mortbay.jetty.HttpParser.parseNext(HttpParser.java:747)
at org.mortbay.jetty.HttpParser.parseAvailable(HttpParser.java:218)
at org.mortbay.jetty.HttpConnection.handle(HttpConnection.java:405)
at org.mortbay.jetty.bio.SocketConnector$Connection.run(SocketConnector.java:228)
at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
at java.lang.Thread.run(Thread.java:662)
Caused by: java.lang.ClassNotFoundException: com.example.some_package_1.SomeClassA
at org.eclipse.osgi.internal.loader.BundleLoader.findClassInternal(BundleLoader.java:513)
at org.eclipse.osgi.internal.loader.BundleLoader.findClass(BundleLoader.java:429)
at org.eclipse.osgi.internal.loader.BundleLoader.findClass(BundleLoader.java:417)
at org.eclipse.osgi.internal.baseadaptor.DefaultClassLoader.loadClass(DefaultClassLoader.java:107)
at java.lang.ClassLoader.loadClass(ClassLoader.java:247)
at org.springframework.util.ClassUtils.forName(ClassUtils.java:211)
at org.springframework.beans.factory.support.AbstractBeanDefinition.resolveBeanClass(AbstractBeanDefinition.java:385)
at org.springframework.beans.factory.support.AbstractBeanFactory.resolveBeanClass(AbstractBeanFactory.java:1138)
... 46 more
2 ответа
На самом деле, решение оказалось почти тривиальным! Как я уже говорил в своем комментарии к @Robin, по-видимому, в OSGi, загрузчик классов текущего потока и загрузчик классов текущего контекста или класса не совпадают!
Итак, все, что я должен был сделать в конце, это установить загрузчик контекста текущего потока в загрузчик классов вызывающего объекта и все! Т.е. даже не нужно копаться в OSGi, чтобы получить загрузчик классов комплекта или скрипку с манифестами, POM или чем-то подобным - просто скажите Spring, чтобы он использовал загрузчик классов "моего" собственного класса, и все готово!
...
// need to set the context class loader to "my" class loader to make Spring work:
Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
ClassPathXmlApplicationContext applicationContext;
applicationContext =
new ClassPathXmlApplicationContext("/META-INF/package1_beans.xml");
SomeInterface obj2 = (SomeInterface) applicationContext.getBean("bean1");
System.out.println("@@ message=\"" + obj2.getSomeString() + "\"");
...
И - да - вероятно, следует установить загрузчик класса потока обратно на первоначальное значение после этого, на всякий случай - так что другой вариант используется для предложения try { ... } finally { ... }.:-)
Спасибо всем за отклик! М.
У меня есть еще одна идея.
Вместо инициализации контекста Spring вручную. Попробуйте добавить это в ваш pom.xml (если вы используете Maven):
<configuration>
<instructions>
<Spring-Context>spring/*.xml</Spring-Context>
Сделайте так, чтобы он указывал на местоположение вашего applContext. Это кажется более аккуратным способом подключения. Если вы не используете Maven, вам нужно добавить его вручную в Manifest, я не уверен, что синтаксис для этого правильный.