001/* 002 * Copyright 2002-2019 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.web.context.support; 018 019import java.util.LinkedHashMap; 020import java.util.Map; 021 022import javax.servlet.ServletContext; 023 024import org.springframework.beans.factory.DisposableBean; 025import org.springframework.beans.factory.ObjectFactory; 026import org.springframework.beans.factory.config.Scope; 027import org.springframework.lang.Nullable; 028import org.springframework.util.Assert; 029 030/** 031 * {@link Scope} wrapper for a ServletContext, i.e. for global web application attributes. 032 * 033 * <p>This differs from traditional Spring singletons in that it exposes attributes in the 034 * ServletContext. Those attributes will get destroyed whenever the entire application 035 * shuts down, which might be earlier or later than the shutdown of the containing Spring 036 * ApplicationContext. 037 * 038 * <p>The associated destruction mechanism relies on a 039 * {@link org.springframework.web.context.ContextCleanupListener} being registered in 040 * {@code web.xml}. Note that {@link org.springframework.web.context.ContextLoaderListener} 041 * includes ContextCleanupListener's functionality. 042 * 043 * <p>This scope is registered as default scope with key 044 * {@link org.springframework.web.context.WebApplicationContext#SCOPE_APPLICATION "application"}. 045 * 046 * @author Juergen Hoeller 047 * @since 3.0 048 * @see org.springframework.web.context.ContextCleanupListener 049 */ 050public class ServletContextScope implements Scope, DisposableBean { 051 052 private final ServletContext servletContext; 053 054 private final Map<String, Runnable> destructionCallbacks = new LinkedHashMap<>(); 055 056 057 /** 058 * Create a new Scope wrapper for the given ServletContext. 059 * @param servletContext the ServletContext to wrap 060 */ 061 public ServletContextScope(ServletContext servletContext) { 062 Assert.notNull(servletContext, "ServletContext must not be null"); 063 this.servletContext = servletContext; 064 } 065 066 067 @Override 068 public Object get(String name, ObjectFactory<?> objectFactory) { 069 Object scopedObject = this.servletContext.getAttribute(name); 070 if (scopedObject == null) { 071 scopedObject = objectFactory.getObject(); 072 this.servletContext.setAttribute(name, scopedObject); 073 } 074 return scopedObject; 075 } 076 077 @Override 078 @Nullable 079 public Object remove(String name) { 080 Object scopedObject = this.servletContext.getAttribute(name); 081 if (scopedObject != null) { 082 synchronized (this.destructionCallbacks) { 083 this.destructionCallbacks.remove(name); 084 } 085 this.servletContext.removeAttribute(name); 086 return scopedObject; 087 } 088 else { 089 return null; 090 } 091 } 092 093 @Override 094 public void registerDestructionCallback(String name, Runnable callback) { 095 synchronized (this.destructionCallbacks) { 096 this.destructionCallbacks.put(name, callback); 097 } 098 } 099 100 @Override 101 @Nullable 102 public Object resolveContextualObject(String key) { 103 return null; 104 } 105 106 @Override 107 @Nullable 108 public String getConversationId() { 109 return null; 110 } 111 112 113 /** 114 * Invoke all registered destruction callbacks. 115 * To be called on ServletContext shutdown. 116 * @see org.springframework.web.context.ContextCleanupListener 117 */ 118 @Override 119 public void destroy() { 120 synchronized (this.destructionCallbacks) { 121 for (Runnable runnable : this.destructionCallbacks.values()) { 122 runnable.run(); 123 } 124 this.destructionCallbacks.clear(); 125 } 126 } 127 128}