From 042e512d3dd4460b3975e50deb1285ac656dd9f4 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Mon, 3 Feb 2014 10:29:17 -0800 Subject: [PATCH] Fix package tangle Fix package tangle between the `org.springframework.boot` and the `event` sub-package. A new `SpringApplicationRunParticipant` abstraction has been introduced, with the `event` package solely responsible for handling event multi-casting. --- .../boot/SpringApplication.java | 208 +++++++----------- .../boot/SpringApplicationRunParticipant.java | 69 ++++++ .../event/EventPublishingRunParticipant.java | 95 ++++++++ .../main/resources/META-INF/spring.factories | 4 + 4 files changed, 248 insertions(+), 128 deletions(-) create mode 100644 spring-boot/src/main/java/org/springframework/boot/SpringApplicationRunParticipant.java create mode 100644 spring-boot/src/main/java/org/springframework/boot/event/EventPublishingRunParticipant.java diff --git a/spring-boot/src/main/java/org/springframework/boot/SpringApplication.java b/spring-boot/src/main/java/org/springframework/boot/SpringApplication.java index 5eafe9d7448..c74d9f74fcc 100644 --- a/spring-boot/src/main/java/org/springframework/boot/SpringApplication.java +++ b/spring-boot/src/main/java/org/springframework/boot/SpringApplication.java @@ -16,6 +16,7 @@ package org.springframework.boot; +import java.lang.reflect.Constructor; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -31,28 +32,18 @@ import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.BeanUtils; -import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.groovy.GroovyBeanDefinitionReader; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanNameGenerator; import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; -import org.springframework.boot.event.ApplicationEnvironmentPreparedEvent; -import org.springframework.boot.event.ApplicationFailedEvent; -import org.springframework.boot.event.ApplicationPreparedEvent; -import org.springframework.boot.event.ApplicationStartedEvent; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextInitializer; -import org.springframework.context.ApplicationEvent; -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.context.ApplicationEventPublisherAware; import org.springframework.context.ApplicationListener; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.AnnotatedBeanDefinitionReader; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.AnnotationConfigUtils; import org.springframework.context.annotation.ClassPathBeanDefinitionScanner; -import org.springframework.context.event.ApplicationEventMulticaster; -import org.springframework.context.event.SimpleApplicationEventMulticaster; import org.springframework.context.support.AbstractApplicationContext; import org.springframework.context.support.GenericApplicationContext; import org.springframework.core.GenericTypeResolver; @@ -72,6 +63,7 @@ import org.springframework.core.io.ResourceLoader; import org.springframework.core.io.support.SpringFactoriesLoader; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; +import org.springframework.util.ReflectionUtils; import org.springframework.util.StopWatch; import org.springframework.util.StringUtils; import org.springframework.web.context.ConfigurableWebApplicationContext; @@ -243,36 +235,6 @@ public class SpringApplication { return true; } - /** - * Returns objects loaded via the {@link SpringFactoriesLoader}. - */ - private Collection getSpringFactoriesInstances(Class type) { - ClassLoader classLoader = SpringApplication.class.getClassLoader(); - - // Use names and ensure unique to protect against duplicates - Set names = new LinkedHashSet( - SpringFactoriesLoader.loadFactoryNames(type, classLoader)); - List instances = new ArrayList(names.size()); - - // Create instances from the names - for (String name : names) { - try { - Class instanceClass = ClassUtils.forName(name, classLoader); - Assert.isAssignable(type, instanceClass); - @SuppressWarnings("unchecked") - T instance = (T) instanceClass.newInstance(); - instances.add(instance); - } - catch (Throwable ex) { - throw new IllegalArgumentException("Cannot instantiate " + type + " : " - + name, ex); - } - } - - AnnotationAwareOrderComparator.sort(instances); - return instances; - } - private Class deduceMainApplicationClass() { try { StackTraceElement[] stackTrace = new RuntimeException().getStackTrace(); @@ -302,23 +264,19 @@ public class SpringApplication { System.setProperty("java.awt.headless", Boolean.toString(this.headless)); - ApplicationEventMulticaster multicaster = createApplicationEventMulticaster(); - try { - // Allow logging and stuff to initialize very early - multicaster.multicastEvent(new ApplicationStartedEvent(this, args)); + Collection participants = createRunParticipants(args); + for (SpringApplicationRunParticipant participant : participants) { + participant.started(); + } + try { // Create and configure the environment ConfigurableEnvironment environment = getOrCreateEnvironment(); addPropertySources(environment, args); setupProfiles(environment); - - // Notify listeners of the environment creation - multicaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(this, - args, environment)); - - // Load the sources (might have changed when environment was applied) - Set sources = getSources(); - Assert.notEmpty(sources, "Sources must not be empty"); + for (SpringApplicationRunParticipant participant : participants) { + participant.environmentPrepared(environment); + } if (this.showBanner) { printBanner(); @@ -326,48 +284,93 @@ public class SpringApplication { // Create, load, refresh and run the ApplicationContext context = createApplicationContext(); - registerApplicationEventMulticaster(context, multicaster); context.registerShutdownHook(); context.setEnvironment(environment); postProcessApplicationContext(context); applyInitializers(context); + for (SpringApplicationRunParticipant participant : participants) { + participant.contextPrepared(context); + } if (this.logStartupInfo) { logStartupInfo(context.getParent() == null); } + // Load the sources + Set sources = getSources(); + Assert.notEmpty(sources, "Sources must not be empty"); load(context, sources.toArray(new Object[sources.size()])); + for (SpringApplicationRunParticipant participant : participants) { + participant.contextLoaded(context); + } - // Notify listeners of intention to refresh - multicaster.multicastEvent(new ApplicationPreparedEvent(this, args, context)); - + // Refresh the context refresh(context); + afterRefresh(context, args); + for (SpringApplicationRunParticipant participant : participants) { + participant.finished(context, null); + } stopWatch.stop(); if (this.logStartupInfo) { new StartupInfoLogger(this.mainApplicationClass).logStarted( getApplicationLog(), stopWatch); } - - afterRefresh(context, args); return context; } - catch (RuntimeException ex) { - handleFailure(context, multicaster, ex, args); - throw ex; + catch (Exception ex) { + for (SpringApplicationRunParticipant participant : participants) { + finishWithException(participant, context, ex); + } + if (context != null) { + context.close(); + } + ReflectionUtils.rethrowRuntimeException(ex); + return context; } - catch (Error ex) { - handleFailure(context, multicaster, ex, args); - throw ex; + finally { } - } - private ApplicationEventMulticaster createApplicationEventMulticaster() { - ApplicationEventMulticaster multicaster = new SpringApplicationEventMulticaster(); - for (ApplicationListener listener : getListeners()) { - multicaster.addApplicationListener(listener); + private Collection createRunParticipants( + String[] args) { + List participants = new ArrayList(); + participants.addAll(getSpringFactoriesInstances( + SpringApplicationRunParticipant.class, new Class[] { + SpringApplication.class, String[].class }, this, args)); + return participants; + } + + private Collection getSpringFactoriesInstances(Class type) { + return getSpringFactoriesInstances(type, new Class[] {}); + } + + @SuppressWarnings("unchecked") + private Collection getSpringFactoriesInstances(Class type, + Class[] parameterTypes, Object... args) { + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + + // Use names and ensure unique to protect against duplicates + Set names = new LinkedHashSet( + SpringFactoriesLoader.loadFactoryNames(type, classLoader)); + List instances = new ArrayList(names.size()); + + // Create instances from the names + for (String name : names) { + try { + Class instanceClass = ClassUtils.forName(name, classLoader); + Assert.isAssignable(type, instanceClass); + Constructor constructor = instanceClass.getConstructor(parameterTypes); + T instance = (T) constructor.newInstance(args); + instances.add(instance); + } + catch (Throwable ex) { + throw new IllegalArgumentException("Cannot instantiate " + type + " : " + + name, ex); + } } - return multicaster; + + AnnotationAwareOrderComparator.sort(instances); + return instances; } private ConfigurableEnvironment getOrCreateEnvironment() { @@ -450,17 +453,6 @@ public class SpringApplication { return (ConfigurableApplicationContext) BeanUtils.instantiate(contextClass); } - private void registerApplicationEventMulticaster( - ConfigurableApplicationContext context, - ApplicationEventMulticaster multicaster) { - context.getBeanFactory().registerSingleton( - AbstractApplicationContext.APPLICATION_EVENT_MULTICASTER_BEAN_NAME, - multicaster); - if (multicaster instanceof BeanFactoryAware) { - ((BeanFactoryAware) multicaster).setBeanFactory(context.getBeanFactory()); - } - } - /** * Apply any relevant post processing the {@link ApplicationContext}. Subclasses can * apply additional processing as required. @@ -624,29 +616,23 @@ public class SpringApplication { ((AbstractApplicationContext) applicationContext).refresh(); } - private void afterRefresh(ConfigurableApplicationContext context, String[] args) { + protected void afterRefresh(ConfigurableApplicationContext context, String[] args) { runCommandLineRunners(context, args); } - protected void handleFailure(ConfigurableApplicationContext context, - ApplicationEventMulticaster multicaster, Throwable exception, String... args) { + private void finishWithException(SpringApplicationRunParticipant participant, + ConfigurableApplicationContext context, Exception exception) { try { - multicaster.multicastEvent(new ApplicationFailedEvent(this, args, context, - exception)); + participant.finished(context, exception); } catch (Exception ex) { - // We don't want to fail here and mask the original exception if (this.log.isDebugEnabled()) { this.log.error("Error handling failed", ex); } else { - this.log.warn("Error handling failed (" + ex.getMessage() == null ? "no error message" - : ex.getMessage() + ")"); - } - } - finally { - if (context != null) { - context.close(); + String message = ex.getMessage(); + message = (message == null ? "no error message" : message); + this.log.warn("Error handling failed (" + message + ")"); } } } @@ -974,38 +960,4 @@ public class SpringApplication { return new LinkedHashSet(list); } - /** - * {@link ApplicationEventMulticaster} and {@link ApplicationEventPublisher} for - * {@link SpringApplication} events. - */ - private static class SpringApplicationEventMulticaster extends - SimpleApplicationEventMulticaster implements ApplicationEventPublisher { - - @Override - public void publishEvent(ApplicationEvent event) { - multicastEvent(event); - } - - @Override - protected Collection> getApplicationListeners( - ApplicationEvent event) { - List> listeners = new ArrayList>(); - listeners.addAll(super.getApplicationListeners(event)); - if (event instanceof ApplicationFailedEvent) { - Collections.reverse(listeners); - } - return listeners; - } - - @Override - public void addApplicationListener(ApplicationListener listener) { - super.addApplicationListener(listener); - if (listener instanceof ApplicationEventPublisherAware) { - ((ApplicationEventPublisherAware) listener) - .setApplicationEventPublisher(this); - } - } - - } - } diff --git a/spring-boot/src/main/java/org/springframework/boot/SpringApplicationRunParticipant.java b/spring-boot/src/main/java/org/springframework/boot/SpringApplicationRunParticipant.java new file mode 100644 index 00000000000..4a138d908e9 --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/SpringApplicationRunParticipant.java @@ -0,0 +1,69 @@ +/* + * Copyright 2012-2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot; + +import org.springframework.context.ApplicationContext; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.io.support.SpringFactoriesLoader; + +/** + * Strategy class to allow dynamic participation when the {@link SpringApplication} + * {@code run} method is called. Participants are loaded via the + * {@link SpringFactoriesLoader} and should declare a public constructor that accepts a + * {@link SpringApplication} instance and a {@code String[]} of arguments. A new + * {@link SpringApplicationRunParticipant} instance will be created for each run. + * + * @author Phillip Webb + */ +public interface SpringApplicationRunParticipant { + + /** + * Called immediately when the run method has first started. Can be used for very + * early initialization. + */ + void started(); + + /** + * Called once the environment has been prepared, but before the + * {@link ApplicationContext} has been created. + * @param environment the environment + */ + void environmentPrepared(ConfigurableEnvironment environment); + + /** + * Called once the {@link ApplicationContext} has been created and prepared, but + * before sources have been loaded. + * @param context the application context + */ + void contextPrepared(ConfigurableApplicationContext context); + + /** + * Called once the application context has been loaded but before it has been + * refreshed. + * @param context the application context + */ + void contextLoaded(ConfigurableApplicationContext context); + + /** + * Called immediately before the run method finishes. + * @param context the application context + * @param exception any run exception or null if run completed successfully. + */ + void finished(ConfigurableApplicationContext context, Throwable exception); + +} diff --git a/spring-boot/src/main/java/org/springframework/boot/event/EventPublishingRunParticipant.java b/spring-boot/src/main/java/org/springframework/boot/event/EventPublishingRunParticipant.java new file mode 100644 index 00000000000..3d3d0ce21ed --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/event/EventPublishingRunParticipant.java @@ -0,0 +1,95 @@ +/* + * Copyright 2012-2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.event; + +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.SpringApplicationRunParticipant; +import org.springframework.context.ApplicationListener; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.event.ApplicationEventMulticaster; +import org.springframework.context.event.SimpleApplicationEventMulticaster; +import org.springframework.context.support.AbstractApplicationContext; +import org.springframework.core.env.ConfigurableEnvironment; + +/** + * {@link SpringApplicationRunParticipant} to publish {@link SpringApplicationEvent}s. + * + * @author Phillip Webb + */ +public class EventPublishingRunParticipant implements SpringApplicationRunParticipant { + + private final ApplicationEventMulticaster multicaster; + + private SpringApplication application; + + private String[] args; + + public EventPublishingRunParticipant(SpringApplication application, String[] args) { + this.application = application; + this.args = args; + this.multicaster = new SimpleApplicationEventMulticaster(); + for (ApplicationListener listener : application.getListeners()) { + this.multicaster.addApplicationListener(listener); + } + } + + @Override + public void started() { + publishEvent(new ApplicationStartedEvent(this.application, this.args)); + } + + @Override + public void environmentPrepared(ConfigurableEnvironment environment) { + publishEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, + environment)); + } + + @Override + public void contextPrepared(ConfigurableApplicationContext context) { + registerApplicationEventMulticaster(context); + } + + private void registerApplicationEventMulticaster( + ConfigurableApplicationContext context) { + context.getBeanFactory().registerSingleton( + AbstractApplicationContext.APPLICATION_EVENT_MULTICASTER_BEAN_NAME, + this.multicaster); + if (this.multicaster instanceof BeanFactoryAware) { + ((BeanFactoryAware) this.multicaster) + .setBeanFactory(context.getBeanFactory()); + } + } + + @Override + public void contextLoaded(ConfigurableApplicationContext context) { + publishEvent(new ApplicationPreparedEvent(this.application, this.args, context)); + } + + @Override + public void finished(ConfigurableApplicationContext context, Throwable exception) { + if (exception != null) { + publishEvent(new ApplicationFailedEvent(this.application, this.args, context, + exception)); + } + } + + private void publishEvent(SpringApplicationEvent event) { + this.multicaster.multicastEvent(event); + } + +} diff --git a/spring-boot/src/main/resources/META-INF/spring.factories b/spring-boot/src/main/resources/META-INF/spring.factories index 18e2f4b1067..2ef15fb4288 100644 --- a/spring-boot/src/main/resources/META-INF/spring.factories +++ b/spring-boot/src/main/resources/META-INF/spring.factories @@ -1,3 +1,7 @@ +# Run Participants +org.springframework.boot.SpringApplicationRunParticipant=\ +org.springframework.boot.event.EventPublishingRunParticipant + # Application Context Initializers org.springframework.context.ApplicationContextInitializer=\ org.springframework.boot.context.initializer.ContextIdApplicationContextInitializer,\