← Назад к вопросам

Какие знаешь области видимости бинов в Spring?

2.2 Middle🔥 151 комментариев
#Spring Framework

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Области видимости бинов в Spring (Scopes)

Scope определяет жизненный цикл бина и как он будет переиспользоваться в приложении. Spring поддерживает несколько встроенных и пользовательских scopes.

1. Singleton (по умолчанию)

Один экземпляр бина на весь контекст приложения:

@Configuration
public class SingletonScopeExample {
    
    // По умолчанию все бины — singleton
    @Bean
    public UserService userService() {
        return new UserService();
    }
    
    // Явно указываем singleton
    @Bean
    @Scope("singleton")
    public UserRepository userRepository() {
        return new UserRepository();
    }
}

@Service
// По умолчанию @Service имеет scope = singleton
public class UserService {
    // Одинцкий экземпляр на всё приложение
}

public class SingletonBehavior {
    public static void main(String[] args) {
        ApplicationContext context = 
            new AnnotationConfigApplicationContext(SingletonScopeExample.class);
        
        UserService service1 = context.getBean(UserService.class);
        UserService service2 = context.getBean(UserService.class);
        
        System.out.println(service1 == service2);  // true! Один объект
        System.out.println(System.identityHashCode(service1));
        System.out.println(System.identityHashCode(service2));  // Одинаковые
    }
}

Характеристики:

  • Один экземпляр на SessionFactory
  • Потокобезопасность: нужна сама
  • Производительность: максимальная (кэширование)
  • Использование: для stateless сервисов (UserService, Repository)

Потокобезопасная реализация:

@Service
@Scope("singleton")
public class SingletonCacheService {
    // Потокобезопасный кэш
    private final ConcurrentHashMap<String, String> cache = 
        new ConcurrentHashMap<>();
    
    public void put(String key, String value) {
        cache.put(key, value);
    }
    
    public String get(String key) {
        return cache.get(key);
    }
}

2. Prototype

Новый экземпляр при каждом запросе:

@Configuration
public class PrototypeScopeExample {
    
    @Bean
    @Scope("prototype")
    public RequestContext requestContext() {
        return new RequestContext();
    }
}

public class PrototypeBehavior {
    public static void main(String[] args) {
        ApplicationContext context = 
            new AnnotationConfigApplicationContext(PrototypeScopeExample.class);
        
        RequestContext ctx1 = context.getBean(RequestContext.class);
        RequestContext ctx2 = context.getBean(RequestContext.class);
        
        System.out.println(ctx1 == ctx2);  // false! Разные объекты
    }
}

Характеристики:

  • Новый экземпляр при каждом getBean()
  • Состояние изолировано: каждый клиент получает свой объект
  • Использование: для stateful объектов (контекст запроса, форма)
  • Внимание: Spring НЕ вызывает destroy методы (утечки ресурсов возможны)

Правильное использование:

@Component
@Scope("prototype")
public class RequestData implements DisposableBean {
    private String data;
    private Connection connection;
    
    public RequestData() {
        this.connection = createConnection();
    }
    
    // Вызывается вручную, Spring не вызывает для prototype
    @Override
    public void destroy() throws Exception {
        if (connection != null) {
            connection.close();
        }
    }
    
    private Connection createConnection() {
        // ...
        return null;
    }
}

3. Request (веб-приложения)

Новый экземпляр для каждого HTTP запроса:

@Configuration
@EnableWebMvc
public class RequestScopeExample {
    
    @Bean
    @Scope("request")
    public UserContext userContext() {
        return new UserContext();
    }
}

@RestController
@RequestMapping("/api/users")
public class UserController {
    @Autowired
    private UserContext userContext;  // Разный для каждого запроса
    
    @GetMapping("/{id}")
    public User getUser(@PathVariable Long id) {
        // userContext содержит данные текущего запроса
        return userContext.getUserData(id);
    }
}

// Контекст запроса
@Component
@Scope("request")
public class UserContext {
    private Long userId;
    private String username;
    private LocalDateTime requestTime = LocalDateTime.now();
    
    public Long getUserId() {
        return userId;
    }
    
    public String getUsername() {
        return username;
    }
}

Характеристики:

  • Один экземпляр на HTTP запрос
  • Автоматическое очищение после завершения запроса
  • Использование: для данных запроса, авторизации, контекста

4. Session (веб-приложения)

Новый экземпляр для каждой HTTP сессии:

@Configuration
@EnableWebMvc
public class SessionScopeExample {
    
    @Bean
    @Scope("session")
    public ShoppingCart shoppingCart() {
        return new ShoppingCart();
    }
}

@RestController
@RequestMapping("/api/cart")
public class CartController {
    @Autowired
    private ShoppingCart cart;  // Одна корзина на сессию
    
    @PostMapping("/add")
    public void addItem(@RequestBody Item item) {
        cart.add(item);
    }
    
    @GetMapping
    public List<Item> getItems() {
        return cart.getItems();
    }
}

// Сессионная корзина
@Component
@Scope("session")
public class ShoppingCart {
    private List<Item> items = new ArrayList<>();
    private LocalDateTime createdAt = LocalDateTime.now();
    
    public void add(Item item) {
        items.add(item);
    }
    
    public List<Item> getItems() {
        return items;
    }
    
    public void clear() {
        items.clear();
    }
}

Характеристики:

  • Один экземпляр на пользовательскую сессию
  • Доступен внутри сессии: разные пользователи = разные объекты
  • Использование: корзина покупок, данные сессии

5. Application (ServletContext scope)

Один экземпляр на всё приложение (как singleton, но для веб):

@Component
@Scope("application")
public class ApplicationSettings {
    private String appName;
    private String version;
    // Общие настройки приложения
}

6. WebSocket Scope

Для WebSocket соединений (Spring 4.0+):

@Component
@Scope("websocket")
public class WebSocketContext {
    private String sessionId;
    private Map<String, Object> attributes = new ConcurrentHashMap<>();
}

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
    
    @Bean
    @Scope("websocket")
    public WebSocketHandler webSocketHandler() {
        return new WebSocketHandler() {
            @Override
            public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) {
                // Один handler на WebSocket соединение
            }
            
            @Override
            public void afterConnectionEstablished(WebSocketSession session) {}
            
            @Override
            public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) {}
            
            @Override
            public boolean supportsPartialMessages() {
                return false;
            }
        };
    }
}

Comparison of Scopes

public class ScopeComparison {
    public static void main(String[] args) {
        // Singleton: один объект на приложение
        UserService service = context.getBean(UserService.class);
        UserService service2 = context.getBean(UserService.class);
        // service == service2  (true)
        
        // Prototype: новый объект каждый раз
        UserData data = context.getBean(UserData.class);
        UserData data2 = context.getBean(UserData.class);
        // data == data2  (false)
        
        // Request: один на запрос
        // Session: один на сессию
        // Application: один на приложение
    }
}

Пользовательские Scopes

// Создание пользовательского scope
@Configuration
public class CustomScopeConfig {
    
    @Bean
    public static CustomScopeConfigurer customScopeConfigurer() {
        CustomScopeConfigurer configurer = new CustomScopeConfigurer();
        
        // Создаём свой scope
        configurer.addScope("tenant", new TenantScope());
        
        return configurer;
    }
}

// Реализация scope
public class TenantScope implements Scope {
    
    private final ThreadLocal<Map<String, Object>> threadLocalScope = 
        ThreadLocal.withInitial(HashMap::new);
    
    @Override
    public Object get(String name, ObjectFactory<?> objectFactory) {
        Map<String, Object> scope = threadLocalScope.get();
        return scope.computeIfAbsent(name, key -> objectFactory.getObject());
    }
    
    @Override
    public Object remove(String name) {
        Map<String, Object> scope = threadLocalScope.get();
        return scope.remove(name);
    }
    
    @Override
    public void registerDestructionCallback(String name, Runnable callback) {
        // Регистрация callback'a на удаление
    }
    
    @Override
    public Object resolveContextualObject(String key) {
        return null;
    }
    
    @Override
    public String getConversationId() {
        return Thread.currentThread().getName();
    }
}

// Использование
@Component
@Scope("tenant")
public class TenantData {
    private String tenantId;
    // Разные данные для каждого tenant'а
}

Проблемы с Scopes

Singleton Bean с Prototype Dependency

// Проблема: singleton получает prototype один раз
@Service
public class UserService {
    @Autowired
    private UserContext context;  // Prototype
    
    public void processUser() {
        // context всегда одинаковый!
        String userId = context.getUserId();
    }
}

// Решение 1: ObjectProvider
@Service
public class FixedUserService {
    @Autowired
    private ObjectProvider<UserContext> contextProvider;
    
    public void processUser() {
        UserContext context = contextProvider.getIfAvailable();
        String userId = context.getUserId();
    }
}

// Решение 2: Proxy
@Service
public class ProxyUserService {
    @Autowired
    @Lazy
    private UserContext context;  // Proxy для ленивой инициализации
    
    public void processUser() {
        String userId = context.getUserId();
    }
}

// Решение 3: Method injection
@Service
public class MethodInjectionService {
    public void processUser() {
        UserContext context = createUserContext();
    }
    
    @Lookup
    protected UserContext createUserContext() {
        // Spring переопределяет этот метод
        return null;
    }
}

Best Practices

public class ScopeBestPractices {
    // 1. ✅ Используй Singleton для stateless сервисов
    // 2. ✅ Используй Prototype для stateful объектов
    // 3. ✅ Используй Request для контекста запроса
    // 4. ✅ Используй Session для данных сессии
    // 5. ✅ Используй ObjectProvider для внедрения prototype в singleton
    // 6. ❌ Избегай prototype если нужна высокая производительность
    // 7. ✅ Явно указывай scope (не полагайся на defaults)
    // 8. ✅ Помни про destroy callbacks для prototype beans
}

Таблица Scopes

ScopeЖизненный циклИспользованиеThread-safe
SingletonПриложениеServices, ReposНужна сама
PrototypeКаждый getBeanForms, RequestsДа (изолировано)
RequestHTTP запросContext данныхДа
SessionHTTP сессияКорзина, ДанныеДа
ApplicationПриложениеSettingsДа
WebSocketWS соединениеWS handlersДа

Вывод

Основные scopes в Spring:

  1. Singleton (default) — один на приложение
  2. Prototype — новый при каждом запросе
  3. Request — один на HTTP запрос
  4. Session — один на сессию пользователя
  5. Application — один на приложение
  6. WebSocket — один на WS соединение

Правильный выбор scope критичен для корректности и производительности приложения.