Руководство spring для начинающих

Spring Framework является одним из самых сложных фремворков для понимания и изучения. Большинство разработчиков изучают его медленно, через практические задачи и гугл. Этот подход не эффективен, так как не даёт полной картины и при этом требует больших затрат.

Я хотел бы предложить вам принципиально новый подход к изучению Спринга. Он заключается в том, что человек проходит через серию специально подготовленных туториалов и самостоятельно реализует функционал спринга. Особенность этого подхода в том, что он, помимо 100%-го понимания изучаемых аспектов Spring даёт ещё большой прирост в Java Core (Annotations, Reflection, Files, Generics).

Статья подарит вам незабываемые ощущения и позволит почувствовать себя разработчиком Pivotal. Шаг за шагом, вы сделаете ваши классы бинами и организуете их жизненный цикл (такой же, как и в реальном спринге). Классы, которые вы будете реализовывать — BeanFactory, Component, Service, BeanPostProcessor, BeanNameAware, BeanFactoryAware, InitializingBean, PostConstruct, PreDestroy, DisposableBean, ApplicationContext, ApplicationListener, ContextClosedEvent.

Немного о себе

Меня зовут Ярослав, и я Java Developer с 4-х летним опытом работы. На данный момент я работаю в компании EPAM Systems (СПБ), и с интересом углубляюсь в те технологии, которые мы используем. Довольно часто приходится иметь дело со спрингом, и я вижу в нём некоторую золотую середину, в которой можно разиваться (Java все итак нормально знают, а слишком специфические инструменты и технологии могут приходить и уходить).

Пару месяцев назад я прошёл сертификацию Spring Professional v5.0 (без прохождения курсов). После этого я задумался над тем, как можно обучать спрингу других людей. К сожалению, на данный момент нет эффективной методики обучения. У большинства разработчиков складывается весьма поверхностное представление о фреймворке и его особенностях. Дебажить исходники спринга слишком тяжело и абсолютно не эффективно с точки зрения обучения (я как-то увлекался этим). Сделать 10 проектов? Да, вы где-то сможете углубить свои знания и получите много практического опыта, но многое из того, что «под капотом», так и не откроется перед вами. Читать книгу Spring in Action? Круто, но затратно по усилиям. Я вот проработал её 40% (во время подготовки к сертификации), но это было не просто.

Единственный способ понять что-то до конца — самостоятельно разработать это. Недавно у меня появилась идея о том, что можно провести человека через интересный туториал, который будет курировать разработку своего DI-фреймворка. Главная его особенность будет заключаться в том, что API будет совпадать с изучаемым API. Офигенность данного подхода в том, что помимо глубокого (без пробелов) понимания спринга, человек получит ОГРОМНОЕ количество опыта по Java Core. Признаюсь честно, я сам много всего нового узнал во время подготовки статьи, как по Spring, так и по Java Core. Давайте приступим к разработке!

Проект с нуля

Итак, первое, что нужно сделать — это открыть любимую IDE и создать проект с чистого листа. Никаких Maven, никаких сторонних библиотек мы подключать не будем. Даже Spring-зависимости подключать не будем. Наша цель — разработать API, максимально похожий на Spring API, и реализовать его самостоятельно.

В чистом проекте создайте 2 главных пакета. Первый пакет — ваше приложение (com.kciray), и класс Main.java внутри него. Второй пакет — org.springframework. Да, мы будем дублировать структуру пакетов оригинального спринга, название его классов и их методов. Есть такой интересный эффект — когда вы создаете что-то свое, это свое начинает казаться простым и понятным. Потом, когда вы будете работать в больших проектах, вам будет казаться, что там все создано на основе вашей заготовки. Такой подход может очень положительно сказаться на понимании работы системы в целом, её улучшении, исправлении багов, решении проблем и так далее.

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

Создаём контейнер

Для начала, поставим задачу. Представим, что у нас есть 2 класса — ProductFacade и PromotionService. Теперь представим, что вы хотите связать эти классы между собой, но так, чтобы сами классы не знали друг о друге (Паттерн DI). Нужен какой-то отдельный класс, который будет управлять всеми этими классами и определять зависимости между ними. Назовём его контейнер. Создадим класс Container… Хотя нет, подождите! В Spring нету единого класса-контейнера. У нас есть много реализаций контейнеров, и все эти реализации можно разделить на 2 типа — фабрики бинов и контексты. Фабрика бинов создаёт бины и связывает их между собой (инъекция зависимостей, DI), а контекст делает примерно то же самое, плюс ещё добавляет некоторые дополнительные функции (например, интернационализация сообщений). Но эти дополнительные функции нам не нужны сейчас, поэтому будем работать с фабрикой бинов.

Создайте новый класс BeanFactory и поместите его в пакет org.springframework.beans.factory. Пускай внутри этого класса хранится Map<String, Object> singletons, в которой id бина замапено на сам бин. Добавьте к нему метод Object getBean(String beanName), который вытаскивает бины по идентификатору.

public class BeanFactory {
    private Map<String, Object> singletons = new HashMap();

    public Object getBean(String beanName){
        return singletons.get(beanName);
    }
}

Обратите внимание на то, что BeanFactory и FactoryBean — это разные вещи. Первое — это фабрика бинов (контейнер), а второе — это бин-фабрика, который сидит внутри контейнера и тоже производит бины. Фабрика внутри фабрики. Если вы путаетесь между этими определениями, можете запомнить, что в английском языке второе существительное является ведущим, а первое — служит чем-то типа прилагательного. В слове BeanFactory Главным словом является фабрика, а в FactoryBean — бин.

Теперь, создадим классы ProductService и PromotionsService. ProductService будет возвращать продукт из БД, но перед этим нужно проверить, применимы ли к этому продукту какие-либо скидки (Promotions). В электронной коммерции работу со скидками часто выделяют в отдельный класс-сервис (а иногда и в сторонний веб-сервис).

public class PromotionsService {

}

public class ProductService {
    private PromotionsService promotionsService;

    public PromotionsService getPromotionsService() {
        return promotionsService;
    }

    public void setPromotionsService(PromotionsService promotionsService) {
        this.promotionsService = promotionsService;
    }
}

Теперь нам надо сделать так, чтобы наш контейнер (BeanFactory) обнаружил наши классы, создал их за нас и инжектировал один в другой. Операции типа new ProductService() должны находится внутри контейнера и делаться за разработчика. Давайте используем самый современный подход (сканирование классов и аннотации). Для этого нам нужно ручками создать аннотацию @Component (пакет org.springframework.beans.factory.stereotype).

@Retention(RetentionPolicy.RUNTIME)
public @interface Component {

}

По умолчанию аннотации не загружаются в память во время работы программы (RetentionPolicy.CLASS). Мы изменили данное поведение через новую политику удержания (RetentionPolicy.RUNTIME).

Теперь добавьте @Component перед классами ProductService и перед PromotionService.

@Component
public class ProductService {
    //...
}
@Component
public class PromotionService {
    //...
}

Нам нужно, чтобы BeanFactory сканировал наш пакет (com.kciray) и находил в нем классы, которые аннотированы @Component. Эта задача совсем не тривиальная. В Java Core нет готового решения, и нам придётся делать костыль самому. Тысячи приложений на спринге используют сканирование компонентов через этот костыль. Вы узнали страшную правду. Вам придется извлекать из ClassLoader названия файлов и проверять, заканчиваются они на «.class» или нет, а потом строить их полное имя и вытаскивать по нему объекты классов!

Сразу хочу предупредить, что будет много проверяемых исключений, поэтому будьте готовы их оборачивать. Но для начала, давайте определимся, чего мы хотим. Мы хотим добавить специальный метод в BeanFactory и вызывать его в Main:

//BeanFactory.java
public class BeanFactory{
    public void instantiate(String basePackage) {

    }
}

//Main.java
BeanFactory beanFactory = new BeanFactory();
beanFactory.instantiate("com.kciray");

Далее, нам нужно получить ClassLoader. Он отвечает за загрузку классов, и добывается довольно просто:

ClassLoader classLoader = ClassLoader.getSystemClassLoader();

Наверно вы уже заметили, что пакеты разделяются точкой, а файлы — прямым слешем. Нам надо преобразовать пакетный путь в путь к папке, и получить что-то типа List<URL> (пути в вашей файловой системе, по которым можно искать class-файлы).

String path = basePackage.replace('.', '/'); //"com.kciray" -> "com/kciray"
Enumeration<URL> resources = classLoader.getResources(path);

Так, подождите! Enumeration<URL> это не List<URL>. Что это вообще такое? О ужас, это же старый прародитель Iterator, доступный ещё с времен Java 1.0. Это легаси, с которым нам приходится иметь дело. Если по Iterable можно пройтись с помощью for (все коллекции его реализуют), то в случае Enumeration вам придётся делать обход ручками, через while(resources.hasMoreElements()) и nextElement(). И ещё там нет возможности удалять элементы из коллекции. Только 1996 год, только хардкор. Ах да, в Java 9 добавили метод Enumeration.asIterator(), так что можете работать через него.

Поехали дальше. Нам надо извлечь папки и проработать содержимое каждой из них. Преобразуем URL в файл, а затем получаем его имя. Тут надо отметить, что мы не будем сканировать вложенные пакеты, чтобы не усложнять код. Можете усложнить себе задачу и сделать рекурсию, если есть желание.

while (resources.hasMoreElements()) {
    URL resource = resources.nextElement();

    File file = new File(resource.toURI());
    for(File classFile : file.listFiles()){
        String fileName = classFile.getName();//ProductService.class

    }
}

Дальше, нам нужно получить название файла без расширения. На дворе 2018 год, Java много лет развивала File I/O (NIO 2), но до сих пор не может отделить расширение от имени файла. Приходится свой велосипед создавать, т.к. мы решили не использовать сторонние библиотеки вроде Apache Commons. Давайте используем старый дедовский способ lastIndexOf("."):

if(fileName.endsWith(".class")){
    String className = fileName.substring(0, fileName.lastIndexOf("."));
}

Далее, мы можем по полному имени класса получить объект класса (для этого вызываем класс класса Class):

Class classObject = Class.forName(basePackage + "." + className);

Окей, теперь наши классы в наших руках. Далее, осталось только выделить среди них те, что имеют аннотацию @Component:

if(classObject.isAnnotationPresent(Component.class)){
    System.out.println("Component: " + classObject);
}

Запустите и проверьте. В консоли должно быть что-то вроде этого:

Component: class com.kciray.ProductService
Component: class com.kciray.PromotionsService

Теперь нам нужно создать наш бин. Надо сделать что-то вроде new ProductService(), но для каждого бина у нас свой класс. Рефлексия в Java предоставляет нам универсальное решение (вызывается конструктор по-умолчанию):

Object instance = classObject.newInstance();//=new CustomClass()

Далее, нам нужно поместить этот бин в Map<String, Object> singletons. Для этого нужно выбрать имя бина (его id). В Java мы называем переменные подобно классам (только первая буква в нижнем регистре). Данный подход может быть применим к бинам тоже, ведь Spring — это Java-фреймворк! Преобразуйте имя бина так, чтобы первая буква была маленькая, и добавьте его в мапу:

String beanName = className.substring(0, 1).toLowerCase() + className.substring(1);
singletons.put(beanName, instance);

Теперь убедитесь в том, что всё работает. Контейнер должен создавать бины, и они должны извлекаться по имени. Обратите внимание на то, что название вашего метода instantiate() и название метода classObject.newInstance(); имеют общий корень. Более того, instantiate() — это часть жизненного цикла бина. В джаве всё взаимосвязано!

//Main.java
BeanFactory beanFactory = new BeanFactory();
beanFactory.instantiate("com.kciray");
ProductService productService = (ProductService) beanFactory.getBean("productService");
System.out.println(productService);//ProductService@612

Попробуйте также реализовать аннотацию org.springframework.beans.factory.stereotype.Service. Она выполняет абсолютно ту же функцию, что и @Component, но называется по-другому. Весь смысл заключён в названии — вы демонстриуете, что класс является сервисом, а не просто компонентом. Это что-то типа концептуальной типизации. В сертификации по спрингу был вопрос «Какие аннотации являются стереотипными? (из перечисленных)». Так вот, стереотипные аннотации — это те, которые находятся в пакете stereotype.

Наполняем свойства

Посмотрите на схему ниже, на ней представлено начало жизненного цикла бина. То, что мы делали до этого, это Instantiate (создание бинов через newInstance()). Следующий этап — это перекрестное инжектирование бинов (инъекция зависимостей, она же инверсия контроля (IoC)). Нужно пройтись по свойствам бинов и понять, какие именно свойства нужно заинжектить. Если вы сейчас вызовете productService.getPromotionsService(), то получите null, т.к. зависимость ещё не добавлена.

Для начала, создадим пакет org.springframework.beans.factory.annotation и добавим в него аннотацию @Autowired. Идея в том, чтобы помечать этой аннотацией те поля, которые являются зависимостями.

@Retention(RetentionPolicy.RUNTIME)
public @interface Autowired {
}

Далее, добавим её к свойству:

@Component
public class ProductService {
    @Autowired
    PromotionsService promotionsService;

    //...
}

Теперь нам нужно научить наш BeanFactory находить эти аннотации и инжектировать зависимости по ним. Добавим отдельный метод для этого, и вызовем его из Main:

public class BeanFactory {
    //...
    public void populateProperties(){
        System.out.println("==populateProperties==");

    }
}

Далее, нам нужно всего-лишь пройтись по всем нашим бинам в мапе singletons, и для каждого бина пройтись по всем его полям (метод object.getClass().getDeclaredFields() возвращает все поля, включая приватные). И проверить, есть ли у поля аннотация @Autowired:

for (Object object : singletons.values()) {
    for (Field field : object.getClass().getDeclaredFields()) {
        if (field.isAnnotationPresent(Autowired.class)) {

        }
    }
}

Далее, нам надо ещё разок пройтись по всем бинам и посмотреть их тип — вдруг это тот тип, который хочет себе взять наш бин. Да, у нас получается трехмерный цикл!

for (Object dependency : singletons.values()) {
    if (dependency.getClass().equals(field.getType())) {

    }
}

Далее, когда мы нашли зависимость, надо её заинжектить. Первое что вам может прийти в голову — это записать поле promotionsService с помощью рефлексии напрямую. Но спринг так не работает. Ведь если поле имеет модификатор private, то нам придется сначала установить его как public, потом записать наше значение, потом снова установить в private (чтобы сохранить целостность). Звучит как большой костыль. Давайте вместо большого костыля сделаем маленький костыль (сформируем название сеттера и вызовем его):

String setterName = "set" + field.getName().substring(0, 1).toUpperCase() + field.getName().substring(1);//setPromotionsService
System.out.println("Setter name = " + setterName);
Method setter = object.getClass().getMethod(setterName, dependency.getClass());
setter.invoke(object, dependency);

Теперь запустите ваш проект и убедитесь, что при вызове productService.getPromotionsService() вместо null возвращается наш бин.

То, что мы реализовали — это инъекция по типу. Есть ещё инъекция по имени (аннотация javax.annotation.Resource). Отличается она тем, что вместо типа поля будет извлекаться его имя, и по нему — зависимость из мапы. Тут всё аналогично, даже в чем-то проще. Я рекомендую вам поэкспериментировать и создать какой-нибудь свой бин, а потом заинжектить его с помощью @Resource и расширить метод populateProperties().

Поддерживаем бины, знающие о своем имени

Бывают случаи, когда внутри бина нужно получить его имя. Такая потребность возникает не часто, т.к. бины, по своей сути, не должны знать друг о друге и о том, что они бины. В первых версиях спринга предполагалось, что бин — это POJO (Plain Old Java Objec, старый добрый Джава-объект), а вся конфигурация вынесена в XML-файлы и отделена от реализации. Но мы реализуем данный функционал, так как инъекция имени — это часть жизненного цикла бина.

Как нам узнать, какой бин хочет узнать, как его зовут, а какой не хочет? Первое, что приходит в голову — это сделать новую аннотацию типа @InjectName и лепить её на поля типа String. Но это решение будет слишком общим и позволяет выстрелить себе в ногу много раз (разместить эту аннотацию на полях неподходящих типов (не String), или же пытаться инжектировать имя в несколько полей в одном классе). Есть другое решение, более аккуратное — создать специальный интерфейс с одним методом-сеттером. Все бины, что его реализуют — получает своё имя. Создайте класс BeanNameAware в пакете org.springframework.beans.factory:

public interface BeanNameAware {
    void setBeanName(String name);
}

Далее, пускай наш PromotionsService его реализует:

@Component
public class PromotionsService implements BeanNameAware {
    private String beanName;

    @Override
    public void setBeanName(String name) {
        beanName = name;
    }

    public String getBeanName() {
        return beanName;
    }
}

И, наконец, добавим новый метод в фабрику бинов. Тут всё просто — мы проходимся по нашим бинам-синглтонам, проверяем, реализует ли бин наш интерфейс, и вызываем сеттер:

public void injectBeanNames(){
    for (String name : singletons.keySet()) {
        Object bean = singletons.get(name);
        if(bean instanceof BeanNameAware){
            ((BeanNameAware) bean).setBeanName(name);
        }
    }
}

Запустите и убедиесь, что всё работает:

BeanFactory beanFactory = new BeanFactory();
beanFactory.instantiate("com.kciray");
beanFactory.populateProperties();
beanFactory.injectBeanNames();

//...

System.out.println("Bean name = " + promotionsService.getBeanName());

Надо отметить, что в спринге есть и другие подобные интерфейсы. Я рекомендую вам самостоятельно реализовать интерфейс BeanFactoryAware, который позволит бинам получать ссылку на фабрику бинов. Реализуется он аналогично.

Инициализируем бины

Представим, что у вас возникла ситуация, когда нужно выполнить некоторый код после того, как зависимости были проинжектированы (свойства бина установлены). Говоря простым языком, нам нужно предоставить бину возможность инициализировать самого себя. Как вариант, мы можем создать интерфейс InitializingBean, и в него поместить сигнатуру метода void afterPropertiesSet(). Реализация данного механизма абсолютно аналогична той, что была представлена для интерфейса BeanNameAware, поэтому решение под спойлером. Потренируйтесь и сделайте его самостоятельно за минуту:

Решение для инициализации бина

//InitializingBean.java
package org.springframework.beans.factory;

public interface InitializingBean {
    void afterPropertiesSet();
}

//BeanFactory.java
public void initializeBeans(){
    for (Object bean : singletons.values()) {
        if(bean instanceof InitializingBean){
            ((InitializingBean) bean).afterPropertiesSet();
        }
    }
}

//Main.java
beanFactory.initializeBeans();

Добавляем пост-процессоры

Представьте себя на месте первых разработчиков спринга. Ваш фреймворк растёт и пользуется огромной популярностью среди девелоперов, на почту каждый день приходят письма с просьбами добавить ту или иную полезную фичу. Если для каждой такой фичи добавлять свой собственный интерфейс и проверять его в жизенном цикле бина, то он (жизненный цикл) окажется засоренным ненужной информацией. Вместо этого, мы можем создать один универсальный интерфейс, который позволит добавлять выполнение некоторой логики (абсолютно любой, будь то проверка на аннотацию, замена бина на другой бин, установка некоторых особых свойств и так далее).

Давайте подумаем, для чего предназначен данный интерфейс. Он должен производить некоторую пост-обработку бинов, следовательно его можно назвать BeanPostProcessor. Но перед нами стоит непростой вопрос — когда следует выполнять логику? Ведь мы можем выполнить её до инициализации, а можем выполнить и после. Для одних задач лучше подходит первый вариант, для других — второй… Как быть?

Мы можем позволить оба варианта сразу. Пускай один пост-процессор несёт две логики, два метода. Один выполняется до инициализации (до метода afterPropertiesSet()), а другой — после. Теперь давайте задумаемся над самими методами — какие параметры у них должны быть? Очевидно, что там должен быть сам бин (Object bean). Для удобства, кроме бина можно передавать имя этого бина. Вы же помните, что бин сам по себе не знает о своём имени. И мы не хотим заставлять все бины реализовывать интерфейс BeanNameAware. Но, на уровне пост-процессора, имя бина может очень даже пригодиться. Поэтом удобавляем его как второй параметр.

А что должен возвращать метод при пост-обработке бина? Сделаем так, чтобы он возвращал сам бин. Это даёт нам супер-гибкость, ведь вместо бина можно подсунуть прокси-объект, который оборачивает его вызовы (и добавляет секьюрити). А можно и вовсе вернуть другой объект, пересоздав бин заново. Разработчикам даётся очень большая свобода действия. Ниже представлена окончательная версия спроектированного интерфейса:

package org.springframework.beans.factory.config;

public interface BeanPostProcessor {
    Object postProcessBeforeInitialization(Object bean, String beanName);
    Object postProcessAfterInitialization(Object bean, String beanName);
}

Далее, нам нужно добавить список из прост-процессоров к нашей фабрике бинов и возможность добавлять новые. Да, это обычный ArrayList.

//BeanFactory.java
private List<BeanPostProcessor> postProcessors = new ArrayList<>();
public void addPostProcessor(BeanPostProcessor postProcessor){
    postProcessors.add(postProcessor);
}

Теперь поменяем метод initializeBeans так, чтобы он учитывал пост-процессоры:

public void initializeBeans() {
    for (String name : singletons.keySet()) {
        Object bean = singletons.get(name);
        for (BeanPostProcessor postProcessor : postProcessors) {
            postProcessor.postProcessBeforeInitialization(bean, name);
        }
        if (bean instanceof InitializingBean) {
            ((InitializingBean) bean).afterPropertiesSet();
        }
        for (BeanPostProcessor postProcessor : postProcessors) {
            postProcessor.postProcessAfterInitialization(bean, name);
        }
    }
}

Давайте создадим небольшой пост-процессор, который просто трассирует вызовы в консоль, и добавим его в нашу фабрику бинов:

public class CustomPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) {
        System.out.println("---CustomPostProcessor Before " + beanName);
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) {
        System.out.println("---CustomPostProcessor After " + beanName);
        return bean;
    }
}

//Main.java
BeanFactory beanFactory = new BeanFactory();
beanFactory.addPostProcessor(new CustomPostProcessor());

Теперь запустите и убедитесь, что всё работает. В качестве тренировочного задания создайте пост-процессор, который будет обеспечивать работу аннотации @PostConstruct (javax.annotation.PostConstruct). Она предоставляет альтернативный способ инициализации (имеющий корни в Java, а не в спринге). Суть его в том, что вы размещаете аннотацию на некотором методе, и этот метод будет вызван ПЕРЕД стандартной спринговой инициализацией (InitializingBean).

Обязательно создавайте все аннотации и пакеты (даже javax.annotation) вручную, не подключайте зависимости! Это поможет вам увидеть разницу между ядром спринга и его расширениями (поддержка javax), и запомнить её. Это позволит придерживаться одного стиля в будущем.

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

На последок, я вам рекомендую добавить метод void close() в класс BeanFactory и отработать ещё два механизма. Первый — аннотация @PreDestroy (javax.annotation.PreDestroy), предназначена для методов, которые должны быть вызваны при закрытии контейнера. Второй — интерфейс org.springframework.beans.factory.DisposableBean, который содержит метод void destroy(). Все бины, исполняющие данный интерфейс, будут иметь возможность сами себя уничтожить (освободить ресурсы, например).

@PreDestroy + DisposableBean

//DisposableBean.java
package org.springframework.beans.factory;

public interface DisposableBean {
    void destroy();
}

//PreDestroy.java
package javax.annotation;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface PreDestroy {
}

//DisposableBean.java
public void close() {
    for (Object bean : singletons.values()) {
        for (Method method : bean.getClass().getMethods()) {
            if (method.isAnnotationPresent(PreDestroy.class)) {
                try {
                    method.invoke(bean);
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                }
            }
        }
        if (bean instanceof DisposableBean) {
            ((DisposableBean) bean).destroy();
        }
    }
}

Полный жизненный цикл бина


Вот мы и реализовали полный жизненный цикл бина, в его современном виде. Я надеюсь, что такой подход поможет вам его запомнить.

Наш любимый контекст

Программисты очень часто используют термин контекст, но не все понимают, что же он на самом деле значит. Сейчас мы расставим всё по-полочкам. Как я уже отметил в начале статьи, контекст — это реализация контейнера, как и BeanFactory. Но, кроме базовых функций (DI), она ещё добавляет некоторые крутые фичи. Одна из таких фич — это отправка и обработка событий между бинами.

Статья получилась слишком большой и содержимое стало обрезаться, поэтому я поместил информацию по контексту под спойлер.

Реализуем контект

Начнем с заготовки контекста. Создайте пакет org.springframework.context, и класс ApplicationContext внутри него. Пусть он содержит внутри себя экземпляр класса BeanFactory. Все этапы инициализации поместим в конструктор, а также добавим перенаправление метода close().

public class ApplicationContext {
    private BeanFactory beanFactory = new BeanFactory();

    public ApplicationContext(String basePackage) throws ReflectiveOperationException{
        System.out.println("******Context is under construction******");

        beanFactory.instantiate(basePackage);
        beanFactory.populateProperties();
        beanFactory.injectBeanNames();
        beanFactory.initializeBeans();
    }

    public void close(){
        beanFactory.close();
    }
}

Добавьте его в класс Main, запустите и убедитесь, что он работает:

ApplicationContext applicationContext = new ApplicationContext("com.kciray");
applicationContext.close();

Теперь давайте подумаем, как организовать события. Поскольку у нас уже есть метод close(), мы можем создать событие «Закрытие контекста» и перехватить его внутри какого-нибудь бина. Создайте простой класс, представляющий данное событие:

package org.springframework.context.event;

public class ContextClosedEvent {
} 

Теперь нам надо создать интерфейс ApplicationListener, который позволит бинам слушать наши события. Поскольку мы решили представлять события в виде классов, то имеет смысл типизировать этот интерфейс по классу события (ApplicationListener<E>). Да, мы будем использовать Java-дженерики, и вы получите немножко опыта по работе с ними. Далее, вам нужно придумать название для метода, который будет обрабатывать событие:

package org.springframework.context;

public interface ApplicationListener<E>{
    void onApplicationEvent(E event);
}

Теперь вернёмся к классу ApplicationContext. Нам нужно в методе close() пройтись по всем нашим бинам, и выяснить, какие из них являются слушателями событий. Если бин заимплементил ApplicationListener<ContextClosedEvent>, значит нужно вызвать его onApplicationEvent(ContextClosedEvent). Кажется просто и логично, не так ли?

public void close(){
    beanFactory.close();
    for(Object bean : beanFactory.getSingletons().values()) {
        if (bean instanceof ApplicationListener) {

        }
    }
}

Но нет. Тут возникает трудность. Мы НЕ МОЖЕМ сделать проверку типа bean instanceof ApplicationListener<ContextClosedEvent>. Это связано с особенностью реализации Java. При компиляции происходит так называемая очистка типов (type erasure), при которой все <T> заменяются на <Object>. Как же быть, что же делать? Как нам выловить бины, которые имплементят именно ApplicationListener<ContextClosedEvent>, а не другие типы событий?

На самом деле, информация о типах сохраняется в метаданных класса, и очень даже успешно извлекается через рефлексию. Для начала, нам нужно получить список из интерфейсов, которые данный бин реализует, и отфильтровать среди них те, которые имеют параметры:

for (Type type: bean.getClass().getGenericInterfaces()){
    if(type instanceof ParameterizedType){
        ParameterizedType parameterizedType = (ParameterizedType) type;

    }
}

Далее, мы всего лишь извлекаем тип первого параметра, и убеждаемся, что он — наш класс события. Если это так, мы получаем наш метод через рефлексию и вызываем его:

Type firstParameter = parameterizedType.getActualTypeArguments()[0];
if(firstParameter.equals(ContextClosedEvent.class)){
    Method method = bean.getClass().getMethod("onApplicationEvent", ContextClosedEvent.class);
    method.invoke(bean, new ContextClosedEvent());
}

Пускай один из ваших классов реализует интерфейс ApplicationListener:

@Service
public class PromotionsService implements BeanNameAware, ApplicationListener<ContextClosedEvent> {
    //...

    @Override
    public void onApplicationEvent(ContextClosedEvent event) {
        System.out.println(">> ContextClosed EVENT");
    }
}

Далее, тестируете ваш контекст в Main и убеждаетесь, что он также работает, и событие отправляется:

//Main.java
void testContext() throws ReflectiveOperationException{
    ApplicationContext applicationContext = new ApplicationContext("com.kciray");
    applicationContext.close();
}

Заключение

Изначально я планировал данную статью для Baeldung на английском, но потом подумал, что аудитория хабры может положительно оценить данный подход к обучению. Если вам понравились мои идеи, обязательно поддержите статью. Если она наберёт рейтинг более 30, то обещаю продолжение. При написании статьи, я старался показать именно те знания Spring Core, которе используются наиболее часто, а также с опорой на Core Spring 5.0 Certification Study Guide. В будущем, с помощью таких туториалов можно покрыть всю сертификацию и сделать спринг более доступным для Java-разработчиков.

Update 10/05/2018

Мне постоянно приходят письма с вопросами «а когда продолжение, мы его ждём». Но вот времени совсем нету, и другие личные проекты в приоритете. Однако, если кому-то из вас действительно понравилась идея, вы можете изучить узкий раздел спринга и написать статью-продолжение. Если у вас нету аккаунта хабры, то я могу опубликовать статью от моего акка или помочь вам получить инвайт.

Распределение тем:
Spring Container — [имя пользователя]
Spring AOP — [имя пользователя]
Spring Web — [имя пользователя]
Spring Cloud — [имя пользователя]

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

Тема для следующей статьи


50.23%
Spring Container, углубляемся ещё больше (@Bean, @Configuration, Context, Prototype, XML)
110


11.42%
Spring AOP, пишем прокси своими руками (@Aspect, @Pointcut)
25


29.68%
Spring Web, веб-сервер с нуля (@Contoller, @RequestMapping)
65

Проголосовали 219 пользователей.

Воздержались 28 пользователей.

Discover the secrets of the Spring Framework. From the worlds of IOC (Inversion of Control), DI (Dependency Injection), and Application Context to Spring Boot, AOP, JDBC, and JPA. Prepare for an amazing adventure.

Spring Framework is still as popular now as it was 12 years ago when I first used it.

How is this feasible in a very dynamic environment where designs have altered dramatically?

Course Link

Image

What You will learn

  • Spring Framework fundamentals will be covered, including Dependency Injection, IOC Container, Application Context, and Bean Factory.
  • You will be able to utilise Spring Annotations such as @Autowired, @Component, @Service, @Repository, @Configuration, @Primary….
  • You will get a thorough understanding of Spring MVC, including DispatcherServlet, Model, Controllers, and ViewResolver.
  • Spring Boot Starters such as Spring Boot Starter Web, Starter Data Jpa, and Starter Test will be used.
  • Spring Boot, Spring AOP, Spring JDBC, and JPA fundamentals will be covered.
  • You’ll study the fundamentals of Eclipse, Maven, JUnit, and Mockito.
  • Step by step, you will create a simple Web application utilising JSP Servlets and Spring MVC.
  • You will learn to construct unit tests with XML, Java Application Contexts and Mockito.

Getting Started

Installing Tools

  • Installation
    • Video : https://www.youtube.com/playlist?list=PLBBog2r6uMCSmMVTW_QmDLyASBvovyAO3
    • PDF : https://github.com/in28minutes/SpringIn28Minutes/blob/master/InstallationGuide-JavaEclipseAndMaven_v2.pdf
    • More Details : https://github.com/in28minutes/getting-started-in-5-steps
  • Troubleshooting
    • A 50 page troubleshooting guide with more than 200 Errors and Questions answered

Running Examples

  • Clone the Git repository or download the zip file.
  • Unzip the zip archive (if you downloaded one)
  • Open Command Prompt and go to the folder containing pom.xml.
  • Open Eclipse
    • File -> Import -> Existing Maven Project -> Navigate to the folder where you unzipped the zip
    • Select the right project
  • Search for the Spring Boot Application file (@SpringBootApplication).
  • Right-click the file and select Run as Java Program.
  • You are all Set
  • For help : use our installation guide — https://www.youtube.com/playlist?list=PLBBog2r6uMCSmMVTW_QmDLyASBvovyAO3

Spring Level 1 through Level 6 Section Overview

Title Category Github
Spring Framework in 10 Steps Spring — Level 1 Project Folder on Github
Spring in Depth Spring — Level 2 Project Folder on Github
Unit Testing with Spring Framework Spring — Level 3 Project Folder on Github
Spring Boot in 10 Steps Spring — Level 4 Project Folder on Github
Spring AOP Spring — Level 5 Project Folder on Github
Spring JDBC and JPA Spring — Level 6 Project Folder on Github
Title Category Github
Eclipse in 5 Steps Introduction Project Folder on Github
Maven in 5 Steps Introduction Project Folder on Github
JUnit in 5 Steps Introduction Project Folder on Github
Mockito in 5 Steps Introduction Project Folder on Github
Basic Web Application with Spring MVC Introduction Project Folder on Github

Section Details

Spring Level 1 — First 10 Steps in Spring

Title Category Github
Spring Framework in 10 Steps Spring — Level 1 Project Folder on Github
  • Step 1 : Setting up a Spring Project using htttp://start.spring.io
  • Step 2 : Understanding Tight Coupling using the Binary Search Algorithm Example
  • Step 3 : Making the Binary Search Algorithm Example Loosely Coupled
  • Step 4 : Using Spring to Manage Dependencies — @Component, @Autowired
  • Step 5 : What is happening in the background?
  • Step 6 : Dynamic auto wiring and Troubleshooting — @Primary
  • Step 7 : Constructor and Setter Injection
  • Step 8 : Spring Modules
  • Step 9 : Spring Projects
  • Step 10 : Why is Spring Popular?

Step 1 : Setting up a Spring Project using htttp://start.spring.io

Dependency injection is a crucial element of the Spring Framework. The Spring framework aids in the development of loosely linked applications. To appreciate dependency injection, you must first understand tight coupling and how to build loosely linked programmes. To begin, we will construct a basic example in order to comprehend tight couplings and dependency injection.

Making a Spring Project using Spring Initializr is a piece of cake.

Spring Initializr http://start.spring.io/ is great tool to bootstrap your Spring Boot projects.

Image

As shown in the image above, following steps have to be done

  • Launch Spring Initializr and choose the following
    • Choose com.in28minutes.spring.basics as Group
    • Choose spring-in-5-steps as Artifact
    • Do not choose any dependencies
      • By default Basic Starter is included, which has the core spring framework and the spring test starter.
  • Click Generate Project.
  • Import the project into Eclipse.
  • If you want to understand all the files that are part of this project, you can go here.

Step 2 : Understanding Tight Coupling using the Binary Search Algorithm Example

Create a tight coupling example using the Binary Search and Bubble Sort Algorithms, as seen in the image below.

Image

Unfortunately, there is an issue with the aforementioned implementation. I’d have to update the code if we wanted to utilise binary search with a new sort technique.

We wish to loosely link the binary search algorithm such that it can operate with any algorithm.

Consider about the solution before advancing to next stage!

Step 3 : Making the Binary Search Algorithm Example Loosely Coupled

Provide an interface to loosely tie the Binary Search to the sort algorithm.

package com.in28minutes.spring.basics.springin5steps;

public interface SortAlgorithm {
	public int[] sort(int[] numbers);
}
public class BinarySearchImpl {

	private SortAlgorithm sortAlgorithm;

Step 4 : Using Spring to Manage Dependencies — @Component, @Autowired

We developed code to construct objects for the bubble sort algorithm and binary search in the previous phases. We also took care of the dependencies. It would be wonderful indeed if some framework can take charge of generation of the beans and autowiring the dependencies.

This is where Spring Framework comes into play!

Let’s get started with autowiring using Spring.

Notes

  • Sort algorithm is a dependency of the binary search.
@Component
public class BinarySearchImpl {

	@Autowired
	private SortAlgorithm sortAlgorithm;
@Component
public class BubbleSortAlgorithm implements SortAlgorithm {
	public int[] sort(int[] numbers) {
		// Logic for Bubble Sort
		return numbers;
	}
}

Step 5 : What is happening in the background?

Activate debug logging to see what’s going on in the background.

/src/main/resources/application.properties

logging.level.org.springframework = debug
  • Spring does a Component scan on the parent package “com.in28minutes.spring.basics.springin5steps” to find the components — classes that have @Component on them.
  • It identifies components and dependencies
  • It identifies that BinarySearchImpl has a dependency SortAlgorithm
  • It identifies that SortAlgorithm does not have a dependency. So, it creates an instance of it and autowires it into an instance of BinarySearchImpl

Step 6 : Dynamic auto wiring and Troubleshooting — @Primary

What if we add one more SortAlgorithm?

package com.in28minutes.spring.basics.springin5steps;

import org.springframework.stereotype.Component;

@Component
public class QuickSortAlgorithm implements SortAlgorithm {
	public int[] sort(int[] numbers) {
		// Logic for Quick Sort
		return numbers;
	}
}

There are now two SortAlgorithm instances that match. Spring throws an exception because it is unsure which to utilise.

We use @Primary to indicate which SortAlgorithm implementation is the most critical!

package com.in28minutes.spring.basics.springin5steps;

import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;

@Component
@Primary
public class BubbleSortAlgorithm implements SortAlgorithm {
	public int[] sort(int[] numbers) {
		// Logic for Bubble Sort
		return numbers;
	}
}

Step 7 : Constructor and Setter Injection

Constructor Injection
Image

Setter Injection
Image

Step 8 : Spring Modules

Spring is built in a very modular way and this enables use to use specific modules without using the other modules of Spring.

Image

Step 9 : Spring Projects

Spring projects provide solutions for different problems faced by enterprise applications.

Image

What we’re looking at are seven of the Spring projects that are just touching the tip of the iceberg. There are a lot of other Spring projects like Spring webservices, Spring session, Spring social, Spring mobile and Spring Android which are solving problems in various different spaces.

Spring has not really restricted itself to just the Spring framework and got involved in a lot of wide variety of projects.

Step 10 : Why is Spring Popular?

Image
Spring is one of the very few frameworks that remains as popular today as it was 15 years back.

Spring Level 2 — Spring in depth

Title Category Github
Spring in Depth Spring — Level 2 Project Folder on Github
  • Step 11 — Dependency Injection — A few more examples
  • Step 12 — Autowiring in Depth — by Name and @Primary
  • Step 13 — Autowiring in Depth — @Qualifier annotation
  • Step 14 — Scope of a Bean — Prototype and Singleton
  • Step 15 — Complex scenarios with Scope of a Spring Bean — Mix of Prototype and Singleton
  • Step 15B — Difference Between Spring Singleton and GOF Singleton
  • Step 16 — Using Component Scan to scan for beans
  • Step 17 — Lifecycle of a Bean — @PostConstruct and @PreDestroy
  • Step 18 — Container and Dependency Injection (CDI) — @Named, @Inject
  • Step 19 — Removing Spring Boot in Basic Application
  • Step 20 — Fixing minor stuff — Add Logback and Close Application Context
  • Step 21 — Defining Spring Application Context using XML — Part 1
  • Step 22 — Defining Spring Application Context using XML — Part 2
  • Step 23 — Mixing XML Context with Component Scan for Beans defined with Annotations
  • Step 24 — IOC Container vs Application Context vs Bean Factory
  • Step 25 — @Component vs @Service vs @Repository vs @Controller
  • Step 26 — Read values from external properties file

Step 11 — Dependency Injection — A few more examples

Step 12 — Autowiring in Depth — by Name and @Primary

Step 13 — Autowiring in Depth — @Qualifier annotation

@Component
@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
public class BinarySearchImpl {

	@Autowired
	@Qualifier("bubble")
	private SortAlgorithm sortAlgorithm;
@Component
@Qualifier("bubble")
public class BubbleSortAlgorithm implements SortAlgorithm {
@Component
@Qualifier("quick")
public class QuickSortAlgorithm implements SortAlgorithm {

Step 14 — Scope of a Bean — Prototype and Singleton

Step 15 — Complex scenarios with Scope of a Spring Bean — Mix of Prototype and Singleton

package com.in28minutes.spring.basics.springin5steps.scope;

import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.stereotype.Component;

@Component
@Scope(value=ConfigurableBeanFactory.SCOPE_PROTOTYPE, 
		proxyMode = ScopedProxyMode.TARGET_CLASS)
public class JdbcConnection {
	public JdbcConnection() {
		System.out.println("JDBC Connection");
	}
}
package com.in28minutes.spring.basics.springin5steps.scope;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class PersonDAO {

	@Autowired
	JdbcConnection jdbcConnection;

	public JdbcConnection getJdbcConnection() {
		return jdbcConnection;
	}

	public void setJdbcConnection(JdbcConnection jdbcConnection) {
		this.jdbcConnection = jdbcConnection;
	}
}
package com.in28minutes.spring.basics.springin5steps;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;

import com.in28minutes.spring.basics.springin5steps.scope.PersonDAO;

@SpringBootApplication
public class SpringIn5StepsScopeApplication {
	
	private static final Logger LOGGER = 
			LoggerFactory.getLogger(SpringIn5StepsScopeApplication.class); 
	
	public static void main(String[] args) {

		var applicationContext = 
				SpringApplication.run(SpringIn5StepsScopeApplication.class, args);
		
		var personDao = 
				applicationContext.getBean(PersonDAO.class);
		
		var personDao2 = 
				applicationContext.getBean(PersonDAO.class);
		
		LOGGER.info("{}", personDao);
		LOGGER.info("{}", personDao.getJdbcConnection());
		
		LOGGER.info("{}", personDao2);
		LOGGER.info("{}", personDao.getJdbcConnection());
		
	}
}

Step 15B — Difference Between Spring Singleton and GOF Singleton

Step 16 — Using Component Scan to scan for beans

package com.in28minutes.spring.basics.componentscan;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class ComponentDAO {

	@Autowired
	ComponentJdbcConnection jdbcConnection;

	public ComponentJdbcConnection getJdbcConnection() {
		return jdbcConnection;
	}

	public void setComponentJdbcConnection(ComponentJdbcConnection jdbcConnection) {
		this.jdbcConnection = jdbcConnection;
	}
}
package com.in28minutes.spring.basics.componentscan;

import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.stereotype.Component;

@Component
@Scope(value=ConfigurableBeanFactory.SCOPE_PROTOTYPE, 
		proxyMode = ScopedProxyMode.TARGET_CLASS)
public class ComponentJdbcConnection {
	public ComponentJdbcConnection() {
		System.out.println("JDBC Connection");
	}
}
package com.in28minutes.spring.basics.springin5steps;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.ComponentScan;

import com.in28minutes.spring.basics.componentscan.ComponentDAO;

@SpringBootApplication
@ComponentScan("com.in28minutes.spring.basics.componentscan")
public class SpringIn5StepsComponentScanApplication {
	
	private static Logger LOGGER = 
			LoggerFactory.getLogger(SpringIn5StepsComponentScanApplication.class); 
	
	public static void main(String[] args) {

		var applicationContext = 
				SpringApplication.run(SpringIn5StepsComponentScanApplication.class, args);
		
		var componentDAO = 
				applicationContext.getBean(ComponentDAO.class);
		
		LOGGER.info("{}", componentDAO);
		
	}
}

Step 17 — Lifecycle of a Bean — @PostConstruct and @PreDestroy

BinarySearchImpl.java

	@PostConstruct
	public void postConstruct() {
		logger.info("postConstruct");
	}

	@PreDestroy
	public void preDestroy() {
		logger.info("preDestroy");
	}

Step 18 — Container and Dependency Injection (CDI) — @Named, @Inject

/pom.xml

<dependency>		
	<groupId>javax.inject</groupId>
	<artifactId>javax.inject</artifactId>
	<version>1</version>
</dependency>
package com.in28minutes.spring.basics.springin5steps;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;

import com.in28minutes.spring.basics.springin5steps.cdi.SomeCdiBusiness;

@SpringBootApplication
public class SpringIn5StepsCdiApplication {
	
	private static Logger LOGGER = 
			LoggerFactory.getLogger(SpringIn5StepsCdiApplication.class); 
	
	public static void main(String[] args) {

		var applicationContext = 
				SpringApplication.run(SpringIn5StepsCdiApplication.class, args);
		
		var business = 
				applicationContext.getBean(SomeCdiBusiness.class);
		
		LOGGER.info("{} dao-{}", business, business.getSomeCDIDAO());
	}
}
package com.in28minutes.spring.basics.springin5steps.cdi;

import javax.inject.Inject;
import javax.inject.Named;

@Named
public class SomeCdiBusiness {
	
	@Inject
	SomeCdiDao someCdiDao;

	public SomeCdiDao getSomeCDIDAO() {
		return someCdiDao;
	}

	public void setSomeCDIDAO(SomeCdiDao someCdiDao) {
		this.someCdiDao = someCdiDao;
	}
}
package com.in28minutes.spring.basics.springin5steps.cdi;

import javax.inject.Named;

@Named
public class SomeCdiDao {

}

Step 19 — Removing Spring Boot in Basic Application

pom.xml

<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-core</artifactId>
</dependency>
<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-context</artifactId>
</dependency>
<dependency>
	<groupId>org.slf4j</groupId>
	<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
	<groupId>ch.qos.logback</groupId>
	<artifactId>logback-classic</artifactId>
</dependency>

package com.in28minutes.spring.basics.springin5steps;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

import com.in28minutes.spring.basics.springin5steps.basic.BinarySearchImpl;

@Configuration
@ComponentScan
public class SpringIn5StepsBasicApplication {

	public static void main(String[] args) {

		var applicationContext =
				new AnnotationConfigApplicationContext(SpringIn5StepsBasicApplication.class);

Step 20 — Fixing minor stuff — Add Logback and Close Application Context

<dependency>
	<groupId>ch.qos.logback</groupId>
	<artifactId>logback-classic</artifactId>
</dependency>
@Configuration
@ComponentScan
public class SpringIn5StepsBasicApplication {

	public static void main(String[] args) {

		try (var applicationContext = 
				new AnnotationConfigApplicationContext(
				SpringIn5StepsBasicApplication.class)) {
			//No change in code
		}
	}
}

Same changes in

  • SpringIn5StepsCdiApplication
  • SpringIn5StepsComponentScanApplication
  • SpringIn5StepsScopeApplication

Step 21 — Defining Spring Application Context using XML — Part 1

Step 22 — Defining Spring Application Context using XML — Part 2

package com.in28minutes.spring.basics.springin5steps;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.in28minutes.spring.basics.springin5steps.xml.XmlPersonDAO;

@Configuration
@ComponentScan
public class SpringIn5StepsXMLContextApplication {

	public static void main(String[] args) {

		try (var applicationContext = new ClassPathXmlApplicationContext(
				"applicationContext.xml")) {

			var personDao = applicationContext.getBean(XmlPersonDAO.class);
			System.out.println(personDao);
			System.out.println(personDao.getXmlJdbcConnection());
		}
	}
}
package com.in28minutes.spring.basics.springin5steps.xml;

public class XmlJdbcConnection {
	public XmlJdbcConnection() {
		System.out.println("JDBC Connection");
	}
}
package com.in28minutes.spring.basics.springin5steps.xml;

public class XmlPersonDAO {

	XmlJdbcConnection xmlJdbcConnection;

	public XmlJdbcConnection getXmlJdbcConnection() {
		return xmlJdbcConnection;
	}

	public void setXmlJdbcConnection(XmlJdbcConnection jdbcConnection) {
		this.xmlJdbcConnection = jdbcConnection;
	}
}
<?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.xsd">

    <bean id="xmlJdbcConnection" 
    	class="com.in28minutes.spring.basics.springin5steps.xml.XmlJdbcConnection">
    </bean>

    <bean id="xmlPersonDAO" class="com.in28minutes.spring.basics.springin5steps.xml.XmlPersonDAO">
    		<property name="xmlJdbcConnection" ref="xmlJdbcConnection"/>
    </bean>

</beans>

Step 23 — Mixing XML Context with Component Scan for Beans defined with Annotations

public class SpringIn5StepsXMLContextApplication {

	private static final Logger LOGGER = LoggerFactory.getLogger(SpringIn5StepsScopeApplication.class);

	public static void main(String[] args) {

		try (var applicationContext = new ClassPathXmlApplicationContext(
				"applicationContext.xml")) {

			LOGGER.info("Beans Loaded -> {}", (Object) applicationContext.getBeanDefinitionNames());
			// [xmlJdbcConnection, xmlPersonDAO]
	<context:component-scan base-package="com.in28minutes.spring.basics"/>

Step 24 — IOC Container vs Application Context vs Bean Factory

Step 25 — @Component vs @Service vs @Repository vs @Controller

@Repository
public class ComponentDAO {

@Service
@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
public class BinarySearchImpl {
@Service
@Qualifier("bubble")
public class BubbleSortAlgorithm implements SortAlgorithm {
@Service
@Qualifier("quick")
public class QuickSortAlgorithm implements SortAlgorithm {
@Repository
public class PersonDAO {

Step 26 — Read values from external properties file

package com.in28minutes.spring.basics.springin5steps;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;

import com.in28minutes.spring.basics.springin5steps.properties.SomeExternalService;

@Configuration
@ComponentScan
// 
@PropertySource("classpath:app.properties")
public class SpringIn5StepsPropertiesApplication {

	public static void main(String[] args) {

		try (var applicationContext = new AnnotationConfigApplicationContext(
				SpringIn5StepsPropertiesApplication.class)) {

			var service = applicationContext.getBean(SomeExternalService.class);
			System.out.println(service.returnServiceURL());
		}
	}
}
package com.in28minutes.spring.basics.springin5steps.properties;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class SomeExternalService {
	
	@Value("${external.service.url}")
	private String url;
	
	public String returnServiceURL(){
		return url;
	}

}

/src/main/resources/app.properties

external.service.url=http://someserver.dev.com/service

Spring Level 3 — Unit Testing with Spring Framework

Title Category Github
Unit Testing with Spring Framework Spring — Level 3 Project Folder on Github
  • Step 27 — Spring Unit Testing with a Java Context
  • Step 28 — Spring Unit Testing with an XML Context
  • Step 29 — Spring Unit Testing with Mockito

Step 27 — Spring Unit Testing with a Java Context

<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-test</artifactId>
</dependency>
<dependency>
	<groupId>junit</groupId>
	<artifactId>junit</artifactId>
</dependency>
@RunWith(SpringRunner.class)
//@SpringBootTest
public class SpringIn5StepsBasicApplicationTests {
package com.in28minutes.spring.basics.springin5steps.basic;

import static org.junit.jupiter.api.Assertions.assertEquals;

import com.in28minutes.spring.basics.springin5steps.SpringIn5StepsBasicApplication;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;

// Load the context
// replaced @RunWith with @ExtendWith
// replaced SpringRunner.class with SpringExtension.class
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = SpringIn5StepsBasicApplication.class)
public class BinarySearchTest {

    // Get this bean from the context
    @Autowired
    BinarySearchImpl binarySearch;

    @Test
    public void testBasicScenario() {

        // call method on binarySearch
        int actualResult = binarySearch.binarySearch(new int[]{}, 5);

        // check if the value is correct
        assertEquals(3, actualResult);

    }

}

Step 28 — Spring Unit Testing with an XML Context

/src/test/resources/testContext.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"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">
	
	<import resource="classpath:applicationContext.xml"/>
	
</beans>
package com.in28minutes.spring.basics.springin5steps.basic;

import static org.junit.jupiter.api.Assertions.assertEquals;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;

// Load the context
// replaced @RunWith with @ExtendWith
// replaced SpringRunner.class with SpringExtension.class
@ExtendWith(SpringExtension.class)
@ContextConfiguration(locations = "/testContext.xml")
public class BinarySearchXMLConfigurationTest {

    // Get this bean from the context
    @Autowired
    BinarySearchImpl binarySearch;

    @Test
    public void testBasicScenario() {

        // call method on binarySearch
        int actualResult = binarySearch.binarySearch(new int[]{}, 5);

        // check if the value is correct
        assertEquals(3, actualResult);

    }

}

Step 29 — Spring Unit Testing with Mockito

public class SomeCdiBusiness {

	// SAME OLD CODE

	public int findGreatest() {
		int greatest = Integer.MIN_VALUE;
        int[] data = someCdiDao.getData();
        var result = Arrays.stream(data).max();

        if (result.isPresent()) {
            greatest = result.getAsInt();
        }

        return greatest;
	}

}

Add a new method

package com.in28minutes.spring.basics.springin5steps.cdi;

import javax.inject.Named;

@Named
public class SomeCdiDao {
	
	public int[] getData() {
		return new int[] {5, 89,100};
	}

}
package com.in28minutes.spring.basics.springin5steps.cdi;

import static org.junit.jupiter.api.Assertions.assertEquals;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;

// replaced @RunWith with @ExtendWith
// replaced MockitoJUnitRunner.class with MockitoExtension.class
@ExtendWith(MockitoExtension.class)
public class SomeCdiBusinessTest {

    // Inject Mock
    @InjectMocks
    SomeCdiBusiness business;

    // Create Mock
    @Mock
    SomeCdiDao daoMock;

    @Test
    public void testBasicScenario() {
        Mockito.when(daoMock.getData()).thenReturn(new int[]{2, 4});
        assertEquals(4, business.findGreatest());
    }

    @Test
    public void testBasicScenario_NoElements() {
        Mockito.when(daoMock.getData()).thenReturn(new int[]{});
        assertEquals(Integer.MIN_VALUE, business.findGreatest());
    }

    @Test
    public void testBasicScenario_EqualElements() {
        Mockito.when(daoMock.getData()).thenReturn(new int[]{2, 2});
        assertEquals(2, business.findGreatest());
    }

}

<dependency>
	<groupId>org.mockito</groupId>
	<artifactId>mockito-core</artifactId>
</dependency>

Spring Level 4 — Introduction To Spring Boot

Title Category Github
Spring Boot in 10 Steps Spring — Level 4 Project Folder on Github
  • Step 1 : Introduction to Spring Boot — Goals and Important Features
  • Step 2 : Developing Spring Applications before Spring Boot
  • Step 3 : Using Spring Initializr to create a Spring Boot Application
  • Step 4 : Creating a Simple REST Controller
  • Step 5 : What is Spring Boot Auto Configuration?
  • Step 6 : Spring Boot vs Spring vs Spring MVC
  • Step 7 : Spring Boot Starter Projects — Starter Web and Starter JPA
  • Step 8 : Overview of different Spring Boot Starter Projects
  • Step 9 : Spring Boot Actuator
  • Step 10 : Spring Boot Developer Tools

Step 1 : Introduction to Spring Boot — Goals and Important Features

Goals

  • Enable building production ready applications quickly
  • Provide common non-functional features
    • embedded servers
    • metrics
    • health checks
    • externalized configuration

What Spring Boot is NOT!

  • ZERO code generation
  • Neither an application server nor a web server

Features

  • Quick Starter Projects with Auto Configuration
    • Web
    • JPA
  • Embedded Servers
    • Tomcat, Jetty or Undertow
  • Production-ready features
    • metrics and health checks
    • externalized configuration

Step 2 : Developing Spring Applications before Spring Boot

Recommended Reading — http://www.springboottutorial.com/spring-boot-vs-spring-mvc-vs-spring

Step 3 : Using Spring Initializr to create a Spring Boot Application

https://start.spring.io

Step 4 : Creating a Simple REST Controller

/src/main/java/com/in28minutes/springboot/basics/springbootin10steps/BooksController.java

package com.in28minutes.springboot.basics.springbootin10steps;

import java.util.List;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class BooksController {
	@GetMapping("/books")
	public List<Book> getAllBooks() {
		return List.of(
				new Book(1l, "Mastering Spring 5.2", "Ranga Karanam"));
	}
}

Step 5 : What is Spring Boot Auto Configuration?

Recommended Reading — http://www.springboottutorial.com/spring-boot-auto-configuration

  • Spring based applications have a lot of configuration. When we use Spring MVC, we need to configure component scan, dispatcher servlet, a view resolver, web jars(for delivering static content) among other things. When we use Hibernate/JPA, we would need to configure a datasource, an entity manager factory, a transaction manager among a host of other things. Spring Boot brings in new thought process around this — Can we bring more intelligence into this? When a spring mvc jar is added into an application, can we auto configure some beans automatically?

Step 6 : Spring Boot vs Spring vs Spring MVC

Recommended Reading — http://www.springboottutorial.com/spring-boot-vs-spring-mvc-vs-spring

  • Spring is about Dependency Injection. It makes it easy to develop loosely coupled applications. It makes applications testable.
  • Spring MVC brings loose coupling to web mvc application development with features like Dispatcher Servlet, View Resolver etc
  • Spring Boot eliminates the need for manual configuration with Spring and Spring MVC. You can use Spring and Spring MVC without needing a lot of configuration.
  • Spring Boot aims to enable production ready applications in quick time.
    • Actuator : Enables Advanced Monitoring and Tracing of applications.
    • Embedded Server Integrations — Since server is integrated into the application, I would NOT need to have a separate application server installed on the server.
    • Default Error Handling

Step 7 : Spring Boot Starter Projects — Starter Web and Starter JPA

Recommended Reading — http://www.springboottutorial.com/spring-boot-starter-projects

  • Starters are a set of convenient dependency descriptors that you can include in your application. You get a one-stop-shop for all the Spring and related technology that you need, without having to hunt through sample code and copy paste loads of dependency descriptors. For example, if you want to get started using Spring and JPA for database access, just include the spring-boot-starter-data-jpa dependency in your project, and you are good to go.

Step 8 : Overview of different Spring Boot Starter Projects

As we see from Spring Boot Starter Web, starter projects help us in quickly getting started with developing specific types of applications.

Examples

  • spring-boot-starter-web-services — SOAP Web Services
  • spring-boot-starter-web — Web & RESTful applications
  • spring-boot-starter-test — Unit testing and Integration Testing
  • spring-boot-starter-jdbc — Traditional JDBC
  • spring-boot-starter-hateoas — Add HATEOAS features to your services
  • spring-boot-starter-security — Authentication and Authorization using Spring Security
  • spring-boot-starter-data-jpa — Spring Data JPA with Hibernate
  • spring-boot-starter-cache — Enabling Spring Framework’s caching support
  • spring-boot-starter-data-rest — Expose Simple REST Services using Spring Data REST
  • spring-boot-starter-actuator — To use advanced features like monitoring & tracing to your application out of the box
  • spring-boot-starter-undertow, spring-boot-starter-jetty, spring-boot-starter-tomcat — To pick your specific choice of Embedded Servlet Container
  • spring-boot-starter-logging — For Logging using logback
  • spring-boot-starter-log4j2 — Logging using Log4j2

Step 9 : Spring Boot Actuator

Spring Boot starting actuator offers a plethora of REST services, all of which are compatible with the HAL specification. And we would use a hal browser to browse through the data offered by these services.

Spring Boot Actuator exposes a lot of data

application info, metrics, dump, beans, env, config properties, audit events, heap dump, loggers, trace, health mappings and auto config.

Actuator provides more metadata about your application.

Step 10 : Spring Boot Developer Tools

- Why do you need to restart your server for every java and jsp change?
- Spring Boot Developer Tools enables dynamic reloading of modified changes.

Spring Level 5 — Spring AOP

Title Category Github
Spring AOP Spring — Level 5 Project Folder on Github
  • Step 01 — Setting up AOP Example — Part 1
  • Step 02 — Setting up AOP Example — Part 2
  • Step 03 — Defining an @Before advice
  • Step 04 — Understand AOP Terminology — Pointcut, Advice, Aspect, Join Point, Weaving and Weaver
  • Step 05 — Using @After, @AfterReturning, @AfterThrowing advices
  • Step 06 — Using @Around advice to implement performance tracing
  • Step 07 — Best Practice : Use common Pointcut Configuration
  • Step 08 — Quick summary of other Pointcuts
  • Step 09 — Creating Custom Annotation and an Aspect for Tracking Time

Step 01 — Setting up AOP Example — Part 1

Creating a Spring AOP Project with Spring Initializr is a cake walk.

Spring Initializr http://start.spring.io/ is great tool to bootstrap your Spring Boot projects.

Notes

  • Launch Spring Initializr and choose the following
    • Choose com.in28minutes.spring.aop as Group
    • Choose spring-aop as Artifact
    • Choose the following Dependencies
      • AOP
  • Click Generate Project.
  • Import the project into Eclipse.
  • If you want to understand all the files that are part of this project, you can go here.

Step 02 — Setting up AOP Example — Part 2

package com.in28minutes.spring.aop.springaop;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class Business1 {
	
	private final Logger logger = LoggerFactory.getLogger(this.getClass());

	@Autowired
	private Dao1 dao1;
	
	public String calculateSomething(){
		String value = dao1.retrieveSomething();
		logger.info("In Business - {}", value);
		return value;
	}
}

package com.in28minutes.spring.aop.springaop;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class Business2 {
	
	@Autowired
	private Dao2 dao2;
	
	public String calculateSomething(){
		//Business Logic
		return dao2.retrieveSomething();
	}
}

package com.in28minutes.spring.aop.springaop;

import org.springframework.stereotype.Repository;

@Repository
public class Dao1 {

	public String retrieveSomething(){
		return "Dao1";
	}

}

package com.in28minutes.spring.aop.springaop;

import org.springframework.stereotype.Repository;

@Repository
public class Dao2 {

	public String retrieveSomething(){
		return "Dao2";
	}

}

Step 03 — Defining an @Before advice

public class SpringAopApplication implements CommandLineRunner {

	private final Logger logger = LoggerFactory.getLogger(this.getClass());

	@Autowired
	private Business1 business1;

	@Autowired
	private Business2 business2;
	
	@Override
	public void run(String... args) throws Exception {
		logger.info(business1.calculateSomething());
		logger.info(business2.calculateSomething());

package com.in28minutes.spring.aop.springaop.aspect;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Configuration;

//AOP
//Configuration
@Aspect
@Configuration
public class UseAccessAspect {
	
	private final Logger logger = LoggerFactory.getLogger(this.getClass());
	
	//What kind of method calls I would intercept
	//execution(* PACKAGE.*.*(..))
	
	@Before("execution(* com.in28minutes.spring.aop.springaop.business.*.*(..))")
	public void before(JoinPoint joinPoint){
		logger.info(" Check for user access ");
		logger.info(" Allowed execution for {}", joinPoint);
	}
}

Step 04 — Understand AOP Terminology — Pointcut, Advice, Aspect, Join Point, Weaving and Weaver

Step 05 — Using @After, @AfterReturning, @AfterThrowing advices

Step 06 — Using @Around advice to implement performance tracing

package com.in28minutes.spring.aop.springaop.aspect;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Configuration;

//AOP
//Configuration
@Aspect
@Configuration
public class AfterAopAspect {

	private final Logger logger = LoggerFactory.getLogger(this.getClass());

	@AfterReturning(value = "execution(* com.in28minutes.spring.aop.springaop.business.*.*(..))", 
			returning = "result")
	public void afterReturning(JoinPoint joinPoint, Object result) {
		logger.info("{} returned with value {}", joinPoint, result);
	}
	
	@After(value = "execution(* com.in28minutes.spring.aop.springaop.business.*.*(..))")
	public void after(JoinPoint joinPoint) {
		logger.info("after execution of {}", joinPoint);
	}
}
package com.in28minutes.spring.aop.springaop.aspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Configuration;

@Aspect
@Configuration
public class MethodExecutionCalculationAspect {

	private final Logger logger = LoggerFactory.getLogger(this.getClass());

	@Around("execution(* com.in28minutes.spring.aop.springaop.business.*.*(..))")
	public void around(ProceedingJoinPoint joinPoint) throws Throwable {
		long startTime = System.currentTimeMillis();

		joinPoint.proceed();

		long timeTaken = System.currentTimeMillis() - startTime;
		logger.info("Time Taken by {} is {}", joinPoint, timeTaken);
	}
}
package com.in28minutes.spring.aop.springaop.aspect;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Configuration;

//AOP
//Configuration
@Aspect
@Configuration
public class UserAccessAspect {
	
	private final Logger logger = LoggerFactory.getLogger(this.getClass());
	
	//What kind of method calls I would intercept
	//execution(* PACKAGE.*.*(..))
	//Weaving & Weaver
	@Before("execution(* com.in28minutes.spring.aop.springaop.data.*.*(..))")
	public void before(JoinPoint joinPoint){
		//Advice
		logger.info(" Check for user access ");
		logger.info(" Allowed execution for {}", joinPoint);
	}
}

Step 07 — Best Practice : Use common Pointcut Configuration

package com.in28minutes.spring.aop.springaop.aspect;

import org.aspectj.lang.annotation.Pointcut;

public class CommonJoinPointConfig {
	
	@Pointcut("execution(* com.in28minutes.spring.aop.springaop.data.*.*(..))")
	public void dataLayerExecution(){}
	
	@Pointcut("execution(* com.in28minutes.spring.aop.springaop.business.*.*(..))")
	public void businessLayerExecution(){}

}
public class MethodExecutionCalculationAspect {

	private Logger logger = LoggerFactory.getLogger(this.getClass());

	@Around("com.in28minutes.spring.aop.springaop.aspect.CommonJoinPointConfig.businessLayerExecution()")

public class AfterAopAspect

	@AfterReturning(value = "com.in28minutes.spring.aop.springaop.aspect.CommonJoinPointConfig.businessLayerExecution()", returning = "result")
	@After(value = "com.in28minutes.spring.aop.springaop.aspect.CommonJoinPointConfig.businessLayerExecution()")
public class UserAccessAspect {
	
	@Before("com.in28minutes.spring.aop.springaop.aspect.CommonJoinPointConfig.dataLayerExecution()")

Step 08 — Quick summary of other Pointcuts

Step 09 — Creating Custom Annotation and an Aspect for Tracking Time

package com.in28minutes.spring.aop.springaop.aspect;

import org.aspectj.lang.annotation.Pointcut;

public class CommonJoinPointConfig {
	
	@Pointcut("execution(* com.in28minutes.spring.aop.springaop.data.*.*(..))")
	public void dataLayerExecution(){}
	
	@Pointcut("execution(* com.in28minutes.spring.aop.springaop.business.*.*(..))")
	public void businessLayerExecution(){}
	
	@Pointcut("dataLayerExecution() && businessLayerExecution()")
	public void allLayerExecution(){}
	
	@Pointcut("bean(*dao*)")
	public void beanContainingDao(){}
	
	@Pointcut("within(com.in28minutes.spring.aop.springaop.data..*)")
	public void dataLayerExecutionWithWithin(){}

	@Pointcut("@annotation(com.in28minutes.spring.aop.springaop.aspect.TrackTime)")
	public void trackTimeAnnotation(){}

}
package com.in28minutes.spring.aop.springaop.aspect;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TrackTime {

}
@Aspect
@Configuration
public class MethodExecutionCalculationAspect {

	@Around("com.in28minutes.spring.aop.springaop.aspect.CommonJoinPointConfig.trackTimeAnnotation()")
	public void around(ProceedingJoinPoint joinPoint) throws Throwable {
public class Business1 {
		
	@TrackTime
	public String calculateSomething(){

@Repository
public class Dao1 {
	
	@TrackTime
	public String retrieveSomething(){

Spring Level 6 — Spring JDBC and JPA

Title Category Github
Spring JDBC and JPA Spring — Level 6 Project Folder on Github
  • Step 01 — Setting up a project with JDBC, JPA, H2 and Web Dependencies
  • Step 02 — Launching up H2 Console
  • Step 03 — Creating a Database Table in H2
  • Step 04 — Populate data into Person Table
  • Step 05 — Implement findAll persons Spring JDBC Query Method
  • Step 06 — Execute the findAll method using CommandLineRunner
  • Step 07 — A Quick Review — JDBC vs Spring JDBC
  • Step 08 — Whats in the background? Understanding Spring Boot Autoconfiguration
  • Step 09 — Implementing findById Spring JDBC Query Method
  • Step 10 — Implementing deleteById Spring JDBC Update Method
  • Step 11 — Implementing insert and update Spring JDBC Update Methods
  • Step 12 — Creating a custom Spring JDBC RowMapper
  • Step 13 — Quick introduction to JPA
  • Step 14 — Defining Person Entity
  • Step 15 — Implementing findById JPA Repository Method
  • Step 16 — Implementing insert and update JPA Repository Methods
  • Step 17 — Implementing deleteById JPA Repository Method
  • Step 18 — Implementing findAll using JPQL Named Query
  • Step 19 — Introduction to Spring Data JPA
  • Step 20 — Connecting to Other Databases

Step 01 — Setting up a project with JDBC, JPA, H2 and Web Dependencies

Creating a Spring JDBC Project with Spring Initializr is a cake walk.

Spring Initializr http://start.spring.io/ is great tool to bootstrap your Spring Boot projects.

Notes

  • Launch Spring Initializr and choose the following
    • Choose com.in28minutes.database as Group
    • Choose database-demo as Artifact
    • Choose the following Dependencies
      • Web
      • JDBC
      • JPA
      • H2
  • Click Generate Project.
  • Import the project into Eclipse.
  • If you want to understand all the files that are part of this project, you can go here.

Step 02 — Launching up H2 Console

/src/main/resources/application.properties

spring.h2.console.enabled=true

Launching H2

  • URL — http://localhost:8080/h2-console
  • Make sure to check the db url — jdbc:h2:mem:testdb

Step 03 — Creating a Database Table in H2

/src/main/resources/data.sql

create table person
(
   id integer not null,
   name varchar(255) not null,
   location varchar(255),
   birth_date timestamp,
   primary key(id)
);

Step 04 — Populate data into Person Table

Step 05 — Implement findAll persons Spring JDBC Query Method

package com.in28minutes.database.databasedemo.entity;

import java.util.Date;

public class Person {
	private int id;
	private String name;
	private String location;
	private Date birthDate;

	public Person(int id, String name, String location, Date birthDate) {
		super();
		this.id = id;
		this.name = name;
		this.location = location;
		this.birthDate = birthDate;
	}

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getLocation() {
		return location;
	}

	public void setLocation(String location) {
		this.location = location;
	}

	public Date getBirthDate() {
		return birthDate;
	}

	public void setBirthDate(Date birthDate) {
		this.birthDate = birthDate;
	}

}

/src/main/java/com/in28minutes/database/databasedemo/jdbc/PersonJbdcDao.java

package com.in28minutes.database.databasedemo.jdbc;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

import com.in28minutes.database.databasedemo.entity.Person;

@Repository
public class PersonJbdcDao {
	@Autowired
	JdbcTemplate jdbcTemplate;

	public List<Person> findAll() {
		return jdbcTemplate.query("select * from person", 
				new BeanPropertyRowMapper(Person.class));
	}
}

Add insert statements into data.sql
/src/main/resources/data.sql

INSERT INTO PERSON (ID, NAME, LOCATION, BIRTH_DATE ) 
VALUES(10001,  'Ranga', 'Hyderabad',CURRENT_DATE());
INSERT INTO PERSON (ID, NAME, LOCATION, BIRTH_DATE ) 
VALUES(10002,  'James', 'New York',CURRENT_DATE());
INSERT INTO PERSON (ID, NAME, LOCATION, BIRTH_DATE ) 
VALUES(10003,  'Pieter', 'Amsterdam',CURRENT_DATE());

Step 06 — Execute the findAll method using CommandLineRunner

public class DatabaseDemoApplication implements CommandLineRunner {
	
	private final Logger logger = LoggerFactory.getLogger(this.getClass());
	
	@Autowired
	PersonJbdcDao dao;
	
	@Override
	public void run(String... args) throws Exception {
		logger.info("All users -> {}", dao.findAll());

Modified

@Repository
public class PersonJbdcDao {
	@Autowired
	JdbcTemplate jdbcTemplate;

	public List<Person> findAll() {
		return jdbcTemplate.query("select * from person", 
				new BeanPropertyRowMapper<Person>(Person.class));
	}
}

Step 07 — A Quick Review — JDBC vs Spring JDBC

Step 08 — Whats in the background? Understanding Spring Boot Autoconfiguration

Step 09 — Implementing findById Spring JDBC Query Method

Step 10 — Implementing deleteById Spring JDBC Update Method

Modified

package com.in28minutes.database.databasedemo.jdbc;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

import com.in28minutes.database.databasedemo.entity.Person;

@Repository
public class PersonJbdcDao {

	@Autowired
	JdbcTemplate jdbcTemplate;

	public List<Person> findAll() {
		return jdbcTemplate.query("select * from person", new BeanPropertyRowMapper<Person>(Person.class));
	}

	public Person findById(int id) {
		return jdbcTemplate.queryForObject
				("select * from person where id=?", new Object[] { id },
				new BeanPropertyRowMapper<Person>(Person.class));
	}
	
	public int deleteById(int id) {
		return jdbcTemplate.update
				("delete from person where id=?", new Object[] { id });
	}

	
}

DatabaseDemoApplication

		logger.info("User id 10001 -> {}", dao.findById(10001));
		logger.info("Deleting 10002 -> No of Rows Deleted - {}", dao.deleteById(10002));

Step 11 — Implementing insert and update Spring JDBC Update Methods

	public int deleteById(int id) {
		return jdbcTemplate.update("delete from person where id=?", new Object[] { id });
	}

	public int insert(Person person) {
		return jdbcTemplate.update("insert into person (id, name, location, birth_date) " + "values(?,  ?, ?, ?)",
				new Object[] { person.getId(), person.getName(), person.getLocation(),
						new Timestamp(person.getBirthDate().getTime()) });
	}

	public int update(Person person) {
		return jdbcTemplate.update("update person " + " set name = ?, location = ?, birth_date = ? " + " where id = ?",
				new Object[] { person.getName(), person.getLocation(), new Timestamp(person.getBirthDate().getTime()),
						person.getId() });
	}
	logger.info("Deleting 10002 -> No of Rows Deleted - {}", 
				dao.deleteById(10002));
		
		logger.info("Inserting 10004 -> {}", 
				dao.insert(new Person(10004, "Tara", "Berlin", new Date())));
		
		logger.info("Update 10003 -> {}", 
				dao.update(new Person(10003, "Pieter", "Utrecht", new Date())));
	

Step 12 — Creating a custom Spring JDBC RowMapper

Inner class in PersonJbdcDao

static class PersonRowMapper implements RowMapper<Person>{
	@Override
	public Person mapRow(ResultSet rs, int rowNum) throws SQLException {
		Person person = new Person();
		person.setId(rs.getInt("id"));
		person.setName(rs.getString("name"));
		person.setLocation(rs.getString("location"));
		person.setBirthDate(rs.getTimestamp("birth_date"));
		return person;
	}
	
}

public List<Person> findAll() {
	return jdbcTemplate.query("select * from person", new PersonRowMapper());
}

PersonJbdcDao

Step 13 — Quick introduction to JPA

Step 14 — Defining Person Entity

package com.in28minutes.database.databasedemo.entity;

import java.util.Date;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;

@Entity
public class Person {
	
	@Id
	@GeneratedValue
	private int id;
	
	//No change in rest of the code	
	
}

Step 15 — Implementing findById JPA Repository Method

DatabaseDemoApplication renamed to SpringJdbcDemoApplication

package com.in28minutes.database.databasedemo.jpa;

import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.transaction.Transactional;

import org.springframework.stereotype.Repository;

import com.in28minutes.database.databasedemo.entity.Person;

@Repository
@Transactional
public class PersonJpaRepository {
	
	//connect to the database
	@PersistenceContext
	EntityManager entityManager;
	
	public Person findById(int id) {
		return entityManager.find(Person.class, id);//JPA
	}
}

/src/main/resources/application.properties

spring.jpa.show-sql=true
spring.datasource.url=jdbc:h2:mem:testdb
spring.data.jpa.repositories.bootstrap-mode=default

/src/main/resources/data.sql — Comment Everything

JpaDemoApplication

package com.in28minutes.database.databasedemo;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import com.in28minutes.database.databasedemo.jpa.PersonJpaRepository;

@SpringBootApplication
public class JpaDemoApplication implements CommandLineRunner {

	private final Logger logger = LoggerFactory.getLogger(this.getClass());

	@Autowired
	PersonJpaRepository repository;

	public static void main(String[] args) {
		SpringApplication.run(JpaDemoApplication.class, args);
	}

	@Override
	public void run(String... args) throws Exception {
		
		logger.info("User id 10001 -> {}", repository.findById(10001));

		/*
		logger.info("All users -> {}", repository.findAll());
		logger.info("Deleting 10002 -> No of Rows Deleted - {}", 
				repository.deleteById(10002));
		
		logger.info("Inserting 10004 -> {}", 
				repository.insert(new Person(10004, "Tara", "Berlin", new Date())));
		
		logger.info("Update 10003 -> {}", 
				repository.update(new Person(10003, "Pieter", "Utrecht", new Date())));
		*/
	}
}

Step 16 — Implementing insert and update JPA Repository Methods

Step 17 — Implementing deleteById JPA Repository Method

Step 18 — Implementing findAll using JPQL Named Query

		logger.info("Inserting -> {}", 
				repository.insert(new Person("Tara", "Berlin", new Date())));
		repository.deleteById(10002);

@Entity
@NamedQuery(name="find_all_persons", query="select p from Person p")
public class Person
package com.in28minutes.database.databasedemo.jpa;

import java.util.List;

import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.persistence.TypedQuery;
import jakarta.transaction.Transactional;

import org.springframework.stereotype.Repository;

import com.in28minutes.database.databasedemo.entity.Person;

@Repository
@Transactional
public class PersonJpaRepository {

	// connect to the database
	@PersistenceContext
	EntityManager entityManager;

	public List<Person> findAll() {
		var namedQuery = entityManager.createNamedQuery("find_all_persons", Person.class);
		return namedQuery.getResultList();
	}

	public Person findById(int id) {
		return entityManager.find(Person.class, id);// JPA
	}

	public Person update(Person person) {
		return entityManager.merge(person);
	}

	public Person insert(Person person) {
		return entityManager.merge(person);
	}

	public void deleteById(int id) {
		var person = findById(id);
		entityManager.remove(person);
	}

}

Step 19 — Introduction to Spring Data JPA

JpaDemoApplication — comment out @SpringBootApplication

package com.in28minutes.database.databasedemo.springdata;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import com.in28minutes.database.databasedemo.entity.Person;

@Repository
public interface PersonSpringDataRepository 
				extends JpaRepository<Person, Integer>{
}
package com.in28minutes.database.databasedemo;

import java.util.Date;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import com.in28minutes.database.databasedemo.entity.Person;
import com.in28minutes.database.databasedemo.springdata.PersonSpringDataRepository;

@SpringBootApplication
public class SpringDataDemoApplication implements CommandLineRunner {

	private final Logger logger = LoggerFactory.getLogger(this.getClass());

	@Autowired
	PersonSpringDataRepository repository;

	public static void main(String[] args) {
		SpringApplication.run(SpringDataDemoApplication.class, args);
	}

	@Override
	public void run(String... args) throws Exception {
		
		logger.info("User id 10001 -> {}", repository.findById(10001));
		
		logger.info("Inserting -> {}", 
				repository.save(new Person("Tara", "Berlin", new Date())));
		
		logger.info("Update 10003 -> {}", 
				repository.save(new Person(10003, "Pieter", "Utrecht", new Date())));
		
		repository.deleteById(10002);

		logger.info("All users -> {}", repository.findAll());
	}
}

Step 20 — Connecting to Other Databases

Connecting to My SQL and Other Databases

Spring Boot makes it easy to switch databases! Yeah really simple.

Steps
  • Install MySQL and Setup Schema
  • Remove H2 dependency from pom.xml
  • Add MySQL (or your database) dependency to pom.xml
    «`xml

mysql
mysql-connector-java

- Configure application.properties

```properties
spring.jpa.hibernate.ddl-auto=none
spring.datasource.url=jdbc:mysql://localhost:3306/person_example
spring.datasource.username=personuser
spring.datasource.password=YOUR_PASSWORD
  • Restart the app and You are ready!

Spring Boot can setup the database for you using Hibernate

Things to note:

  • Spring Boot chooses a default value for you based on whether it thinks your database is embedded (default create-drop) or not (default none).
  • spring.jpa.hibernate.ddl-auto is the setting to perform SchemaManagementTool actions automatically
    • none : No action will be performed.
    • create-only : Database creation will be generated.
    • drop : Database dropping will be generated.
    • create : Database dropping will be generated followed by database creation.
    • validate : Validate the database schema
    • update : Update the database schema
  • Reference : https://docs.jboss.org/hibernate/orm/5.2/userguide/html_single/Hibernate_User_Guide.html#configurations-hbmddl

application.properties

#none, validate, update, create, create-drop
spring.jpa.hibernate.ddl-auto=create
Installing and Setting Up MySQL
  • Install MySQL https://dev.mysql.com/doc/en/installing.html
    • More details — http://www.mysqltutorial.org/install-mysql/
    • Trouble Shooting — https://dev.mysql.com/doc/refman/en/problems.html
  • Startup the Server (as a service)
  • Go to command prompt (or terminal)
    • Execute following commands to create a database and a user
mysql --user=user_name --password db_name
create database person_example;
create user 'personuser'@'localhost' identified by 'YOUR_PASSWORD';
grant all on person_example.* to 'personuser'@'localhost';
  • Execute following sql queries to create the table and insert the data

Table

create table person
(
	id integer not null,
	birth_date timestamp,
	location varchar(255),
	name varchar(255),
	primary key (id)
);

Data

INSERT INTO PERSON (ID, NAME, LOCATION, BIRTH_DATE ) VALUES(10001,  'Ranga', 'Hyderabad',CURRENT_DATE());
INSERT INTO PERSON (ID, NAME, LOCATION, BIRTH_DATE ) VALUES(10002,  'James', 'New York',CURRENT_DATE());
INSERT INTO PERSON (ID, NAME, LOCATION, BIRTH_DATE ) VALUES(10003,  'Pieter', 'Amsterdam',CURRENT_DATE());

Notes

JdbcTemplate AutoConfiguration
=========================
AUTO-CONFIGURATION REPORT
=========================

DataSourceAutoConfiguration matched:
   - @ConditionalOnClass found required classes 'javax.sql.DataSource', 'org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType'; @ConditionalOnMissingClass did not find unwanted class (OnClassCondition)

DataSourceTransactionManagerAutoConfiguration matched:
   - @ConditionalOnClass found required classes 'org.springframework.jdbc.core.JdbcTemplate', 'org.springframework.transaction.PlatformTransactionManager'; @ConditionalOnMissingClass did not find unwanted class (OnClassCondition)

H2ConsoleAutoConfiguration matched:
   - @ConditionalOnClass found required class 'org.h2.server.web.WebServlet'; @ConditionalOnMissingClass did not find unwanted class (OnClassCondition)
   - found ConfigurableWebEnvironment (OnWebApplicationCondition)
   - @ConditionalOnProperty (spring.h2.console.enabled=true) matched (OnPropertyCondition)

JdbcTemplateAutoConfiguration matched:
   - @ConditionalOnClass found required classes 'javax.sql.DataSource', 'org.springframework.jdbc.core.JdbcTemplate'; @ConditionalOnMissingClass did not find unwanted class (OnClassCondition)
   - @ConditionalOnSingleCandidate (types: javax.sql.DataSource; SearchStrategy: all) found a primary bean from beans 'dataSource' (OnBeanCondition)

JdbcTemplateAutoConfiguration.JdbcTemplateConfiguration#jdbcTemplate matched:
   - @ConditionalOnMissingBean (types: org.springframework.jdbc.core.JdbcOperations; SearchStrategy: all) did not find any beans (OnBeanCondition)

Bonus Section — Basic Web Application

  • Understand Basics of HTTP
  • HttpRequest — GET/POST, Request Parameters
  • HTTP Response — Response Status — 404,200,500 etc
  • Introduction to JSP, Servlets, Scriptlets and EL
  • HTML Form — Method, Action & Form Data
  • Understand Basics of using Maven, Tomcat and Eclipse
  • Using Request Attributes for passing Model between Servlet and View
  • Step 11 : Configure application to use Spring MVC
  • Step 12 : First Spring MVC Controller, @ResponseBody, @Controller
  • Step 13 : Redirect to Login JSP — LoginController, @ResponseBody and View Resolver
  • Step 14 : DispatcherServlet and Log4j
  • Step 15 : Show userid and password on the welcome page — ModelMap and @RequestParam
  • Step 16 : LoginService and Remove all JEE Servlets based code
  • Step 17 : Spring Auto-wiring and Dependency Management — @Autowired and @Service

Step 01 : Up and running with a Web Application in Tomcat

In this step, we will quickly setup a running web application.

Tip : This is one of the few steps where you copy code in! We would want to ensure that you have a running web application without any mistakes.

You can run the project using Run as > Maven build > tomcat7:run.

You can copy code from

  • Step 01 on Github Repository

\pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.in28minutes</groupId>
	<artifactId>in28Minutes-first-webapp</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>war</packaging>
	<dependencies>
		<dependency>
			<groupId>javax</groupId>
			<artifactId>javaee-web-api</artifactId>
			<version>8.0.1</version>
			<scope>provided</scope>
		</dependency>
	</dependencies>
	<build>
		<pluginManagement>
			<plugins>
				<plugin>
					<groupId>org.apache.maven.plugins</groupId>
					<artifactId>maven-compiler-plugin</artifactId>
					<version>3.2</version>
					<configuration>
						<verbose>true</verbose>
						<source>1.7</source>
						<target>1.7</target>
						<showWarnings>true</showWarnings>
					</configuration>
				</plugin>
				<plugin>
					<groupId>org.apache.tomcat.maven</groupId>
					<artifactId>tomcat7-maven-plugin</artifactId>
					<version>2.2</version>
					<configuration>
						<path>/</path>
						<contextReloadable>true</contextReloadable>
					</configuration>
				</plugin>
			</plugins>
		</pluginManagement>
	</build>
</project>

\src\main\java\webapp\LoginServlet.java

package webapp;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/*
 * Browser sends Http Request to Web Server
 * 
 * Code in Web Server => Input:HttpRequest, Output: HttpResponse
 * JEE with Servlets
 * 
 * Web Server responds with Http Response
 */


@WebServlet(urlPatterns = "/login.do")
public class LoginServlet extends HttpServlet {

	@Override
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
		PrintWriter out = response.getWriter();
		out.println("<html>");
		out.println("<head>");
		out.println("<title>Yahoo!!!!!!!!</title>");
		out.println("</head>");
		out.println("<body>");
		out.println("My First Servlet");
		out.println("</body>");
		out.println("</html>");

	}

}

\src\main\webapp\WEB-INF\web.xml

<!-- webapp/WEB-INF/web.xml -->
<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
	version="3.0">

	<display-name>To do List</display-name>

	<welcome-file-list>
		<welcome-file>login.do</welcome-file>
	</welcome-file-list>

</web-app>

Java Platform, Enterprise Edition (Java EE) JEE6

Servlet is a Java programming language class used to extend the capabilities of servers that host applications accessed by means of a request-response programming model.

Notes

  • extends javax.servlet.http.HttpServlet — All servlets should extend HttpServlet class
  • @WebServlet(urlPatterns = "/login.do") — Provide the url pattern to access the servlet
  • doGet(HttpServletRequest request, HttpServletResponse response) — To handle the RequestMethod GET we need to implement doGet method.

Configuring welcome-file-list in web.xml will ensure that url http://localhost:8080/ redirects to http://localhost:8080/login.do

<welcome-file-list>
	<welcome-file>login.do</welcome-file>
</welcome-file-list>

Step 02 : First JSP

Complete code

Notes

  • Create LoginServlet again
  • Redirect to a view — JSP

Code Snippets and Examples

Redirect to a view — JSP

\src\main\java\webapp\LoginServlet.java

request
 .getRequestDispatcher("/WEB-INF/views/login.jsp")
 .forward(request, response);

\src\main\webapp\WEB-INF\views\login.jsp

<html>
<head>
<title>Yahoo!!</title>
</head>
<body>
My First JSP!!!
</body>
</html>

Step 03 : Adding a Get Parameter name

Complete code

Notes

  • Passing a Request Parameter Name

Code Snippets and Examples

We read the request parameter and set it as a request attribute. Request attributes can be accessed from the view (jsp).

\src\main\java\webapp\LoginServlet.java

request.setAttribute("name", 
		request.getParameter("name"));

\src\main\webapp\WEB-INF\views\login.jsp

My First JSP!!! My name is ${name}

Step 04 : Adding another Get Parameter Password

Complete code

Code Snippets and Examples

\src\main\java\webapp\LoginServlet.java

request.setAttribute("password",
                  request.getParameter("password"));

\src\main\webapp\WEB-INF\views\login.jsp

My First JSP!!! My name is ${name} and password is ${password}

Step 05 : Let’s add a form

Complete code

Code Snippets and Examples

\src\main\java\webapp\LoginServlet.java

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
		throws IOException, ServletException {
	request
		.getRequestDispatcher("/WEB-INF/views/login.jsp")
		.forward(request, response);
}

\src\main\webapp\WEB-INF\views\login.jsp

<html>
<head>
<title>Yahoo!!</title>
</head>
<body>
	<form action="/login.do" method="POST">
		Name : <input type="text" /> <input type="submit" />
	</form>
</body>
</html>

\src\main\webapp\WEB-INF\views\welcome.jsp

<html>
<head>
<title>Yahoo!!</title>
</head>
<body>
Welcome ${name}
</body>
</html>

Step 06 : New Form and doPost

Complete code

\src\main\java\webapp\LoginServlet.java

@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
		throws IOException, ServletException {
	request.setAttribute("name", request.getParameter("name"));
	request.getRequestDispatcher("/WEB-INF/views/welcome.jsp").forward(request, response);
}

\src\main\webapp\WEB-INF\views\welcome.jsp

<html>
<head>
<title>Yahoo!!</title>
</head>
<body>
Welcome ${name}
</body>
</html>

Step 07 : Adding Password and Validation of User Id

Complete code

Code Snippets and Examples

\src\main\java\webapp\LoginService.java

public class LoginService {
	public boolean validateUser(String user, String password) {
		return user.equalsIgnoreCase("in28Minutes") && password.equals("dummy");
	}

}

\src\main\java\webapp\LoginServlet.java

@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
		throws IOException, ServletException {
	String name = request.getParameter("name");
	String password = request.getParameter("password");

	boolean isValidUser = service.validateUser(name, password);

	if (isValidUser) {
		request.setAttribute("name", name);
		request.getRequestDispatcher("/WEB-INF/views/welcome.jsp")
				.forward(request, response);
	} else {
		request.setAttribute("errorMessage", "Invalid Credentials!!");
		request.getRequestDispatcher("/WEB-INF/views/login.jsp")
				.forward(request, response);
	}
}

\src\main\webapp\WEB-INF\views\login.jsp

<html>
<head>
<title>Yahoo!!</title>
</head>
<body>
	<p><font color="red">${errorMessage}</font></p>
	<form action="/login.do" method="POST">
		Name : <input name="name" type="text" /> Password : <input name="password" type="password" /> <input type="submit" />
	</form>
</body>
</html>

Step 11 : Configure application to use Spring MVC

What we will do

Before we start with the Flows, we need to configure application to use Spring MVC

  • Lets do a little bit of Refactoring. Mini Step 1: Rename package webapp to com.in28minutes.jee
  • We need Spring MVC Framework and its dependencies. Mini Step 2 : Add required jars to the project
  • Spring MVC uses Front Controller Pattern -> Dispatcher Servlet. Mini Step 3 : Add Dispatcher Servlet to web.xml
  • DispatcherServlet needs an Spring Application Context to launch. We will create an xml (/WEB-INF/todo-servlet.xml). Mini Step 4: Add Spring Context

Useful Snippets

pom.xml

		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-webmvc</artifactId>
			<version>6.0.6</version>
		</dependency>

web.xml

	    <servlet>
	        <servlet-name>dispatcher</servlet-name>
	        <servlet-class>
	            org.springframework.web.servlet.DispatcherServlet
	        </servlet-class>
	        <init-param>
	            <param-name>contextConfigLocation</param-name>
	            <param-value>/WEB-INF/todo-servlet.xml</param-value>
	        </init-param>
	        <load-on-startup>1</load-on-startup>
	    </servlet>
	
	    <servlet-mapping>
	        <servlet-name>dispatcher</servlet-name>
	        <url-pattern>/spring-mvc/*</url-pattern>
	    </servlet-mapping>

todo-servlet.xml

	<beans xmlns="http://www.springframework.org/schema/beans"
	    xmlns:context="http://www.springframework.org/schema/context"
	    xmlns:mvc="http://www.springframework.org/schema/mvc"
	    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.xsd
	    http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
	    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
	
	    <context:component-scan base-package="com.in28minutes" />
	
	    <mvc:annotation-driven />
	    
	</beans>

Flows:

  • Flow 1. Login Servlet -> GET -> login.jsp
  • Flow 2. Login Servlet -> POST (Success) -> welcome.jsp
  • Flow 3. Login Servlet -> POST (Failure) -> login.jsp (with error message)

Files List

\src\main\webapp\WEB-INF\views\login.jsp Deleted

\pom.xml Deleted

\src\main\java\webapp\LoginService.java Deleted

\src\main\java\webapp\LoginServlet.java Deleted

\src\main\webapp\WEB-INF\views\welcome.jsp Deleted

\src\main\webapp\WEB-INF\web.xml Deleted

/pom.xml New
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.in28minutes</groupId>
	<artifactId>in28Minutes-springmvc</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>war</packaging>

	<dependencies>
		<dependency>
			<groupId>javax</groupId>
			<artifactId>javaee-web-api</artifactId>
			<version>8.0.1</version>
			<scope>provided</scope>
		</dependency>

		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-webmvc</artifactId>
			<version>6.0.6</version>
		</dependency>
	</dependencies>

	<build>
		<pluginManagement>
			<plugins>
				<plugin>
					<groupId>org.apache.maven.plugins</groupId>
					<artifactId>maven-compiler-plugin</artifactId>
					<version>3.2</version>
					<configuration>
						<verbose>true</verbose>
						<source>1.8</source>
						<target>1.8</target>
						<showWarnings>true</showWarnings>
					</configuration>
				</plugin>
				<plugin>
					<groupId>org.apache.tomcat.maven</groupId>
					<artifactId>tomcat7-maven-plugin</artifactId>
					<version>2.2</version>
					<configuration>
						<path>/</path>
						<contextReloadable>true</contextReloadable>
					</configuration>
				</plugin>
			</plugins>
		</pluginManagement>
	</build>
</project>
/src/main/java/com/in28minutes/jee/LoginService.java New
package com.in28minutes.jee;

public class LoginService {
	public boolean validateUser(String user, String password) {
		return user.equalsIgnoreCase("in28Minutes") && password.equals("dummy");
	}

}
/src/main/java/com/in28minutes/jee/LoginServlet.java New
package com.in28minutes.jee;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet(urlPatterns = "/login.do")
public class LoginServlet extends HttpServlet {

	private LoginService service = new LoginService();

	@Override
	protected void doGet(HttpServletRequest request,
			HttpServletResponse response) throws IOException, ServletException {
		request.getRequestDispatcher("/WEB-INF/views/login.jsp").forward(
				request, response);
	}

	@Override
	protected void doPost(HttpServletRequest request,
			HttpServletResponse response) throws IOException, ServletException {
		String name = request.getParameter("name");
		String password = request.getParameter("password");

		boolean isValidUser = service.validateUser(name, password);

		if (isValidUser) {
			request.setAttribute("name", name);
			request.getRequestDispatcher("/WEB-INF/views/welcome.jsp").forward(
					request, response);
		} else {
			request.setAttribute("errorMessage", "Invalid Credentials!!");
			request.getRequestDispatcher("/WEB-INF/views/login.jsp").forward(
					request, response);
		}
	}

}
/src/main/webapp/WEB-INF/todo-servlet.xml New
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    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.xsd
    http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="com.in28minutes" />

    <mvc:annotation-driven />
    
</beans>
/src/main/webapp/WEB-INF/views/login.jsp New
<html>
<head>
<title>Yahoo!!</title>
</head>
<body>
    <p><font color="red">${errorMessage}</font></p>
    <form action="/login.do" method="POST">
        Name : <input name="name" type="text" /> Password : <input name="password" type="password" /> <input type="submit" />
    </form>
</body>
</html>
/src/main/webapp/WEB-INF/views/welcome.jsp New
<html>
<head>
<title>Yahoo!!</title>
</head>
<body>
Welcome ${name}
</body>
</html>
/src/main/webapp/WEB-INF/web.xml New
<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
    version="3.0">

    <display-name>To do List</display-name>

    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>
            org.springframework.web.servlet.DispatcherServlet
        </servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/todo-servlet.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>/spring-mvc/*</url-pattern>
    </servlet-mapping>
</web-app>

Step 12 : First Spring MVC Controller, @ResponseBody, @Controller

#First Spring MVC Controller

  • @RequestMapping(value = “/login”, method = RequestMethod.GET)
  • http://localhost:8080/spring-mvc/login
  • web.xml — /spring-mvc/*
  • Why @ResponseBody?
  • Importance of RequestMapping method
  • Can I have multiple urls rendered from Same Controller?

#Snippets

package com.in28minutes.springmvc;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class LoginController {

	@RequestMapping(value = "/login")
	@ResponseBody
	public String sayHello() {
		return "Hello World dummy";
	}
}

Step 13 : Redirect to Login JSP — LoginController, @ResponseBody — and View Resolver

/src/main/java/com/in28minutes/springmvc/login/LoginController.java New
package com.in28minutes.springmvc.login;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
public class LoginController {
	@RequestMapping(value = "/login", method = RequestMethod.GET)
	public String showLoginPage() {
		return "login";
	}
}
/src/main/webapp/WEB-INF/todo-servlet.xml Modified

New Lines

    <bean
        class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix">
            <value>/WEB-INF/views/</value>
        </property>
        <property name="suffix">
            <value>.jsp</value>
        </property>
    </bean>

Redirect to Login JSP

  • View Resolver in todo-servlet.xml
  • Update LoginController
  • Remove @ResponseBody
  • More about View Resolver

Snippets

  <bean
        class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix">
            <value>/WEB-INF/views/</value>
        </property>
        <property name="suffix">
            <value>.jsp</value>
        </property>
    </bean>

Step 14 : DispatcherServlet and Log4j

/pom.xml Modified

New Lines

<dependency>
	<groupId>log4j</groupId>
	<artifactId>log4j</artifactId>
	<version>1.2.17</version>
</dependency>
/src/main/resources/log4j.properties New
log4j.rootLogger=TRACE, Appender1, Appender2
 
log4j.appender.Appender1=org.apache.log4j.ConsoleAppender
log4j.appender.Appender1.layout=org.apache.log4j.PatternLayout
log4j.appender.Appender1.layout.ConversionPattern=%-7p %d [%t] %c %x - %m%n
 
/src/main/webapp/WEB-INF/views/login.jsp Modified

New Lines

    <form action="/spring-mvc/login" method="POST">
/src/main/webapp/WEB-INF/views/welcome.jsp Modified

New Lines

What we want to do:

  • Understand importance of DispatcherServlet.
  • Add Logging Framework Log4j to understand the flow much more.

Spring MVC Request Flow

  • DispatcherServlet receives HTTP Request.
  • DispatcherServlet identifies the right Controller based on the URL.
  • Controller executes Business Logic.
  • Controller returns a) Model b) View Name Back to DispatcherServlet.
  • DispatcherServlet identifies the correct view (ViewResolver).
  • DispatcherServlet makes the model available to view and executes it.
  • DispatcherServlet returns HTTP Response Back.
  • Flow : http://docs.spring.io/spring-framework/docs/2.0.8/reference/images/mvc.png

Step 15 : Show userid and password on the welcome page — ModelMap and @RequestParam

  • Show userid and password on the welcome page.
  • We will not use Spring Security for now.
  • ModelMap model
  • @RequestParam String name
/src/main/java/com/in28minutes/springmvc/login/LoginController.java Modified

New Lines


	@RequestMapping(value = "/login", method = RequestMethod.POST)
	public String handleUserLogin(ModelMap model, @RequestParam String name,
			@RequestParam String password) {
		model.put("name", name);
		model.put("password", password);
		return "welcome";
	}
}
/src/main/webapp/WEB-INF/views/welcome.jsp Modified

New Lines

Welcome ${name}. You entered ${password}

Step 16 : LoginService and Remove all JEE Servlets based code

  • Use LoginService to validate userid and password.
  • Remove all the old controller code and lets use only Spring MVC here on.
  • For now : We are not using Spring Autowiring for LoginService.
  • Change URL to http://localhost:8080/login
/src/main/java/com/in28minutes/jee/LoginService.java Deleted
/src/main/java/com/in28minutes/jee/LoginServlet.java Deleted
/src/main/java/com/in28minutes/springmvc/login/LoginController.java Deleted
/src/main/java/com/in28minutes/login/LoginController.java New
package com.in28minutes.login;

import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

import com.in28minutes.login.LoginService;

@Controller
public class LoginController {

	private LoginService loginService = new LoginService();

	@RequestMapping(value = "/login", method = RequestMethod.GET)
	public String showLoginPage() {
		return "login";
	}

	@RequestMapping(value = "/login", method = RequestMethod.POST)
	public String handleUserLogin(ModelMap model, @RequestParam String name,
			@RequestParam String password) {

		if (!loginService.validateUser(name, password)) {
			model.put("errorMessage", "Invalid Credentials");
			return "login";
		}

		model.put("name", name);
		return "welcome";
	}
}
/src/main/java/com/in28minutes/login/LoginService.java New
package com.in28minutes.login;

public class LoginService {
	public boolean validateUser(String user, String password) {
		return user.equalsIgnoreCase("in28Minutes") && password.equals("dummy");
	}

}
/src/main/webapp/WEB-INF/views/login.jsp Modified

New Lines

    <form action="/login" method="POST">
/src/main/webapp/WEB-INF/views/welcome.jsp Modified

New Lines

Welcome ${name}. You are now authenticated.
/src/main/webapp/WEB-INF/web.xml Modified

New Lines

        <url-pattern>/</url-pattern>

Step 17 : Spring Auto-wiring and Dependency Management — @Autowired and @Service

  • Learn about Spring Auto-wiring and Dependency Management.
  • Use Auto-wiring to wire LoginService.
  • @Autowired, @Service
/src/main/java/com/in28minutes/login/LoginController.java Modified

New Lines

import org.springframework.beans.factory.annotation.Autowired;
	@Autowired
	private LoginService loginService;
/src/main/java/com/in28minutes/login/LoginService.java Modified

New Lines

import org.springframework.stereotype.Service;
@Service
public class LoginService {

Other Introduction Sections

Title Category Github
Eclipse in 5 Steps Introduction Project Folder on Github
Maven in 5 Steps Introduction Project Folder on Github
JUnit in 5 Steps Introduction Project Folder on Github
Mockito in 5 Steps Introduction Project Folder on Github

Just Released

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

Разработанные для изучения за 15-30 минут, эти материалы содержат краткие практические
иструкции для реализации «Hello Word» и других подобных задач. В большинстве случаев,
предварительно необходимо установить JDK и текстовый редактор.

Обработка данных

Как с использованием Spring Integration создать приложение для сбора и обработки данных и записи результатов в файл.

Обработка настроек сайта

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

Разработанные для изучения эти материалы содержат практические
иструкции для реализации «Hello Word» и других задач, которые отражают функциональность и применимость
Spring Framework, а также сопутствующих его проектов. В большинстве случаев, предварительно необходимо
установить JDK и текстовый редактор.

Недавно мне пришла мысль, для закрепления своих знаний и, чтобы сделать свой маленький вклад в развитие IT, сделать небольшую серию руководств по различным технологиям.

spring-by-pivotal

Начать я решил с технологии Spring Framework.

Итак, приступим.

Для кого

Это руководство предназначено для Java разработчиков, которые хотят детально понять Spring Framework, его архитектуру и конкретное применение.

Необходимые знания

Для того чтобы Вы могли понимать вещи, которые описаны в этом руководстве, Вам необходимо хорошее понимание Java Core и опыт работы с Intellij Idea.

Введение

Spring один из самых популярных фреймворков для J2EE. Разработчики по всему миру используют Spring для создания надёжных и качественных приложений. Он был разработан в Июне 2003 года Родом Джонсоном.

С помощью Spring можно разработать любое Java приложение.

Внедрение зависимостей (Dependency Injection)

Невозможно понять, что такое Spring без понимания термина Dependency Injection (DI)  – один из видов инверсии управления (Inversion of Control – IoC).

При написании действительно крупных и сложных проектов разработчики сталкиваются с необходимостью делать классы приложения как можно более независимыми друг от друга для возможности повторного использования и юнит-тестирования. Именно DI устанавливает связи между этими классами, при этом сохраняя их независимость друг от друга.

Так что же DI такое?

Это ситуация, когда мы внедряем элемент одного класса в другой (конкретные примеры мы рассмотрим позже). На практике DI осуществляется путём передачи параметров конструктора или с помощью setter-ов.

Другим ключевым компонентом Spring является

Аспекто-ориентированное программирование (Aspect oriented programming – AOP)

Это функции, которые охватывают несколько узлов приложения, называются cross-cutting concerns и эти cross-cutting concerns отделены от непосредственной бизнес-логики приложения.

Примечание: придётся немного потерпеть и почитать пока непонятные термины, но позже, на практике, всё станет существенно яснее.

В ООП ключевой единицей является класс, в то время, как в АОП ключевым элементом является аспект. DI помогает разделить классы приложения на отдельные модули, и АОП – помогает отделить cross-cutting concerns от объектов на которые они влияют. Более подробно АОП будет рассмотрено далее. Крайней вещью, которая будет рассмотрена в этом руководстве будет непосредственно Архитектура Spring Framework. На данный момент Spring разделён на некоторое количество отдельных модулей, что позволяет Вам самим решать, какие из них использовать в Вашем приложении.

spring_architecture

Основной контейнер (Core Container) включает в себя Beans, Core, Context и SpEL (expression language).

Beans отвечает за BeanFactory которая является сложной реализацией паттерна Фабрика (GoF).

Модуль Core обеспечивает ключевые части фреймворка, включая свойства IoC и DI.

Context построен на основе Beans и Core и позволяет получить доступ к любому объекту, который определён в настройках. Ключевым элементом модуля Context является интерфейс ApplicationContext.

Модуль SpEL обеспечивает мощный язык выражений для манипулирования объектами во время исполнения.

Контейнер Data Access/Integration состоит из JDBC, ORM, OXM, JMS и модуля Transatcions.

JDBC обеспечивает абстрактный слой JDBC и избавляет разработчика от необходимости вручную прописывать монотонный код, связанный с соединением с БД.

ORM обеспечивает интеграцию с такими популярными ORM, как Hibernate, JDO, JPA и т.д.

Модуль OXM отвечает за связь Объект/XML – XMLBeans, JAXB и т.д.

Модуль JMS (Java Messaging Service) отвечает за создание, передачу и получение сообщений.

Transactions поддерживает управление транзакциями для классов, которые реализуют определённые методы.

Web

Этот слой состоит из Web, Web-MVC, Web-Socket, Web-Portlet

Модуль Web обеспечивает такие функции, как загрузка файлов и т.д.

Web-MVC содержит реализацию Spring MVC для веб-приложений.

Web-Socket обеспечивает поддержку связи между клиентом и сервером, используя Web-Socket-ы в веб-приложениях.

Web-Portlet обеспечивает реализацию MVC в среде портлетов.

Прочее

Spring также включает в себя ряд других важных модулей, таких как AOP, Aspects, Instrumentation, Messaging и Test

AOP реализует аспекто-ориентированное программирование и позволяет использовать весь арсенал возможностей АОП.

Модуль Aspects обеспечивает интеграцию с AspectJ, которая также является мощным фреймворком АОП.

Instrumentation отвечает за поддержку class instrumentation и class loader, которые используются в серверных приложениях.

Модуль Messaging обеспечивает поддержку STOMP.

И наконец, модуль Test обеспечивает тестирование с использованием TestNG или JUnit Framework.

На этом я завершаю вводную часть нашего руководства по Spring. В следующей части рассмотрим как создать простое Spring приложение с помощью IDE Intellij Idea.

#статьи


  • 0

Разбираемся в модулях фреймворка и в их основных различиях, запускаем на компьютере и создаём первый проект.

Иллюстрация: Colowgee для Skillbox Media

Антон Яценко

Изучает Python, его библиотеки и занимается анализом данных. Любит путешествовать в горах.

Spring — это популярный фреймворк для разработки на Java, который используют для создания корпоративных приложений, например CRM. При этом применять его могут не только Java-разработчики, но и те, кто работает с Kotlin или Groovy.

Иногда Spring называют фреймворком фреймворков, поскольку он состоит из отдельных модулей: Struts, Hibernate, Tapestry, EJB, JSF и других. Каждый модуль — это набор инструментов для решения технических задач, объединённых общей логикой Spring.

Давайте разберёмся, почему появился фреймворк Spring и как с ним работать на примере Java.

Для разработки веб-приложений программисты долго использовали JavaBeans. Это классы в языке Java, написанные по определённым правилам. Они облегчали разработку компонентов пользовательского интерфейса, но были сильно ограничены в своих возможностях. Например, с ними нельзя управлять транзакциями и безопасностью или реализовывать многопоточность, что очень важно для больших приложений.

Чаще всего эти проблемы беспокоили тех, кто разрабатывал корпоративные приложения, направленные на решение конкретной задачи в рамках большой компании. Например, бухгалтерское программное обеспечение, управление биллингом или CRM-систему.

Решить проблемы JavaBeans пытались с помощью Enterprise JavaBeans (EJB), технологии для написания и поддержки серверной части приложений. Её внедрение позволило облегчить работу над приложениями для бизнеса, так как в ней уже из коробки были поддержка многопоточности, распределённые транзакции и другие подходы, которые отсутствовали в JavaBeans.

Но работа с EJB сильно усложняла код из-за необходимости реализации методов обратного вызова жизненного цикла, Home- и Remote-интерфейсов, а также других подходов. Поэтому разработчики не успокоились и продолжали искать другое, более удобное решение для создания корпоративных приложений.

И такое решение появилось в июне 2003 года, когда Род Джонсон выпустил первую версию фреймворка Spring. А уже через год, в марте 2004 года, вышла первая стабильная версия фреймворка — 1.0. На июль 2022 года стабильная версия Spring — 5.3.х. Разберёмся, почему именно Spring стал удачным решением для создания корпоративных приложений.

Spring — это набор взаимосвязанных мини-фреймворков, созданных для работы над разными частями приложения. Подключать их можно по отдельности, в зависимости от задач.

Структура модулей в Spring
Инфографика: Spring Framework

Вот какие популярные модули входят в состав Spring.

Модуль для работы с реляционными и нереляционными базами данных: MySQL, Redis, Microsoft Azure Cosmos DB и другими. Включает в себя набор интерфейсов для работы с данными через JPA Entity. Подробно о возможностях модуля написано в официальной документации.

Позволяет работать в распределённых системах, включая персональный компьютер, PaaS-платформы, центры обработки данных и так далее. Внутри Spring Cloud существуют отдельные модули для конкретных решений — например, Spring Cloud Azure интегрирует Spring со службами Azure.

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

Java — объектно-ориентированный язык, но Spring основан на другой парадигме: аспектно-ориентированном программировании (АОП).

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

Классический пример АОП — применение политики безопасности. С одной стороны, она должна применяться ко всем частям приложения, а с другой стороны — быть самостоятельной сущностью, которую можно развивать отдельно от остального кода. Если работать с политикой безопасности по АОП, то она выносится в отдельный модуль и дальнейшая работа над ней, а также поддержка и рефакторинг проводятся независимо от основного приложения.

Подробнее про АОП в Spring написано в официальной документации.

Транзакция в Java — это последовательность запросов к базе данных, объединённая в один блок. Модуль транзакций в Spring позволяет управлять такими блоками, повышая безопасность и эффективность работы с СУБД. Работать можно с любыми вариантами транзакций: вложенными, локальными и глобальными. Узнать о возможностях модуля и особенностях его работы можно из документации.

Это лишь самые популярные модули, а в Spring существует ещё несколько десятков других модулей, которые подробно разобраны в документации к фреймворку. И лучше всего знакомиться с ними на практике.

Если искать информацию про работу и установку фреймворка Spring, то часто можно встретить упоминание Spring Boot. Spring Boot — это дополнение к Spring, которое облегчает и ускоряет работу с ним. Сам Spring Boot представляет собой набор утилит, автоматизирующих настройки фреймворка. Вот что он берёт на себя:

  • упаковывает зависимости в starter-пакеты;
  • автоматически конфигурирует приложения с помощью jar-зависимостей;
  • создаёт веб-сервер, что позволяет локально запускать на нём приложения.

Для работы с фреймворком Spring предстоит пройти несколько шагов:

  • Подготовить IDE и JDK.
  • Создать новый проект на Spring Boot.
  • Подготовить проект в IDE к работе.
  • Запустить код и увидеть результат.

Для начала работы вам потребуются две вещи:

  • Любая интегрированная среда разработки (IDE). Подойдёт одна из популярных IDE: IntelliJ IDEA, Spring Tools для Eclipse, Visual Studio Code.
  • Средства разработки Java (JDK). Официальный сайт фреймворка Spring рекомендует воспользоваться BellSoft Liberica JDK версии 8 или 11.

Самый простой способ подготовить фреймворк для работы — воспользоваться сайтом start.spring.io для создания веб-проекта.

Важно: в пункте Dependencies не забудьте добавить зависимость Web. После этого нажмите кнопку Generate и скачайте ZIP-архив. Распакуйте его в нужную папку на вашем компьютере, и у вас будет готов каркас будущего проекта. Интерфейс сборки — Spring Boot. Версия — последняя стабильная. На 3 июля 2022 года это 2.7.1.

Скриншот: Spring Framework / Skillbox Media

Проекты, созданные на start.spring.io, уже содержат Spring Boot. Это фреймворк, который делает Spring готовым к работе внутри вашего приложения, но не требует написания большого количества кода или сложного конфигурирования. Spring Boot — самый быстрый и популярный способ запуска Spring-проектов.

Некоторые IDE необходимо подготовить к работе, например Visual Studio Code. Для этого внутри приложения необходимо перейти в раздел «Расширения» и найти расширение Spring Boot Extension Pack. После его установки Visual Studio Code будет готова к работе.

Для работы с Visual Studio Code необходимо установить расширение Spring Boot Extension Pack
Скриншот: Visual Studio Code / Skillbox Media

Если вы используете IntelliJ IDEA, то она уже готова к работе с фреймворком Spring. Откройте установленную IDE и создайте новый проект. Назовите его удобным для себя образом. Рассмотрим следующие шаги на примере IntelliJ IDEA.

Выберите в меню пункт File и команду Open. Найдите файл DemoApplication.java в папке src/main/java/com/example/demo

Открытие файла в интерфейсе IntelliJ IDEA
Скриншот: IntelliJ IDEA / Skillbox Media

Открытие файла в интерфейсе IntelliJ IDEA
Скриншот: IntelliJ IDEA / Skillbox Media

Теперь измените содержимое файла, добавив дополнительный метод hello() и аннотации @SpringBootApplication и @RestController, показанные в приведённом ниже коде. Вы можете просто скопировать и вставить код в текст файла:

package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
@RestController
public class DemoApplication {
                
                  public static void main(String[] args) {
                  SpringApplication.run(DemoApplication.class, args);
                  }
                  
                  @GetMapping("/hello")
                  public String hello(@RequestParam(value = "name", defaultValue = "World") String name) {
                  return String.format("Hello %s!", name);
                  }
                
              }

Метод hello(), который мы добавили, принимает параметр name с типом String, а затем объединяет этот параметр со словом «Hello» в коде. Это означает, что если вы зададите в запросе имя «Антон», то ответом будет «Hello Антон». Имя прописывается вручную в параметре defaultValue.

Аннотация @RestController сообщает Spring, что этот код описывает конечную точку, которая должна быть доступна через веб. Аннотация @GetMapping («/hello») указывает Spring, что надо использовать наш метод hello() для ответа на запросы, отправленные на адрес http://localhost:8080/hello. Наконец, @RequestParam указывает Spring, что в запросе должно быть значение name, а если его там нет, то использовать по умолчанию строку «World».

Теперь давайте соберём и запустим программу. Так как наша IDE уже готова к работе с фреймворком Spring, а сам проект создан в Spring Boot, который мы скачали с официального сайта, то сделать это просто.

Откройте командную строку (или терминал) и перейдите в папку, где находятся файлы проекта. Мы можем собрать и запустить приложение, выполнив следующую команду:

macOS/Linux:

Windows:

На выходе получим что-то подобное:

Результат запуска кода в терминале
Скриншот: Spring Framework / Skillbox Media

Последние несколько строк говорят нам о том, что Spring запущен. Встроенный в Spring Boot сервер Apache Tomcat работает в качестве веб-сервера и прослушивает запросы на порту localhost 8080. Откройте браузер и в адресной строке введите http://localhost:8080/hello. Вы должны получить такой ответ:

Работа локального сервера
Скриншот: Spring Framework / Skillbox Media

Поздравляем! Вы сделали свой первый проект на фреймворке Spring. Теперь можно углубиться в документацию фреймворка или пойти на специализированный курс по работе со Spring.

На «Хабр Карьере» доступно более 300 вакансий разработчиков со знанием Spring. Для сравнения, количество вакансий для Java-разработчиков — более 1100, а для Node.js-разработчиков — 180. Кроме фреймворка необходимо знать сам язык Java, работать с разными базами данных, пользоваться Git и таск-трекерами типа Jira.

Зарплата зависит от уровня разработчика. Джуниоры получают от 50 тысяч рублей, а сеньоры — от 200 тысяч. Разработчик среднего уровня может рассчитывать на зарплату от 100 до 250 тысяч рублей.

Скриншот: «Хабр Карьера» / Skillbox Media

Фреймворк Spring помогает облегчить разработку приложений на Java с помощью специализированных модулей и аспектно-ориентированного программирования. Углубиться в изучение фреймворка лучше с помощью официальной документации.

Как зарабатывать больше с помощью нейросетей?
Бесплатный вебинар: 15 экспертов, 7 топ-нейросетей. Научитесь использовать ИИ в своей работе и увеличьте доход.

Узнать больше

Понравилась статья? Поделить с друзьями:
  • Land rover defender 110 руководство по эксплуатации
  • Скачать руководство по ремонту ваз 2110 ваз 2111 ваз 2112
  • Nissan laurel c35 мануал
  • Стиль руководства к самовыражению
  • Лекарство слабилен в каплях инструкция по применению