Commit 04177ba3 by SunWei峰

Spring上下文

parent 307c9ae2
...@@ -124,7 +124,6 @@ import org.springframework.util.ReflectionUtils; ...@@ -124,7 +124,6 @@ import org.springframework.util.ReflectionUtils;
* @author Sam Brannen * @author Sam Brannen
* @author Sebastien Deleuze * @author Sebastien Deleuze
* @author Brian Clozel * @author Brian Clozel
* @since January 21, 2001
* @see #refreshBeanFactory * @see #refreshBeanFactory
* @see #getBeanFactory * @see #getBeanFactory
* @see org.springframework.beans.factory.config.BeanFactoryPostProcessor * @see org.springframework.beans.factory.config.BeanFactoryPostProcessor
...@@ -132,6 +131,7 @@ import org.springframework.util.ReflectionUtils; ...@@ -132,6 +131,7 @@ import org.springframework.util.ReflectionUtils;
* @see org.springframework.context.event.ApplicationEventMulticaster * @see org.springframework.context.event.ApplicationEventMulticaster
* @see org.springframework.context.ApplicationListener * @see org.springframework.context.ApplicationListener
* @see org.springframework.context.MessageSource * @see org.springframework.context.MessageSource
* @since January 21, 2001
*/ */
public abstract class AbstractApplicationContext extends DefaultResourceLoader public abstract class AbstractApplicationContext extends DefaultResourceLoader
implements ConfigurableApplicationContext { implements ConfigurableApplicationContext {
...@@ -139,6 +139,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader ...@@ -139,6 +139,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
/** /**
* Name of the MessageSource bean in the factory. * Name of the MessageSource bean in the factory.
* If none is supplied, message resolution is delegated to the parent. * If none is supplied, message resolution is delegated to the parent.
*
* @see MessageSource * @see MessageSource
*/ */
public static final String MESSAGE_SOURCE_BEAN_NAME = "messageSource"; public static final String MESSAGE_SOURCE_BEAN_NAME = "messageSource";
...@@ -146,6 +147,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader ...@@ -146,6 +147,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
/** /**
* Name of the LifecycleProcessor bean in the factory. * Name of the LifecycleProcessor bean in the factory.
* If none is supplied, a DefaultLifecycleProcessor is used. * If none is supplied, a DefaultLifecycleProcessor is used.
*
* @see org.springframework.context.LifecycleProcessor * @see org.springframework.context.LifecycleProcessor
* @see org.springframework.context.support.DefaultLifecycleProcessor * @see org.springframework.context.support.DefaultLifecycleProcessor
*/ */
...@@ -154,6 +156,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader ...@@ -154,6 +156,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
/** /**
* Name of the ApplicationEventMulticaster bean in the factory. * Name of the ApplicationEventMulticaster bean in the factory.
* If none is supplied, a default SimpleApplicationEventMulticaster is used. * If none is supplied, a default SimpleApplicationEventMulticaster is used.
*
* @see org.springframework.context.event.ApplicationEventMulticaster * @see org.springframework.context.event.ApplicationEventMulticaster
* @see org.springframework.context.event.SimpleApplicationEventMulticaster * @see org.springframework.context.event.SimpleApplicationEventMulticaster
*/ */
...@@ -174,68 +177,106 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader ...@@ -174,68 +177,106 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
} }
/** Logger used by this class. Available to subclasses. */ /**
* Logger used by this class. Available to subclasses.
*/
protected final Log logger = LogFactory.getLog(getClass()); protected final Log logger = LogFactory.getLog(getClass());
/** Unique id for this context, if any. */ /**
* Unique id for this context, if any.
*/
private String id = ObjectUtils.identityToString(this); private String id = ObjectUtils.identityToString(this);
/** Display name. */ /**
* Display name.
*/
private String displayName = ObjectUtils.identityToString(this); private String displayName = ObjectUtils.identityToString(this);
/** Parent context. */ /**
* Parent context.
*/
@Nullable @Nullable
private ApplicationContext parent; private ApplicationContext parent;
/** Environment used by this context. */ /**
* Environment used by this context.
*/
@Nullable @Nullable
private ConfigurableEnvironment environment; private ConfigurableEnvironment environment;
/** BeanFactoryPostProcessors to apply on refresh. */ /**
* BeanFactoryPostProcessors to apply on refresh.
*/
private final List<BeanFactoryPostProcessor> beanFactoryPostProcessors = new ArrayList<>(); private final List<BeanFactoryPostProcessor> beanFactoryPostProcessors = new ArrayList<>();
/** System time in milliseconds when this context started. */ /**
* System time in milliseconds when this context started.
*/
private long startupDate; private long startupDate;
/** Flag that indicates whether this context is currently active. */ /**
* Flag that indicates whether this context is currently active.
*/
private final AtomicBoolean active = new AtomicBoolean(); private final AtomicBoolean active = new AtomicBoolean();
/** Flag that indicates whether this context has been closed already. */ /**
* Flag that indicates whether this context has been closed already.
*/
private final AtomicBoolean closed = new AtomicBoolean(); private final AtomicBoolean closed = new AtomicBoolean();
/** Synchronization monitor for the "refresh" and "destroy". */ /**
* Synchronization monitor for the "refresh" and "destroy".
*/
private final Object startupShutdownMonitor = new Object(); private final Object startupShutdownMonitor = new Object();
/** Reference to the JVM shutdown hook, if registered. */ /**
* Reference to the JVM shutdown hook, if registered.
*/
@Nullable @Nullable
private Thread shutdownHook; private Thread shutdownHook;
/** ResourcePatternResolver used by this context. */ /**
* ResourcePatternResolver used by this context.
*/
private final ResourcePatternResolver resourcePatternResolver; private final ResourcePatternResolver resourcePatternResolver;
/** LifecycleProcessor for managing the lifecycle of beans within this context. */ /**
* LifecycleProcessor for managing the lifecycle of beans within this context.
*/
@Nullable @Nullable
private LifecycleProcessor lifecycleProcessor; private LifecycleProcessor lifecycleProcessor;
/** MessageSource we delegate our implementation of this interface to. */ /**
* MessageSource we delegate our implementation of this interface to.
*/
@Nullable @Nullable
private MessageSource messageSource; private MessageSource messageSource;
/** Helper class used in event publishing. */ /**
* Helper class used in event publishing.
*/
@Nullable @Nullable
private ApplicationEventMulticaster applicationEventMulticaster; private ApplicationEventMulticaster applicationEventMulticaster;
/** Application startup metrics. **/ /**
* Application startup metrics.
**/
private ApplicationStartup applicationStartup = ApplicationStartup.DEFAULT; private ApplicationStartup applicationStartup = ApplicationStartup.DEFAULT;
/** Statically specified listeners. */ /**
* Statically specified listeners.
*/
private final Set<ApplicationListener<?>> applicationListeners = new LinkedHashSet<>(); private final Set<ApplicationListener<?>> applicationListeners = new LinkedHashSet<>();
/** Local listeners registered before refresh. */ /**
* Local listeners registered before refresh.
*/
@Nullable @Nullable
private Set<ApplicationListener<?>> earlyApplicationListeners; private Set<ApplicationListener<?>> earlyApplicationListeners;
/** ApplicationEvents published before the multicaster setup. */ /**
* ApplicationEvents published before the multicaster setup.
*/
@Nullable @Nullable
private Set<ApplicationEvent> earlyApplicationEvents; private Set<ApplicationEvent> earlyApplicationEvents;
...@@ -249,6 +290,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader ...@@ -249,6 +290,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
/** /**
* Create a new AbstractApplicationContext with the given parent context. * Create a new AbstractApplicationContext with the given parent context.
*
* @param parent the parent context * @param parent the parent context
*/ */
public AbstractApplicationContext(@Nullable ApplicationContext parent) { public AbstractApplicationContext(@Nullable ApplicationContext parent) {
...@@ -265,6 +307,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader ...@@ -265,6 +307,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
* Set the unique id of this application context. * Set the unique id of this application context.
* <p>Default is the object id of the context instance, or the name * <p>Default is the object id of the context instance, or the name
* of the context bean if the context is itself defined as a bean. * of the context bean if the context is itself defined as a bean.
*
* @param id the unique id of the context * @param id the unique id of the context
*/ */
@Override @Override
...@@ -294,6 +337,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader ...@@ -294,6 +337,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
/** /**
* Return a friendly name for this context. * Return a friendly name for this context.
*
* @return a display name for this context (never {@code null}) * @return a display name for this context (never {@code null})
*/ */
@Override @Override
...@@ -317,6 +361,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader ...@@ -317,6 +361,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
* default with this method is one option but configuration through {@link * default with this method is one option but configuration through {@link
* #getEnvironment()} should also be considered. In either case, such modifications * #getEnvironment()} should also be considered. In either case, such modifications
* should be performed <em>before</em> {@link #refresh()}. * should be performed <em>before</em> {@link #refresh()}.
*
* @see org.springframework.context.support.AbstractApplicationContext#createEnvironment * @see org.springframework.context.support.AbstractApplicationContext#createEnvironment
*/ */
@Override @Override
...@@ -350,6 +395,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader ...@@ -350,6 +395,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
/** /**
* Return this context's internal bean factory as AutowireCapableBeanFactory, * Return this context's internal bean factory as AutowireCapableBeanFactory,
* if already available. * if already available.
*
* @see #getBeanFactory() * @see #getBeanFactory()
*/ */
@Override @Override
...@@ -370,6 +416,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader ...@@ -370,6 +416,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
* <p>Note: Listeners get initialized after the MessageSource, to be able * <p>Note: Listeners get initialized after the MessageSource, to be able
* to access it within listener implementations. Thus, MessageSource * to access it within listener implementations. Thus, MessageSource
* implementations cannot publish events. * implementations cannot publish events.
*
* @param event the event to publish (may be application-specific or a * @param event the event to publish (may be application-specific or a
* standard framework event) * standard framework event)
*/ */
...@@ -383,6 +430,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader ...@@ -383,6 +430,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
* <p>Note: Listeners get initialized after the MessageSource, to be able * <p>Note: Listeners get initialized after the MessageSource, to be able
* to access it within listener implementations. Thus, MessageSource * to access it within listener implementations. Thus, MessageSource
* implementations cannot publish events. * implementations cannot publish events.
*
* @param event the event to publish (may be an {@link ApplicationEvent} * @param event the event to publish (may be an {@link ApplicationEvent}
* or a payload object to be turned into a {@link PayloadApplicationEvent}) * or a payload object to be turned into a {@link PayloadApplicationEvent})
*/ */
...@@ -393,6 +441,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader ...@@ -393,6 +441,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
/** /**
* Publish the given event to all listeners. * Publish the given event to all listeners.
*
* @param event the event to publish (may be an {@link ApplicationEvent} * @param event the event to publish (may be an {@link ApplicationEvent}
* or a payload object to be turned into a {@link PayloadApplicationEvent}) * or a payload object to be turned into a {@link PayloadApplicationEvent})
* @param eventType the resolved event type, if known * @param eventType the resolved event type, if known
...@@ -405,8 +454,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader ...@@ -405,8 +454,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
ApplicationEvent applicationEvent; ApplicationEvent applicationEvent;
if (event instanceof ApplicationEvent) { if (event instanceof ApplicationEvent) {
applicationEvent = (ApplicationEvent) event; applicationEvent = (ApplicationEvent) event;
} } else {
else {
applicationEvent = new PayloadApplicationEvent<>(this, event, eventType); applicationEvent = new PayloadApplicationEvent<>(this, event, eventType);
if (eventType == null) { if (eventType == null) {
eventType = ((PayloadApplicationEvent<?>) applicationEvent).getResolvableType(); eventType = ((PayloadApplicationEvent<?>) applicationEvent).getResolvableType();
...@@ -416,8 +464,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader ...@@ -416,8 +464,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
// Multicast right now if possible - or lazily once the multicaster is initialized // Multicast right now if possible - or lazily once the multicaster is initialized
if (this.earlyApplicationEvents != null) { if (this.earlyApplicationEvents != null) {
this.earlyApplicationEvents.add(applicationEvent); this.earlyApplicationEvents.add(applicationEvent);
} } else {
else {
getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType); getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
} }
...@@ -425,8 +472,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader ...@@ -425,8 +472,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
if (this.parent != null) { if (this.parent != null) {
if (this.parent instanceof AbstractApplicationContext) { if (this.parent instanceof AbstractApplicationContext) {
((AbstractApplicationContext) this.parent).publishEvent(event, eventType); ((AbstractApplicationContext) this.parent).publishEvent(event, eventType);
} } else {
else {
this.parent.publishEvent(event); this.parent.publishEvent(event);
} }
} }
...@@ -434,6 +480,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader ...@@ -434,6 +480,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
/** /**
* Return the internal ApplicationEventMulticaster used by the context. * Return the internal ApplicationEventMulticaster used by the context.
*
* @return the internal ApplicationEventMulticaster (never {@code null}) * @return the internal ApplicationEventMulticaster (never {@code null})
* @throws IllegalStateException if the context has not been initialized yet * @throws IllegalStateException if the context has not been initialized yet
*/ */
...@@ -458,6 +505,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader ...@@ -458,6 +505,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
/** /**
* Return the internal LifecycleProcessor used by the context. * Return the internal LifecycleProcessor used by the context.
*
* @return the internal LifecycleProcessor (never {@code null}) * @return the internal LifecycleProcessor (never {@code null})
* @throws IllegalStateException if the context has not been initialized yet * @throws IllegalStateException if the context has not been initialized yet
*/ */
...@@ -479,6 +527,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader ...@@ -479,6 +527,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
* <p><b>Do not call this when needing to resolve a location pattern.</b> * <p><b>Do not call this when needing to resolve a location pattern.</b>
* Call the context's {@code getResources} method instead, which * Call the context's {@code getResources} method instead, which
* will delegate to the ResourcePatternResolver. * will delegate to the ResourcePatternResolver.
*
* @return the ResourcePatternResolver for this context * @return the ResourcePatternResolver for this context
* @see #getResources * @see #getResources
* @see org.springframework.core.io.support.PathMatchingResourcePatternResolver * @see org.springframework.core.io.support.PathMatchingResourcePatternResolver
...@@ -498,6 +547,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader ...@@ -498,6 +547,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
* {@linkplain ConfigurableEnvironment#merge(ConfigurableEnvironment) merged} with * {@linkplain ConfigurableEnvironment#merge(ConfigurableEnvironment) merged} with
* this (child) application context environment if the parent is non-{@code null} and * this (child) application context environment if the parent is non-{@code null} and
* its environment is an instance of {@link ConfigurableEnvironment}. * its environment is an instance of {@link ConfigurableEnvironment}.
*
* @see ConfigurableEnvironment#merge(ConfigurableEnvironment) * @see ConfigurableEnvironment#merge(ConfigurableEnvironment)
*/ */
@Override @Override
...@@ -594,9 +644,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader ...@@ -594,9 +644,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
// 12,完成刷新过程, 通知生命周期处理器 LifecycleProcessor 刷新过程同时发出 ContextRefreshEvent通知别人 // 12,完成刷新过程, 通知生命周期处理器 LifecycleProcessor 刷新过程同时发出 ContextRefreshEvent通知别人
finishRefresh(); finishRefresh();
} } catch (BeansException ex) {
catch (BeansException ex) {
if (logger.isWarnEnabled()) { if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " + logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex); "cancelling refresh attempt: " + ex);
...@@ -610,9 +658,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader ...@@ -610,9 +658,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
// Propagate exception to caller. // Propagate exception to caller.
throw ex; throw ex;
} } finally {
finally {
// Reset common introspection caches in Spring's core, since we // Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore... // might not ever need metadata for singleton beans anymore...
resetCommonCaches(); resetCommonCaches();
...@@ -634,8 +680,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader ...@@ -634,8 +680,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
if (logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
logger.trace("Refreshing " + this); logger.trace("Refreshing " + this);
} } else {
else {
logger.debug("Refreshing " + getDisplayName()); logger.debug("Refreshing " + getDisplayName());
} }
} }
...@@ -651,8 +696,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader ...@@ -651,8 +696,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
// Store pre-refresh ApplicationListeners... // Store pre-refresh ApplicationListeners...
if (this.earlyApplicationListeners == null) { if (this.earlyApplicationListeners == null) {
this.earlyApplicationListeners = new LinkedHashSet<>(this.applicationListeners); this.earlyApplicationListeners = new LinkedHashSet<>(this.applicationListeners);
} } else {
else {
// Reset local application listeners to pre-refresh state. // Reset local application listeners to pre-refresh state.
this.applicationListeners.clear(); this.applicationListeners.clear();
this.applicationListeners.addAll(this.earlyApplicationListeners); this.applicationListeners.addAll(this.earlyApplicationListeners);
...@@ -665,6 +709,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader ...@@ -665,6 +709,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
/** /**
* <p>Replace any stub property sources with actual instances. * <p>Replace any stub property sources with actual instances.
*
* @see org.springframework.core.env.PropertySource.StubPropertySource * @see org.springframework.core.env.PropertySource.StubPropertySource
* @see org.springframework.web.context.support.WebApplicationContextUtils#initServletPropertySources * @see org.springframework.web.context.support.WebApplicationContextUtils#initServletPropertySources
*/ */
...@@ -674,6 +719,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader ...@@ -674,6 +719,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
/** /**
* Tell the subclass to refresh the internal bean factory. * Tell the subclass to refresh the internal bean factory.
*
* @return the fresh BeanFactory instance * @return the fresh BeanFactory instance
* @see #refreshBeanFactory() * @see #refreshBeanFactory()
* @see #getBeanFactory() * @see #getBeanFactory()
...@@ -688,6 +734,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader ...@@ -688,6 +734,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
/** /**
* Configure the factory's standard context characteristics, * Configure the factory's standard context characteristics,
* such as the context's ClassLoader and post-processors. * such as the context's ClassLoader and post-processors.
*
* @param beanFactory the BeanFactory to configure * @param beanFactory the BeanFactory to configure
*/ */
protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) { protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) {
...@@ -757,6 +804,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader ...@@ -757,6 +804,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
* initialization. All bean definitions will have been loaded, but no beans * initialization. All bean definitions will have been loaded, but no beans
* will have been instantiated yet. This allows for registering special * will have been instantiated yet. This allows for registering special
* BeanPostProcessors etc in certain ApplicationContext implementations. * BeanPostProcessors etc in certain ApplicationContext implementations.
*
* @param beanFactory the bean factory used by the application context * @param beanFactory the bean factory used by the application context
*/ */
protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) { protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
...@@ -785,6 +833,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader ...@@ -785,6 +833,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
* <p>Must be called before any instantiation of application beans. * <p>Must be called before any instantiation of application beans.
*/ */
protected void registerBeanPostProcessors(ConfigurableListableBeanFactory beanFactory) { protected void registerBeanPostProcessors(ConfigurableListableBeanFactory beanFactory) {
// 开始注册 BeanPostProcessor
PostProcessorRegistrationDelegate.registerBeanPostProcessors(beanFactory, this); PostProcessorRegistrationDelegate.registerBeanPostProcessors(beanFactory, this);
} }
...@@ -807,8 +856,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader ...@@ -807,8 +856,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
if (logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
logger.trace("Using MessageSource [" + this.messageSource + "]"); logger.trace("Using MessageSource [" + this.messageSource + "]");
} }
} } else {
else {
// Use empty MessageSource to be able to accept getMessage calls. // Use empty MessageSource to be able to accept getMessage calls.
DelegatingMessageSource dms = new DelegatingMessageSource(); DelegatingMessageSource dms = new DelegatingMessageSource();
dms.setParentMessageSource(getInternalParentMessageSource()); dms.setParentMessageSource(getInternalParentMessageSource());
...@@ -823,19 +871,25 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader ...@@ -823,19 +871,25 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
/** /**
* Initialize the ApplicationEventMulticaster. * Initialize the ApplicationEventMulticaster.
* Uses SimpleApplicationEventMulticaster if none defined in the context. * Uses SimpleApplicationEventMulticaster if none defined in the context.
*
* @see org.springframework.context.event.SimpleApplicationEventMulticaster * @see org.springframework.context.event.SimpleApplicationEventMulticaster
*/ */
protected void initApplicationEventMulticaster() { protected void initApplicationEventMulticaster() {
// 作用就是就是将事件通知给监听者
ConfigurableListableBeanFactory beanFactory = getBeanFactory(); ConfigurableListableBeanFactory beanFactory = getBeanFactory();
// 当前是否包含了名为 applicationEventMulticaster 的事件多播器bean注入
if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) { if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {
// 若存在,则实例化该多播器,并赋值给 applicationEventMulticaster 变量
this.applicationEventMulticaster = this.applicationEventMulticaster =
beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class); beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
if (logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
logger.trace("Using ApplicationEventMulticaster [" + this.applicationEventMulticaster + "]"); logger.trace("Using ApplicationEventMulticaster [" + this.applicationEventMulticaster + "]");
} }
} }
// 若不存在,构造一个 SimpleApplicationEventMulticaster 多播器对象,并赋值给applicationEventMulticaster 变量
else { else {
this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory); this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
// 将该对象注入bean工厂
beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster); beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);
if (logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
logger.trace("No '" + APPLICATION_EVENT_MULTICASTER_BEAN_NAME + "' bean, using " + logger.trace("No '" + APPLICATION_EVENT_MULTICASTER_BEAN_NAME + "' bean, using " +
...@@ -847,6 +901,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader ...@@ -847,6 +901,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
/** /**
* Initialize the LifecycleProcessor. * Initialize the LifecycleProcessor.
* Uses DefaultLifecycleProcessor if none defined in the context. * Uses DefaultLifecycleProcessor if none defined in the context.
*
* @see org.springframework.context.support.DefaultLifecycleProcessor * @see org.springframework.context.support.DefaultLifecycleProcessor
*/ */
protected void initLifecycleProcessor() { protected void initLifecycleProcessor() {
...@@ -857,8 +912,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader ...@@ -857,8 +912,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
if (logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
logger.trace("Using LifecycleProcessor [" + this.lifecycleProcessor + "]"); logger.trace("Using LifecycleProcessor [" + this.lifecycleProcessor + "]");
} }
} } else {
else {
DefaultLifecycleProcessor defaultProcessor = new DefaultLifecycleProcessor(); DefaultLifecycleProcessor defaultProcessor = new DefaultLifecycleProcessor();
defaultProcessor.setBeanFactory(beanFactory); defaultProcessor.setBeanFactory(beanFactory);
this.lifecycleProcessor = defaultProcessor; this.lifecycleProcessor = defaultProcessor;
...@@ -874,6 +928,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader ...@@ -874,6 +928,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
* Template method which can be overridden to add context-specific refresh work. * Template method which can be overridden to add context-specific refresh work.
* Called on initialization of special beans, before instantiation of singletons. * Called on initialization of special beans, before instantiation of singletons.
* <p>This implementation is empty. * <p>This implementation is empty.
*
* @throws BeansException in case of errors * @throws BeansException in case of errors
* @see #refresh() * @see #refresh()
*/ */
...@@ -887,12 +942,14 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader ...@@ -887,12 +942,14 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
*/ */
protected void registerListeners() { protected void registerListeners() {
// Register statically specified listeners first. // Register statically specified listeners first.
// 硬编码方式注册
for (ApplicationListener<?> listener : getApplicationListeners()) { for (ApplicationListener<?> listener : getApplicationListeners()) {
getApplicationEventMulticaster().addApplicationListener(listener); getApplicationEventMulticaster().addApplicationListener(listener);
} }
// Do not initialize FactoryBeans here: We need to leave all regular beans // Do not initialize FactoryBeans here: We need to leave all regular beans
// uninitialized to let post-processors apply to them! // uninitialized to let post-processors apply to them!
// 配置文件方式注册
String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false); String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);
for (String listenerBeanName : listenerBeanNames) { for (String listenerBeanName : listenerBeanNames) {
getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName); getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);
...@@ -914,6 +971,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader ...@@ -914,6 +971,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
*/ */
protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) { protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
// Initialize conversion service for this context. // Initialize conversion service for this context.
// 1.初始化此上下文的转换服务
if (beanFactory.containsBean(CONVERSION_SERVICE_BEAN_NAME) && if (beanFactory.containsBean(CONVERSION_SERVICE_BEAN_NAME) &&
beanFactory.isTypeMatch(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)) { beanFactory.isTypeMatch(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)) {
beanFactory.setConversionService( beanFactory.setConversionService(
...@@ -923,6 +981,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader ...@@ -923,6 +981,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
// Register a default embedded value resolver if no BeanFactoryPostProcessor // Register a default embedded value resolver if no BeanFactoryPostProcessor
// (such as a PropertySourcesPlaceholderConfigurer bean) registered any before: // (such as a PropertySourcesPlaceholderConfigurer bean) registered any before:
// at this point, primarily for resolution in annotation attribute values. // at this point, primarily for resolution in annotation attribute values.
// 2.如果beanFactory之前没有注册嵌入值解析器,则注册默认的嵌入值解析器:主要用于注解属性值的解析。
if (!beanFactory.hasEmbeddedValueResolver()) { if (!beanFactory.hasEmbeddedValueResolver()) {
beanFactory.addEmbeddedValueResolver(strVal -> getEnvironment().resolvePlaceholders(strVal)); beanFactory.addEmbeddedValueResolver(strVal -> getEnvironment().resolvePlaceholders(strVal));
} }
...@@ -966,6 +1025,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader ...@@ -966,6 +1025,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
/** /**
* Cancel this context's refresh attempt, resetting the {@code active} flag * Cancel this context's refresh attempt, resetting the {@code active} flag
* after an exception got thrown. * after an exception got thrown.
*
* @param ex the exception that led to the cancellation * @param ex the exception that led to the cancellation
*/ */
protected void cancelRefresh(BeansException ex) { protected void cancelRefresh(BeansException ex) {
...@@ -976,11 +1036,12 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader ...@@ -976,11 +1036,12 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
* Reset Spring's common reflection metadata caches, in particular the * Reset Spring's common reflection metadata caches, in particular the
* {@link ReflectionUtils}, {@link AnnotationUtils}, {@link ResolvableType} * {@link ReflectionUtils}, {@link AnnotationUtils}, {@link ResolvableType}
* and {@link CachedIntrospectionResults} caches. * and {@link CachedIntrospectionResults} caches.
* @since 4.2 *
* @see ReflectionUtils#clearCache() * @see ReflectionUtils#clearCache()
* @see AnnotationUtils#clearCache() * @see AnnotationUtils#clearCache()
* @see ResolvableType#clearCache() * @see ResolvableType#clearCache()
* @see CachedIntrospectionResults#clearClassLoader(ClassLoader) * @see CachedIntrospectionResults#clearClassLoader(ClassLoader)
* @since 4.2
*/ */
protected void resetCommonCaches() { protected void resetCommonCaches() {
ReflectionUtils.clearCache(); ReflectionUtils.clearCache();
...@@ -995,6 +1056,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader ...@@ -995,6 +1056,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
* {@code SpringContextShutdownHook} with the JVM runtime, closing this * {@code SpringContextShutdownHook} with the JVM runtime, closing this
* context on JVM shutdown unless it has already been closed at that time. * context on JVM shutdown unless it has already been closed at that time.
* <p>Delegates to {@code doClose()} for the actual closing procedure. * <p>Delegates to {@code doClose()} for the actual closing procedure.
*
* @see Runtime#addShutdownHook * @see Runtime#addShutdownHook
* @see ConfigurableApplicationContext#SHUTDOWN_HOOK_THREAD_NAME * @see ConfigurableApplicationContext#SHUTDOWN_HOOK_THREAD_NAME
* @see #close() * @see #close()
...@@ -1020,6 +1082,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader ...@@ -1020,6 +1082,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
* Close this application context, destroying all beans in its bean factory. * Close this application context, destroying all beans in its bean factory.
* <p>Delegates to {@code doClose()} for the actual closing procedure. * <p>Delegates to {@code doClose()} for the actual closing procedure.
* Also removes a JVM shutdown hook, if registered, as it's not needed anymore. * Also removes a JVM shutdown hook, if registered, as it's not needed anymore.
*
* @see #doClose() * @see #doClose()
* @see #registerShutdownHook() * @see #registerShutdownHook()
*/ */
...@@ -1032,8 +1095,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader ...@@ -1032,8 +1095,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
if (this.shutdownHook != null) { if (this.shutdownHook != null) {
try { try {
Runtime.getRuntime().removeShutdownHook(this.shutdownHook); Runtime.getRuntime().removeShutdownHook(this.shutdownHook);
} } catch (IllegalStateException ex) {
catch (IllegalStateException ex) {
// ignore - VM is already shutting down // ignore - VM is already shutting down
} }
} }
...@@ -1044,6 +1106,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader ...@@ -1044,6 +1106,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
* Actually performs context closing: publishes a ContextClosedEvent and * Actually performs context closing: publishes a ContextClosedEvent and
* destroys the singletons in the bean factory of this application context. * destroys the singletons in the bean factory of this application context.
* <p>Called by both {@code close()} and a JVM shutdown hook, if any. * <p>Called by both {@code close()} and a JVM shutdown hook, if any.
*
* @see org.springframework.context.event.ContextClosedEvent * @see org.springframework.context.event.ContextClosedEvent
* @see #destroyBeans() * @see #destroyBeans()
* @see #close() * @see #close()
...@@ -1060,8 +1123,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader ...@@ -1060,8 +1123,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
try { try {
// Publish shutdown event. // Publish shutdown event.
publishEvent(new ContextClosedEvent(this)); publishEvent(new ContextClosedEvent(this));
} } catch (Throwable ex) {
catch (Throwable ex) {
logger.warn("Exception thrown from ApplicationListener handling ContextClosedEvent", ex); logger.warn("Exception thrown from ApplicationListener handling ContextClosedEvent", ex);
} }
...@@ -1069,8 +1131,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader ...@@ -1069,8 +1131,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
if (this.lifecycleProcessor != null) { if (this.lifecycleProcessor != null) {
try { try {
this.lifecycleProcessor.onClose(); this.lifecycleProcessor.onClose();
} } catch (Throwable ex) {
catch (Throwable ex) {
logger.warn("Exception thrown from LifecycleProcessor on context close", ex); logger.warn("Exception thrown from LifecycleProcessor on context close", ex);
} }
} }
...@@ -1103,6 +1164,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader ...@@ -1103,6 +1164,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
* <p>Can be overridden to add context-specific bean destruction steps * <p>Can be overridden to add context-specific bean destruction steps
* right before or right after standard singleton destruction, * right before or right after standard singleton destruction,
* while the context's BeanFactory is still active. * while the context's BeanFactory is still active.
*
* @see #getBeanFactory() * @see #getBeanFactory()
* @see org.springframework.beans.factory.config.ConfigurableBeanFactory#destroySingletons() * @see org.springframework.beans.factory.config.ConfigurableBeanFactory#destroySingletons()
*/ */
...@@ -1140,8 +1202,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader ...@@ -1140,8 +1202,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
if (!this.active.get()) { if (!this.active.get()) {
if (this.closed.get()) { if (this.closed.get()) {
throw new IllegalStateException(getDisplayName() + " has been closed already"); throw new IllegalStateException(getDisplayName() + " has been closed already");
} } else {
else {
throw new IllegalStateException(getDisplayName() + " has not been refreshed yet"); throw new IllegalStateException(getDisplayName() + " has not been refreshed yet");
} }
} }
...@@ -1364,6 +1425,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader ...@@ -1364,6 +1425,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
/** /**
* Return the internal bean factory of the parent context if it implements * Return the internal bean factory of the parent context if it implements
* ConfigurableApplicationContext; else, return the parent context itself. * ConfigurableApplicationContext; else, return the parent context itself.
*
* @see org.springframework.context.ConfigurableApplicationContext#getBeanFactory * @see org.springframework.context.ConfigurableApplicationContext#getBeanFactory
*/ */
@Nullable @Nullable
...@@ -1394,6 +1456,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader ...@@ -1394,6 +1456,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
/** /**
* Return the internal MessageSource used by the context. * Return the internal MessageSource used by the context.
*
* @return the internal MessageSource (never {@code null}) * @return the internal MessageSource (never {@code null})
* @throws IllegalStateException if the context has not been initialized yet * @throws IllegalStateException if the context has not been initialized yet
*/ */
...@@ -1458,6 +1521,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader ...@@ -1458,6 +1521,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
* <p>A subclass will either create a new bean factory and hold a reference to it, * <p>A subclass will either create a new bean factory and hold a reference to it,
* or return a single BeanFactory instance that it holds. In the latter case, it will * or return a single BeanFactory instance that it holds. In the latter case, it will
* usually throw an IllegalStateException if refreshing the context more than once. * usually throw an IllegalStateException if refreshing the context more than once.
*
* @throws BeansException if initialization of the bean factory failed * @throws BeansException if initialization of the bean factory failed
* @throws IllegalStateException if already initialized and multiple refresh * @throws IllegalStateException if already initialized and multiple refresh
* attempts are not supported * attempts are not supported
...@@ -1477,6 +1541,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader ...@@ -1477,6 +1541,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
* <p>Note: Subclasses should check whether the context is still active before * <p>Note: Subclasses should check whether the context is still active before
* returning the internal bean factory. The internal factory should generally be * returning the internal bean factory. The internal factory should generally be
* considered unavailable once the context has been closed. * considered unavailable once the context has been closed.
*
* @return this application context's internal bean factory (never {@code null}) * @return this application context's internal bean factory (never {@code null})
* @throws IllegalStateException if the context does not hold an internal bean factory yet * @throws IllegalStateException if the context does not hold an internal bean factory yet
* (usually if {@link #refresh()} has never been called) or if the context has been * (usually if {@link #refresh()} has never been called) or if the context has been
......
...@@ -175,7 +175,7 @@ final class PostProcessorRegistrationDelegate { ...@@ -175,7 +175,7 @@ final class PostProcessorRegistrationDelegate {
// Now, invoke the postProcessBeanFactory callback of all processors handled so far. // Now, invoke the postProcessBeanFactory callback of all processors handled so far.
// 6.调用所有 BeanDefinitionRegistryPostProcessor 的 postProcessBeanFactory 方法 // 6.调用所有 BeanDefinitionRegistryPostProcessor 的 postProcessBeanFactory 方法
// (BeanDefinitionRegistryPostProcessor继承自BeanFactoryPostProcessor) // (BeanDefinitionRegistryPostProcessor 继承自 BeanFactoryPostProcessor)
invokeBeanFactoryPostProcessors(registryProcessors, beanFactory); invokeBeanFactoryPostProcessors(registryProcessors, beanFactory);
// 7.最后, 调用入参 beanFactoryPostProcessors 中的普通 BeanFactoryPostProcessor 的 postProcessBeanFactory 方法 // 7.最后, 调用入参 beanFactoryPostProcessors 中的普通 BeanFactoryPostProcessor 的 postProcessBeanFactory 方法
invokeBeanFactoryPostProcessors(regularPostProcessors, beanFactory); invokeBeanFactoryPostProcessors(regularPostProcessors, beanFactory);
...@@ -263,20 +263,27 @@ final class PostProcessorRegistrationDelegate { ...@@ -263,20 +263,27 @@ final class PostProcessorRegistrationDelegate {
// to ensure that your proposal does not result in a breaking change: // to ensure that your proposal does not result in a breaking change:
// https://github.com/spring-projects/spring-framework/issues?q=PostProcessorRegistrationDelegate+is%3Aclosed+label%3A%22status%3A+declined%22 // https://github.com/spring-projects/spring-framework/issues?q=PostProcessorRegistrationDelegate+is%3Aclosed+label%3A%22status%3A+declined%22
// 1.找出所有实现BeanPostProcessor接口的类
String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanPostProcessor.class, true, false); String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanPostProcessor.class, true, false);
// Register BeanPostProcessorChecker that logs an info message when // BeanPostProcessorChecker 是一个普通的信息打印,可能会有些情况,
// a bean is created during BeanPostProcessor instantiation, i.e. when // 当Spring中的后置处理器还没有被注册就已经开始了bean的初始化时,
// a bean is not eligible for getting processed by all BeanPostProcessors. // 便会打印 BeanPostProcessorChecker 中的信息
// BeanPostProcessor的目标计数
int beanProcessorTargetCount = beanFactory.getBeanPostProcessorCount() + 1 + postProcessorNames.length; int beanProcessorTargetCount = beanFactory.getBeanPostProcessorCount() + 1 + postProcessorNames.length;
// 2.添加BeanPostProcessorChecker(主要用于记录信息)到beanFactory中
beanFactory.addBeanPostProcessor(new BeanPostProcessorChecker(beanFactory, beanProcessorTargetCount)); beanFactory.addBeanPostProcessor(new BeanPostProcessorChecker(beanFactory, beanProcessorTargetCount));
// Separate between BeanPostProcessors that implement PriorityOrdered, // 3.定义不同的变量用于区分:
// Ordered, and the rest. // 实现PriorityOrdered接口的BeanPostProcessor、实现Ordered接口的BeanPostProcessor、普通BeanPostProcessor
// 第一个是对象链表,第二三个是 beanName链表
// 用于存放实现PriorityOrdered接口的BeanPostProcessor
List<BeanPostProcessor> priorityOrderedPostProcessors = new ArrayList<>(); List<BeanPostProcessor> priorityOrderedPostProcessors = new ArrayList<>();
// 用于存放Spring内部的BeanPostProcessor
List<BeanPostProcessor> internalPostProcessors = new ArrayList<>(); List<BeanPostProcessor> internalPostProcessors = new ArrayList<>();
List<String> orderedPostProcessorNames = new ArrayList<>(); List<String> orderedPostProcessorNames = new ArrayList<>();
List<String> nonOrderedPostProcessorNames = new ArrayList<>(); List<String> nonOrderedPostProcessorNames = new ArrayList<>();
// 4.遍历 postProcessorNames, 将 BeanPostProcessors分开
for (String ppName : postProcessorNames) { for (String ppName : postProcessorNames) {
if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) { if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class); BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);
...@@ -321,11 +328,13 @@ final class PostProcessorRegistrationDelegate { ...@@ -321,11 +328,13 @@ final class PostProcessorRegistrationDelegate {
registerBeanPostProcessors(beanFactory, nonOrderedPostProcessors); registerBeanPostProcessors(beanFactory, nonOrderedPostProcessors);
// Finally, re-register all internal BeanPostProcessors. // Finally, re-register all internal BeanPostProcessors.
// 重新注册内部PostProcessors,(相当于内部的BeanPostProcessor会被移到处理器链的末尾)
sortPostProcessors(internalPostProcessors, beanFactory); sortPostProcessors(internalPostProcessors, beanFactory);
registerBeanPostProcessors(beanFactory, internalPostProcessors); registerBeanPostProcessors(beanFactory, internalPostProcessors);
// Re-register post-processor for detecting inner beans as ApplicationListeners, // Re-register post-processor for detecting inner beans as ApplicationListeners,
// moving it to the end of the processor chain (for picking up proxies etc). // moving it to the end of the processor chain (for picking up proxies etc).
// 重新注册 ApplicationListenerDetector(跟上面类似,主要是为了移动到处理器链的末尾)
beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(applicationContext)); beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(applicationContext));
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment