Перейти к содержимому

Что такое бин в java

  • автор:

Создание компонентов

Основными элементами в контексте приложения, использующего Spring Framework, являются компоненты или бины (beans), используемые для внедрения зависимостей (Dependency Injection, DI). Есть два основных способа создания таких компонентов.

Создание компонента при помощи аннотаций

Самый простой и наиболее распространённый способ создания компонента — при помощи аннотации @Component или аннотаций, наследующих её, таких как @Service, @Repository или @Controller из пакета org.springframework.stereotype.

Пример простого компонента:

import org . springframework . stereotype . Component ;
public class SimpleBean < public String hello ( ) < return "Hello world!" ;

В данном случае будет создан компонент типа SimpleBean с именем и идентификатором simpleBean. Контекст Spring автоматически найдёт этот класс и создаст объект этого типа, который будет использоваться в дальнейшем для внедрения зависимостей.

Создание компонента при помощи @Bean-метода

Ещё один популярный способ создания компонентов — при помощи метода, помеченного аннотацией @Bean. В этом случае сам тип компонента не нужно помечать аннотацией @Component.

import org . acruxsource . sandbox . filters . SimpleBean ;
import org . springframework . context . annotation . Bean ;
import org . springframework . context . annotation . Configuration ;
@ Configuration
public class ContextConfig < public SimpleBean simpleBean ( ) < return new SimpleBean ( ) ; public SimpleBean anotherSimpleBean ( ) < return new SimpleBean ( ) ;

В данном случае будут созданы 2 компонента типа SimpleBean и с именами и идентификаторами simpleBean и anotherSimpleBean. Данный способ удобен тем, что можно создавать компоненты сторонних классов. Так же отмечу тот момент, что создание компонентов при помощи @Bean-методов возможно только в классах, помеченных аннотацией @Confuguration, как это показано в примере выше. Так же этот код соответствует следующей XML-конфигурации:

При создании компонентов таким способом стоит помнить, что если в классе, объект которого вы создаёте, есть зависимости внедряемые при помощи аннотации @Autowired, они будут автоматически внедрены.

Использование компонентов

Теперь, когда мы разобрались с созданием компонентов, можно приступать к рассмотрению примеров их использования. Компоненты можно внедрять в другие компоненты, и самый распространённый пример — внедрение репозитория в контроллер. Внедрять зависимости можно тоже несколькими способами.

Внедрение через свойство класса

Самый распространённый, но не самый удобный для тестирования, способ внедрения, о чём я как-нибудь напишу в заметке о тестировании.

@Controller
public class SimpleController < private SimpleBean simpleBean ;

В данном случае контекст Spring находит класс, помеченный аннотацией @Controller, создаёт объект этого класса, находит свойство помеченное аннотацией @Autowired и присваивает указанному свойству компонент нужного класса, находящийся в контексте Spring.

Тут может произойти две ошибки: в контексте Spring не окажется компонента класса SimpleBean, либо в контексте окажется более одного компонента класса SimpleBean. Об этом я расскажу чуть ниже.

Внедрение через конструктор

Внедрение через конструктор значительно удобнее, так как при тестировании даёт возможность вручную создать объект класса и передать ему мок вместо реального компонента.

@Controller
class SimpleController < private final SimpleBean simpleBean ; private final AnotherSimpleBean simpleBean ; public SimpleController ( SimpleBean simpleBean , AnotherSimpleBean anotherSimpleBean ) < this . simpleBean = simpleBean ; this . anotherSimpleBean = anotherSimpleBean ;

Данный код соответствует XML-конфигурации:

Аннотации @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:

Spring под капотом

Мы пишем логику через аннотации или настраиваем бины в XML либо пользуемся java-config и все чудесным образом работает.
Но как оно работает под капотом ? Я задался тем же вопросом.

Рассмотрим для пример на основе XML. Остальные будут рассмотрены ниже.

Важное значение имеет XmlBeanDefinitionReader (реализаует интерфейс BeanDefinitionReader). С его помощью происходит настройка контекста. Мы в XML описываем бины, из этого XML поднимаем контекст и с помощью XmlBeanDefinitionReader это все работает, т.е. он является внутренним компонентом Spring: он сканирует XML и все что мы там пишем переводит в BeanDefinitions — это объекты, которые хранят информацию про бины.

Давайте более подробно остановимся на этом этапе.

В XML мы прописали бины из каких-то классов. Когда поднимается контекст на первом этапе XmlBeanDefinitionReader считывает из XML все декларации бинов.

А именно, XmlBeanDefinitionReader загружает документ при помощи DefaultDocumentLoader. Каждый элемент документа обрабатывается и если он является бином, то создается BeanDefinition на основе каких-то заполненных данных. Каждый BeanDefinition помещается в Map и хранится в классе DefaultListableBeanFactory.

/** Map of bean definition objects, keyed by bean name */ 
private final MapString, BeanDefinition> beanDefinitionMap = new ConcurrentHashMapString, BeanDefinition>(64);

Создались BeanDefinitions.

Что такое BeanDefinitions ? В первую очередь это интерфейс.
Можно представить его в виде map, который в качестве ключа хранит id бина, а в качестве значения — его декларацию.
Что такое декларация ? Это то, из какого класса надо создавать бин, есть ли у него init() метод и как он называется, какие у него проперти и все остальные подробности бина, которые мы прописываем в XML. Другими словами это интерфейс, через который можно получить доступ к метаданным будущего бина.

У интерфейса BeanDefinitions есть много имплементаций.
Основные из них, это:
— AbstractBeanDefinition
— AnnotatedBeanDefinition — если используем аннотацию @Component
— ConfigurationClassBeanDefinition — с помощью него мы можем узнать то, что нам нужно (какого типа и из какого класса создался бин).
— GenericBeanDefinition
— ScannedGenericBeanDefinition
…

Пока бины не созданы, мы имеем доступ к метаданным класса.

Далее отрабатывает BeanFactoryPostProcessor.

Это интерфейс, который позволяет настраивать BeanDefinitions до того, как создаются бины. Он работает на этапе, когда кроме BeanDefinitions еще ничего нету (когда никаких бинов еще нет, ничего нет) и может что-то “подкрутить” в BeanDefinitions.

Он создается через BeanDefinitionRegistryPostProcessor, который при помощи класса ConfigurationClassParser парсирует JavaConfig(например если используем конфигурацию через аннотации) и создает BeanDefinition.

Он содержит единственный метод postProcessorBeanFactory()(который в качестве параметра принимает ConfigurableListableBeanFactory — о ней написано ниже) у которого есть доступ к BeanFactory, т.е. мы можем как-то повлиять на BeanFactory до того, как он начнет работать и повлиять на BeanDefinitions до того, как BeanFactory из них начнет создавать бины.

BeanFactory — основной ключевой игрок на поле Spring. О нем поговорим чуть позже.

Далее BeanDefinitionReader кладет бины в map BeanDefinition.

BeanFactory из этих BeanDefinition вначале достает определения этих BeanPostProcessor-ов, создает их и кладет в сторону, т.е. в первую очередь он создаст те бины, которые имплементируют интерфейс BeanPostProcessor и с их помощью будет потом настраивать все остальные бины, которые мы с вами прописали в XML.

BeanFactory — важный элемент структуры, т.к. он отвечает за создание и хранение всех объектов (занимается созданием экземпляров бинов, которые создаются на основе ранее созданных BeanDefinition). Это generic интерфейс. Он изначально был сделан для того, чтобы у разработчика была возможность управлять процессом создания бинов.
После создания BeanDefinition, BeanFactory начинает по ним работать, создает из классов объекты и все бины складывает в контейнер.

BeanPostProcessor — это интерфейс. Он позволяет настраивать бины прежде чем они попадут в контейнер. Здесь задействован паттерн “Change Of Responsibility” (“цепочка обязанностей””).

У этого интерфейса обязательно нужно переопределить 2 метода, если вы собираетесь его имплементировать:
— postProcessBeforeInitialization() — вызывается до init() метода.
— postProcessAfterInitialization() — вызывается после init() метода.

После того, как объект создался и настроился (т.е. BeanFactory его вначале настроит согласно конфигурации) и до init() метода, BeanFactory сначала передает его BeanPostProcessor-у чтобы он сделал с ним что-нибудь (метод postProcessBeforeInitialization()). Он что-то сделал или ничего не сделал и вернул объект обратно в BeanFactory. Объект прошел первую стадию настройки при помощи BeanPostProcessor.

Затем вызывается init() метод у данного бина.

Затем после init() метода идет еще один проход: BeanFactory передает объект BeanPostProcessor-у чтобы он сделал с ним что-нибудь (метод postProcessAfterInitialization()). Он что-то сделал или ничего не сделал и вернул объект обратно в BeanFactory. Объект прошел вторую стадию настройки при помощи BeanPostProcessor.

В итоге мы получаем полностью настроенные объекты.

В init() методе мы пишем логику, которая инициализирует бин.

Почему для этого нельзя воспользоваться конструктором ?

На этапе работы конструктора еще ничего не настроено, ничего нету, бин не настроен.

Потому что вначале просканировался XML, затем создались BeanDefinitions, Spring создает бин (при условии что он “singleton”): при помощи Reflection запускается конструктор, конструктор отработал, объект создался. И только когда объект создан Spring может его настроить, т.е. если в конструкторе мы пытаемся обратиться к каким-то вещам, которые должен настроить Spring (ведь их нет еще), то в лучшем случае мы получим NPE.

@PostConstruct — это иными словами init() метод, а на этапе init() метода бин еще не до конца сконфигурирован и поэтому если у бина есть какие-то proxy, аспекты, которые для него что-то делают, этого на этапе @PostConstruct еще не существует. Поэтому нежелательно использовать @PostConstruct для вызова бизнес логики.

По умолчанию XML ничего не знает про аннотации. Про аннотации знают BeanPostProcessor. Аннотацию @PostConstruct (обозначает init() метод) должен обрабатывать какой-то BeanPostProcessor, а именно CommonAnnotationBeanPostProcessor. Для этого в XML файле надо добавить его.

Но если детальней провалиться внутрь, то мы увидим класс CommonAnnotationBeanPostProcessor, который имплементирует интерфейс BeanPostProcessor и отвечает за аннотацию @PostConstruct.

Вы можете написать свой собственный BeanPostProcessor, вам ничто не мешает это сделать. Только не забудьте пометить его как бин (с помощью XML или аннотаций @Component), иначе он у вас не отработает.

Давайте рассмотрим такой пример, в котором вы написали свою аннотацию и хотите чтобы над всеми методами, которые помеченные этой аннотацией, выполнялась какая-то ваша логика.

Вы напишите свой BeanPоstProcessor, который будет относиться к вашей аннотации.
Как я уже упоминал выше, интерфейс BeanPоstProcessor имеет 2 метода, которые вам необходимо переопределить.

Есть 2 важных правила написания:
1) Если ваш BeanPоstProcessor настраивает объект — пишите логику в методе postProcessBeforeInitialization().
— Затем выполняется init() метод.
Почему именно здесь ?
Т.к. мы хотим вначале полностью настроить объект а потом заворачивать его в proxy. Т.к. например если мы попытаемся искать аннотацию @PostConstruct после того, как сделали proxy, то мы ее не найдем, потому что в proxy ее не будет.
2) Если ваш BeanPоstProcessor меняет поведение (аспекты, транзакции, и т.д. — некая бизнес логика) методов бина путем создания proxy — пишите логику в методе postProcessAfterInitialization().

Что он будет делать ?

Он будет получать бин от BeanFactory, и будет проверять не стоит ли над классом аннотация @Вася (ваша аннотация) и если стоит, то он должен будет в каждый метод этого бина дописывать какую-то логику.

В java к сожалению (а может и к счастью) нельзя просто взять и подставить новую логику в уже существующий метод. Надо будет сгенерировать новый класс proxy.

Есть важное правило: никто не должен заметить подмены!

2 правила создания:
1. CGLib (Code Generation Library)— он должен наследовать от оригинального класса и переопределять его методы и добавлять туда свою логику (считается хуже, т.к. мы не от любого класса можем наследовать, также есть такое понятие как final методы и т.д.).
2. Dynamic proxy (JDK) — он должен имплементировать те же самые интерфейсы.

Немного об аннотациях:

Создали свою аннотацию:

@Target(value=ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTestAnnotation Class newImplMyTestAnnotation();
>

где newImplMyTestAnnotation — ваша новая имплементация.

Аннотация @Retention позволяет указать жизненный цикл аннотации.

SOURCE — видна только в исходном коде. Когда мы компилируем, в байткоде ничего не будет, в рантайме тоже ничего о ней не знаем.
CLASS — (по умолчанию) видна в скомпилированном файле. Аннотация в байткод попасть должна, но через Reflection в рантайме мы ее считать не сможем, т.е. присутствует только в момент выполнения компилятора.
RUNTIME — видна в процессе выполнения и можно считать через Reflection.

Аннотация @Target указывает, что именно мы можем пометить этой аннотацией: это может быть поле, метод, тип и т.д. Иными словами область ее применения.
ElementType.ANNOTATION_TYPE — наша аннотация может присутствовать только над другими аннотациями
ElementType.CONSTRUCTOR — аннотация может присутствовать только над конструкторами
ElementType.FIELD — аннотация может присутствовать только над полем класса (любым)
ElementType.LOCAL_VARIABLE — аннотация может присутствовать только над локальной переменной метода
ElementType.METHOD — аннотация может присутствовать только над методом
ElementType.PACKAGE — аннотация может присутствовать только над пакетом (будет распространяться на все классы в пакете)
ElementType.PARAMETER — аннотация может присутствовать только над аргументом метода
ElementType.TYPE — аннотация может присутствовать только над классом или интерфейсом

@Inherited — наследуется потомками (аннотация будет распространяться на всех потомков)
@Documented — в Javadoc попадает информация о том, что данный класс помечен данной аннотацией

Когда мы написали свою аннотацию, чтобы она отработала нам надо в XML для нее указать свой BeanPostProcessor, иначе не отработает, т.к. про нее никто не знает.

либо добавить @Component над MyTestAnnotationBeanPostProcessor, указав его как бин.

Для примера мы можем задать смысловую логику аннотации @MyTestAnnotation как Deprecated и в случае ее использования рекомендовать использовать новую имплементацию с какой-то новой логикой. В таком случае указываем новую имплементацию в самой аннотации и пишем свой BeanFactoryPostProcessor, который будет содержать следующий алгоритм:

  1. получаем все BeanDefinitions при помощи ConfigurableListableBeanFactory.
    Зачем нам использовать этот интерфейс если можно сделать getBean() ?
    Потому что только ConfigurableListableBeanFactory умеет делать getBeanDefinition() — получаем все BeanDefinitions.
    Получать бин через getBean() бесполезно и неправильно.
    Например: кто-то создал бин lazy (“singleton” по умолчанию создаются все и сразу, но если он lazy, то он не будет сразу создаваться, а только тогда, когда его попросят). Может получится неприятная ситуация когда мы создадим бин, который не должен сейчас создаваться.

У BeanFactoryPostProcessor есть PropertySourcesPlaceholderConfigurer, который “проинжектит” значения из property файла в нужные поля класса и в результате создания экземпляра класса вместо @Value(“$”) над полями класса мы увидим готовые значения из файла property.

Также PropertySourcesPlaceholderConfigurer может быть добавлен в цикл настройки созданных BeanDefinition.

Поэтому вытаскивать сами бины неправильно. Надо вытаскивать их BeanDefinitions и уже в них искать ту информацию, которая интересует.

2. пробегаемся по всем BeanDefinitions

3. получаем конкретный BeanDefinition (здесь еще содержится просто метадата, мы можем получить имя класса как String, но не можем получить сам класс, т.к. он еще не загружен)

4. вытаскиваем название (имя) класса из BeanDefinition (из какого класса будет создаваться бин).

Но эта информация будет пуста, если мы определяем бин через Java-config !

5. вытаскиваем класс для BeanDefinitions по имени

6. с помощью beanClass.getAnnotation(MyTestAnnotation.class) достаем аннотацию @MyTestAnnotation

7. проверяем, если аннотация есть, значит бин устарел и в его BeanDefinitions надо установить новое имя для класса (новая имплементация), которое можно взять из этой аннотации (newImplMyTestAnnotation)

public class MyTestAnnotationBeanFactoryPostProcessor implements BeanFactoryPostProcessor

@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)

String[] names = beanFactory.getBeanDefinitionNames();
for (String name : names) BeanDefinition beanDefinition = beanFactory.getBeanDefinition(name);
String beanClassName = beanDefinition.getBeanClassName();
try Class beanClass = Class.forName(beanClassName);
DeprecatedClass annotation = beanClass.getAnnotation(DeprecatedClass.class);
if (annotation != null) beanDefinition.setBeanClassName(annotation.newImplMyTestAnnotation().getName());
>
> catch (Exception e) e.printStackTrace();
>
>
>

>

Данные махинации опасны для production среды, т.к. мы прописали один бин и на него можем сделать proxy, а в итоге нам придет совершенно другой класс, т.к. наш BeanFactoryPostProcessor изменил BeanDefinitions и заменил их.

Пару слов о proxy:

Если Spring аспекту нужно сделать proxy на какой-то объект, то он вначале смотрит на его интерфейсы, и если они есть, то использует Dynamic proxy, а если их нету, то CGLib. (С 3.2 версии CGLib идет вместе с Spring)

Т.е., согласно документации, для создания прокси объектов может использоваться как Dynamic Proxy (JDK) так и CGLib, но предпочтение должно отдаваться Dynamic Proxy. И, если класс имеет хотя бы один интерфейс, то именно Dynamic Proxy и будет использоваться (хотя это можно изменить, явно задав флаг proxy-target-class). При создании прокси объекта с помощью Dynamic Proxy на вход передаются все интерфейсы класса и метод для имплементации нового поведения. В результате получаем объект, который абсолютно точно реализует паттерн Proxy. Все это происходит на этапе создания бинов, поэтому, когда начинается внедрение зависимостей, то в реальности внедрен будет этот самый прокси-объект. И все обращения будут производиться именно к нему. Но выполнив свою часть функционала, он обратиться к объекту исходного класса и передаст ему управление.

Если же этот объект сам обратиться к одному из своих методов, то это будет уже прямой вызов без всяких прокси.

Еще на этапе создания бинов есть то самое “магическое” место, где собственно и производится выбор какую из библиотек использовать. Происходит это в классе DefaultAopProxyFactory, а именно в методе

@Override
public
AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) Class targetClass = config.getTargetClass();
if (targetClass == null) throw new AopConfigException("TargetSource cannot determine target class: " +
"Either an interface or a target is required for proxy creation.");
>
if (
targetClass.isInterface() || Proxy.isProxyClass(targetClass)) return new JdkDynamicAopProxy(config);
>
return new
ObjenesisCglibAopProxy(config);
>
else return new
JdkDynamicAopProxy(config);
>
>

Другими словами, Spring AOP не просто использует CGLib для создания наследников от классов бинов, а реализует на этом этапе полноценный прокси объект (т.е. объект соответствующий паттерну Proxy). Абсолютно без разницы, какую библиотеку под капотом использует Spring. В любом случае поведение его будет одинаковым. В любом случае для организации сквозного программирования будет создан прокси объект, который собственно и обеспечит вызовы методов объекта реального класса. С другой стороны, если какие-либо методы вызываются из методов этого же класса, то перехватить (перекрыть) средствами Spring AOP их уже не получиться.

public class Test  
@Transactional
public void A() B();
>

@Transactional
public void B() System.out.println("B");
>
>

Здесь в момент вызова метода A() на самом деле вызывается метод прокси объекта. Сам прокси объект не выполняет вызовов методов целевого объекта, он содержит цепочку интерсепторов, которые делают это. Создается новая транзакция и далее происходит вызов метода B() класса Test. А когда из A() вызовем B(), обращения к прокси нет, вызывается уже сразу метод нашего класса и, соответственно, никаких новых транзакций создаваться не будет.

Вернемся к нашим BPP:

Те BeanPostProcessor-ы, которые в классе что-то меняют, они должны это делать на этапе postProcessAfterInitialization(), а не на этапе postProcessBeforeInitialization().

Нельзя ставить @PostConstruct над методом, который что-то принимает).

Аннотация @PostCоnstruct всегда работает на оригинальный метод до того, как все proxy на него “навесились”.

Если мы заменяем оригинальный объект на proxy до того, как отработал init() метод, то тот механизм в Spring, который ищет аннотацию @PostConstruct и запускает init() метод просто не сможет ее найти, т.к. он уже имеет дело не с оригинальным классом, а с proxy, в котором нет никаких аннотаций.

Вывод:

Dynamic proxy могут перекрывать только public методы. CGLib proxy — кроме public, также и protected методы и package. Соответственно, если мы явно не указали для среза (pointcut) ограничение “только для public методов”, то потенциально можем получить неожиданное поведение. Ну а если есть какие-либо сомнения, можно принудительно включить использование CGLib для генерации прокси-объектов (в последних версиях Spring идет по умолчанию).

Пару слов про CTW и LTW

У нас есть возможность использовать компилятор AspectJ. Т.е. фреймворк написан так, что если мы для описания аспектов используем аннотации @AspectJ, то нам не придется вносить никаких изменений, что бы перейти от runtime weaving к CTW (compile-time weaving).

Для этого нам надо только подключить плагин, который и выполнит компиляцию.

public class Test  
@MyAnnotation
public void A() System.out.println("A");
B();
>

@MyAnnotation
public void B() System.out.println("B");
>
>

При выполнении этого кода (при условии что мы создали свою аннотацию @MyAnnotation и написали для нее некую логику

@Aspect
@Component
public class MyAspect1
@Pointcut("@annotation(com.example.AOPTest.MyAnnotation)")
public void annotated() <>

@Before("annotated()")
public void printABit() System
.out.println("Aspect1-Lalala");
>
>

при использовании Spring AOP мы получим:

Aspect1-Lalala
A
B

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

Aspect1-Lalala
Aspect1-Lalala
A
Aspect1-Lalala
Aspect1-Lalala
B

т.к. связывание кода произошло не только в месте исполнения методов, но и в месте их вызовов. Дело в том, что Spring AOP имеет ограничение, которое позволяет связывать код только по месту исполнения. Возможности AspectJ в этом плане значительно шире.

Также для компилятора AspectJ не нужна аннотация @Component. Но если мы ее уберем, то Spring AOP не найдет такого бина и аспект не будет задействован.

2. LTW (Load-time weaving)

LTW в Spring доступно из коробки. Все что надо сделать — в классе конфигурации добавить еще одну аннотацию: @EnableLoadTimeWeaving

@SpringBootApplication
@EnableLoadTimeWeaving
public class YourMainClass .
>

Для этого случая выше написанный код выполнится так:

Aspect1-Lalala
A
B

Для выполнения должен использоваться класс InstrumentationLoadTimeWeaver. В нем метод, который должен вызываться на этапе создания бинов

public void addTransformer(ClassFileTransformer transformer)

И в этом случае Spring AOP создает все те же прокси объекты, о которых мы говорили чуть выше. С тем лишь отличием, что связывание теперь будет производиться не на этапе исполнения, а уже на этапе загрузки классов.

Вывод:

Spring AOP является proxy-based фреймворком. Это значит, что всегда будут создаваться прокси-объекты на любой наш бин подпадающий под действие аспекта.

Если нам все-таки надо добиться, что бы в рассматриваемом примере выполнялся вызов одного метода класса другим (с аспектами), мы можем “заинжектить” сервис сам в себя при условии, что бин —singleton (используя последнюю версию Spring):

public class Test  
@Autowired
private Test test;

@MyAnnotation
public void A() System.out.println("A");
test.B();
>

@MyAnnotation
public void B() System.out.println("B");
>
>

Не могу обойти стороной транзакции.

Пару слов о том, если объявлена аннотация @Transactional над классом. Более подробно про транзакции в Spring я хочу рассказать в отдельной статье.

В какой момент BeanPostProcessor, отвечающий за транзакцию, добавит в класс логику, связанную с этой транзакцией ?

Он это сделает после того, как PostConstruct (метод init()) отработал, т.к. транзакции на этапе PostConstruct еще не существуют (она еще просто не настроена).

Напоминаю, у нас есть 3 этапа:
— вначале postProcessBeforeInitialization()
— потом PostConstruct (метод init())
— потом postProcessAfterInitialization() — на этом этапе уже все настроилось!

т.е. другими словами PostConstruct работает до того, как настроились все proxy! (включая те proxy, которые отвечают за транзакции).

Например если метод будет одновременно транзакционными и помечен как @PostConstruct, то он отработает без транзакции!

Потому что Spring вначале создает объект, потом настраивает его, отрабатывает его init() метод и только потом на него накручиваются все proxy !

Т.е. init() метод работает “до” того, как есть proxy, а транзакция как раз имплементируются про помощи proxy.

Если бин “singleton”, то по умолчанию он создается сразу, как только поднимается контекст и складывается в контейнер. Они находятся в контексте Spring, он их кэширует. А все “prototype” создаются в тот момент, когда они нужны. Spring их нигде не хранит.

Если у “singleton” бина есть destroy() метод, то после завершения контекста Spring проходит по всем бинам, которые хранятся в контейнере, находит у них destroy() методы (если они прописаны) и вызывает их и следовательно удалит “singleton” бин. Важно заметить, что у “prototype” не вызовет, потому что Spring нигде его не хранит. Как только кому-то потребовался “prototype” бин, Spring его сделал, отдал вам и забыл про него. Это важно знать!

Немаловажный игрок на нашей арене — это ApplicationListener.

Он умеет слушать контекст Spring, все “events”, которые с ним происходят. Работает на этапе, когда все уже создано. Также он имеет дженерики <>, в которых мы можем указать что конкретно мы хотим слушать. Обозначается аннотацией @EventListener.

— contextStartedEvent — контекст начал свое построение (не построился, а только начал)
— contextRefreshedEvent — когда контекст заканчивает свое построение, он всегда делает refresh.
— contextStoppedEvent
— contextClosedEvent

Теперь мы получше знаем как происходит поэтапное создание объектов, используя XML. А что на счет других вариантов, таких как аннотации и java-config ?

Какие есть способы настройки Spring:
1) XML
2) Аннотации
3) Java-config
4) Groovy-config

Пару слов про конфигурацию через аннотации:

Чтобы кто-то просканировал пакеты, в которых лежат классы, аннотированные @Component, есть 2 пути:
1. использовать неймспейс: и указываем package, который надо просканировать
2. new AnnotationConfigApplicationContext(“…”);

Как работает AnnotationConfigApplicationContext ?

У него есть 2 поля:

private final AnnotatedBeanDefinitionReader reader;
private final ClassPathBeanDefinitionScanner scanner;

Его ClassPathBeanDefinitionScanner является аналогом XmlBeanDefinitionReader для XML, описанный выше.

ClassPathBeanDefinitionScanner не является по своей структуре BeanPostProcessor-ом. Он подгружает дополнительные ресурсы, т.е. сканирует пакет и ищет все бины, аннотированные @Component или любой другой аннотацией, которая относится к бину (@Component, @Service, @Repository, @Controller). Он создает дополнительные BeanDefenitions из всех классов, аннотированные @Component и т.д. Найденные классы парсируются и для них создаются BeanDefinition.

Затем AnnotatedBeanDefinitionReader начинает работать:
1) регистрирует все @Configuration для дальнейшего парсирования
2) происходит регистрация BeanFactoryPostProcessor (BeanDefinitionRegistryPostProcessor), который парсирует Java-сonfig при помощи класса ConfigurationClassParser и создает BeanDefinition.

Java-config:

Java-config сам по себе является бином.
Дает возможность настраивать наши бины какой-то своей логикой, т.е. мы пишем код, который будет создавать наши объекты. В таком случае нам не нужны BeanFactory и т.д. Используется аннотация @Configuration.

Сканирует пакеты AnnotationConfigApplicationContext, как и в случае с аннотациями.

Java-config парсирует класс AnnotatedBeanDefinitionReader, аналог XMLBeanDefinitionReader для XML.

Когда мы создаем AnnotationConfigApplicationContext и передаем в него 1 или несколько Java-сonfig, то у AnnotationConfigApplicationContext есть внутри AnnotatedBeanDefinitionReader, который делает всю логику: он только регистрирует все Java-сonfig-и а также является частью ApplicationContext.

Java-сonfig — это Java файл и на него при помощи CGLib накручивается proxy и каждый раз, когда нужен бин, происходит делегация в тот метод, который мы пропишем в Java-сonfig.

Пару слов о BeanDefinitions, сделанных через Java-Config.

BeanDefinitions для тех бинов, которые декларируются через Java-config отличаются. В них нету информации о том, из какого класса они сделаны.

т.к. пока не запущен метод никаким образом нельзя узнать что этот метод вернет!
Если метод помечен аннотацией @Bean мы не сможем узнать тип возвращаемого значения.

Когда мы пишем бины в Java-config, мы в основном концентрируемся на том, что говорим Spring при помощи какой логики надо создать бин.

В Groovy-config, XML, аннотациях — все работает через Reflection: Spring сам знает название класса и через Reflection его создает.
В Java-config нет Reflection: Spring сам знает, для того, чтобы создать этот бин надо вызвать этот код. Этот код вернет ему объект и только тогда, когда объект будет получен можно узнать из какого класса он был создан.

В @Bean мы можем указать как будет называться init() метод, destroy() метод, также можем указать id для бина (по умолчанию Spring возьмет имя метода в качестве id бина), но указать из какого класса будет создан бин мы никак не можем!

В какой момент мы можем узнать из какого класса создался бин ?

Когда создался объект и сделав у него getClass(), но это уже происходит на этапе ”после”.

Groovy-config:

Начиная с версии Spring 4 появилась возможность прописывать бины через Groovy-config.

Создается при помощи new GenericGroovyApplicationContext(“context.groovy”);
Парсируется с помощью GroovyBeanDefinitionReader.

Из существенных минусов можно выделить то, что в нем пока нету таких штуковин, как ComponentScan.

В чем отличие Java-config от Groovy-config ?

С помощью Java-config мы пишем код, который будет создавать наши объекты.
С помощью Groovy-config мы пишем код, который описывает то, как надо создавать BeanDefinition. Мы никак не можем прописать то, каким образом надо создавать сам бин!

Он считается немного лучше XML тем, что в нем можно писать код, но этот код не относится к созданию объекта.

Пример: Название бина и его имплементация. Просто и лаконично.

beans coolDao(CoolDaoImpl)

coolService(CoolService)
bean.scope = ‘prototype’
bean.initMethod = ‘init’
bean.destroyMethod = ‘closeResources’
>
>

Какие типы контекста мы знаем ?

1. ClassPathXmlApplicationContext
2. FileSystemXmlApplicationContext
3. XmlWebApplicationContext
4. AnnotationConfigApplicationContext
5. GenericGroovyApplicationContext
6. StaticApplicationContext

…

Они отличаются друг от друга тем, каким способом задаются мета-данные и где хранится эта конфигурация.

Например:
— ClassPathXmlApplicationContext — метаданные конфигурируются XML и они лежат в classpath, т. е. в ресурсах модуля
— FileSystemXmlApplicationContext — метаданные тоже конфигурируются XML, но они находятся где-то в файловой системе
— AnnotationConfigApplicationContext — для аннотаций и для Java-Config
— GenericGroovyApplicationContext — для Groovy-Config

Надеюсь данная статья будет полезна.

На написание этой статьи на меня повлияли видео талантливого человека Евгений Борисова. Огромное ему спасибо!

Если вы нашли неточности в описании данной статьи, вы можете написать мне на email и я с радостью вам отвечу.

Kirill Sereda

What is Bean in Java Spring

This article explores “What is Bean in Java Spring”- Inversion of Control and the most commonly used spring bean, how to use it, bean scopes in spring, and the bean lifecycle. We will also explain the semantics represented by each of the Spring stereotype annotations. The table of contents below allows you to quickly navigate to different sections of the Java bean string tutorial in detail.

Get Trained And Certified

Get Trained And Certified
search here
search here
Java Spring Articles

  • Spring Boot Interview Questions
  • Spring Boot vs Spring MVC
  • Spring Cloud Tutorial
  • Spring Tutorial
  • Java Spring Interview Questions
  • Spring Cloud Interview Questions
  • Spring Boot Tutorial
  • Spring MVC Interview Questions
  • Spring Boot Projects

Java Spring Community
Java Spring Quiz
Table of Contents

Java Bean is a modular programming framework. Beans are the objects that make up the framework of your Spring application and are controlled by the Spring IoC container so that they can be accessed in different contexts. Not only that, but it’s also very low maintenance.

Learn how Java spring bean annotations lead to a clean, layered design and how the different parts of an application can be kept separate. Also, they make configuration smaller because we no longer have to define beans manually. The configuration metadata you give the container is used to make these beans. So get into brief!

What is bean in java spring?

The Spring Bean is the fundamental component of any Spring application. To put it simply, a bean is an object that is created, constructed, and managed by the Spring IoC container. We must understand it before we begin working with the Spring Framework.

The Spring Framework relies heavily on the idea of a Spring Bean. Understanding this concept is critical for grasping the framework and using it effectively. The Spring IoC manages the life cycle of the Spring Bean.

JavaBeans are classes that contain many objects into one object (the bean). The word «Bean» encompasses the following standards, which strive to produce reusable Java software components.

  1. All Java Bean properties must be private and have a public getter and setter methods.
  2. It must have a public zero-argument function Object().
  3. Must implement Serializable.

Inversion of Control (IOC)

To put it briefly, Inversion of Control (IoC) is a technique whereby an object declares its dependencies without really creating them. When passed to an IoC container, this object instructs it to construct such dependencies.

Before we get into the IoC, let’s start by declaring a few domain classes.

  • Domain Classes
  • Traditional Approach
  • Bean Configuration
  • IOC in action
1. Domain Classes

Let’s say there’s a class declaration:

public class Company < private Address address; public Company(Address address) < this.address = address; >// getter, setter and other properties > Copy

This class requires a partner of type Address:

public class Address < private String street; private int number; public Address(String street, int number) < this.street = street; this.number = number; >// getters and setters >
2. Traditional Approach

Typically, we use the constructors for a class to create objects:

Address address = new Address("High Street", 1000); Company company = new Company(address);

Consider a programme with dozens or perhaps hundreds of classes. Sometimes we wish to share a single instance of a class throughout the entire application, other times we require a unique object for each use case, etc.

The management of so many objects is nothing short of a nightmare. Here is when control inversion comes to the rescue.

Instead of manually creating its dependencies, an object can retrieve them through an IoC container. All that is required is to give the container with configuration details.

3. Bean Configuration

First of all, let’s add the @Component annotation to the Company class:

@Component public class Company < // this body is the same as before >Here's a configuration class supplying bean metadata to an IoC container: @Configuration @ComponentScan(basePackageClasses = Company.class) public class Config < @Bean public Address getAddress() < return new Address("High Street", 1000); >>

A bean of type Address is made by the configuration class. It also has an annotation called @ComponentScan, which tells the container to look for beans in the same package as the Company class.

When a Spring IoC container makes objects of these types, they are all called Spring beans because the IoC container manages them.

4. IoC in Action

To construct a container from the beans we defined in the configuration class, we require an instance of the AnnotationConfigApplicationContext class:

ApplicationContext context = new AnnotationConfigApplicationContext(Config.class);

A quick test proves that our beans exist and what they are worth:

Company company = context.getBean("company", Company.class); assertEquals("High Street", company.getAddress().getStreet()); assertEquals(1000, company.getAddress().getNumber());

The result shows that the beans were made and set up correctly by the IoC container

Get Beans

Basically, the method responsible for retrieving a bean instance from the Spring container is the BeanFactory.getBean() method.

Usually, in the place of BeanFactory, we use one of the known implementing classes as by documentation here. Whatever implementation you use, there are (by BeanFactory) different signatures by which we can retrieve a bean from the Spring container:

  • By name
  • By name and type
  • By type
  • By name with constructor parameters
  • By type with constructor parameters
By name:

If you give it a name, it will return an object if there is a bean in the application context with that name. If not, it will throw a NoSuchBeanDefinitionException.

#by name Object animal = context.getBean("animal");
By name and type:

In the presence of a name and a type, this method returns a bean of that type if it exists in the application environment; this method can be helpful because it eliminates the need to cast the object returned.

In the case that no such bean definition exists, this method will throw NoSuchBeanDefinitionException; otherwise, it will throw BeanNotOfRequiredTypeException.

#by name and type Animal animal = context.getBean("animal", Animal.class);
By type:

it’s very much like the previous one, except that there may be multiple instantiated beans of the same type, leading to a NoUniqueBeanDefinitionException.

#by type Animal animal = context.getBean(Animal.class);
By name with constructor parameters:

It is possible to use the following syntax to specify the bean name and function Object() parameters:

#by name and constructor parameters Animal fuffyAnimal = (Animal) context.getBean("animal", "Fuffy", 5);

In this case, we get a 5-year-old animal named Fuffy. But if the bean is singleton (not a prototype), we can get BeanDefinitionStoreException.

By type with constructor parameters:

It is similar to the last, but we have to pass the type instead of the name, and the bean can’t be a singleton:

#by type and constructor parameters Animal fuffyAnimal = (Animal) context.getBean(Animal.class, "Fuffy", 5);

To retrieve beans from the container, we typically use the ApplicationContext to invoke these primary methods provided by the BeanFactory interface.

Moreover, we rely on auto wiring and dependency injection to get the beans inside another Spring bean so that we don’t have to combine logic with a framework-related technique for getting beans.

Most used Spring Bean annotations

Some of the most used spring bean annotations include:

  1. @Component
  2. @Repository
  3. @Service
  4. @Controller
  5. @Configuration
1. @Component

@Component is an annotation that goes on a class. Spring Framework automatically finds classes marked with @Component during the component scan:

@Component class CarUtility < // . >

As a default, all bean instances of this class will share the same name as the class name, but with a lowercase beginning. The value argument of this annotation is optional and can be used to define a different name.

Beans annotated with @Configuration, @Service, @Repository, or @Controller all follow the same conventions for naming due to the fact that they are all meta-annotations of @Component. Spring also detects them mechanically during the scanning procedure for individual components.

2. @Repository

Classes annotated with @Repository are typically DAOs or repositories and constitute the application’s database access layer.

@Repository class VehicleRepository < // . >

The use of this annotation allows for the automatic translation of persistence exceptions, which is a definite plus. By default, Spring will convert any native exceptions thrown inside a class annotated with @Repository using a persistence framework like Hibernate into derived classes of Spring’s DataAccessException.

3. @Service

The @Service annotation will be used to designate a class as part of the service layer, which typically houses the application’s business logic:

@Service public class VehicleService < // . >
4. @Controller

In Spring MVC, a class’s use as a controller is indicated to the Spring Framework using the @Controller class annotation.

@Controller public class VehicleController < // . >

Related Article>>> Spring Boot vs Spring MVC

5. @Configuration

Methods annotated with @Bean can be found in configuration classes that define beans.

@Configuration class VehicleFactoryConfig < @Bean Engine engine() < return new Engine(); >>

Related Article>>> Spring Cloud Tutorial

Bean Scopes In Spring

The bean’s scope determines how long it will live and who can see it in various scenarios. Singleton and prototype are examples of basic scopes, while request, session, application, and WebSocket are examples of web-aware scopes that can be used with beans.

You can declare a bean’s scope when you define it. For the spring bean example, to make a new bean instance every time one is needed, you should set the bean’s scope attribute to prototype.

There are a total of five scopes available within the Spring Framework. However, three of them require a web-aware ApplicationContext in order to be used.

In the spring, there are many ways to use your beans. The most important parts of the framework are

  • singleton – a single instance
  • prototype – multiple instances

In addition, Spring provides its own set of web-specific bean scopes in spring:

  • request
  • session
  • global session
  • Application

SINGLETON

  • The bean definition is now limited to a single instance per Spring IoC container (default).
  • For each container, only one instance of the bean will be created. This is the spring beans’ default scope. When using this scope, make sure the bean does not have any shared instance variables, as this could cause data inconsistency issues.
Singleton Java configuration:
@Configuration public class AppConfiguration < @Bean @Scope("singleton") //This statement is redundant - singleton is default scope public class BeanClass < System.out.println("A new BeanClass instance created"); return new BeanClass(); >>

PROTOTYPE

  • A single bean definition can therefore be used to create an unlimited number of objects.
  • Each time you request the bean, a new instance will be produced.
Prototype Java configuration:
@Bean @Scope("prototype") public class BeanClass

REQUEST

  • This restricts the application of a bean definition to a certain HTTP request. This only applies when the Spring ApplicationContext is one that is aware of the web.
  • Similar to prototype scope, but created with web apps in mind. Every HTTP request will produce a fresh instance of the bean.
Request Java configuration:
@Bean @Scope("request") public class BeanClass

SESSION

  • This limits the definition of a bean to an HTTP session. Only works in a Spring ApplicationContext that knows about the web.
  • Each HTTP session will cause the container to make a new bean.
Session Java configuration:
@Bean @Scope("session") public class BeanClass

APPLICATION

  • During a ServletContext’s lifetime, there will only ever be a single instance produced and active at any given time. This only applies when the Spring ApplicationContext is one that is aware of the web.
Application Java configuration:
@Bean @Scope("application") public class BeanClass

WebSocket

  • WebSocket will only create and make available one instance for the whole time it is used. Only true for a Spring application that knows about the web. Context
WebSocket Java configuration:
@Component @Scope("websocket") public class BeanClass

Populating Bean properties

Each and every metadata configuration holds the attributes necessary to define a Spring bean.

Almost all of Spring’s properties have default settings that can be used to quickly and easily create working bean declarations. It is useful to understand how to alter the default settings.

Bean class

When you define a bean, you link it to a specific class in your programme. The primary characteristic of a bean is this class itself.

When Spring searches for a bean’s dependents, the class property is used as the default identifier. Does that rule out the possibility of having different bean definitions for the same class? No. It’s not impossible. However, in such a scenario, you need to use a different bean identifier: a name, to avoid confusion.

This property must always be present. This file outlines the Spring Framework bean class that must be present in order to construct the bean.

Bean name

Spring bean name is a string that is unique to each bean that Spring uses to identify it. In contrast to the bean class, names must be unique throughout the entire application. You can’t talk about two different kinds of beans with the same name.

You don’t have to set a name for every bean you make, which is good news. Spring makes up names on the fly that it uses internally. You can use the default setup if you don’t want to use bean names to find them.

The main time you need to use bean names is when you use the @Bean annotation to define multiple beans for the same class. In this case, names let you find a specific instance of a bean that you want to use as a dependency for another bean.

Bean dependencies

Beans are objects that can be utilized to do a variety of tasks by utilizing other beans. Whenever Spring builds an object with defined dependencies, the framework must first generate those dependencies. There may be further requirements within these dependencies.

Object-oriented programming typically involves dealing with a massive network of interconnected items. We do not need to deliberate on the details of building this graph. Object creation doesn’t require us to consider a certain timeline. For us, spring accomplishes all of this.

Spring Bean Life Cycle annotations?

The spring container is in charge of the entire bean life cycle. The spring container is the first thing that gets started whenever we run the programme. The container then constructs the requested bean instance and injects any necessary dependencies. When the spring container is closed, the bean is permanently closed.

To implement the bean life cycle in Spring, the Spring framework gives you two annotations:

In most cases, the names of annotations explain what they are for. This method is called «postConstructs» because it is performed after the class’s properties have been constructed. The term «PreDestroy» refers to the action taken just before the bean is destroyed.

Step#1: Create a Project in Eclipse/STS

1) Start up your Eclipse IDE and go to new > project > Maven Project > next.

2) Click «Create a simple project (skip archetype selection)»

3) Tell me what the Group Id and Artifact Id are. Group Id uniquely identifies your project among all. So it could be something like com.dev.spring.lifecycle. Artifact Id could be something like a project name, so we could put SpringLifeCycleExample as the artifact id here.

4) Hit the «Finish» button. You’ll get a new project based on Maven.

Step#2: Modify pom.xml

A pom.xml file is generated whenever a new project is started. Providing the dependencies for the most fundamental Spring jars is what this file is about.

The @PostConstruct and @PreDestroy annotations are not part of the Spring, which is a key distinction to make. They are included in the common-annotations.jar file that is part of the Java EE (JEE) package. In order to use these annotations after Java EE was deprecated in Java 9 and deleted in Java 11, an additional dependency, javax.annotation-api, is required. There is no need to add this dependency if you are not using Java 8 or higher.

Put the following code inside the dependencies> element in your pom.xml file. The most recent stable release of your choosing can be provided if you so want.

 org.springframework spring-core 4.1.4.RELEASE  org.springframework spring-context 4.1.4.RELEASE  javax.annotation javax.annotation-api 1.3.2 

When the pom.xml file is saved, the necessary jars are downloaded immediately.

Step#3: Create a Pojo Class

Now, we must now develop a POJO class with two life cycle methods that use annotations: @PostConstruct and @PreDestroy.

package com.dev.spring.bean.lifecycle; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; public class Person < private String name; private Integer age; public Person() < super(); >public String getName() < return name; >public void setName(String name) < this.name = name; >public Integer getAge() < return age; >public void setAge(Integer age) < System.out.println("Setting Person's Age"); this.age = age; >@Override public String toString() < return "Person [name=" + name + ", age=" + age + "]"; >@PostConstruct public void initMethod() < System.out.println("Inside init method"); >@PreDestroy public void destroyMethod() < System.out.println("Inside destroy method"); >>

Don’t forget to annotate the two life cycle approaches you to deploy.

Step#4: Create a configuration file (config.xml)

In addition to defining the bean, the tag must be specified. Annotations within the programme will become functional. This tag is required for using annotations in your application.

Spring’s documentation on XSD Configuration can be consulted for the necessary schema definitions before constructing an XML configuration file. To facilitate our planned use of the Spring framework’s beans and context module, we have extended the schema to include only those components.

Step#5: Create a Test class

Let’s define a main() method in a test class and see if our code works.

package com.dev.spring.bean.lifecycle; import org.springframework.context.support.AbstractApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Test < public static void main(String[] args) < // ApplicationContext ctx= new ClassPathXmlApplicationContext("com/dev/spring/bean/lifecycle/config.xml"); AbstractApplicationContext ctx= new ClassPathXmlApplicationContext("com/dev/spring/bean/lifecycle/config.xml"); Person person = (Person) ctx.getBean("person"); System.out.println(person); //registering shutdown hook ctx.registerShutdownHook(); >>

In order to use the destruct function of the spring bean life cycle, we need to call registerShutdownHook() method. AbstractApplicationContext provides access to this function.

Hence, we are utilizing AbstractApplicationContext interface in place of ApplicationContext interface to access spring context.

Step#6: Test Output

When the programme is executed, the results will appear as shown below:

Setting Person's Age Inside init method Person [name=Person-1, age=24] Inside destroy method

From the output, it’s clear that the init() method is called after all the properties have been set. At the last call to the destroy() method.

Bean in Java Spring FAQs

1. What is a bean in spring boot with example?

In a Spring boot application, a bean is just a Java object that is made when the application starts by the Spring framework. The object could be a configuration, a service, a database connection factory, or just about anything else.

Example:

We can use both annotation-based and XML-based configurations for stand-alone apps. The only rule is that the context has to be set up somewhere in the programme before it can be used.

package com.journaldev.spring.main; import java.util.Date; public class MyService < public void log(String msg)< System.out.println(new Date()+"::"+msg); >>

2. How to create beans in spring boot?

Here, we’ll go through three distinct methods for creating a bean in spring boot.

  1. Using @Bean Annotation
  2. Using @Component Annotation
  3. Creating Bean Inside an XML Configuration File (beans.xml)

3. What is the scope of beans in spring?

The scope of spring beans enables more granular control over the production of bean instances. Sometimes we want to construct a bean instance as a singleton, while in other instances, we may want it to be created once per request or session.

4. What is IOC in spring?

IoC Spring (Inversion of Control) is the heart of the Spring Framework as the container. Everything about the objects’ creation, configuration, and assembly, as well as their full life cycle, is handled by this. The Container employs Dependency Injection(DI) to manage the components that make up the application.

5. How do beans work in Spring?

The spring container takes care of the life cycle of beans.

  • First, the spring container starts up when we run the programme.
  • After that, the container creates an instance of a bean based on the request.
  • And then dependencies are injected.
  • And finally, when the spring container is shut, the bean is destroyed.

6. Why are Java Beans Serializable?

Java Bean is serializable at all times. Serializable does not declare any methods; instead, it serves as a marker to inform the Object Serialization tools that your bean class is serializable. Marking a class as Serializable indicates to the Java Virtual machine (JVM) that the class is compatible with the default serialization method.

7. What is JavaBean used for?

JavaBeans is a model that can be moved around and works on any platform. It is written in the Java Programming Language. The parts of it are called «beans.» JavaBeans are classes that put together a group of objects into a single object. It makes it easier to get to these objects from more than one place.

8. Are JavaBeans still used?

Enterprise JavaBeans or EJB is one piece of technology that may seem old-fashioned now that APIs are becoming more popular, but it is still used by more than 23,500 companies around the world.

9. What is the difference between JavaBean and Spring bean?

  • Spring IOC does not manage Java Bean, but Spring Bean is.
  • Spring Bean doesn’t have to be able to serialize, but Java Bean always is.
  • Java Bean must have a no-args function Object() < [native code] >by default, but Spring Bean does not.
  • A Java object can be at the same time a JavaBean, a POJO, and a Spring bean.

10. What are the features of JavaBeans?

  • The default function Object() provided by JavaBeans lacks any precondition or arguments.
  • JavaBeans are capable of serialization and can implement the Serializable interface.
  • JavaBeans typically contain multiple getter and setter methods.
  • Multiple properties can be read or written for JavaBeans.

Conclusion

To wrap up, this article has covered the Java spring bean definition and configuration metadata. In addition, we saw how the Spring IoC container handles them. A working example was also defined in order to obtain a comprehensive analysis of the topic. Now, you are also familiar with how Spring builds objects based on the bean definitions you provide. Feel free to ask any questions in the comment section.To enrich your career in Java spring, enroll your names on Java Spring Training Course to gain in-depth knowledge of the spring configurations.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Luxury Car Rental in Miami | Reserve a Premium Vehicle with RealCar