Spring Framework. Введение.

Привет!
Пока администрация JavaRush работает над новыми уровнями я хочу начать серию обучающих статей по Spring Framework. Да, я знаю, что в сети уже много материала на эту тему, но, как показывает практика, все они на уровне Hello World’a. Я же хочу рассказать не о том, как правильно расставить аннотации, а о том, как это все устроено «под капотом». Статья рассчитана на тех, кто уже так или иначе работал с этим фреймворком и знаком с основными понятиями.

Инициализация контекста.

Итак, начнем с основ. На мой взгляд, одним из наиважнейших моментов является понимание того, как происходит настройка контекста и инициализаци бинов.
Как известно, прежде чем Spring начнет работать его необходимо сконфигурировать. В допотопные времена это делали с помощью xml файлов (на некоторых проектах, преимущественно старых, продолжают делать это до сих пор). Вот небольшой пример такого конфигурационного файла:
<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

   <bean id="helloWorld" class="ru.javarush.HelloWorld">
       <property name="message" value="Hello World!"/>
   </bean>

</beans>

По-большому счету, этого достаточно, чтобы создать пару контроллеров и запустить стартап (который не взлетит). Но как эта конфигурация заставит работать Spring? А вот тут начинается самое интересно. Для того чтобы наша конфигурация была понята Spring’ом существует XmlBeanDefinitionReader. Это внутренний компонент Spring’a, который сканирует (парсит) xml и на основе того, что мы там написали создает BeanDefinition’ы. BeanDefinition – это объект, который хранит в себе информацию о бине. Сюда входит: из какого класса его (бин) надо создать; scope; установлена ли ленивая инициализация; нужно ли перед данным бином инициализировать другой и иные проперти, которые описаны в xml. Все полученные BeanDefinition’ы складываются в HashMap, в которой идентификатором является имя бина (установленное вами или присвоенное спрингом) и сам BeanDefinition объект.
После того, как все BeanDefinition’ы созданы на сцену выходит новый герой – BeanFactory. Этот объект итерируется по HashMap’e с BeanDefinition’ами, создает на их основе бины и складывает в IoC контейнер. Здесь есть нюанс, на самом деле, при старте приложения, в IoC контейнер попадут бины, которые имеют scope Singleton (устанавливается по-умолчанию), остальные же создаются, тогда когда они нужны (prototype, request, session). А теперь небольшое отступление, познакомимся с еще одним персонажем.

Встречайте — BeanPostProcessor. (BPP)

bean post processor

Дело в том, что бин это не обязательно класс бизнес-логики вашего приложения. Бином также называют инфраструктурный бин. Вкратце, инфраструктурный бин это класс, который настраивает бины вашей бизнес-логики (да-да, слишком много бинов). Подробнее о нём я расскажу ниже, но чтобы было чуточку понятнее, что именно BPP настраивает, приведу пример. Всем же знакома аннотация @Autowired? Так вот, именно AutowiredAnnotationBeanPostProcessor ответственен за то, чтобы все ваши классы были внедрены друг в друга.
uknowimean

Вернемся к BeanFactory.
Зная теперь о BPP, нужно уточнить, что итерируясь по HashMap’e с BeanDefinition’ами сперва создаются и кладутся отдельно (не в IoC контейнер) все BeanPostProcessor’ы. После этого создаются обычные бины нашей бизнес-логики, складываются в IoC-контейнер и начинается их настройка с помощью отдельно отложенных BPP.
А происходит это вот как, каждый BPP имеет 2 метода:
postProcessorBeforeInitialization(Object bean, String beanName);
postProcessorAfterInitialization(Object bean, String beanName);
.
Происходит итерация по нашим бинам дважды. В первый раз вызывается метод postProcessorBeforeInitialization, а во второй раз вызывается метод postProcessorAfterInitialization. Наверняка возник вопрос, зачем нужны два метода, объясняю. Дело в том, что для обработки некоторых аннотаций (таких как @Transactional, например) наш бин заменяется proxy классом. Чтобы понять зачем это делается, нужно знать как работает @Transactional, а работает это вот как. В метод, помеченный данной аннотацией необходимо налету добавить еще пару строк кода. Как это сделать? Верно, с помощью создания класса proxy, внутри которого и будет добавлен необходимый код в нужный метод. А теперь представим такую ситуацию, у нас есть класс:
class A {
    @Autowired
    private SomeClass someClass;

    @Transactional
    public void method() {
        // модификатор доступа обязательно public
    }
}

В этом классе 2 аннотации @Autowired и @Transactional. Обе аннотации обрабатываются разными BPP. Если первым отработает AutowiredBPP, то все будет в порядке, но если нет, то мы столкнемся с вот какой проблемой. Дело в том, что когда создается класс proxy, то вся мета-информация теряется. Другими словами, информации об аннотации @Autowired в proxy классе не будет, а значит и AutowiredBPP не отработает, а значит наше поле someClass будет иметь значение null, что, скорее всего, приведет к NPE. Также стоит знать, что между вызовами методов postProcessorBeforeInitialization и postProcessorAfterInitialization происходит вызов init-метода, если он есть. Это по-большому счету второй конструктор, но отличие в том, что в этот момент все наши зависимости уже внедрены в класс и мы можем к ним обратиться из init-метода.

Итак, еще раз алгоритм инициализации контекста:
1. XmlBeanDefinitionReader сканирует наш xml-конфигурационный файл.
2. Создает BeanDefinition’ы и кладет их в HashMap.
3. Приходит BeanFactory и из этой HashMap отдельно складывает все BeanPostProcessor’ы.
4. Создает из BeanDefinition’ов бины и кладет их в IoC-контейнер.
5. Тут приходят BPP и настраивают эти бины с помощью 2х методов.
6. Готово.

Собственно, на этом всё, пишите понравилась вам статья и стоит ли продолжать писать подобные туториалы.
  • ,

Продолжение разбора тестового задания

В продолжение топика про полученное мной задание публикую разбор таких вопросов как прикручивание БД к проекту Spring Boot, соединение БД к контроллеру, вывод данных из бд в браузер в формате JSON.
  • ,

Тестовое задание на трудоустройство, давайте разберемся..

Друзья, всем привет.

хочу поделиться с вами опытом решения тестового задания на позицию java developer'а российской компании. Сразу скажу, реализовать основной функционал задания не представляет особой сложности, но как всегда важны детали и мелочи, которые помешали мне сдать его в срок, по заданию мне так ничего и не ответили — вакансия у них уже закрыта была когда я высылал им. Предлагаю разобраться с заданием всё ли я сделал что от меня требовалось. А тем кто понятия не имеет, как его сделать, я добавлю много воды, о том как я с ним разделывался.

Если это вдруг кому интересно — добро пожаловать под кат.
  • ,

3 примера как разобрать HTML-файл в Java используя Jsoup.

3 Examples of Parsing HTML File in Java using Jsoup


by Javin Paul on September 23rd, 2014

HTML это ядро WEB, все интернет-страницы которые Вы видите, являются ли они динамически сгенерированы средствами JavaScript, JSP, PHP, ASP или другими веб-технологиями, основаны на HTML. На самом деле, Ваш браузер разбирает HTML и отображает его в удобном для Вас виде. Но что делать если Вам нужно разобрать HTML-документ и найти в нем некоторый элемент, тэг, атрибут или проверить существует или нет конкретный элемент при помощи программы на Java.