• 3.31

  • +16.09

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. Готово.

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

Уровень 26. Ответы на вопросы к собеседованию по теме уровня. Часть 1. Вопросы 1-5, 10.

Конспект получился довольно громоздким, поэтому разделил его на две части. Во второй части собраны ответы на вопросы касательно канкаренси и многопоточности. В первой части остальные.
Написание далось довольно-таки тяжело. Многого все равно не понимаю, поэтому как всегда, комментарии, замечания, дополнения — приветствуются)


1. Как пользоваться интерфейсом Comparable?

В интерфейсе Comparable объявлен всего один метод compareTo(Object obj), предназначенный для реализации упорядочивания объектов класса. Его удобно использовать при сортировке упорядоченных списков или массивов объектов.

Данный метод сравнивает вызываемый объект с obj. В отличие от метода equals, который возвращает true или false, compareTo возвращает:
  • 0, если значения равны;
  • Отрицательное значение, если вызываемый объект меньше параметра;
  • Положительное значение, если вызываемый объект больше параметра.

Прежде всего он удобен для сортировки упорядоченных списков (java.util.List) и массивов объектов. Если список/массив содержит элементы, реализующие этот интерфейс, то они могут быть отсортированы автоматически методами java.util.Collections.sort(List)/Arrays.sort(Object[]).

С интерфейсом Comparable связано понятие натурального упорядочивания, потому как он устанавливает натуральный порядок следования экземпляров любого класса, реализующего этот интерфейс. Иначе говоря, порядок (x, y) соответствует выполнению условия x.compareTo(y) <= 0.

Правила реализации Comparable, а вернее, его метода compareTo(Object) следующие (x и y – экземпляры класса, реализующего Comparable):
  • x.compareTo(y) возвращает -1 или 1, если x должен находиться, соответственно, раньше или позже y. Если метод возвращает 0, то порядки (x, y) и (y, x) эквивалентны.
  • Если sign(a) – функция, возвращающая -1,0,1 для а, соответственно, меньше 0, равного 0 и больше 0, то должно выполняться равенство sign(x.compareTo(y))==-sign(y.compareTo(x)). Что логично: если x идет раньше y, то y должен идти позже x, и наоборот.
  • Если x.compareTo(y) > 0 и y.compareTo(z) > 0, то x.compareTo(z) > 0 – соотношение транзитивности неравенств.
  • Если x.compareTo(y) == 0, то sign(x.compare(z)) == sign(y.compareTo(z)), для любых z.
  • Вызов x.compareTo(null) должен бросать исключение NullPointerException. В этом есть расхождение с логикой реализации equals (напомню, x.equals(null) возвращает false).
  • Если y по своему типу не может быть сравнен с x, то вызов x.compareTo(y) должен бросать исключение ClassCastException.
  • (x.compareTo(y) == 0) == x.equals(y), т.е. вызов x.compareTo(y) должен возвращать 0 тогда и только тогда, когда x.equals(y) возвращает true. Это правило непротиворечивости, и его очень важно учитывать.

Источники:
echuprina.blogspot.ru/2012/02/comparable-comparator.html
www.skipy.ru/technics/objCompTh.html#comparable

2. Как пользоваться интерфейсом Comparator?

В интерфейсе Comparator объявлено два метода compare(Object obj1, Object obj2) и equals(Object obj).
При использовании интерфейса Comparator, логика сравнения пары объектов не прячется внутрь класса/объекта, а реализуется в отдельном классе.

Метод compare(x,y) в точности соответствует по своей сути вызову x.compareTo(y). Точно так же должны выполняться все правила, что и правила для реализации метода compareTo(Object) интерфейса Comparable.
Comparator может использоваться в любом месте, где нужна сортировка. При этом, во-первых, появляется необходимая гибкость – возможность реализации нескольких правил сортировки. А во-вторых, сортируемые объекты могут не реализовывать интерфейс Comparable. В случае, если они его все-таки реализуют, Comparator имеет приоритет.

Интерфейс Comparator определяет еще и метод equals(Object), как это ни парадоксально. Этот метод служит для сравнения самих экземпляров интерфейса Comparator и должен возвращать true только в том случае, если сравниваемые объекты обеспечивают одинаковый порядок сортировки. Однако всегда безопасно оставлять исходную реализацию Object.equals(Object) нетронутой

Источник:
echuprina.blogspot.ru/2012/02/comparable-comparator.html
www.skipy.ru/technics/objCompTh.html#comparable

3. Какие методы есть у класса Collections?

public static <T> boolean addAll(Collection<? super T> c, T… elements)
Метод добавляет элементы массива elements в коллекцию Collection<? super T> c. Элементы могут быть указаны по одиночке, либо как массив. Когда элементы указанны по отдельности данный метод предоставляет возможность удобно добавить все элементы в имеющуюся коллекцию:
Collections.addAll(flavors, "Peaches 'n Plutonium", "Rocky Racoon");


public static <T> int binarySearch(List<? extends Comparable<? super T>> list, T key)
public static <T> int binarySearch(List<? extends T> list, T key, Comparator<? super T> c)


Оба метода ищут в списке переданном в параметре объект переданный в параметре используя алгоритм двоичного поиска. Возвращают индекс элемента, если такой элемент в списке есть, иначе индекс первого элемента списка большего key, если все элементы меньше key, возвращает list.size().

Перед использованием данных методов списки должны быть отсортированы. В первом случае отсортированы по возрастанию в «естественном» порядке следования элементов списка (такой же, как при использовании Collections.sort(list)). Во втором случае список должен быть отсортирован по возрастанию в порядке следования, который обеспечивает переданный компаратор (такой же порядок, как при использовании Collections.sort(list, c)[здесь «с» — компаратор из параметров описываемого метода])

public static <E> Collection<E> checkedCollection(Collection<E> c, Class<E> type)

Преамбула:
Механизм дженериков в языке обеспечивает проверку типов во время компиляции. Обычно этого и достаточно, однако бывают случаи, когда все-таки нет. К примеру мы нашу коллекцию передаем в код библиотеки, куда-нибудь на сторону, нам неизвестную, и нам очень сильно хочется, чтоб код этой «third-party library» не вставил в нашу коллекцию элемент неправильного типа. Это вот возможная проблема номер 1.
Возможная проблема номер 2 следующая. Предположим наша программа выдает нам ClassCastException, который оповещает нас о том, что в коллекцию был вставлен элемент неправильного типа. К сожалению данное исключение может вылететь в любой момент, после того, как неправильный элемент был вставлен, и обычно предоставляет нам совсем немного или вообще ноль информации об источнике проблемы.
Конец преамбулы.

Используя метод метод checkedCollection мы можем избавить себя от проблемы один и два, т.к. этот метод создает коллекцию проверяемую на этапе выполнения.
Решение проблемы номер два, с помощью данного метода:
К примеру мы имеем вот это, и у нас вываливается ClassCastException.
Collection<String> c = new HashSet<String>();

Код выше можно временно заменить на:
Collection<String> c = Collections.checkedCollection(
         new HashSet<String>(), String.class);

При запуске программы снова мы локализуем строку кода, которая вставляет элемент неправильного типа в нашу коллекцию.

Родственные на мой взгляд методы:
public static <E> List<E> checkedList(List<E> list,Class<E> type)
public static <K,V> Map<K,V> checkedMap(Map<K,V> m, Class<K> keyType,Class<V> valueType)
public static <E> Set<E> checkedSet(Set<E> s,Class<E> type)
public static <K,V> SortedMap<K,V> checkedSortedMap(SortedMap<K,V> m,Class<K> keyType,Class<V> valueType)
public static <E> SortedSet<E> checkedSortedSet(SortedSet<E> s,Class<E> type)


public static <T> void copy(List<? super T> dest,List<? extends T> src)
Метод копирует элементы src в dest. индексы у копированных элементов будут совпадать.

public static <T extends Object & Comparable<? super T>> T min(Collection<? extends T> coll)
public static <T extends Object & Comparable<? super T>> T max(Collection<? extends T> coll)
public static <T> T min(Collection<? extends T> coll,Comparator<? super T> comp)
public static <T> T max(Collection<? extends T> coll,Comparator<? super T> comp)


методы возвращают минимальный\максимальный элемент в коллекции с точки зрения «естественного порядка»(интерфейс Comparable) либо порядка переданного компаратора.

public static boolean disjoint(Collection<?> c1,Collection<?> c2)
Возвращает true если у коллекций нет одинаковых элементов.

<T> List <T> emptyList(), <K,V> Map <K,V> emptyMap(),
<T> Set <T> emptySet()
– возвращают пустой список, карту отображения
и множество соответственно;

<T> void fill(List<? super T> list, T obj) – заполняет список заданным элементом;

int frequency(Collection<?> c, Object o) – возвращает количество вхождений в коллекцию заданного элемента;

<T> List <T> nCopies(int n, T o) – возвращает список из n заданных элементов;

<T> boolean replaceAll(List<T> list, T oldVal, T newVal) – заменяет все заданные элементы новыми;

void reverse(List<?> list) – “переворачивает” список;

void rotate(List<?> list, int distance) – сдвигает список циклически на заданное число элементов;

void shuffle(List<?> list) – перетасовывает элементы списка;

<T> Set <T> singleton(T o), singletonList(T o), singletonMap(K key, V value) – создают множество, список и карту отображения, состоящие из одного элемента;

<T extends Comparable<? super T>> void sort(List<T> list),
<T> void sort(List<T> list, Comparator<? super T> c)
– сортировка списка, естественным порядком и используя Comparator соответственно;

void swap(List<?> list, int i, int j) – меняет местами элементы списка стоящие на заданных позициях.

Источники:
crypto.pp.ua/2010/06/klass-collections-v-java/
docs.oracle.com/javase/7/docs/api/java/util/Collections.html

4. Какие методы есть у класса Arrays?

Полный перечень методов класса Arrays можно увидеть в документации. В данном конспекте приведу лишь некоторые из них. [переводил методы из документации, и к сожалению потерял большую часть своего перевода. Обидно, и тратить время на тоже самое не хочется, так что вставлю нагугленное]

public static <T> List<T> asList(T… a)
формирует список на основе массива. Массив при этом используется для внутреннего представления списка. Таким образом сохраняется связь между списком и исходным массивом:

изменения в массиве отразятся на списке:
String[] a = { "foo", "bar", "baz"};
List<String> list = Arrays.asList(a);
System.out.println(list); // [foo, bar, baz]

a[0] = "aaa";
System.out.println(list); // [aaa, bar, baz]

изменения в списке отразятся на массиве:
String[] a = { "foo", "bar", "baz"};
List<String> list = Arrays.asList(a);
System.out.println(list); // [foo, bar, baz]

list.set(0, "bbb");
System.out.println(Arrays.toString(a)); // [bbb, bar, baz]

Если массив содержит объекты, очевидно, и массив и список будут ссылаться на одни и те же экземпляры:
Object[] a = { new Object(), new Object(), new Object()};
List<Object> list = Arrays.asList(a);
System.out.println(a[0] == list.get(0)); // true


int binarySearch(параметры) – перегруженный метод организации бинарного поиска значения в массивах примитивных и объектных типов. Возвращает позицию первого совпадения;

void fill(параметры) – перегруженный метод для заполнения массивов значениями различных типов и примитивами;

void sort(параметры) – перегруженный метод сортировки массива или его части с использованием интерфейса Comparator и без него;

static <T> T[] copyOf(T[] original, int newLength) –заполняет массив определенной длины, отбрасывая элементы или заполняя null при необходимости;

static <T> T[] copyOfRange(T[] original, int from, int to) – копирует заданную область массива в новый массив;

<T> List<T> asList(T… a) – метод, копирующий элементы массива в объект типа List<T>.

Источник:
crypto.pp.ua/2010/06/klass-arrays-v-java/

5. Как называется сортировка, которая используется при вызове Collections.sort()?

Из документации:
Реализация является адаптированным вариантом сортировки списка для Python Тима Петерса (TimSort). Данная реализация сбрасывает список в массив, сортирует массив, затем проходит по списку и перезагружает каждый элемент списка из соответствующего элемента массива. Это позволяет избежать сложности n*n log(n), которая возникла бы при попытки отсортировать связный список напрямую

Из вики:
Timsort — гибридный алгоритм сортировки, сочетающий сортировку вставками и сортировку слиянием, опубликованный в 2002 году Тимом Петерсом. В настоящее время Timsort является стандартным алгоритмом сортировки в Python, OpenJDK 7 и реализован в Android JDK 1.5. Основная идея алгоритма в том, что в реальном мире сортируемые массивы данных часто содержат в себе упорядоченные подмассивы. На таких данных Timsort существенно быстрее многих алгоритмов сортировки.

10. Что такое итератор?


Представленный в релизе JDK 1.2 языка Java интерфейс java.util.Iterator обеспечивает итерацию контейнерных классов. Каждый Iterator реализует методы next() и hasNext() и дополнительно может поддерживать метод remove(). Итераторы создаются соответствующими контейнерными классами, как правило методом iterator().

Метод next() переводит итератор на следующее значение и возвращает указываемое значение итератору. При первоначальном создании итератор указывает на специальное значение, находящееся перед первым элементом, поэтому первый элемент можно получить только после первого вызова next(). Для определения момента, когда все элементы в контейнере были перебраны, используется тестовый метод hasNext(). Следующий пример демонстрирует простое использование итераторов:
Iterator iter = list.iterator();
//Iterator<MyType> iter = list.iterator(); в J2SE 5.0
while (iter.hasNext())
    System.out.println(iter.next());

Для коллекции типов, поддерживающей подобное, метод итератора remove() удаляет последний 'посещенный' элемент из контейнера. Почти все остальные типы модификации контейнера во время итерации являются небезопасными.

Кроме того, для java.util.List существует java.util.ListIterator со схожим API, но позволяющем прямую и обратную итерации, обеспечивая определение текущего индекса в списке и переход к элементу по его позиции.

Источник:
ru.wikipedia.org/wiki/%D0%98%D1%82%D0%B5%D1%80%D0%B0%D1%82%D0%BE%D1%80#Java

Часть 2.
  • ,

Пособие для будущего Java разработчика. Enterprise — часть 1

«Не слишком гордитесь этими техническими достижениями, которые вы построили. Способность уничтожить планету — ничто по сравнению с могуществом Силы», — Дарт Вейдер о Звезде Смерти.

Intro
Наверное, следующие две части из цикла статей для многих самые ожидаемые, и неспроста. Что же находится там, за горизонтом, за чистой Java? Чем дышат Java девелоперы в каждом проекте? Считайте это настоящим полноценным руководством для самообучения любого back-end engineer’а средней руки, для которого основной язык программирования — именно Java.

Я намерен охватить максимально среднее значение по больнице и описать не только наиболее популярные фреймворки, но и решения, который считаются актуальными на данный момент. Естественно, инструментов очень много, и понять, какие есть самые важные и лучшее — это путь в никуда. Каждый из вас рассматривал раздел «Работа» на DOU и находил стек технологий, которые постоянно повторяются от вакансии к вакансии. Понимаю, описать все невозможно, но придумать общие рамки — вполне, поэтому попробуем следовать этому направлению.
  • ,

Особенности Java 8 – максимальное руководство (часть 1)

Первая часть перевода статьи Java 8 Features – The ULTIMATE Guide. Вторая часть тут (ссылка может поменяться).

От редакции: статья опубликована в то время как Java 8 была доступна общественности и все указывает на то, что это действительно major-версия.
Здесь мы в изобилии предоставили руководства Java Code Geeks, такие как Играем с Java 8 – Лямбды и Параллелизм, Java 8 руководство по API даты и времени: LocalDateTime и Абстрактный Класс против Интерфейса в эру Java 8.

Мы также ссылаемся на 15 необходимых к прочтению руководств по Java 8 из других источников. Конечно мы рассматриваем некоторые из недостатков, например, Темная сторона Java 8.

Итак, пришло время собрать все основные особенности Java 8 в одном месте для вашего удобства. Наслаждайтесь!