Подготовка к Spring Professional Certification. Контейнер, IoC, бины
Сегодня я решил представить вам перевод цикла статей для подготовки к Spring Professional Certification.
Это перевод только первой статьи, если он зайдет аудитории, я продолжу выпуск переводов.
Зачем я это делаю, ведь уже есть куча профильных материалов?
- Часто в них информация не структурирована, не собрана, или не актуальна
- Молодые разработчики могут не знать английский. Этот цикл можно использовать не только для сертификации, но и для самообучения/повторения материалов.
- Этими материалами можно пользоваться при подготовке к собеседованию, т.к. они выстроены в виде вопросов и ответов.
- Важное, и самое главное преимущество — этот QA сделан из вопросов из официального Study Guide от Pivotal.
- Некоторые вопросы, которые казались мне лишними или которых не было в гиде я осознанно упустил.
Оглавление
- Внедрение зависимостей, контейнер, IoC, бины
- AOP (аспектно-ориентированное программирование)
- JDBC, транзакции, JPA, Spring Data
- Spring Boot
- Spring MVC
- Spring Security
- REST
- Тестирование
Сразу напишу список источников, из которых автор брал материалы
- Spring 5 Design Patterns
- Spring in Action 4th edition
- Spring Security — Third Edition
- Core Spring 5 Certification in Detail by Ivan Krizsan
- Spring Documentation and Spring API javadocs
Что такое внедрение зависимостей(DI) и в чем его преимущества?
Внедрение зависимостей — это специальный паттерн, который уменьшает связь между Spring компонентами. Таким образом, при применении DI, ваш код становится чище, проще, его становится легче понять и тестировать.
Согласно паттерну DI, создание объектов для зависимостей переходит на фабрику или отдается третьей стороне. Это означает, что мы можем сосредоточиться на использовании этих объектов вместо их создания.
Преимущества DI
- Уменьшенная связь между частями приложения
- Улучшенное тестирование
- Улучшенная архитектура приложения
- Уменьшает шаблонный код
- Стандартизирует разработку приложения
Почему для создания Spring beans рекомендуются интерфейсы?
- Улучшенное тестирование. В тестах бин может быть заменен специальным объектом(mock или stub), который реализует интерфейс бина.
- Позволяет использовать механизм динамических прокси из JDK(например, при создании репозитория через Spring Data)
- Позволяет скрывать реализацию
Что такое application context?
В Spring Framework интерфейс org.springframework.factory.BeanFactory предоставляет фабрику для бинов, которая в то же время является IoC контейнером приложения. Управление бинами основано на конфигурации(java или xml).
Интерфейс org.springframework.context.ApplicationContext — это обертка над bean factory, предоставляющая некоторые дополнительные возможности, например AOP, транзакции, безопасность, i18n, и т.п.
Что такое контейнер и какой у него жизненный цикл?
Основа Spring Framework — контейнер, и наши объекты «живут» в этом контейнере.
Контейнер обычно создает множество объектов на основе их конфигураций и управляет их жизненным циклом от создания объекта до уничтожения.
Контейнер — это объект, реализующий интерфейс ApplicationContext.
Жизненный цикл контейнера
- Контейнер создается при запуске приложения
- Контейнер считывает конфигурационные данные
- Из конфигурационных данных создается описание бинов
- BeanFactoryPostProcessors обрабатывают описание бина
- Контейнер создает бины используя их описание
- Бины инициализируются — значения свойств и зависимости внедряются в бин
- BeanPostProcessor запускают методы обратного вызова(callback methods)
- Приложение запущено и работает
- Инициализируется закрытие приложения
- Контейнер закрывается
- Вызываются callback methods
Как создать экземпляр ApplicationContext?
Spring обеспечивает несколько разновидностей контекста.
Есть несколько основных реализаций интерфейса ApplicationContext:
- FileSystemXmlApplicationContext
- ClassPathXmlApplicationContext
- AnnotationConfigApplicationContext
- XmlWebApplicationContext
- AnnotationConfigWebApplicationContext
Примеры создания контекста:
ApplicationContext ctx = new FileSystemXmlApplicationContext( "c:/bean_properties.xml"); ApplicationContext ctx = new AnnotationConfigApplicationContext( "com.springdemoapp.JavaConfig.class");
Можете ли вы описать жизненный цикл бина в контейнере?

- Загрузка описаний бинов, создание графа зависимостей(между бинами)
- Создание и запуск BeanFactoryPostProcessors
- Создание бинов
- Spring внедряет значения и зависимости в свойства бина
- Если бин реализует метод setBeanName() из интерфейса NameBeanAware, то ID бина передается в метод
- Если бин реализует BeanFactoryAware, то Spring устанавливает ссылку на bean factory через setBeanFactory() из этого интерфейса.
- Если бин реализует интерфейс ApplicationContextAware, то Spring устанавливает ссылку на ApplicationContext через setApplicationContext() .
- BeanPostProcessor это специальный интерфейс(о нем ниже), и Spring позволяет бинам имплементировать этот интерфейс. Реализуя метод postProcessBeforeInitialization() , можно изменить экземпляр бина перед его(бина) инициализацией(установка свойств и т.п.)
- Если определены методы обратного вызова, то Spring вызывает их. Например, это метод, аннотированный @PostConstruct или метод initMethod из аннотации @Bean .
- Теперь бин готов к использованию. Его можно получить с помощью метода ApplicationContext#getBean() .
- После того как контекст будет закрыт(метод close() из ApplicationContext), бин уничтожается.
- Если в бине есть метод, аннотированный @PreDestroy , то перед уничтожением вызовется этот метод. Если бин имплементирует DisposibleBean, то Spring вызовет метод destroy() , чтобы очистить ресурсы или убить процессы в приложении. Если в аннотации @Bean определен метод destroyMethod , то вызовется и он.
Как получить ApplicationContext в интеграционном тесте?
Если вы используете JUnit 5, то вам нужно указать 2 аннотации:
- @ExtendWith(TestClass.class) — используется для указания тестового класса
- @ContextConfoguration(classes = JavaConfig.class) — загружает java/xml конфигурацию для создания контекста в тесте
Можно использовать аннотацию @SpringJUnitConfig , которая сочетает обе эти аннотации.
Для теста веб-слоя можно использовать аннотацию @SpringJUnitWebConfig .
Как завершить работу контекста в приложении?
Если это не веб-приложение, то есть 2 способа:
- Регистрация shutdown-hook с помощью вызова метода registerShutdownHook() , он также реализован в классе AbstractApplicationContext. Это предпочтительный способ.
- Можно вызвать метод close() из класса AbstractApplicationContext.
В Spring Boot приложении:
- Spring Boot самостоятельно зарегистрирует shutdown-hook за вас.
Что такое Java-конфигурация? Как она применяется?
Чтобы создать класс с конфигурацией на основе Java-кода, нужно аннотировать его с помощью
@Configuration .
Этот класс будет содержать фабричные методы для создания бинов в контейнере.
Эти методы должны быть аннотированы аннотацией @Bean .
@Configuration public class DSConfig < @Bean public DataSource dataSource() < return DataSourceBuilder .create() .username("") .password("") .url("") .driverClassName("") .build(); >>
Этот класс поместит в контейнер экземпляр класса DataSource. Позднее его можно будет использовать при доступе к базе данных.
DI используя аннотации, сканирование классов
Component scanning(сканирование компонентов) — Spring автоматически обнаруживает бины, которые будут находиться в контейнере. Это бины с аннотациями-стереотипами.
Однако сканирование компонентов не включено по умолчанию.
Чтобы включить сканирование, аннотируйте @Configuration-класс аннотацией @ComponentScanning . Spring будет автоматически сканировать тот пакет, в котором находится этот класс и все его подпакеты.
Можно указать и другие пакеты для сканирования, и даже классы:
//сканирует 2 пакета @Configuration(basePackages = )
//сканирует класс @Configuration(basePackageClasses = "MyClass.class")
Autowiring(внедрение) — Spring автоматически внедрит зависимости во время сканирования или помещения бина в контейнер.
Для внедрения зависимостей используется аннотация @Autowire .
Что такое stereotypes(аннотации-стереотипы)?
Стереотипы — это аннотации, обозначающие специальную функциональность.
Все стереотипы включают в себя аннотацию @Component .
| Component | Корневая аннотация, которая помечает класс как кандидат для автовнедрения |
| Controller | Указывает, что класс является контроллером для отправления данных на фронт. |
| @RestController | Указывает, что класс является контроллером для REST. Содержит аннотации Controller и @ResponseBody |
| Service | Указывает, что класс является сервисом для выполнения бизнес-логики |
| Repository | Указывает, что класс является репозиторием для работы с бд |
| @Configuration | Указывает, что класс содержит Java-конфигурацию(@Bean-методы) |
Какие существуют области видимости у бинов? Какая у них видимость по умолчанию?
Область видимости — scope, скоуп. Существует 2 области видимости по умолчанию.
| Singleton | Область видимости по умолчанию. В контейнере находится всего 1 экземпляр бина |
| Prototype | В контейнере может находится любое количество экземпляров бина |
И 4 области видимости в веб-приложении.
| Request | Область видимости — 1 HTTP запрос. На каждый запрос создается новый бин |
| Session | Область видимости — 1 сессия. На каждую сессию создается новый бин |
| Application | Область видимости — жизненный цикл ServletContext |
| WebSocket | Область видимости — жизненный цикл WebSocket |
Область видимости указывается с помощью аннотации @Scope на @Bean методах.
Как связаны различные скоупы и многопоточность?
Prototype Scope не потокбезопасный, т.к. он не гарантирует что один и тот же экземпляр будет вызываться только в 1 потоке.
Singleton Scope же наоборот потокобезопасный.
Как создаются бины: сразу или лениво? Как изменить это поведение?
Singleton-бины обычно создаются сразу при сканировании.
Prototype-бины обычно создаются только после запроса.
Чтобы указать способ инициализации, можно использовать аннотацию @Lazy .
Она ставится на @Bean-методы, на @Configuration-классы, или на @Component-классы.
В зависимости от параметра(true или false), который принимает аннотация, инициализация будет или ленивая, или произойдет сразу. По умолчанию(т.е. без указания параметра) используется true.
Что будет если бин с одним скоупом внедрить в бин с другим скоупом?
Singleton bean можно внедрять в любой другой бин.
В сам singleton можно внедрить только prototype или singleton .
Если внедрять prototype, то для каждого singleton будет создан уникальный prototype.
Prototype может быть зависимостью для любого бина.
Внедрять можно только singleton или prototype.
Что такое BeanFactoryPostProcessor и когда он используется?
- BeanFactoryPostProcessor работает над описаниями бинов или конфигурационными метаданными перед тем, как бин будет создан.
- Spring поставляет несколько полезных реализаций BeanFactoryPostProcessor , например, читающий property-файлы и получающий из них свойства бинов.
- Вы можете написать собственную реализацию BFPP.
Зачем вам может понадобится static @Bean-метод?
Для того чтобы использовать кастомный BFPP. Вы можете переопределить механизм получения данных из метафайлов.
@Bean public static PropertySourcesPlaceholderConfigurer pspc() < //создать, сконфигурировать и вернуть pspc >
Опишите свойства аннотации @Bean
- destroyMethod — указывает на метод обратного вызова. Метод находится в бине.
- initMethod — указывает на метод обратного вызова. Метод находится в бине.
- name — имя бина. По умолчанию именем бина является имя метода.
- value — алиас для name()
Что такое BeanPostProcessor и чем он отличается от BeanFactoryPostProcessor?
Spring использует несколько BeanPostProcessor’ов.
Например, CommonAnnotationPostProcessor или AutowiredAnnotationBeanPostProcessor .
BPP работает с экземплярами бинов, т.е. контейнер создает бин, а затем начинает работать BPP.

Что такое callback methods и как их использовать?
Есть 3 варианта для создания таких методов:

- @PreDestroy и @PostConstruct аннотации
- Параметры initMethod и destroyMethod в аннотации @Bean , указывающие на методы в классе бина
- Переопределенные InitializingBean#afterPropertiesSet() и DisposableBean#destroy() . Для переопределения этих методов нужно имплементировать соответствующие интерфейсы.
Как можно использовать аннотацию @Autowire и в чем отличие между способами?
Ниже перечислены типы DI, которые могут быть использованы в вашем приложении:
DI через конструктор считается самым лучшим способом, т.к. для него не надо использовать рефлексию, а также он не имеет недостатков DI через сеттер.
DI через поле не рекомендуется использовать, т.к. для этого применяется рефлексия, снижающая производительность.
DI через конструктор может приводить к циклическим зависимостям. Чтобы этого избежать, можно использовать ленивую инициализацию бинов или DI через сеттер.
Опишите поведение аннотации @Autowired
- Контейнер определяет тип объекта для внедрения
- Контейнер ищет бины в контексте(он же контейнер), которые соответствуют нужному типу
- Если есть несколько кандидатов, и один из них помечен как @Primary , то внедряется он
- Если используется аннотации @Autowire + Qualifier , то контейнер будет использовать информацию из @Qualifier , чтобы понять, какой компонент внедрять
- В противном случае контейнер попытается внедрить компонент, основываясь на его имени или ID
- Если ни один из способов не сработал, то будет выброшено исключение
Контейнер обрабатывает DI с помощью AutowiredAnnotationBeanPostProcessor. В связи с этим, аннотация не может быть использована ни в одном BeanFactoryPP или BeanPP.
Если внедряемый объект массив, коллекция, или map с дженериком, то Spring внедрит все бины подходящие по типу в этот массив(или другую структуру данных). В случае с map ключом будет имя бина.
//параметр указывает, требуется ли DI @Authowired(required = true/false)
Как произвести DI в private поле?
Вы можете использовать разные типы внедрения:
Как использование @Qualifier дополняет @Autowired?
Spring предоставляет аннотацию Qualifier, чтобы преодолеть проблему неоднозначности при DI.
@Bean @Qualifier("SomeClass1") public SomeClass getField() //… @Autowire @Qualifier("SomeField1") public SomeClass someField;
Если в контейнере есть несколько бинов одного типа(SomeClass), то контейнер внедрит именно тот бин, над @Bean-методом которого стоит соответствующий квалификатор. Также можно не ставить квалификатор на метод, а использовать имя бина в качестве параметра квалификатора.
Имя бина можно можно указать через параметр аннотации Bean, а по умолчанию это имя фабричного метода.
Что такое прокси-объекты и какие типы прокси-объектов может создавать Spring?
Прокси это специальный объект, который имеет такие же публичные методы как и бин, но у которого есть дополнительная функциональность.
Два вида прокси:
- JDK-proxy — динамическое прокси. API встроены в JDK. Для него необходим интерфейс
- CGLib proxy — не встроен в JDK. Используется когда интерфейс объекта недоступен
- Позволяют добавлять доп. логику — управление транзакциями, безопасность, логирование
- Отделяет некоторый код(логирование и т.п.) от основной логики
Как внедряется singleton-бин?
Если в контейнере нет экземпляра бина, то вызывается @Bean-метод. Если экземпляр бина есть, то возвращается уже созданный бин.
Что такое профили? Какие у них причины использования?
При использовании Java-конфигурации вы можете использовать аннотацию @Profile .
Она позволяет использовать разные настройки для Spring в зависимости от указанного профиля.
Ее можно ставить на @Configuration и Component классы, а также на Bean методы.
Profile("!test") //загружать со всеми прифилями, кроме теста
@Bean("dataSource") @Profile("production") public DataSource jndiDataSource() @Bean("dataSource") @Profile("development") public DataSource standaloneDataSource()
Как внедрить простые значения в свойства в Spring?
Для этого можно использовать аннотацию @Value .
Такие значения можно получать из property файлов, из бинов, и т.п.
@Value("$some.key") public String stringWithDefaultValue;
В эту переменную будет внедрена строка, например из property или из view.
Области видимости в JavaScript
В JavaScript область видимости — это важная, но неоднозначная концепция. Области видимости, при правильном подходе к их использованию, позволяют применять надёжные шаблоны проектирования, помогают избежать нежелательных побочных эффектов в программах. В этом материале мы проанализируем различные типы областей видимости в JavaScript, поговорим о том, как они работают. Хорошее понимание этого механизма позволит вам улучшить качество кода.

Картинка по запросу «области видимости». Извините, если вызвали приступ ностальгии )
Элементарное определение области видимости выглядит так: это область, где компилятор ищет переменные и функции, когда они ему нужны. Думаете, что звучит это слишком просто? Предлагаем разобраться вместе.
Интерпретатор JavaScript
Прежде чем говорить об областях видимости, нужно обсудить интерпретатор JavaScript, рассмотреть то, как он воздействует на различные области видимости. При исполнении JS-кода интерпретатор проходится по нему дважды.
Первый проход по коду, называемый ещё проходом компиляции — это то, что наиболее сильно воздействует на области видимости. Интерпретатор просматривает код в поисках объявлений переменных и функций и поднимает эти объявления в верхнюю часть текущей области видимости. Важно отметить, что поднимаются только объявления переменных, а операции присвоения остаются как есть — для следующего прохода, называемого проходом исполнения.
Для того, чтобы лучше это понять, рассмотрим простой фрагмент кода:
'use strict' var foo = 'foo'; var wow = 'wow'; function bar (wow) < var pow = 'pow'; console.log(foo); // 'foo' console.log(wow); // 'zoom' >bar('zoom'); console.log(pow); // ReferenceError: pow is not defined
Этот код, после компиляции, будет выглядеть примерно так:
'use strict' // Переменные подняты в верхнюю часть текущей области видимости var foo; var wow; // Объявления функций подняты целиком, вместе с присвоением, в верхнюю часть текущей области видимости function bar (wow) < var pow; pow = 'pow'; console.log(foo); console.log(wow); >foo = 'foo'; wow = 'wow'; bar('zoom'); console.log(pow); // ReferenceError: pow is not defined
Здесь надо обратить внимание на то, что объявления поднимаются в верхнюю часть их текущей области видимости. Это, как будет видно ниже, очень важно для понимания областей видимости в JavaScript.
Например, переменная pow была объявлена в функции bar , так как это — её область видимости. Обратите внимание на то, что переменная объявлена не в родительской, по отношению к функции, области видимости.
Параметр wow функции bar так же объявлен в области видимости функции. На самом деле, все параметры функции неявно объявлены в её области видимости, и именно поэтому команда console.log(wow) в девятой строке, внутри функции, выводит zoom вместо wow .
Лексическая область видимости
Рассмотрев особенности работы интерпретатора JavaScript и затронув тему поднятия переменных и функций, мы можем перейти к разговору об областях видимости. Начнём с лексической области видимости. Можно сказать, что это — область видимости, которая формируется во время компиляции. Другими словами, решение о границах этой области видимости принимается во время компиляции. Для целей этой статьи мы проигнорируем исключения из этого правила, которые возникают в коде, который использует команды eval или with . Полагаем, что эти команды, в любом случае, использовать не стоит.
Второй проход интерпретатора — это тот, в ходе которого выполняется присвоение значений переменным и исполняются функции. В вышеприведённом примере кода именно во время этого прохода выполняется вызов bar() в строке 12.
Интерпретатору нужно найти объявление bar прежде чем выполнить этот вызов, делает он это, начиная с выполнения поиска в текущей области видимости. В тот момент текущей является глобальная область видимости. Благодаря первому проходу, то есть компиляции, мы знаем, что объявление bar находится в верхней части кода, поэтому интерпретатор может найти его и выполнить функцию.
Если мы посмотрим на строку 8, где находится команда console.log(foo); , интерпретатору, прежде чем исполнить эту команду, понадобится найти объявление foo . Первое, что он делает, опять же, ищет в текущей области видимости, которой в этот момент является область видимости функции bar , а не глобальная область видимости. Объявлена ли переменная foo в области видимости функции? Нет, это не так. Затем он переходит на уровень вверх, к родительской области видимости, и ищет объявление переменной там. Область видимости, в которой объявлена функция — это глобальная область видимости. Объявлена ли переменная foo в глобальной области видимости? Да, это так. Поэтому интерпретатор может взять значение переменной и исполнить команду.
В целом, можно сказать, что смысл лексической области видимости заключается в том, что область видимости определяется после компиляции, и когда интерпретатору надо найти объявление переменной или функции, сначала он смотрит в текущей области видимости, но, если найти то, что ему нужно, не удаётся, он переходит в родительскую область видимости, продолжая поиск по тому же принципу. Самый высокий уровень, на который он может перейти, называется глобальной областью видимости.
Если того, что ищет интерпретатор, нет и в глобальной области видимости, он выдаст ошибку ReferenceError .
Кроме того, так как интерпретатор сначала ищет то, что ему нужно, в текущей области видимости, а уже потом — в родительской, лексическая область видимости вводит концепцию затенения переменных в JavaScript. Это означает, что переменная foo , объявленная в текущей области видимости функции, затенит или скроет переменную с тем же именем, объявленную в родительской области видимости. Взглянем на следующий пример для того, чтобы лучше разобраться с этой идеей:
'use strict' var foo = 'foo'; function bar () < var foo = 'bar'; console.log(foo); >bar();
Этот код выведет в консоль строку bar , а не foo , так как объявление переменной foo в шестой строке перекроет объявление переменной с таким же именем в третьей строке.
Затенение переменных — это шаблон проектирования, который может быть полезен в том случае, если нужно замаскировать некоторые переменные и предотвратить доступ к ним из конкретных областей видимости. Надо сказать, что я обычно избегаю использования этого приёма, применяя его только если без него совершенно невозможно обойтись, так как я уверен в том, что использование одинаковых имён переменных ведёт к путанице при командной разработке. Использование затенения способно привести к тому, что разработчик может решить, что в переменной хранится не то, что в ней на самом деле есть.
Функциональная область видимости
Как мы видели, рассматривая лексическую область видимости, интерпретатор объявляет переменные в текущей области видимости, что означает, что переменные, объявленные в функции, объявлены в функциональной области видимости. Эта область видимости ограничена самой функцией и её потомками — другими функциями, объявленными внутри этой функции.
К переменным, объявленным в функциональной области видимости, нельзя получить доступ извне. Это очень мощный шаблон проектирования, который можно задействовать, если вы хотите создать приватные свойства, и иметь к ним доступ только внутри функциональной области видимости. Вот как это выглядит:
'use strict' function convert (amount) < var _conversionRate = 2; // Доступно только в функциональной области видимости return amount * _conversionRate; >console.log(convert(5)); console.log(_conversionRate); // ReferenceError: _conversionRate is not defined
Блочная область видимости
Блочная область видимости похожа на функциональную, но она ограничена не функцией, а блоком кода.
В ES3 выражение catch в конструкции try / catch имеет блочную область видимости, что означает, что у этого выражения есть собственная область видимости. Важно отметить, что выражение try не имеет блочной области видимости, она есть только у выражения catch . Рассмотрим пример:
'use strict' try < var foo = 'foo'; console.log(bar); >catch (err) < console.log('In catch block'); console.log(err); >console.log(foo); console.log(err);
Этот код выдаст ошибку на пятой строке, когда мы попытаемся получить доступ к bar , что приведёт к тому, что интерпретатор перейдёт к выражению catch . В области видимости выражения объявлена переменная err , которая не будет доступна извне. На самом деле, ошибка будет выдана, когда мы попытаемся вывести в лог значение переменной err в строке console.log(err) ;. Вот что выведет этот код:
In catch block ReferenceError: bar is not defined (. Error stack here. ) foo ReferenceError: err is not defined (. Error stack here. )
Обратите внимание на то, что переменная foo доступна за пределами конструкции try / catch , а err — нет.
Если говорить о ES6, то при использовании ключевых слов let и const переменные и константы неявно присоединяются к текущей блочной области видимости вместо функциональной области видимости. Это означает, что эти конструкции ограничены блоком, в котором они используются, будет ли это блок if , блок for , или функция. Вот пример, который поможет лучше это понять:
'use strict' let condition = true; function bar () < if (condition) < var firstName = 'John'; // Доступно во всей функции let lastName = 'Doe'; // Доступно только в блоке if const fullName = firstName + ' ' + lastName; // Доступно только в блоке if >console.log(firstName); // John console.log(lastName); // ReferenceError console.log(fullName); // ReferenceError > bar();
Ключевые слова let и const позволяют нам использовать принцип наименьшего раскрытия (principle of least disclosure). Следование этому принципу означает, что переменная должна быть доступна в наименьшей из возможных областей видимости. До ES6 разработчики часто добивались эффекта блочной области видимости, пользуясь стилистическим приёмом объявления переменных с ключевым словом var в немедленно исполняемом функциональном выражении (Immediately Invoked Function Expression, IIFE), но теперь, благодаря let и const , можно применить функциональный подход. Некоторые из основных преимуществ этого принципа заключаются в избежании нежелательного доступа к переменным, и, таким образом, снижении вероятности ошибок. Кроме того, это позволяет сборщику мусора освобождать память от ненужных переменных при выходе из блочной области видимости.
Немедленно исполняемые функциональные выражения
IIFE — это весьма популярный шаблон проектирования JavaScript, который позволяет функции создать новую блочную область видимости. IIFE — это обычные функциональные выражения, которые мы исполняем сразу после того, как они будут обработаны интерпретатором. Вот пример IIFE:
'use strict' var foo = 'foo'; (function bar () < console.log('in function bar'); >)() console.log(foo);
Этот код выведет строку in function bar до вывода foo , так как функция bar исполняется немедленно, без необходимости явно вызывать её, используя конструкцию вида bar() . Это происходит по следующим причинам:
- Тут есть открывающая скобка перед ключевым словом function (и соответствующая ей закрывающая), что превращает эту конструкцию, из объявления функции, в функциональное выражение.
- Здесь имеются две скобки в конце, благодаря которым функциональное выражение исполняется немедленно.
IIFE, кроме того, очень полезны, если вы выполняете асинхронную операцию и хотите сохранить состояние переменных в области видимости IIFE. Вот пример подобного поведения:
'use strict' for (var i = 0; i < 5; i++) < setTimeout(function () < console.log('index: ' + i); >, 1000); >
Вполне можно ожидать, что этот код выведет 0, 1, 2, 3, 4. Однако, реальный результат выполнения данного цикла for , в котором вызывается асинхронная операция setTimeout , будет выглядеть так:
index: 5 index: 5 index: 5 index: 5 index: 5
Причина этого в том, что к тому времени, как истечёт 1000 миллисекунд, выполнение цикла for завершится и счётчик i окажется равным 5.
Для того, чтобы код работал так, как ожидается, выводил последовательность чисел от 0 до 4, нам нужно использовать IIFE для сохранения необходимой нам области видимости:
'use strict' for (var i = 0; i < 5; i++) < (function logIndex(index) < setTimeout(function () < console.log('index: ' + index); >, 1000); >)(i) >
В этом примере мы передаём значение i в IIFE. У функционального выражения будет собственная область видимости, на которую то, что происходит в цикле for , уже не подействует. Вот что выведет этот код:
index: 0 index: 1 index: 2 index: 3 index: 4
Итоги
Мы рассмотрели различные области видимости в JavaScript, поговорили об их особенностях, описали некоторые простые шаблоны проектирования. На самом деле, об областях видимости в JavaScript можно ещё говорить и говорить, однако я полагаю, что этот материал даёт хорошую базу, воспользовавшись которой, вы сможете самостоятельно углубить и расширить ваши знания.
Надеюсь, этот рассказ помог вам лучше понять области видимости в JavaScript, а значит, улучшить качество ваших программ. Также можем порекомендовать для прочтения эту публикацию на Хабре.
Уважаемые JS-разработчики! Просим вас поделиться интересными приёмами работы с областями видимости в JavaScript.
- JavaScript
- разработка
- области видимости
- Блог компании RUVDS.com
- JavaScript
- Node.JS
Области видимости Request, Session, Application, и WebSocket в Spring
Области видимости request , session , application и websocket доступны, только если вы используете реализацию ApplicationContext в фреймворке Spring с поддержкой веб (например, XmlWebApplicationContext ). Если вы используете эти области видимости с обычными IoC-контейнерами Spring, такими как ClassPathXmlApplicationContext , будет сгенерирован IllegalStateException c сообщением о неизвестной области видимости бина.
Начальная веб-конфигурация
Для поддержки создания области видимости бинов на уровне request , session , application и websocket (web-scoped бины) перед определением бина требуется провести небольшую начальную настройку. (Данная начальная настройка не требуется для стандартных областей видимости: singleton и prototype ).
То, каким образом вы выполните эту начальную настройку, зависит от вашей конкретной среды сервлетов.
Если вы обращаетесь к бинам, находящимся в области видимости, в Spring Web MVC, то есть в рамках запроса, который обрабатывается Spring DispatcherServlet , никакой специальной настройки не требуется. DispatcherServlet уже раскрывает все соответствующие состояния.
Если вы используете веб-контейнер Servlet 2.5, в котором запросы обрабатываются вне Spring DispatcherServlet (например, при использовании JSF или Struts), вам необходимо зарегистрировать org.springframework.web.context.request.RequestContextListener ServletRequestListener . Для Servlet 3.0+ это можно сделать программно, используя интерфейс WebApplicationInitializer . В качестве альтернативы или для более старых версий контейнеров добавьте следующее объявление в файл web web.xml вашего веб-приложения:
. org.springframework.web.context.request.RequestContextListener .
В качестве альтернативы, если есть проблемы с настройкой слушателя, рассмотрите возможность использования RequestContextFilter в Spring. Отображение фильтров зависит от окружающей конфигурации веб-приложения, поэтому вы должны изменить его соответствующим образом. В следующем листинге показана часть фильтра веб-приложения:
. requestContextFilter org.springframework.web.filter.RequestContextFilter requestContextFilter /* .
DispatcherServlet , RequestContextListener и RequestContextFilter делают одно и то же, а именно связывают объект HTTP-запроса с Thread , который обслуживает этот запрос. Это позволяет получить доступ к бинам, входящим в область видимости request или session, далее по цепочке вызовов.
Область видимости request
Рассмотрим следующую XML-конфигурацию для определения бина:
Контейнер Spring создает новый экземпляр бина LoginAction с помощью определения бина loginAction для каждого HTTP-запроса. То есть бин loginAction находится в области видимости на уровне HTTP-запроса. Можно сколько угодно изменять внутреннее состояние создаваемого экземпляра, поскольку другие экземпляры, созданные на основе того же определения бина loginAction , не видят этих изменений состояния. Они зависят от конкретного запроса. Если запрос завершает обработку, бин, находящийся в области видимости запроса, исключается.
При использовании компонентов, управляемых аннотациями, или конфигурации Java, аннотацию @RequestScope можно использовать для назначения компонента области видимости request . В следующем примере показано, как это сделать:
@RequestScope @Component public class LoginAction < // . >
@RequestScope @Component class LoginAction < // . >
Область видимости в пределах сеанса
Рассмотрим следующую XML-конфигурацию для определения бина:
Контейнер Spring создает новый экземпляр бина UserPreferences с помощью определения бина userPreferences на время жизни одной HTTP Session . Иными словами, бин userPreferences эффективно располагается в области видимости на уровне HTTP Session . Как и в случае с бинами, находящимися в области видимости request, вы можете изменять внутреннее состояние созданного экземпляра сколько угодно, зная, что другие экземпляры HTTP Session , которые также используют экземпляры, созданные на основе того же определения бина userPreferences , не заметят этих изменений состояния, поскольку они относятся к отдельной HTTP Session . Если HTTP Session в конечном итоге исключается, бин, который привязан к этой конкретной HTTP Session , также исключается.
При использовании компонентов, управляемых аннотациями, или конфигурации Java можно использовать аннотацию @SessionScope , чтобы назначить компонент области видимости session .
@SessionScope @Component public class UserPreferences < // . >
@SessionScope @Component class UserPreferences < // . >
Область видимости в пределах приложения
Рассмотрим следующую XML-конфигурацию для определения бина:
Контейнер Spring создает новый экземпляр бина AppPreferences , используя определение бина appPreferences единожды для всего веб-приложения. То есть бин appPreferences находится в области видимости на уровне ServletContext и хранится как обычный атрибут ServletContext . Он в некоторой степени похож на бин-одиночку Spring, но отличается от него двумя важными особенностями: Он является объектом-одиночкой для каждого ServletContext , а не для Spring ApplicationContext (которых может быть несколько в любом конкретном веб-приложении), и он фактически открывается, поэтому и виден как атрибут ServletContext .
При использовании компонентов, управляемых аннотациями, или конфигурации Java можно использовать аннотацию @ApplicationScope для назначения компонента области видимости application . В следующем примере показано, как это сделать:
@ApplicationScope @Component public class AppPreferences < // . >
@ApplicationScope @Component class AppPreferences < // . >
Область видимости в пределах WebSocket
Область видимости WebSocket связана с жизненным циклом сессии WebSocket и применяется к приложениям STOMP over WebSocket.
Руководство по Spring. Область видимости бинов.
Когда мы определяем bean в Spring Framework, у нас есть возможность объявить область видимости этого компонента.

Например, если мы хотим, чтобы Spring возвращал нам один и тот же бин при каждом запросе, мы должны выбрать область видимости singleton .
В Spring Framework имеются пять возможных значений свойства scope:
singleton
Определяет один единственный бин для каждого контейнера Spring IoC (используется по умолчанию).
prototype
Позволяет иметь любое количество экземпляров бина.
request
Создаётся один экземпляр бина на каждый HTTP запрос. Касается исключительно ApplicationContext.
session
Создаётся один экземпляр бина на каждую HTTP сессию. Касается исключительно ApplicationContext.
global-session
Создаётся один экземпляр бина на каждую глобальную HTTP сессию. Касается исключительно ApplicationContext.
На данный момент мы ещё не касались Spring ApplicationContext, поэтому сейчас обсудим только singleton и prototype.
Singleton
Если мы устанавливаем свойству scope значение singleton, то в это случае контейнер Spring IoC создаёт только один экземпляр объекта определённого в бине. Этот экземпляр помещается в кэш таких же бинов (синглтонов) и все последующие вызовы бина с таким именем будут возвращать объект из кэша.
По умолчанию область видимости устанавливается singleton, но если вы хотите акцентировать внимание на этом, то можно использовать такую запись:
Пример небольшой программы:
Архив с исходным кодом проекта можно скачать по ЭТОЙ ССЫЛКЕ.



Конфигурационный файл message-bean.xml

Результат работы программы

Prototype
Когда мы присваиваем свойству scope значение prototype, контейнер Spring IoC создаёт новый экземпляр бина на каждый полученный запрос.
Бин с областью видимости prototype можно создать следующим образом:В то время как
Пример простого приложения:
Исходный код проекта можно скачать по ЭТОЙ ССЫЛКЕ.



Конфигурационный файл message-bean.xml

Результат работы программы

Как мы могли увидеть, в случае применения singleton мы получили два одинаковых сообщения, так как использовался один и тот же экземпляр бина. В то время как при использовании prototype мы получили во втором сообщении null, так как был создан абсолютно новый объект, которому мы ничего не присваивали.
В следующем уроке мы с Вами рассмотрим жизненный цикл бинов, а на сегодня всё. До связи.
