Vor kurzem habe ich eine Spring-Demonstration für jclouds, die Java-Cloud-Bibliothek, zusammengestellt. Dies entwickelte sich schnell zu einem unerwarteten, mehrdimensionalen Experiment zur Integration von Guice, Google App Engine und Spring, aber nach vielen Versuchen und Fehlern bin ich schließlich auf eine Konfiguration gestoßen, die den Zweck erfüllt - oder zumindest so gut funktioniert1, wie es auf GAE möglich scheint.
Die tweetstore Demo
Tweetstore ist eine einfache Webanwendung, die die Cloud-Fähigkeiten von jclouds demonstriert. Sie fragt Twitter nach Erwähnungen des Benutzerkontos ab und erstellt ein "Backup" dieser unbezahlbaren Aufzeichnung Ihres aktuellen Images in drei Cloud-Speichern: Amazon S3, Microsofts Azure und Rackspace. Nichts weniger als doppelte Redundanz ist gut genug für Ihre Popularität! Die ursprüngliche Tweetstore-Anwendung verwendet Guice für Dependency Injection und Request Mapping. Um den Vergleich zwischen der Spring-Version und dem Original so einfach wie möglich zu machen, habe ich beschlossen, so viel wie möglich in Java-Code zu machen. Eine perfekte Gelegenheit für mich, mir die Hände mit der Unterstützung von Spring 3.0 für die Java-Konfiguration schmutzig zu machen.
Bootstrapping der Java-Konfiguration von Spring
Der erste, relativ unkomplizierte Schritt bestand darin, das Guice-Servlet-Modul in eine entsprechende Spring @Configuration zu konvertieren: [java] @Configuration public class SpringServletConfig extends LoggingConfig implements ServletConfigAware { private ServletConfig servletConfig; ... @PostConstruct public void initialize() { ... } @Bohne public StoreTweetsController storeTweetsController() { StoreTweetsController controller = new StoreTweetsController(providerTypeToBlobStoreMap, container, twitterClient); injectServletConfig(controller); Controller zurückgeben; } @Bohne public AddTweetsController addTweetsController() { AddTweetsController controller = new AddTweetsController(providerTypeToBlobStoreMap, serviceToStoredTweetStatuses()); injectServletConfig(controller); Controller zurückgeben; } private void injectServletConfig(Servlet servlet) { versuchen { servlet.init(checkNotNull(servletConfig)); } catch (ServletException exception) { throw new BeanCreationException("Konnte nicht instanziiert werden " + Servlet, exception); } } @Bohne ServiceToStoredTweetStatuses serviceToStoredTweetStatuses() { return new ServiceToStoredTweetStatuses(providerTypeToBlobStoreMap, container); } @Bohne public HandlerMapping handlerMapping() { SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping(); Karte<String, Objekt> urlMap = Maps.newHashMapWithExpectedSize(2); urlMap.put("/store/", storeTweetsController()); urlMap.put("/tweets/", addTweetsController()); mapping.setUrlMap(urlMap); /*
- "/store" und "/tweets" sind Teil der Servlet-Zuordnung und werden daher entfernt
- durch das Mapping, wenn Sie Standardeinstellungen verwenden.
*/
mapping.setAlwaysUseFullPath(true);
return mapping;
}
@Bean
public HandlerAdapter servletHandlerAdapter() {
return new SimpleServletHandlerAdapter();
}
...
}
[/java]
ServletConfigAware ist nicht gerade die beste Spring-Praxis, aber das Ziel war es, so nah wie möglich an der ursprünglichen Konfiguration zu bleiben, anstatt eine Spring-Referenzanwendung zu erstellen.
Der nächste Schritt war das Einbinden von Spring in den GAE-Startup. Das Spring-Referenzbeispiel war mein erster Versuch:
[xml]
<Web-Applikation>
<Servlet>
<Servlet-Name>Dispatcher</Servlet-Name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- Konfigurieren Sie das DispatcherServlet so, dass es den JavaConfigWebApplicationContext
anstelle des standardmäßigen XmlWebApplicationContext -- verwendet.>
<init-param>
<param-name>contextClass</param-name>
<param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
</init-param>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>org.jclouds.demo.tweetstore.config.SpringServletConfig</param-value>
</init-param>
</servlet>
<servlet-mapping>
...
</web-app>
[/xml]
Leider wurde mir dies kurzerhand durch eine Sicherheitsverletzung zum Verhängnis: AnnotationConfigWebApplicationContext (und alle Spring-Kontexte, die einen CommonAnnotationBeanPostProcessor registrieren) versucht, javax.annotation.Resource zu laden, um die Unterstützung für JSR-250 zu ermitteln, und diese Klasse fehlt im Gegensatz zu anderen im javax.annotation-Paket zufällig in der GAE-Whitelist.
Es ist nicht vorbei, bis die App Engine singt
Die Rückkehr zu einer Standard -servlet.xml-Datei beseitigt das tt>@Resource Problem, aber ich hatte immer noch eine Ausnahme, die von Guice verursacht wurde, das jclouds intern für Dependency Injection verwendet. Später habe ich herausgefunden, dass die Ausnahme relativ harmlos ist und ignoriert werden kann2. Aber natürlich mag kein Entwickler Ausnahmen - wie harmlos sie auch sein mögen - in den Boot-Protokollen, also fragte ich mich, ob die Ausführung dieses Aufrufs zu einem früheren Zeitpunkt in der Boot-Sequenz das Problem lösen könnte. Also verlagerte ich den beanstandeten Aufruf vom Servlet in den Anwendungskontext, da ich davon ausging, dass der ContextLoaderListener mehr Rechte haben könnte als ein Servlet: [xml] <Web-Applikation> <!-- kann aufgrund der Sicherheitseinschränkungen von GAE keine Java-Konfiguration verwenden --> <Kontext-Parameter> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/tweetstoreContext.xml</param-value> </context-param> <Hörer> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <Servlet> ... [/xml] Und es hat funktioniert! Oder besser gesagt, es funktionierte auf dem Java Development Server, dem App Engine "Simulator", der mit dem SDK geliefert wird und für Entwicklungstests gedacht ist. Aber es funktionierte nicht auf der App Engineselbst3. Ein Wort der Vorsicht also: Testen Sie auf der "echten" App Engine, bevor Sie zu dem Schluss kommen, dass etwas funktioniert, vor allem, wenn es mit Sicherheitseinschränkungen zu tun hat!
Rettung einiger @AnnotationConfig
Zu diesem Zeitpunkt wusste ich, dass ich nicht davonkommen würde, ohne zumindest ein bisschen Spring XML zu schreiben, aber ich wollte trotzdem so weit wie möglich bei Java bleiben. Nachdem ich ausgiebig in den Spring-Quellen gestöbert und ein wenig experimentiert hatte, fand ich heraus, dass von den Bean-Postprozessoren, die derzeit über <context:annotation-config/> 4 registriert sind und die mich interessierten, nur der CommonAnnotationBeanPostProcessor tatsächlich ein Problem darstellt. Sie können sogar seine unmittelbare Oberklasse, InitDestroyAnnotationBeanPostProcessor, verwenden, die die tt>@PostConstruct und tt>@PreDestroy Unterstützung bietet, die ich gesucht habe: [xml name="foo"] <beans xmlns="...> <!-- die übliche <context:annotation-config/> kann nicht verwendet werden, da der CommonAnnotationBeanPostProcessor eine Sicherheitsausnahme in GAE verursacht, wenn er versucht, javax.annotation.Resource -- zu laden.> <bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor" /> <bean class="org.springframework.context.annotation.ConfigurationClassPostProcessor" /> <bean class="org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor"> <property name="initAnnotationType" value="javax.annotation.PostConstruct" /> <property name="destroyAnnotationType" value="javax.annotation.PreDestroy" /> </bean> <bean class="org.jclouds.demo.tweetstore.config.SpringServletConfig" /> </beans> [/xml] Wenn Sie also auf tt>@Resource verzichten können, scheint es so, als ob Sie die meisten der annotationsgesteuerten Konfigurationen von Spring in der GAE nicht benötigen.
Debuggen des Java Development Servers Das GAE SDK startet den Java Development Server mit einem plattformunabhängigen Launcher namens KickStart, der sich recht einfach in Ihre Testsuite integrieren lässt. Leider startet KickStart den Server nicht in einem neuen Thread, sondern in einem völlig separaten Prozess, so dass das Anhängen eines Debuggers von Ihrer IDE aus, nun ja, schwierig ist. Glücklicherweise akzeptiert KickStart jvm_flag-Argumente, wie in den Javadocs5 beschrieben: Zurzeit ist die einzige gültige Option für KickStart selbst: --jvm_flag=<vm_arg> Übergibt <vm_arg> als JVM-Argument für die untergeordnete JVM. Kann wiederholt werden Hier können Sie die Standard-JVM-Debugging-Parameter hinzufügen.Fußnoten- Nun, zum Zeitpunkt der Erstellung dieses Artikels kommt es zu einer Zeitüberschreitung, aber das hat nichts mit der Spring-Konfiguration zu tun. Wirklich ;-)
- Das bedeutet, dass der Bootstrapping-Prozess nicht unterbrochen wird (er findet in einem separaten Thread statt) und Ihre Anwendung problemlos läuft. Hier ist es:
com.google.inject.internal.FinalizableReferenceQueue : Failed to start reference finalizer thread. Reference cleanup will only occur when new references are created. java.lang.reflect.InvocationTargetException ... at com.google.inject.internal.InjectorBuilder.initializeStatically(InjectorBuilder.java:134) at com.google.inject.internal.InjectorBuilder.build(InjectorBuilder.java:108) at com.google.inject.Guice.createInjector(Guice.java:93) at com.google.inject.Guice.createInjector(Guice.java:70) - Ich vermute, dass dies etwas damit zu tun hat, wie und wann der Sicherheitsmanager auf dem Dev Server installiert wird, habe dies aber noch nicht näher untersucht.
- In den Spring-Dokumenten wird der ConfigurationClassPostProcessor aus irgendeinem Grund nicht erwähnt, aber laut Javadoc ist er "standardmäßig registriert, wenn Sie <context:annotation-config/> oder <context:component-scan/> verwenden."
- Die aus irgendeinem Grund nicht online verfügbar zu sein scheinen.
Verfasst von
Andrew Phillips
Unsere Ideen
Weitere Blogs
Contact



