IoC, DI, IoC-контейнер — Просто о простом
Думаю сейчас слова IoC, DI, IoC-контейнер, как минимум у многих на слуху. Одни этим активно пользуются, другие пытаются понять, что же это за модные веяния.
На данный момент, на эту тему уже довольно сказано, написано, в том числе и на хабре, но как раз из-за обилия информации сложно найти действительно полезный контент. Кроме того, данные понятия часто смешивают и/или путают. Проанализировав множества материалов я решил изложить вам свое видение предмета.
Теория
Для меня взаимосвязь между IoC и DI такая же как между Agile и Scrum, т.е.
Inversion of Control (инверсия управления) — это некий абстрактный принцип, набор рекомендаций для написания слабо связанного кода. Суть которого в том, что каждый компонент системы должен быть как можно более изолированным от других, не полагаясь в своей работе на детали конкретной реализации других компонентов.
Dependency Injection (внедрение зависимостей) — это одна из реализаций этого принципа (помимо этого есть еще Factory Method, Service Locator).
IoC-контейнер — это какая-то библиотека, фреймворк, программа если хотите, которая позволит вам упростить и автоматизировать написание кода с использованием данного подхода на столько, на сколько это возможно. Их довольно много, пользуйтесь тем, чем вам будет удобно, я продемонстрирую все на примере Ninject.
Практика
Согласно подходу инверсии управления если у нас есть клиент, который использует некий сервис, то он должен делать это не напрямую, а через посредника, своего рода аутсорсинг.
То как технически это будет сделано и определяет каждая из реализаций подхода IoC.
Мы будем использовать DI, на простом примере:
Скажем есть некий класс, который создает расписание, а другой класс его отображает (их как правило много, скажем один для десктоп-приложения, другой для веба и т.д.).
Если бы мы ничего не знали о IoC, DI мы бы написали что-то вроде этого:
class ScheduleManager < public Schedule GetSchedule() < // Do Something by init schedule. >> class ScheduleViewer < private ScheduleManager _scheduleManager = new ScheduleManager(); public void RenderSchedule() < _scheduleManager.GetSchedule(); // Do Something by render schedule. >>
Вроде бы все хорошо, код решает поставленную задачу, но что если мы захотим в последствии изменить реализацию менеджера расписаний и/или же иметь несколько таких менеджеров и динамически их заменять. Тогда в последствии нам придется менять и что-то в ScheduleViewer, а значит и снова его тестировать.
К счастью, разработчики, ленивые люди в хорошем смысле этого слова, и не любят делать одно и тоже дважды.
Мы, например, воспользуемся внедрением зависимостей (DI) для того, чтобы разорвать этот клубок стальных ниток — сделаем связь между этими классами более слабой, добавив прослойку в виде интерфейса IScheduleManager. И будем разрешать ее одним из способов техники DI, а именно Constructor Injection (помимо этого есть Setter Injection и Method Injection — если в двух словах, то везде используется интерфейс вместо конкретного класса, например в типе свойства или в типе аргумента метода):
interface IScheduleManager < Schedule GetSchedule(); >class ScheduleManager : IScheduleManager < public Schedule GetSchedule() < // Do Something by init schedule. >> class ScheduleViewer < private IScheduleManager _scheduleManager; public ScheduleViewer(IScheduleManager scheduleManager) < _scheduleManager = scheduleManager; >public void RenderSchedule() < _scheduleManager.GetSchedule(); // Do Something by render schedule. >>
И далее там где мы хотим воспользоваться нашим классом для отображения расписания мы пишем:
ScheduleViewer scheduleViewer = new ScheduleViewer(new ScheduleManager());
Вот уже почти идеально, но что если у нас много различных ScheduleViewer, разбросанных по проекту, которые использует всегда именно ScheduleManager (придется его руками каждый раз создавать) и/или мы хотим как-либо настроить поведение, так что бы в одной ситуации везде использовать ScheduleManager, а в другой скажем AnotherScheduleManager и т.д.
Решить эту проблему как раз и призваны IoC-контейнеры.
IoC-контейнеры
Они помогают уменьшить количество рутины, позволяя задать соответствие между интерфейсом и его конкретной реализацией, чтобы потом везде этим пользоваться.
Как я уже говорил выше, мы будем рассматривать это на примере Ninject —
1. Сначала мы создаем конфигурацию контейнера:
class SimpleConfigModule : NinjectModule < public override void Load() < Bind().To(); // нижняя строка необязательна, это поведение стоит по умолчанию: // т.е. класс подставляет сам себя Bind().ToSelf(); > >
Теперь везде где требуется IScheduleManager будет подставляться ScheduleManager.
2. Создаем сам контейнер, указывая его конфигуратор:
IKernel ninjectKernel = new StandardKernel(new SimpleConfigModule()); // там где нужно создать экземпляр ScheduleViewer мы вместо new, делаем так: ScheduleViewer scheduleViewer= ninjectKernel.Get();
Контейнер сам создаст экземпляр класса ScheduleManager, вызовет конструктор ScheduleViewer и подставит в него свежесозданный экземпляр ScheduleManager.
Заключение
И напоследок хочется сказать, что IoC, DI — очень хороший инструмент, но и как любой другой механизм использовать его нужно осознанно и к месту. Скажем, одно дело какое-нибудь небольшое консольное приложение, в котором вряд ли что-то будет меняться, или серьезный крупный проект, где пожелания заказчика часто изменчивы и противоречивы.
Вот и все, буду очень рад услышать ваши комментарии, конструктивные замечания.
Удачного всем деплоя на продакшене.
Урок 2: Введение в Spring IoC контейнер
Этот урок освещает работу с Spring Framework IoC контейнером и основан на оригинальной документации §5. The IoC container.
Что вы создадите
Вы создадите некоторое количество классов, в которых будет рассмотрена функциональность Spring Framework IoC контейнера.
Что вам потребуется
- Любимый текстовый редактор или IDE
- JDK 7 и выше
- Maven 3.0+
- Исходный код предыдущего урока
Настройка проекта
Прежде чем вы начнете изучать этот урок, вам необходимо внести некоторые изменения в проект. Для начала создайте структуру папок src/main/resources и переместите в него файл настроек логгирования log4j.properties , тем самым поместив его в classpath проекта. Теперь немного измените файл сборки pom.xml , добавив и изменив в нем следующее:
. 1.7 1.5.8 . org.slf4j jcl-over-slf4j $ org.slf4j slf4j-api $ org.slf4j slf4j-log4j12 $ . org.apache.maven.plugins maven-compiler-plugin 3.2 $ $ $ -Xlint:all true true .
И наконец, создайте структуру папок src/main/java/lessons/starter/ . В данном пакете вы будете создавать классы с методами public static void main(String[] args) , которые вы будете запускать для того, чтобы можно было видеть результаты действий в процессе изучения данного материала.
Введение
Inversion of Control (IoC), также известное как Dependency Injection (DI), является процессом, согласно которому объекты определяют свои зависимости, т.е. объекты, с которыми они работают, через аргументы конструктора/фабричного метода или свойства, которые были установлены или возвращены фабричным методом. Затем контейнер inject(далее «внедряет») эти зависимости при создании бина. Этот процесс принципиально противоположен, поэтому и назван Inversion of Control, т.к. бин сам контролирует реализацию и расположение своих зависимостей, используя прямое создание классов или такой механизм, как шаблон Service Locator.
Основными пакетами Spring Framework IoC контейнера являются org.springframework.beans и org.springframework.context . Интерфейс BeanFactory предоставляет механизм конфигурации по управлению любым типом объектов. ApplicationContext — наследует нитерфейс BeanFactory и добавляет более специфичную функциональность. Ниже в таблице представлены различия между ними:
Функционал
BeanFactory
ApplicationContext
Инициализация/автоматическое связывание бина
Автоматическая регистрация BeanPostProcessor
Автоматическая регистрация BeanFactoryPostProcessor
Удобный доступ к MessageSource (для i18n)
ApplicationEvent публикация
В большинстве случаев предпочтительно использовать ApplicationContext , поэтому в дальнейшем будет использоваться только он и его реализации. Поскольку он включает в себя всю функциональность BeanFactory , его можно и нужно использовать, за исключением случаев, когда приложение запускается на устройствах с ограниченными ресурсами, в которых объем потребляемой памяти может быть критичным, даже в пределах нескольких килобайт, либо когда вы разрабатываете приложение, в котором необходима поддержка совместимости со сторонними библиотеками, использующими JDK 1.4 или не поддерживают JSR-250. Spring Framework активно использует BeanPostProcessor для проксирования и др., поэтому, если вам необходима поддержка такой функциональности, как AOP и транзакций, то при использовании BeanFactory необходимо добавить вручную регистрацию BeanPostProcessor и BeanFactoryPostProcessor , как показано ниже:
ConfigurableBeanFactory factory = new XmlBeanFactory(. ); // теперь зарегистрируем необходимый BeanPostProcessor экземпляр MyBeanPostProcessor postProcessor = new MyBeanPostProcessor(); factory.addBeanPostProcessor(postProcessor); // запускаем, используя factory
XmlBeanFactory factory = new XmlBeanFactory(new FileSystemResource("beans.xml")); // получаем какое-то значения свойства из Properties-файла PropertyPlaceholderConfigurer cfg = new PropertyPlaceholderConfigurer(); cfg.setLocation(new FileSystemResource("jdbc.properties")); // теперь заменяем значение свойства на новое cfg.postProcessBeanFactory(factory);
Аннотации @Autowired , @Inject , @Resource и @Value обрабатываются Spring реализацией BeanPostProcessor , поэтому вы не можете их применять в своих собственных BeanPostProcessor и BeanFactoryPostProcessor , а только лишь явной инициализацией через XML или @Bean метод.
Описание работы IoC контейнера
Ниже представлена диаграмма, отражающая, как работает Spring. Ваши классы приложения совмещаются с метаданными конфигурации, в результате чего будет создан и инициализирован ApplicationContext , а на выходе вы получите полностью настроенное и готовое к выполнению приложение.
ApplicationContext представляет собой Spring IoC контейнер и необходим для инициализации, настройки и сборки бинов для построения приложения.
В метаданных конфигурации разработчик описывает как инициализировать, настроить IoC контейнер и собрать объекты в вашем приложении. В данном и других уроках этого цикла везде, где возможно, будет использоваться подход на основе аннотаций и Java-конфигурации. Если вы сторонник XML-конфигурации, либо хотите посмотреть как делать тоже самое через XML, обратитесь к оригинальной документации по Spring Framework или соответствующего модуля/проекта.
Настройка IoC контейнера
Основными признаками и частями Java-конфигурации IoC контейнера являются классы с аннотацией @Configuration и методы с аннотацией @Bean . Аннотация @Bean используется для указания того, что метод создает, настраивает и инициализирует новый объект, управляемый Spring IoC контейнером. Такие методы можно использовать как в классах с аннотацией @Configuration , так и в классах с аннотацией @Component (или её наследниках). Класс с аннотацией @Configuration говорит о том, что он является источником определения бинов. Самая простейшая из возможных конфигураций выглядит следующим образом:
package lessons; import org.springframework.context.annotation.Configuration; /** * Конфигурационный класс Spring IoC контейнера */ @Configuration public class LessonsConfiguration
Полный @Configuration vs легкий @Bean режимы
Когда методы с аннотацией @Bean определены в классах, не имеющих аннотацию @Configuration , то относятся к обработке в легком режиме, то же относится и к классам с аннотацией @Component . Иначе, такие методы относятся к полному режиму обработки.
В отличие от полного, в легком режиме @Bean методы не могут просто так объявлять внутренние зависимости. Поэтому, в основном предпочтительно работать в полном режиме, во избежание трудноуловимых ошибок.
Для того, чтобы приступить к настройке и изучению Spring IoC контейнера, вы должны инициализировать ApplicationContext , который поможет также с разрешением зависимостей. Для обычной Java-конфигурации применяется AnnotationConfigApplicationContext , в качестве аргумента к которому передается класс, либо список классов с аннотацией @Configuration , либо с любой другой аннотацией JSR-330, в том числе и @Component :
public class Starter < private static final Logger logger = LogManager.getLogger(Starter.class); public static void main(String[] args) < logger.info("Starting configuration. "); ApplicationContext context = new AnnotationConfigApplicationContext(LessonsConfiguration.class); >>
Как вариант, можно инициализировать контекст(ы) таким образом:
public class Starter < private static final Logger logger = LogManager.getLogger(Starter.class); public static void main(String[] args) < logger.info("Starting configuration. "); AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); context.register(LessonsConfiguration.class); context.refresh(); >>
Использование @Bean аннотации
Как упоминалось выше, для того, чтобы объявить Bean-объект(далее просто бин), достаточно указать аннотацию @Bean тому методу, который возвращает тип бина как в классах с аннотацией @Configuration , так и в классах с аннотацией @Component (или её наследниках). Например, определим интерфейс какого-нибудь сервиса и его реализацию:
package lessons.services; public interface GreetingService
package lessons.services; public class GreetingServiceImpl implements GreetingService < @Override public String sayGreeting() < return "Greeting, user!"; >>
Теперь, для того, чтобы объект с типом GreetingService был доступен для использования, необходимо описать его в конфигурации следующим образом:
@Configuration public class LessonsConfiguration < @Bean GreetingService greetingService() < return new GreetingServiceImpl(); >>
А для того, чтобы использовать его, достаточно выполнить следующее:
public class Starter < private static final Logger logger = LogManager.getLogger(Starter.class); public static void main(String[] args) < logger.info("Starting configuration. "); ApplicationContext context = new AnnotationConfigApplicationContext(LessonsConfiguration.class); GreetingService greetingService = context.getBean(GreetingService.class); logger.info(greetingService.sayGreeting()); // "Greeting, user!" >>
Метод getBean() может принимать в качестве аргумента как класс(как показано выше), так и названия бина(подробнее будет рассмотрено ниже), либо другие варианты, с которыми вы можете ознакомится в документации. Однако такой подход не рекомендуется использовать в production-конфигурациях, т.к. для подобных целей существует механизм Dependency Injection (DI), собственно говоря, для чего и предназначен Spring IoC контейнер. Использование DI будет рассмотрено ниже в отдельной главе.
Именовать бины принято в соответствии со стандартным соглашением по именованию полей Java-классов. Т.е. имена бинов должны начинаться со строчной буквы и быть в «Верблюжьей» нотации.
По умолчанию, так, как будет назван метод определения бина, по такому имени и нужно получать бин через метод getBean() или автоматическое связывание. Однако вы можете переопределить это имя или указать несколько псевдонимов, через параметр name аннотации @Bean . Выглядеть это будет примерно так:
@Bean(name = "gServiceName")
@Bean(name = )
Иногда полезно предоставить более подробное описание бина, например, в целях мониторинга. Для этого существует аннотация @Description :
@Bean @Description("Текстовое описание бина greetingService") GreetingService greetingService()
Жизненный цикл бина
Для управления контейнером жизненным циклом бина, вы можете реализовать метод afterPropertiesSet() интерфейса InitializingBean и метод destroy() интерфейса DisposableBean . Метод afterPropertiesSet() позволяет выполнять какие-либо действий после инициализации всех свойств бина контейнером, метод destroy() выполняется при уничтожении бина контейнером. Однако их не рекомендуется использовать, поскольку они дублируют код Spring. Как вариант, предпочтительно использовать методы с JSR-250 аннотациями @PostConstruct и @PreDestroy . Также существует вариант определить аналогичные методы как параметры аннотации @Bean , например так: @Bean(initMethod = «initMethod», destroyMethod = «destroyMethod») .В качестве примера применения данных методов, интерфейсов и аннотаций вы можете ознакомиться в классе GreetingServiceImpl .
При совместном использовании методов, интерфейсов и аннотаций, описанных выше, учитывайте их порядок вызовов. Для методов инициализации порядок будет следующий:
- Методы с аннотациями @PostConstruct в порядке их определения в классе
- Метод afterPropertiesSet()
- Метод, указанный в параметре initMethod аннотации @Bean
Для методов разрушения порядок будет следующий:
- Методы с аннотациями @PreDestroy в порядке их определения в классе
- Метод destroy()
- Метод, указанный в параметре destroyMethod аннотации @Bean
Если вам необходимо реализовать свою собственную модель жизненного цикла бина, то в таком случае бин должен реализовывать один из интерфейсов, приведенных ниже:
public interface Lifecycle
public interface LifecycleProcessor extends Lifecycle
public interface SmartLifecycle extends Lifecycle, Phased
SmartLifecycle интересен тем, что наследует интерфейс Phased , в котором есть метод int getPhase(); . Суть в том, что порядок создания бинов, реализующих этот интерфейс, зависит от возвращаемого методом значения и чем оно меньше, тем раньше всех будет создан бин и тем позже он будет разрушен.
Если вы на данном этапе запустите Starter.java , то в логах увидите, что методы разрушения не вызываются, однако программа завершает свою работу корректно. Дело в том, что для обычных приложений для этих целей стоит инициализировать контекст с типом AbstractApplicationContext , который также реализует ApplicationContext и имеет метод registerShutdownHook() . В итоге, у вас должно быть премерно следующее:
public class Starter < private static final Logger logger = LogManager.getLogger(Starter.class); public static void main(String[] args) < logger.info("Starting configuration. "); AbstractApplicationContext context = new AnnotationConfigApplicationContext(LessonsConfiguration.class); GreetingService greetingService = context.getBean(GreetingService.class); logger.info(greetingService.sayGreeting()); // "Greeting, user!" context.registerShutdownHook(); >>
После этого у вас появятся результаты работы методов при разрушении бина. Однако стоит заметить ещё раз, что это относится к обычным приложения, не относящимся к web-приложения(поскольку для них применяется отдельный тип контекста и подобный метод в них уже есть).
В некоторых случаях необходимо производить манипуляции с ApplicationContext ‘ом, например, в самом бине. Для этого существуют интерфейсы *Aware , полный список которых приведен в таблице 5.4 документации. Поэтому когда ApplicationContext создает экземпляр бина, он учитывает соответствующий интерфейс и передает ссылку на соответствующий ресурс.
Как было описано выше, Spring IoC контейнеру требуются метаданные для конфигурации. Одну из таких аннотаций мы уже рассмотрели, это @Bean , рассмотрим теперь и другие.
Другой основной аннотацией является @Component , а также её наследники @Repository , @Service и @Controller . Все они являются общими шаблонами для любых компонентов, управляемыми контейнеером. @Repository , @Service и @Controller рекомендуется использовать в тех случаях, когда вы можете отнести аннотируемый класс к определенному слою, например DAO, либо когда вам необходима поддержка функциональности, которую предоставляет аннотация. Также эти аннотации могут иметь дополнительный смысл в будущих версиях Spring Framework. В остальных же случаях достаточно использовать аннотацию @Component .
Для того, чтобы ваша конфигурация могла знать о таких компонентах и вы могли бы их использовать, существует специальная аннотация для класса вашей конфигурации @ComponentScan .
@Configuration @ComponentScan public class LessonsConfiguration < @Bean GreetingService greetingService() < return new GreetingServiceImpl(); >>
По умолчанию, такая конфигурация сканирует на наличие классов с аннотацией @Component и его потомков в том пакете, в котором сама находится, а также в подпакетах. Однако, если вы хотите, чтобы сканирование было по определенным каталогам, то это можно настроить, просто добавив в аннотацию @ComponentScan параметр basePackages с указанием одного или нескольких пакетов. Выглядеть это будет примерно таким образом: @ComponentScan(basePackages = «lessons.services») , а классу GreetingServiceImpl при этом необходимо добавить аннотацию @Component .
Стоит упомянуть ещё одну мета-аннотацию @Required . Данная аннотация применяется к setter-методу бина и указывает на то, чтобы соответствующее свойство метода было установлено на момент конфигурирования значением из определения бина или автоматического связывания. Если же значение не будет установлено, будет выброшено исключение. Использование аннотации позволит избежать NullPointerException в процессе использования свойства бина. Пример использования:
package lessons.services; public class GreetingServiceImpl implements GreetingService < private ApplicationContext context; @Required public void setContext(ApplicationContext context) < this.context = context; >>
Области видимости(scopes) бинов
Когда вы создаете определение бинов, вы вы создаете рецепт для создания экземпляров класса, который определяет бин. Важно понять, что определение бинов является рецептом, потому что он означает, какого класса вы можете создать множество экземпляров по этому рецепту.
Вы можете контролировать не только какие зависимости и значения конфигурации вы можете подключить в объекте, который создан из определения бина, но также область видимости из того же определения бина. Это мощный и гибкий подход, при котором вы можете выбрать область видимости создаваемых объектов. Изначально, Spring Framework поддерживает несколько вариантов, некоторые доступны, только если вы используете web-aware ApplicationContext . Также вы можете создать свою собственную облать видимости. Ниже приведен список областей видимостей, описанных в документации на момент написания урока:
- singleton — По умолчанию. Spring IoC контейнер создает единственный экземпляр бина. Как правило, используется для бинов без сохранения состояния(stateless)
- prototype — Spring IoC контейнер создает любое количество экземпляров бина. Новый экземпляр бина создается каждый раз, когда бин необходим в качестве зависимости, либо через вызов getBean() . Как правило, используется для бинов с сохранением состояния(stateful)
- request — Жизненный цикл экземпляра ограничен единственным HTTP запросом; для каждого нового HTTP запроса создается новый экземпляр бина. Действует, только если вы используете web-aware ApplicationContext
- session — Жизненный цикл экземпляра ограничен в пределах одной и той же HTTP Session . Действует, только если вы используете web-aware ApplicationContext
- global session — Жизненный цикл экземпляра ограничен в пределах глобальной HTTP Session (обычно при использовании portlet контекста). Действует, только если вы используете web-aware ApplicationContext
- application — Жизненный цикл экземпляра ограничен в пределах ServletContext . Действует, только если вы используете web-aware ApplicationContext
С более подробной информацией о настройке приложения для применения областей видимости request , session , global session и application вы можете ознакомиться в документации. Пример реализации собственной области видимости будет рассмотрено в отдельном уроке.
Для того, чтобы указать область видимости бина, отличный от singleton , необходимо добавить аннотацию @Scope(«область_видимости») методу объявления бина или классу с аннотацией @Component :
@Component @Scope("prototype") public class GreetingServiceImpl implements GreetingService < //. >
Использование @Configuration аннотации
Как упоминалось выше, классы с аннотацией @Configuration указывают на то, что они являются источниками определения бинов, public-методов с аннотацией @Bean .
Кода бин имеет зависимость от другого бина, то зависимость выражается просто как вызов метода:
@Configuration @ComponentScan public class LessonsConfiguration < @Bean BeanWithDependency beanWithDependency() < return new BeanWithDependency(greetingService()); >@Bean GreetingService greetingService() < return new GreetingServiceImpl(); >>
Однако работает такое взаимодействие только в @Configuration -классах, в @Component -классах такое не работает.
Представим теперь ситуацию, когда у вас есть бин с областью видимости singleton , который имеет зависимость от бина с областью видимости prototype .
public abstract class CommandManager
@Configuration @ComponentScan public class LessonsConfiguration < @Bean @Scope("prototype") public Object asyncCommand() < return new Object(); >@Bean public CommandManager commandManager() < // возвращаем новую анонимную реализацию CommandManager // с новым объектом return new CommandManager() < protected Object createCommand() < return asyncCommand(); >>; > >
Большая часть приложений строится по модульной архитектуре, разделенная по слоям, например DAO, сервисы, контроллеры и др. Создавая конфигурацию, можно также её разбивать на составные части, что также улучшит читабельность и панимание архитектуры вашего приложения. Для этого в конфигурацию необходимо добавить аннотацию @Import , в параметрах которой указываются другие классы с аннотацией @Configuration , например:
@Configuration public class AnotherConfiguration < @Bean BeanWithDependency beanWithDependency() < return new BeanWithDependency(); >>
@Configuration @ComponentScan @Import(AnotherConfiguration.class) public class LessonsConfiguration < @Bean GreetingService greetingService() < return new GreetingServiceImpl(); >>
Таким образом, при инициализации контекста вам не нужно дополнительно указывать загрузку из конфигурации AnotherConfiguration , все останется так, как и было:
public class Starter < private static final Logger logger = LogManager.getLogger(Starter.class); public static void main(String[] args) < logger.info("Starting configuration. "); ApplicationContext context = new AnnotationConfigApplicationContext(LessonsConfiguration.class); GreetingService greetingService = context.getBean(GreetingService.class); BeanWithDependency withDependency = context.getBean(BeanWithDependency.class); logger.info(greetingService.sayGreeting()); // "Greeting, user!" logger.info(withDependency.printText()); // "Some text!" >>
В большинстве случаев, имеются такие случаи, когда бин в одной конфигурации имеет зависимость от бина в другой конфигурации. Поскольку конфигурация является источником определения бинов, то разрешить такую зависимость не является проблемой, достаточно объявить поле класса конфигурации с аннотацией @Autowired (более подробно оисано в отдельной главе):
@Configuration public class AnotherConfiguration < @Autowired GreetingService greetingService; @Bean BeanWithDependency beanWithDependency() < //что-нибудь делаем с greetingService. return new BeanWithDependency(); >>
При этом LessonsConfiguration остается без изменений:
@Configuration @ComponentScan @Import(AnotherConfiguration.class) public class LessonsConfiguration < @Bean GreetingService greetingService() < return new GreetingServiceImpl(); >>
Классы с аннотацией @Configuration не стремятся на 100% заменить конфигурации на XML, при этом, если вам удобно или имеется какая-то необходимость в использовании XML конфигурации, то к вашей Java-конфигурации необходимо добавить аннотацию @ImportResource , в параметрах которой необходимо указать нужное вам количество XML-конфигураций. Выглядит это следующим способом:
@Configuration @ImportResource("classpath:/lessons/xml-config.xml") public class LessonsConfiguration < @Value("$") String url; //. >
jdbc.url=jdbc:hsqldb:hsql://localhost/xdb
Процесс разрешения зависимостей
IoC контейнер выполняет разрешение зависимостей бинов в следующем порядке:
- Создается и инициализируется ApplicationContext с метаданными конфигурации, которые описывают все бины. Эти метаданные могут быть описаны через XML, Java-код или аннотации
- Для каждого бина и его зависимостей вычисляются свойства, аргументы конструктора или аргументы статического фабричного метода, либо обычного(без аргументов) конструктора. Эти зависимости предоставляются бину, когда он(бин) уже создан. Сами зависимости инициализируются рекурсивно, в зависимости от вложенности в себе других бинов. Например, при инициализации бина А, котый имеет зависимость В, а В зависит от С, сначала инициализируется бин С, потом В, а уже потом А
- Каждому свойству или аргументу конструктора устанавливается значение или ссылка на другой бин в контейнере
- Для каждого свойства или аргумента конструктора подставляемое значение конвертируется в тот формат, который указан для свойства или аргумента. По умолчанию Spring может конвертировать значения из строкового формата во все встроенные типы, такие как int , long , String , boolean и др.
Spring каждый раз при создании контейнера проверяет конфигурацию каждого бина. И только бины с областью видимости(scope) singleton создаются сразу вместе со своими зависимостями, в отличие от остальных, которые создаются по запросу и в соответствии со своей областью видимости. В случае цикличной зависимости(когда класс А требует экземпляр В, а классу В требуется экземпляр А) Spring IoC контейнер обнаруживает её и выбрасывает исключение BeanCurrentlyInCreationException .
Spring контейнер может разрешать зависимости между бинами через autowiring(далее, автоматическое связывание). Данный механизм основан на просмотре содержимого в ApplicationContext и имеет следующие преимущества:
- Автоматическое связывание позволяет значительно сократить количество инструкций для указания свойств или аргументов конструктора
- Автоматическое связывание позволяет обновлять конфигурацию, несмотря на развитие ваших объектов. К примеру, вам необходимо добавить зависимость в классе и эта зависимость может быть разрешена без необходимости модификации конфигурации. Поэтому автоматическое связывание может быть особенно полезным при разработке, не исключая возможность переключения на явное описание, когда кодовая база будет стабильна
Для того, чтобы воспользоваться механизмом автоматического связывания, Spring Framework предоставляет аннотацию @Autowired . Примеры применения приведены ниже:
public class AutowiredClass < @Autowired //к полям класса @Qualifier("main") //@Autowired(required = false) //чтобы не бросалось исключение, //если не с кем связать //рекомендуется использовать @Required private GreetingService greetingService; @Autowired //к полям класса в виде массива или коллекции private GreetingService[] services; @Autowired //к Map, где ключами являются имена бинов, значения - сами бины private MapserviceMap; @Autowired //к конструктору public AutowiredClass(@Qualifier("main") GreetingService service) <> @Autowired //к обычным методам с произвольным названием аргументов и их количеством public void prepare(GreetingService prepareContext)* что-то делаем. */> @Autowired //к "традиционному" setter-методу public void setContext(GreetingService service) < this.greetingService = service; >>
Т.к. кандидатов для автоматического связывания может быть несколько, то для установки конкретного экземпляра необходимо использовать аннотацию @Qualifier , как показано ниже. Данная аннотация может быть применена как к отдельному полю класса, так и к отдельному аргументу метода или конструктора:
public class AutowiredClass < //. @Autowired //к полям класса @Qualifier("main") private GreetingService greetingService; @Autowired //к отдельному аргументу конструктора или метода public void prepare(@Qualifier("main") GreetingService greetingService)< /* что-то делаем. */ >; //. >
Соответственно, у одной из реализации GreetingService должна быть установлена соответствующая аннотация @Qualifier :
@Component @Qualifier("main") public class GreetingServiceImpl implements GreetingService < //. >
Spring также поддерживает использование JSR-250 @Resource аннотации автоматического связывания для полей класса или параметров setter-методов:
public class AutowiredClass < //. @Resource //По умолчанию поиск бина с именем "context" private ApplicationContext context; @Resource(name="greetingService") //Поиск бина с именем "greetingService" public void setGreetingService(GreetingService service) < this.greetingService = service; >//. >
Использование стандартных JSR-330 аннотаций
Spring Framework поддерживает JSR-330 аннотации. Эти аннотации работают таким же способом, как и Spring аннотации. Для того, чтобы работать с ними, необходимо добавить в pom.xml следующую зависимость:
javax.inject javax.inject 1
Ниже приведена таблица сравнения JSR-330 и Spring аннотаций для DI:
Что такое IOC?
Чтобы обеспечить безопасность и поддержку работоспособности IT-инфраструктуры, важно знать, что происходит в сети, в которой работает компьютер. Это означает, что менеджеры и другие заинтересованные сотрудники должны знать, если происходит что-то необычное в корпоративной сети. Когда мы используем слово «необычное», мы имеем в виду любую потенциальную угрозу или подозрительную активность, которая может произойти или уже происходит в настоящий момент внутри корпоративной инфраструктуры.
До сих пор основной сервис, который могли бы предложить большинство компаний в сфере анализа безопасности, представлял собой подписку, в рамках которой нас бы предупреждали о последних угрозах, вредоносных программах, IP-адресах и сайтах с вредоносным контентом и т.д. Добавление такой информации в систему безопасности периметра инфраструктуры позволяет инженерам заблаговременно спланировать свои действия и приготовиться к отражению угроз, а также помогает им обнаруживать и предотвращать любые угрозы, которым могут подвергнуться их компании. В IT-индустрии эти обновления очень распространены, и компании, не колеблясь, платят определенную сумму денег в обмен на предлагаемые последние обновления.
Благодаря такому сервису, легко предотвращать вредоносные уязвимости, но можем ли мы полностью защитить нашу инфраструктуру? Ответ: да, но стоимость таких сервисов намного выше, а время жизни их результатов достаточно короткое. Итак, что мы можем сделать, чтобы повысить надежность нашей защиты?
Следующее поколение в обнаружении угроз
Каждый день аналитики безопасности собирают воедино различные события, связанные с новыми угрозами. Когда речь идет о кибер-безопасности, таким аналитикам требуется более быстрый способ обмена информацией, связанной с инцидентом, и они должны иметь максимально короткое время ответа. В некоторых случаях достаточно простого наблюдения (IP-адрес, URL, хэш…), а некоторые инциденты могут быть достаточно сложными, требующими расширенного анализа и обратного инжиниринга. Когда все эти образцы собраны, то результат – это то, что мы называем Индикатор компрометации ( Indicator of Compromise , IOC ) . Это может звучать чуждо для многих из нас, но аналитики безопасности должны быть знакомы с понятием IOC и всеми его возможностями.
Так что же такое IOC ?
В компьютерной среде индикатор компрометации ( IOC ) – это активность и/или вредоносный объект, обнаруженный в сети или на конечной точке. Мы можем идентифицировать эти индикаторы и, таким образом, сможем улучшить наши возможности по обнаружению будущих атак.
Выглядит просто, верно?
Если говорить об их использовании, то это не просто список индикаторов, а первичная информация об инциденте для анализа, исследования и/или реакции, что позволяет получить ответы на вопросы об инциденте: что, кто, почему, как, где и когда? Такой первичной информацией могут быть:
- Письма с ложной информацией (фишинг)
- Образцы вредоносного поведения
- Обнаружение определенной уязвимости и действия для борьбы с нею
- Список определенных подозрительных или вредоносных IP-адресов
- Обмен политиками и образцами поведения, связанными с определенным инцидентом (автоматически или вручную), так что они могут использоваться третьими лицами.
Мы также можем использовать список стандартов для обнаружения индикаторов на основе потребностей (например, последующее обнаружение, определение характеристик или обмен).
Итак, это было краткое описание IOC. Мы продолжим исследовать этот вопрос в статьях, которые мы будем публиковать в будущем. Наша цель – это помочь специалистам безопасности лучше понимать следующее:
- Какие стандарты существуют в настоящее время, чтобы помочь нам находить индикаторы IOC? Преимущества, примеры использования…
- Как мы можем охарактеризовать Индикатор компрометации?
- Как мы можем обмениваться Индикаторами компрометации?
- Точность IOC: качество, время жизни…
Индикатор компрометации (Indicator of Compromise, IoC)
Индикатор компрометации (Indicator of Compromise, IoC) — в сфере компьютерной безопасности наблюдаемый в сети или на конкретном устройстве объект (или активность), который с большой долей вероятности указывает на несанкционированный доступ к системе (то есть ее компрометацию). Такие индикаторы используются для обнаружения вредоносной активности на ранней стадии, а также для предотвращения известных угроз.
Что может быть индикатором компрометации
В качестве индикатора компрометации могут выступать:
- Необычные DNS-запросы.
- Подозрительные файлы, приложения и процессы.
- IP-адреса и домены, принадлежащие ботнетам или командным серверам вредоносного ПО.
- Значительное количество обращений к одному файлу.
- Подозрительная активность в учетных записях администраторов или привилегированных пользователей.
- Неожиданное обновление программных продуктов.
- Передача данных через редко используемые порты.
- Нетипичное для человека поведение на веб-сайте.
- Сигнатура или хеш-сумма вредоносной программы.
- Необычный размер HTML-ответов.
- Несанкционированное изменение конфигурационных файлов, реестров или настроек устройства.
- Большое количество неудачных попыток входа в систему.
Выявление и применение индикаторов компрометации
Индикаторы компрометации, связанные с конкретной угрозой, выделяются при анализе этой угрозы. Например, если киберразведка обнаружила новое вредоносное ПО, в отчете о нем будут приведены такие IoC, как хеши файлов, адреса командных серверов и так далее.
В дальнейшем индикаторы компрометации используются для активного поиска угроз в инфраструктуре организации. Обнаружение IoC в системе указывает на то, что, вероятно, на нее уже ведется кибератака и необходимо принять меры реагирования.
Индикаторы компрометации также добавляют в базы данных пассивных средств мониторинга и антивирусного ПО, чтобы своевременно выявлять и блокировать попытки проникновения. Например, с помощью сигнатур вредоносной программы защитное решение распознает ее и запрещает ей запускаться на устройстве.
IoC с точки зрения простого пользователя
Хотя чаще всего понятие индикаторов компрометации используется в контексте защиты корпоративных инфраструктур, с ними могут сталкиваться и обычные пользователи. Например, многие интернет-сервисы предупреждают владельцев аккаунтов об авторизации с необычного устройства или IP-адреса в другой стране. К таким сообщениям необходимо относиться серьезно, проверять приведенную в них информацию и, если какие-то из перечисленных действий выглядят подозрительно, оперативно менять пароль.
Публикации на схожие темы
- Защищенный айфон на минималках: изучаем фичу Apple Lockdown Mode
- Чем оперативно заменить переставшие работать защитные решения?
- Предприятие с изолированной подсетью: что может пойти не так?
- StripedFly: двуликий и незаметный
- Неделя моды Ducktail
- Шпионский мод WhatsApp распространяется через Telegram, атакует арабоязычных пользователей