ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 01.12.2023
Просмотров: 469
Скачиваний: 3
ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
4. Если используется @Qualifier, то контейнер будет использовать информацию из
@Qualifier, чтобы понять, какой компонент внедрять.
5. В противном случае контейнер внедрит бин, основываясь на его имени или ID.
6. Если ни один из способов не сработал, то будет выброшено исключение.
Контейнер обрабатывает DI с помощью AutowiredAnnotationBeanPostProcessor. В связи с этим аннотация не может быть использована ни в одном BeanFactoryPP или BeanPP.
В аннотации есть один параметр required = true/fals. Он указывает, обязательно ли делать DI.
По умолчанию true. Либо можно не выбрасывать исключение, а оставить поле c null, если нужный бин не был найден – false.
При циклической зависимости, когда объекты ссылаются друг на друга, нельзя ставить над конструктором.
Однако при внедрении прямо в поля не нужно предоставлять прямого способа создания экземпляра класса со всеми необходимыми зависимостями. Это означает, что:
•
существует способ (через вызов конструктора по умолчанию) создать объект с использованием new в состоянии, когда ему не хватает некоторых из его обязательных зависимостей, и использование приведет к NullPointerException;
•
такой класс не может быть использован вне DI-контейнеров (тесты, другие модули) и нет способа кроме рефлексии предоставить ему необходимые зависимости;
•
неизменность;
В отличие от способа с использованием конструктора внедрение через поля не может использоваться для присвоения зависимостей final-полям, что приводит к тому, что объекты становятся изменяемыми.
Расскажите про аннотацию @Resource
@Resource (аннотация java) пытается получить зависимость: по имени, по типу, затем по описанию (Qualifier). Имя извлекается из имени аннотируемого сеттера или поля либо берется из параметра name.
@Resource //По умолчанию поиск бина с именем "context"
private ApplicationContext context;
@Resource(name="greetingService") //Поиск бина с именем "greetingService"
public void setGreetingService(GreetingService service) {
this.greetingService = service;
}
Отличие от
@Autowired:
•
ищет бин сначала по имени, а потом по типу;
•
не нужна дополнительная аннотация для указания имени конкретного бина;
•
@Autowired позволяет отметить место вставки бина как необязательное
@Autowired(required = false);
•
при замене Spring Framework на другой фреймворк менять аннотацию @Resource не нужно.
Расскажите про аннотацию @Inject
@Inject входит в пакет javax.inject. Чтобы ее использовать, нужно добавить зависимость:
@Inject (аннотация java) – аналог @Autowired (аннотация spring) в первую очередь пытается подключить зависимость по типу, затем по описанию и только потом по имени. В ней нет параметров. Поэтому при использовании конкретного имени (Id) бина используется @Named:
@Inject
@Named("yetAnotherFieldInjectDependency")
private ArbitraryDependency yetAnotherFieldInjectDependency;
Расскажите про аннотацию @Lookup
Обычно бины в приложении Spring являтся синглтонами и для внедрения зависимостей используется конструктор или сеттер.
Но бывает и другая ситуация: имеется бин Car – синглтон (singleton bean) – и ему требуется каждый раз новый экземпляр бина Passenger. То есть Car – синглтон, а Passenger – так называемый прототипный бин (prototype bean). Жизненные циклы бинов разные. Бин Car создается контейнером только раз, а бин Passenger создается каждый раз новый. Допустим,
это происходит каждый раз при вызове какого-то метода бина Car. Вот здесь и пригодится внедрение бина с помощью метода Lookup. Оно происходит не при инициализации контейнера, а позднее: каждый раз, когда вызывается метод. Суть в том, что создается метод-заглушка в бине Car и он помечается специальным образом – аннотацией @Lookup.
Этот метод должен возвращать бин Passenger, каждый раз новый. Контейнер Spring под капотом создаст подкласс и переопределит этот метод и будет выдавать новый экземпляр бина Passenger при каждом вызове аннотированного метода. Даже если в заглушке он возвращает null (а так и надо делать, все равно этот метод будет переопределен).
Можно ли вставить бин в статическое поле? Почему?
Spring не позволяет внедрять бины напрямую в статические поля. Это связано с тем, что когда загрузчик классов загружает статические значения, контекст Spring еще не загружен.
Чтобы исправить это, можно создать нестатический сеттер-метод с @Autowired:
private static OrderItemService orderItemService;
@Autowired
public void setOrderItemService(OrderItemService orderItemService) {
TestDataInit.orderItemService = orderItemService;
}
Расскажите про аннотации @Primary и @Qualifier
@Qualifier применяется, если кандидатов для автоматического связывания несколько,
аннотация позволяет указать в качестве аргумента имя конкретного бина, который следует внедрить. Она может быть применена к отдельному полю класса, к отдельному аргументу метода или конструктора:
public class AutowiredClass {
@Autowired //к полям класса
@Qualifier("main")
private GreetingService greetingService;
@Autowired //к отдельному аргументу конструктора или метода
public void prepare(@Qualifier("main") GreetingService greetingService){
/* что-то делаем... */
};
}
Поэтому у одной из реализации
1 ... 17 18 19 20 21 22 23 24 25
GreetingService должна быть установлена соответствующая аннотация @Qualifier:
@Component
@Qualifier("main")
public class GreetingServiceImpl implements GreetingService {
//...
}
@Primary тоже используется, чтобы отдавать предпочтение бину, когда есть несколько бинов одного типа, но в ней нельзя задать имя бина, она определяет значение по умолчанию, в то время как @Qualifier более специфичен.
Если присутствуют аннотации @Qualifier и @Primary, то аннотация @Qualifier будет иметь приоритет.
Как заинжектить примитив?
Для этого можно использовать аннотацию @Value. Можно ставить над полем,
конструктором, методом.
Такие значения можно получать из property файлов, из бинов, и т. п.
@Value("${some.key}")
public String stringWithDefaultValue;
В эту переменную будет внедрена строка, например, из property или из view.
Кроме того, для внедрения значений можно использовать язык SpEL (Spring Expression
Language).
Как заинжектить коллекцию?
Если внедряемый объект массив, коллекция или map с дженериком, то, используя аннотацию @Autowired, Spring внедрит все бины, подходящие по типу в этот массив (или другую структуру данных). В случае с map ключом будет имя бина.
Используя аннотацию @Qualifier можно настроить тип искомого бина.
Бины могут быть упорядочены, если вставляются в списки (не Set или Map) или массивы.
Поддерживаются как аннотация @Order, так и интерфейс Ordered.
Расскажите про аннотацию @Conditional
Spring предоставляет возможность на основе используемого алгоритма включить или выключить определение бина или всей конфигурации через @Conditional, в качестве параметра которой указывается класс, реализующий интерфейс Condition, с единственным методом matches(ConditionContext var1, AnnotatedTypeMetadata var2), возвращающий boolean.
Для создания более сложных условий можно использовать классы AnyNestedCondition,
AllNestedConditions и NoneNestedConditions.
Аннотация @Conditional указывает, что компонент имеет право на регистрацию в
контексте только тогда, когда все условия соответствуют.
Условия проверяются непосредственно перед тем, как должен быть зарегистрирован
BeanDefinition компонента, и они могут помешать регистрации данного BeanDefinition.
Поэтому при проверке условий нельзя допускать взаимодействия с бинами, которых еще не существует, с их BeanDefinition-ами можно.
Для того, чтобы проверить несколько условий, можно передать в @Conditional несколько классов с условиями:
@Conditional(HibernateCondition.class, OurConditionClass.class)
Если класс @Configuration помечен как @Conditional, то на все методы @Bean, аннотации
@Import и аннотации @ComponentScan, связанные с этим классом, также будут распространяться указанные условия.
Расскажите про аннотацию @Profile
Профили – это ключевая особенность Spring Framework, позволяющая относить бины к разным профилям (логическим группам), например, dev, local, test, prod.
Можно активировать разные профили в разных средах, чтобы загрузить только те бины,
которые нужны.
Используя аннотацию @Profile относим бин к конкретному профилю. Ее можно применять на уровне класса или метода. Аннотация @Profile принимает в качестве аргумента имя одного или нескольких профилей. Она фактически реализована с помощью более гибкой аннотации
@Conditional.
Ее можно ставить на @Configuration и Component классы.
Расскажите про жизненный цикл бина, аннотации @PostConstruct и
@PreDestroy()
1. Парсирование конфигурации и создание BeanDefinition.
•
xml-конфигурация – ClassPathXmlApplicationContext(“context.xml”);
•
конфигурация через аннотации с указанием пакета для сканирования –
AnnotationConfigApplicationContext(“package.name”);
•
конфигурация через аннотации с указанием класса (или массива классов),
помеченного аннотацией
@Configuration
–
AnnotationConfigApplicationContext(JavaConfig.class), этот способ конфигурации называется JavaConfig;
•
groovy-конфигурация – GenericGroovyApplicationContext(“context.groovy”).
Если заглянуть внутрь AnnotationConfigApplicationContext, то можно увидеть два поля.
private final AnnotatedBeanDefinitionReader reader;
private final ClassPathBeanDefinitionScanner scanner;
ClassPathBeanDefinitionScanner сканирует указанный пакет на наличие классов, помеченных аннотацией @Component (или ее алиаса). Найденные классы парсируются и для них создаются BeanDefinition. Чтобы было запущено сканирование, в конфигурации должен быть указан пакет для сканирования
@ComponentScan({"package.name"}).
AnnotatedBeanDefinitionReader работает в несколько этапов.
Первый этап – это регистрация всех @Configuration для дальнейшего парсирования. Если в конфигурации используются Conditional, то будут зарегистрированы только те конфигурации,
для которых Condition вернет true.
Второй этап – это регистрация BeanDefinitionRegistryPostProcessor, который при помощи класса ConfigurationClassPostProcessor парсирует JavaConfig и создает BeanDefinition.
Цель первого этапа – это создание всех BeanDefinition. BeanDefinition – это специальный интерфейс, через который можно получить доступ к метаданным будущего бина. В
зависимости от конфигурации будет использоваться тот или иной механизм парсирования конфигурации.
BeanDefinition – это объект, который хранит в себе информацию о бине. Сюда входит: из какого класса бин надо создать, scope, установлена ли ленивая инициализация, нужно ли перед данным бином инициализировать другой, init и destroy методы, зависимости. Все полученные BeanDefinition’ы складываются в ConcurrentHashMap, в которой ключом является имя бина, а объектом – сам BeanDefinition. При старте приложения в IoC контейнер попадут бины, которые имеют scope Singleton (устанавливается по- молчанию), остальные создаются тогда, когда они нужны.
2. Настройка созданных BeanDefinition.
Есть возможность повлиять на бины до их создания, т. е. получить доступ к метаданным класса. Для этого существует специальный интерфейс BeanFactoryPostProcessor,
реализовав который получаем доступ к созданным BeanDefinition и можем их изменять. В
нем один метод.
Метод postProcessBeanFactory принимает параметром ConfigurableListableBeanFactory.
Данная фабрика содержит много полезных методов, в том числе getBeanDefinitionNames,
через который можно получить все BeanDefinitionNames, а уже потом по конкретному имени получить BeanDefinition для дальнейшей обработки метаданных.
Разберем одну из родных реализаций интерфейса BeanFactoryPostProcessor. Обычно настройки подключения к базе данных выносятся в отдельный property-файл, потом при помощи PropertySourcesPlaceholderConfigurer они загружаются и делается inject этих значений в нужное поле. Так как inject делается по ключу, то до создания экземпляра бина нужно заменить этот ключ на само значение из property-файла. Эта замена происходит в классе, который реализует интерфейс BeanFactoryPostProcessor. Название этого класса –
PropertySourcesPlaceholderConfigurer. Он должен быть объявлен как static:
@Bean
public static PropertySourcesPlaceholderConfigurer configurer() {
return new PropertySourcesPlaceholderConfigurer();
}
3. Создание кастомных FactoryBean.
FactoryBean – это generic-интерфейс, которому можно делегировать процесс создания бинов определенного типа. Когда конфигурация была исключительно в xml, разработчикам был необходим механизм, с помощью которого они бы могли управлять процессом создания бинов. Именно для этого и был сделан этот интерфейс.
Создадим фабрику, которая будет отвечать за создание всех бинов типа Color:
public class ColorFactory implements FactoryBean
@Override
public Color getObject() throws Exception {
Random random = new Random();
Color color = new Color(random.nextInt(255), random.nextInt(255), random.nextInt(255));
return color;
}
@Override
public Class> getObjectType() {
return Color.class;
}
@Override
public boolean isSingleton() {
return false;
}
}
Теперь создание бина типа Color.class будет делегироваться ColorFactory, у которого при каждом создании нового бина будет вызываться метод getObject.
Для тех, кто пользуется JavaConfig, этот интерфейс будет абсолютно бесполезен.
4. Создание экземпляров бинов.
Сначала BeanFactory из коллекции Map с объектами BeanDefinition достает те, из которых создает все BeanPostProcessor-ы (инфраструктурные бины), необходимые для настройки обычных бинов.
Создаются экземпляры бинов через BeanFactory на основе ранее созданных BeanDefinition.
Созданием экземпляров бинов занимается BeanFactory на основе ранее созданных
BeanDefinition. Из Map
Создание бинов может делегироваться кастомным FactoryBean.
5. Настройка созданных бинов.
На данном этапе бины уже созданы, их можно лишь донастроить.
Интерфейс BeanPostProcessor позволяет вклиниться в процесс настройки бинов до того, как они попадут в контейнер. ApplicationContext автоматически обнаруживает любые бины с реализацией BeanPostProcessor и помечает их как «post-processors» для того, чтобы создать их определенным способом. Например, в Spring есть реализации BeanPostProcessor-ов,
которые обрабатывают аннотации @Autowired, @Inject, @Value и @Resource.
Интерфейс несет в себе два метода: postProcessBeforeInitialization(Object bean, String beanName) и postProcessAfterInitialization(Object bean, String beanName). У обоих методов параметры абсолютно одинаковые. Разница только в порядке их вызова. Первый вызывается до init-метода, второй – после.
Как правило, BeanPostProcessor-ы, которые заполняют бины через маркерные интерфейсы или тому подобное, реализовывают метод postProcessBeforeInitialization(Object bean, String beanName), тогда как BeanPostProcessor-ы, которые оборачивают бины в прокси, обычно реализуют postProcessAfterInitialization(Object bean, String beanName).
Прокси – это класс-декорация над бином. Например, можно добавить логику бину, но джава- код уже скомпилирован, поэтому нужно на лету сгенерировать новый класс. Этим классом необходимо заменить оригинальный класс так, чтобы никто не заметил подмены.
Есть два варианта создания этого класса:
•
либо он должен наследоваться от оригинального класса (CGLIB) и переопределять его методы, добавляя нужную логику;
•
либо он должен имплементировать те же самые интерфейсы, что и первый класс
(Dynamic Proxy).
По конвенции спринга, если какой-то из BeanPostProcessor-ов меняет что-то в классе, то он должен это делать на этапе postProcessAfterInitialization(). Таким образом есть уверенность,