Что такое внедрение зависимостей spring

Spring dependency injection — внедрение зависимостей в Spring

Что такое внедрение зависимостей spring. Смотреть фото Что такое внедрение зависимостей spring. Смотреть картинку Что такое внедрение зависимостей spring. Картинка про Что такое внедрение зависимостей spring. Фото Что такое внедрение зависимостей spring

Продолжим тему фреймворка Spring и сегодня рассмотрим spring dependency injection или внедрение зависимостей как его еще иногда называют. Для того, чтобы разобраться как работает внедрение зависимостей в данном java фреймворке нужно разобрать понятие Inversion of Control (далее IoC) инверсия управления.

IoC — принцип объектно-ориентированного программирования, который предназначен для уменьшения связанности модулей и классов программы. Данный принцип входит в пятерку SOLID принципов. Если не вдаваться в подробности, то в нем говориться что модули верхних уровней не должны зависеть от модулей нижних уровней. Данные модули должны зависеть от абстракций. А если еще проще, то нужно пытаться программировать на уровне абстракций, а не реализаций. Тогда, когда Ваша реализация будет меняться, ее изменения не затронут верхние уровни.

Dependency injection (DI) — как раз и есть одной из реализаций IoC. Spring Framework имеет очень отличную поддержку DI. Как раз ее реализацию в Spring сейчас на примере и посмотрим.

Мы уже программировали на уровне абстракций. В статье Spring MVC первое веб приложение мы предоставляли доступ к нашей сущности Order через интерфейс OrderRepository и его реализацию OrderRepositoryImpl. Далее мы использовали OrderRepository в сервис слое и уже OrderService мы предоставили как конечную точку доступа к сущности Order.

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

import java.util.ArrayList ;
import java.util.List ;

public class OrderRepositoryImpl implements OrderRepository <

private List Order > orders = new ArrayList Order > ( ) ;

public OrderRepositoryImpl ( ) <
Order order = new Order ( ) ;
order. setId ( 1 ) ;
order. setPrice ( 234d ) ;
order. setTitle ( «Pizza peperoni» ) ;
Order order2 = new Order ( ) ;
order2. setId ( 2 ) ;
order2. setPrice ( 123d ) ;
order2. setTitle ( «Sushi Philadelfia» ) ;
orders. add ( order ) ;
orders. add ( order2 ) ;
>

public void save ( Order order ) <
orders. add ( order ) ;
>

public void delete ( Order order ) <
orders. remove ( order ) ;
>

public List Order > getAll ( ) <
return orders ;
>

public Order getById ( Integer id ) <
return orders. get ( id ) ;
>
>

Код выше — простой DAO класс для стандартных операций над сущностью. Здесь для примера хранение происходило в списке. Если Вы решите изменить хранение данных на другую систему, поменять нужно будет только класс выше. Наш сервис ничего не знает о реализации. Он просто хранит и предоставляет методы.

Теперь о том, как реализована DI в Spring. Для того, чтобы Spring мог делать внедрения в классе есть три базовые аннотации: @Component, @Service, @Repository.

Что такое внедрение зависимостей spring. Смотреть фото Что такое внедрение зависимостей spring. Смотреть картинку Что такое внедрение зависимостей spring. Картинка про Что такое внедрение зависимостей spring. Фото Что такое внедрение зависимостей spring

Эти аннотации нужно добавить над теми классами, которые Вы хотите внедрить в зависимости. На примете простого Spring MVC приложения попробуем реализовать данные аннотации.

Класс OrderRepositoryImpl пометим аннотацией @Repository:

import java.util.ArrayList ;
import java.util.List ;

@ Repository
public class OrderRepositoryImpl implements OrderRepository <

private List Order > orders = new ArrayList Order > ( ) ;

public OrderRepositoryImpl ( ) <
Order order = new Order ( ) ;
order. setId ( 1 ) ;
order. setPrice ( 234d ) ;
order. setTitle ( «Pizza peperoni» ) ;
Order order2 = new Order ( ) ;
order2. setId ( 2 ) ;
order2. setPrice ( 123d ) ;
order2. setTitle ( «Sushi Philadelfia» ) ;
orders. add ( order ) ;
orders. add ( order2 ) ;
>

public void save ( Order order ) <
orders. add ( order ) ;
>

public void delete ( Order order ) <
orders. remove ( order ) ;
>

public List Order > getAll ( ) <
return orders ;
>

public Order getById ( Integer id ) <
return orders. get ( id ) ;
>
>

Класс OrderServiceImpl соответственно аннотацией @Service:

Теперь, чтобы использовать реализацию OrderRepository нужно в сервисе воспользоваться аннотацией @Autowired. Теперь в сервисе вместо private OrderRepository orderRepository = new OrderRepositoryImpl(); будет

Теперь, чтобы использовать сервис в контроллере можно заменить строку: private OrderService orderService = new OrderServiceImpl (); на

@Autowired
private OrderService orderService;

Spring сам найден нужную реализацию выше указанных интерфейсов.

Аннотации @Component, @Service, @Repository всего лишь обозначают для спринг, что это реализации соответствующих интерфейсов.

В данной статье не будет рассмотрено внедрение зависимостей через конструкторы и сеттеры. Я считаю, что непосредственное внедрение зависимостей (на примере выше) вполне достаточно для начального уровня изучающих Spring и дальнейшее усложнение может запутать читателя.

Еще пример dependency injection на видео ниже:

Источник

Внедрение зависимостей DI в Java Spring

Что такое внедрение зависимостей spring. Смотреть фото Что такое внедрение зависимостей spring. Смотреть картинку Что такое внедрение зависимостей spring. Картинка про Что такое внедрение зависимостей spring. Фото Что такое внедрение зависимостей spring

Что такое внедрение зависимостей

Необходимость внедрения зависимостей

Spring IOC разрешает такие зависимости с помощью внедрения зависимостей, что упрощает тестирование и повторное использование кода. Слабая связь между классами может быть возможна путем определения интерфейсов для общих функций, и инжектор создаст экземпляры объектов требуемой реализации. Задача создания экземпляров объектов выполняется контейнером в соответствии с конфигурациями, указанными разработчиком.

Типы внедрения зависимостей Spring

Существует два типа внедрения зависимостей Spring.

в файле bean-config.

Пример: Допустим, есть класс GFG, который использует SDI и задает свойства гиков. Код для него приведен ниже.

Пример: возьмем тот же пример, что и SDI.

Внедрение зависимостей установщика (SDI) против внедрения зависимостей конструктора (CDI)

Пример Spring DI

Мы использовали три класса и интерфейс в качестве компонентов, чтобы проиллюстрировать концепции CDI и SDI. Это классы Vehicle, ToyotaEngine, Tires и интерфейс IEngine соответственно.

В нашем примере мы видим, что класс Vehicle зависит от реализации Engine, который является интерфейсом. (Таким образом, в основном производителю транспортных средств требуется стандартный двигатель, соответствующий индийским нормам выбросов.) Класс ToyotaEngine реализует интерфейс, и его ссылка предоставляется в файле конфигурации bean-компонента, сопоставленном с одним из свойств класса транспортного средства.

Компонент InjectwithConstructor использует аргумент-конструктор элемента с именем атрибута и ссылкой. Атрибут Name коррелирует с именем аргумента конструктора, указанным в определении класса Vehicle. Атрибут ref указывает на ссылку bean-компонента, которую можно использовать для инъекции.

InjectwithSetter использует элемент свойства, чтобы указать «имя» свойства и «значение» свойства. Вместо значения атрибут ref может использоваться для обозначения ссылки на компонент.

pom.xml

Enigne.java

ToyotaEngine.java

Tyres.java

Vehicle.java

springContext.xml

Процесс создания экземпляра bean-компонента и внедрения зависимостей показан на рисунке ниже:

Что такое внедрение зависимостей spring. Смотреть фото Что такое внедрение зависимостей spring. Смотреть картинку Что такое внедрение зависимостей spring. Картинка про Что такое внедрение зависимостей spring. Фото Что такое внедрение зависимостей spring

Резюме

Как обсуждалось выше, избегайте использования инъекции полей, так как это только обеспечивает лучшую читаемость, несмотря на многие недостатки. Инъекции сеттеров и конструкторов имеют свои плюсы и минусы, как обсуждалось выше. Поэтому мы должны использовать комбинацию того и другого, что также предлагается самим сообществом Spring.

Источник

Способы внедрения зависимостей (Dependency Injection) в Spring

Dependency Injection (внедрение зависимостей) – ключевой шаблон проектирования в Spring. Мы говорим фреймворку создать за нас бины (иначе говоря — объекты) и внедрить их в другие бины. И фреймворк это делает.

Но как объяснить фреймворку Spring, что такой-то бин должен стать зависимостью для другого бина? Вариантов немного, а самых частых всего два: бин внедряется либо через конструктор класса, либо с помощью сеттера. Первое называется constructor-based injection, а второе — setter-based injection.
В этой статье мы создадим бин Engine и будем внедрять его в два других бина: в бин CarWithConstructor с помощью конструктора и в CarWithSetter с помощью сеттера.

Конфигурация Maven

Чтобы начать работу с бинами, необходимо добавить в pom.xml зависимость:

Определим классы. Итак, сначала у нас есть три класса. Класс Engine:

Класс CarWithConstructor с конструктором:

И класс CarWithSetter с сеттером:

Чтобы внедрить бин, классов нам недостаточно, Spring имеет дело с бинами, а не классами. Поэтому нужно сконфигурировать эти классы так, чтобы Spring контейнер создал на их основе бины. В конфигурации заодно будут заданы и pзависимости. Конфигурировать бины можно либо с помощью аннотаций, либо с помощью XML. (Но учтите, что XML-конфигурация немного устарела.)

Конфигурация бинов с помощью аннотаций

До того как внедрять бин engine, давайте его определим:

Аннотация @Component говорит фреймворку превратить класс в бин. При запуске Spring создаст экземпляр класса Engine. Этот экземпляр будет синглтоном в нашем случае. Мы сможем его впоследствии получить из контекста приложения с помощью команды:

И он будет внедрен во все бины, где мы зададим его в качестве зависимости. Неважно каким способом – через конструктор или сеттер.
Давайте зададим пакет, в котором хранятся бины, чтобы Spring знал, где их искать. Это делается с помощью аннотации @ComponentScan:

Обычно в классе Config прописываются конфигурации, но в нашем простом приложении он пуст.

В пакете «ru.javalang.injection» Spring будет искать аннотированные с помощью @Component классы, чтобы превратить их в бины при запуске приложения и инициализации контейнера Spring.
Итак, мы определили один бин engine. Теперь можно его внедрять в другие бины. Конечно, эти другие бины тоже надо сконфигурировать. И внутри конфигурации задать зависимости (dependency injection).

Constructor Based Injection

Если в классе есть конструктор, то можно внедрить зависимость через конструктор. При создании класса контейнер Spring вызовет конструктор и передаст зависимость в качестве аргумента конструктора.
Давайте определим бин CarWithConstructor и внедрим в него бин Engine с помощью конструктора:

Аннотация @Component означает, что класс CarWithConstructor надо зарегистрировать в качестве бина.
А аннотация @Autowired перед конструктором говорит фреймворку внедрить бин engine в качестве зависимости в бин CarWithConstructor.
Обратите внимание, что начиная с версии Spring 4.3 аннотацию @Autowired можно опустить, если у класса всего один конструктор. О том, что в конструкторе надо внедрить бин, фреймворк догадается сам.

Setter Based Injection

Если в классе задан сеттер, то зависимость можно внедрить и через него. Тогда при создании экземпляра класса контейнер вызовет конструктор без аргументов, а потом сеттер, чтобы внедрить зависимость во только что созданный бин.
Определим бин CarWithSetter и внедрим в него бин engine с помощью сеттера.
Для этого используем перед сеттером аннотацию @Autowired:

Так же как в предыдущем случае, аннотацию @Autowired перед сеттером можно опустить.
Более того, можно опустить и сеттер. И просто аннотировать поле car:

И внедрение зависимости все равно произойдет. Несмотря на то, что тут нет ни конструктора, ни сеттера, а поле car имеет модификатор private. Это возможно, потому что под капотом фреймворк использует рефлексию для создания бинов.

Чтобы получить экземпляры машин, надо обратиться к контексту приложения:

Переменная carWithConstructor будет иметь ненулевую ссылку на engine. Хотя мы не создавали ни один объект с помощью оператора new. Все бины создал фреймворк и добавил ссылки на зависимости там, где они были определены.
Обратите внимание, что все бины у нас синглтоны, и обе переменные carWithConstructor и carWithSetter ссылаются на один и тот же engine. Сингтон — самый частый жизненный цикл бина.

Конфигурация бинов с XML

А теперь сконфигурируем все то же самое с помощью XML:

Тег bean задает бин, это аналог аннотации @Component.

Constructor Based Injection

Вот часть вышеприведенного XML, которая определяет бин CarWithConstructor:

Тут constructor-arg определяет внедрение зависимости с помощью конструктора.
Атрибут ref содержит ссылку на идентификатор бина engine.

Setter-Based Injection

А это часть вышеприведенного XML, которая задает бин CarWithSetter:

Здесь тег property задает внедрение зависимости с помощью сеттера.
Обратите внимание, что если мы конфигурируем бины с помощью XML, то задать сеттер в классе необходимо. Иначе будет выброшено исключение. Потому что все послабления в конфигурациях пришли с аннотациями, с XML все гораздо строже.
За контекст, созданный с помощью XML, отвечает другой класс:

Бин из XML-контекста получаем аналогично:

Убедимся, что engine внедрен:

Какой способ внедрения зависимости лучше

Для разработчика большой разницы нет. В документации рекомендуется отталкиваться от класса – его структуры и цели. Если зависимость обязательна в данном классе, то логичнее это поле передавать в конструкторе. А значит это будет внедрение через конструктор. Соответственно если какая-то зависимость необязательна, то внедряем ее через сеттер.

Код примера есть на GitHub.

Способы внедрения зависимостей (Dependency Injection) в Spring: 3 комментария

Большое спасибо вам за ваши статьи. Все очень коротко, ястно и лаконично объясняется…Однозначно лучше чем любой видео-урок на ютубе.

Спасибо, будем продолжать). Правда, как раз хочу записать краткие видео-уроки.

Огромная благодарность Автору статей по Spring, это самые ясные и лаконичные разъяснения работы фреймворка, которые я нашел!
Искренне Ваш, Yustas

Источник

Что такое Spring Framework? От внедрения зависимостей до Web MVC

Что такое внедрение зависимостей spring. Смотреть фото Что такое внедрение зависимостей spring. Смотреть картинку Что такое внедрение зависимостей spring. Картинка про Что такое внедрение зависимостей spring. Фото Что такое внедрение зависимостей spring

Вы можете использовать это руководство для различных целей:

9000 слов, вероятно, не стоит читать ее на мобильном устройстве. Добавьте ее в закладки и вернитесь позже. И даже на компьютере ешь читай этого слона по одному кусочку за раз 🙂

Содержание

Примечание переводчика:
Текст статьи весьма большой и в переводе могут остаться неточности несмотря на многократную вычитку текста. Буду благодарен всем за конструктивные замечания.

Введение

Сложность экосистемы Spring

Многие компании используют Spring, однако когда вы захотите узнать об этом фреймворке и перейдете на сайт spring.io, то увидите, что вселенная Spring на самом деле состоит из 21 различных активных проектов. Ой!

Кроме того, если вы начали программировать с помощью Spring в последние пару лет, очень велика вероятность того, что вы перешли непосредственно к Spring Boot или Spring Data.

Однако это руководство касается только одного, самого важного из этих проектов: Spring Framework. Почему?

Потому что важно понимать, что Spring Framework является основой для всех других проектов. Spring Boot, Spring Data, Spring Batch — все это построено поверх Spring.

Это имеет два последствия:

15 минут на чтение этого руководства, которое охватывает самые важные 80% Spring Framework, многократно окупятся в вашей профессиональной карьере.

Что такое Spring Framework?

Краткий ответ:

По сути Spring Framework представляет собой просто контейнер внедрения зависимостей, с несколькими удобными слоями (например: доступ к базе данных, прокси, аспектно-ориентированное программирование, RPC, веб-инфраструктура MVC). Это все позволяет вам быстрее и удобнее создавать Java-приложения.

Только это не очень помогает, не так ли?

К счастью, есть и длинный ответ:

Остальная часть этого руководства.

Основы внедрения зависимостей

Если вы уже знаете, что такое внедрение зависимостей, не стесняйтесь переходить прямо к следующему разделу Контейнер Spring IOC / Dependency Injection. В противном случае читайте дальше.

Что такое зависимость?

Представьте, что вы пишете Java класс, который позволяет вам получить доступ к таблице пользователей в вашей базе данных. Вы бы назвали эти классы DAO (объект доступа к данным) или репозитории. Итак, вы собираетесь написать класс UserDAO.

Ваш класс UserDAO имеет только один метод, который позволяет вам находить пользователей в вашей таблице базы данных по их идентификаторам.

Чтобы выполнить соответствующий SQL запрос, вашему классу UserDAO требуется соединение с базой данных. А в мире Java вы, обычно, получаете соединение с базой данных из другого класса, называемого DataSource. Итак, ваш код теперь будет выглядеть примерно так:

Внедрение зависимостей с помощью new()

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

Это работает, но давайте посмотрим, что произойдет, когда мы расширим наш класс UserDao другим методом, findByFirstName.

К сожалению, этому методу также нужен источник данных для работы. Мы можем добавить этот новый метод к нашему UserDAO и применить некоторые рефакторинги, введя метод newDataSource.

Этот подход работает, но имеет два недостатка:

Что произойдет, если мы хотим создать новый класс ProductDAO, который также выполняет операторы SQL? Ваш ProductDAO также будет иметь зависимость DataSource, которая теперь доступна только в вашем классе UserDAO. Затем у вас будет другой подобный метод или извлечен вспомогательный класс, содержащий ваш DataSource.

Мы создаем совершенно новый источник данных для каждого запроса SQL. Учтите, что DataSource открывает реальное сокет-соединение от вашей Java-программы к вашей базе данных. Это занимает время и довольно дорого. Было бы намного лучше, если бы мы открыли только один источник данных и использовали его повторно, вместо того, чтобы открывать и закрывать их тонны. Одним из способов сделать это может быть сохранение источника данных в закрытом поле в нашем UserDao, чтобы его можно было повторно использовать между методами, но это не помогает при дублировании между несколькими DAO.

Зависимости в глобальном классе приложения

Чтобы решить эти проблемы, вы можете подумать о написании глобального класса Application, который выглядит примерно так:

Ваш класс UserDAO теперь может выглядеть так:

Это улучшение по двум направлениям:

Однако у этого решения есть еще несколько недостатков:

Инверсия управления (IoC, Inversion of Control)

Давайте сделаем еще один шаг вперед.

Было бы неплохо, если бы вам в классе UserDAO вообще не приходилось беспокоиться о поиске зависимостей? Вместо того, чтобы активно вызывать Application.INSTANCE.dataSource(), ваш UserDAO мог бы (как-то) кричать, что он ему нужен, но больше не контролирует, когда / как / откуда он его получает?

Это то, что называется инверсией управления (Inversion of Control).

Давайте посмотрим, как может выглядеть наш класс UserDAO, с новым конструктором.

С точки зрения UserDao это выглядит намного лучше. Он больше не знает ни о классе приложения, ни о том, как создавать сами источники данных. Он только объявляет миру, что «если вы хотите создать (то есть использовать) меня, вам нужно дать мне источник данных».

Но представьте, что вы хотите запустить свое приложение. Если раньше вы могли вызывать «new UserService()», то теперь вам нужно обязательно вызвать новый UserDao(dataSource).

Контейнеры для внедрения зависимостей

Следовательно, проблема в том, что вы, как программист, все еще создаете UserDAO с помощью их конструктора и, таким образом, устанавливаете зависимость DataSource вручную.

Разве не было бы хорошо, если бы кто-то знал, что ваш UserDAO имеет зависимость от конструктора DataSource, и знал, как ее создать? А затем волшебным образом сконструирует для вас оба объекта: работающий DataSource и работающий UserDao?

Этот кто-то является контейнером внедрения зависимостей и является именно тем, что представляет собой среда Spring.

Контейнер Spring IOC / Dependency Injection

Как уже упоминалось в самом начале, Spring Framework по своей сути является контейнером внедрения зависимостей, который управляет написанными вами классами и их зависимостями для вас (см. Предыдущий раздел). Давайте выясним, как это происходит.

Что такое ApplicationContext? Для чего тебе это?

Тот, кто контролирует все ваши классы и может управлять ими соответствующим образом (читай: создайте их с необходимыми зависимостями), называется ApplicationContext во вселенной Spring.

Чего мы хотим добиться, так это следующего кода (я описал UserDao и DataSource в предыдущем разделе, перейдите по ссылке, если вы пришли сюда и пропустили его):

Это довольно круто, не правда ли? Вам, как вызывающей стороне, больше не нужно беспокоиться о создании классов, вы можете просто попросить ApplicationContext предоставить вам рабочие классы!

Но как это работает?

Что такое ApplicationContextConfiguration? Как построить ApplicationContexts из конфигураций.

В приведенном выше коде мы помещаем переменную с именем someConfigClass в конструктор AnnotationConfigApplicationContext. Вот быстрое напоминание:

То, что вы действительно хотите передать в конструктор ApplicationContext, это ссылка на класс конфигурации, который должен выглядеть следующим образом:

Этого класса конфигурации уже достаточно для запуска самого первого приложения Spring.

Теперь давайте выясним, что именно Spring и AnnotationConfigApplicationContext делают с тем классом конфигурации, который вы написали.

Почему мы создали AnnotationConfigApplicationContext? Существуют ли другие классы ApplicationContext?

Существует много способов создания Spring ApplicationContext, например, с помощью файлов XML, аннотированных классов конфигурации Java или даже программно. Для внешнего мира это представлено через единый интерфейс ApplicationContext.

Посмотрите на класс MyApplicationContextConfiguration сверху. Это класс Java, который содержит аннотации Spring. Вот почему вам необходимо создать аннотацию AnnotationConfigApplicationContext.

Если вместо этого вы хотите создать свой ApplicationContext из файлов XML, вы должны создать ClassPathXmlApplicationContext.

Есть и много других, но в современном приложении Spring вы обычно начинаете с контекста приложения, основанного на аннотациях.

Вам придется думать о методах внутри вашего класса конфигурации ApplicationContext как о фабричных методах. На данный момент есть один метод, который знает, как создавать экземпляры UserDao, и один метод, который создает экземпляры DataSource.

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

Но это приводит к вопросу: сколько экземпляров определенного компонента должно быть создано Spring?

Что такое Spring bean scope?

Сколько экземпляров наших DAO следует создать в Spring? Чтобы ответить на этот вопрос, вам нужно узнать о bean scope (область применения бина).

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

Аннотация области (scope annotation) определяет, сколько экземпляров создаст Spring. И, как упоминалось выше, это довольно просто:

Суть: большинство приложений Spring почти полностью состоят из одноэлементных bean-компонентов, в которые время от времени добавляются другие области действия bean-компонента (прототип, запрос, сессия, websocket и т.д.).

Теперь, когда вы знаете о ApplicationContexts, Beans & Scopes, давайте еще раз рассмотрим зависимости или то, как наш UserDAO может получить DataSource.

Что такое Spring Java Config?

До сих пор вы явно настраивали свои bean-компоненты в конфигурации ApplicationContext с помощью аннотированных @Bean методов Java.

Это то, что вы бы назвали Spring Java Config, в отличие от указания всего в XML, который исторически был подходом для Spring. Просто краткий обзор того, как это выглядит:

Это означает, что, если ваш MyApplicationContextConfiguration находится в пакете com.marcobehler, Spring будет сканировать каждый пакет, включая подпакеты, который начинается с com.marcobehler для поиска потенциальных компонентов Spring.

Давайте добавим аннотацию @Component к вашему UserDAO.

Теперь Spring имеет всю информацию, необходимую для создания bean-компонентов UserDAO:

Внедрение зависимости через конструктор и Autowired

В более новых версиях Spring действительно достаточно умен, чтобы внедрять эти зависимости без явной аннотации @Autowired в конструкторе. Так что это также будет работать.

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

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

Что такое Field Injection? Что такое Setter Injection?

Проще говоря, Spring не должен использовать конструктор для внедрения зависимостей.

Он также может напрямую внедрять поля.

Кроме того, Spring также может внедрять сеттеры.

Эти два стиля внедрения (поля, сеттеры) имеют тот же результат, что и внедрение конструктора: вы получите работающий Spring Bean. На самом деле есть еще один метод, называемый внедрением метода, который мы не будем здесь описывать.

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

Внедрение зависимости через конструктор или через поле

Было много споров в сети что лучше внедрение зависимости через конструктор или через поле и множеством громких голосов, утверждавших, что внедрение с помощью метода установки (setter) вредна.

Чтобы не добавлять шума к этим аргументам сформулируем суть этой статьи:

Резюме о контейнере Spring IoC

К настоящему моменту вы должны знать почти все, что вам нужно знать о контейнере зависимостей Spring.

Конечно, это еще не все, но если вы хорошо разбираетесь в ApplicationContexts, Beans, зависимостях и различных методах внедрения зависимостей, то вы уже на правильном пути.

Давайте посмотрим, что еще может предложить Spring, кроме инъекций чистой зависимости.

Spring AOP (Аспектно-ориентированное программирование) и прокси

Внедрение зависимостей поможет вести вас к более структурированным программам, но внедрение зависимости здесь и там не совсем то, в чем заключается суть экосистемы Spring. Давайте еще раз посмотрим на простую ApplicationContextConfiguration:

Вот где проявляется функция скрытая особенность Spring:

Однако Spring может обманывать и создавать что-то еще, кроме вашего класса UserService. Как? Почему?

Spring может создавать прокси

Потому что под капотом любой метод Spring @Bean может вернуть вам то, что (в вашем случае) выглядит и ощущается как UserService, но на самом деле это не так.

Он может вернуть вам прокси.

Прокси-сервер в какой-то момент делегирует службу UserService, которую вы написали, но сначала он выполнит свою собственную функциональность.

Что такое внедрение зависимостей spring. Смотреть фото Что такое внедрение зависимостей spring. Смотреть картинку Что такое внедрение зависимостей spring. Картинка про Что такое внедрение зависимостей spring. Фото Что такое внедрение зависимостей spring

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

Почему Spring желает создавать прокси?

Потому что это позволяет Spring дать вашим компонентам дополнительные функции без изменения кода. В сущности, это то, что является аспектно-ориентированным (или: AOP) программированием.

Spring аннотация @Transactional

Ваша реализация UserService выше может выглядеть примерно так:

Проблема: хотя Spring может создать ваш компонент UserService через конфигурацию applicationContext, он не может переписать ваш UserService. Он не может просто вставить туда код, который открывает соединение с базой данных и фиксирует транзакцию с базой данных.

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

Давайте еще раз посмотрим на эту безопасную ContextConfiguration.

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

Нужно ли Spring использовать прокси Cglib?

Прокси являются выбором по умолчанию при программировании AOP с помощью Spring. Однако вы не ограничены использованием прокси, вы также можете пройти полный маршрут AspectJ, который при желании изменяет ваш фактический байт-код. Однако покрытие AspectJ выходит за рамки данного руководства.

Также смотрите раздел: В чем разница между Spring AOP и AspectJ?

Резюме о поддержке Spring AOP

Управление ресурсами Spring

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

Подумайте, как бы вы попытались получить доступ к файлу в Java через HTTP или FTP. Вы можете использовать класс URL в Java и написать некоторый код.

Точно так же, как бы вы читали в файлах из пути к классам вашего приложения? Или из контекста сервлета, это означает из корневого каталога веб-приложений (по общему признанию, это становится все реже и реже в современном приложении packaged.jar).

Опять же, вам нужно написать довольно много стандартного кода, чтобы он работал, и, к сожалению, код будет отличаться для каждого варианта использования (URL-адреса, пути к классам, контексты сервлетов).

Но есть решение: абстракция ресурсов Spring. Это легко объяснить в коде.

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

Как видите, он позволяет вам выполнять самые распространенные операции с ресурсом:

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

Абстракция ресурсов выглядит как такая крошечная функция, но она действительно сияет в сочетании со следующей удобной функцией, предлагаемой Spring: Properties.

Что такое Spring Environment?

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

Spring пытается упростить вам регистрацию и автоматический поиск свойств во всех этих различных источниках с помощью абстракции environment.

Что такое environment?

В двух словах, среда состоит из одного или многих источников свойств выполняемого Spring приложения. Например:

(Примечание. Среда также состоит из профилей, то есть профилей «dev» или «production», но мы не будем вдаваться в подробности о профилях в этом пересмотре этого руководства).

По умолчанию среда веб-приложения Spring MVC состоит из параметра ServletConfig/Context, источников системных свойств JNDI и JVM. Они также являются иерархическими, это означает, что они имеют порядок важности и перекрывают друг друга.

Тем не менее, довольно легко определить новые @PropertySources самостоятельно:

Теперь стало намного понятнее, почему мы говорили об управлении ресурсами Spring раньше. Потому что обе функции идут рука об руку.

Аннотация @PropertySource работает с любым допустимым классом конфигурации Spring и позволяет вам определять новые дополнительные источники с помощью абстракции ресурсов Spring: помните, что все дело в префиксах: http://, file://, classpath: и т.д.,

Определение свойств через @PropertySources — это хорошо, но разве нет лучшего способа, чем пройти через среду, чтобы получить к ним доступ? Да, есть.

Spring аннотация @Value и внедрение значений свойств

Spring Web MVC

Spring Web MVC, также известный как Spring MVC, является веб-средой Spring. Это позволяет создавать все, что связано с сетью, от небольших веб-сайтов до сложных веб-сервисов. Он также поддерживает фреймворки, такие как Spring Boot.

Что такое MVC?

Если вы совершенно не знакомы с MVC, вы можете прочитать страницу Model-View-Controller в Википедии, прежде чем продолжить чтение.

В контексте рендеринга HTML-страниц, скажем, страницы учетной записи пользователя, вот как выглядит MVC в Spring:

Итак, вашей конечной целью является написание контроллеров с помощью Spring, которые сопоставляются с конкретными HTTP-запросами, такими как (/account), и предоставляют пользователю соответствующую HTML-страницу, которая визуализируется путем объединения представления и данных, необходимых для этой страницы.

Та же логика верна для написания веб-сервисов JSON или XML.

Прежде чем мы рассмотрим реализацию класса типа контроллер, давайте сделаем шаг назад и посмотрим, как мы реализовали бы веб-страницу в низко-низко-низкоуровневом Java: с помощью старого доброго API сервлета Java (на котором основывается Spring MVC). ).

HttpServlet памятка

На данный момент игнорируем MVC: чтобы написать что-нибудь, связанное с HTTP с Java, вы бы использовали сервлеты или, в частности, HttpServlets (примечание для придир: да, есть и другие способы, спасибо за замечание). Сервлеты могут обрабатывать HTTP-запросы, а также возвращать браузеру или клиенту соответствующий HTTP-ответ.

После написания вашего сервлета вы должны зарегистрировать его в контейнере сервлетов, таком как Tomcat или Jetty. Регистрация сервлета всегда включает путь, чтобы указать, за какие URL в вашем веб-приложении отвечает ваш сервлет. Давайте предположим путь «/*», поэтому каждый входящий HTTP-запрос к вашему приложению обрабатывается одним сервлетом.

Вот как может выглядеть этот сервлет:

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

Кроме того, вашему MyServlet (контроллеру) необходимо выполнить довольно много ручного HTTP-специфического подключения, проверки URI запроса, просмотра строк, преобразования requestParameters и ответов и так далее.

Было бы намного приятнее, если бы вам не пришлось заботиться обо всем этом слесарном деле и позволить Spring сделать это за вас. Вот тут и приходит DispatcherServlet.

Что делает DispatcherServlet?

Uber-контроллер в среде Spring MVC Spring представляет собой сервлет, который называется DispatcherServlet.

Он называется DispatcherServlet, потому что он может буквально обрабатывать любой входящий HTTP-запрос, анализировать его содержимое и пересылать данные в виде симпатичных маленьких объектов Java в класс Controller.

Он также достаточно умен, чтобы брать выходные данные с этих контроллеров и конвертировать их в HTML / JSON / XML, в зависимости от того, что подходит. Весь процесс выглядит следующим образом (с пренебрежением большим количеством промежуточных классов, потому что DispatcherServlet не выполняет всю работу сам).

Что такое внедрение зависимостей spring. Смотреть фото Что такое внедрение зависимостей spring. Смотреть картинку Что такое внедрение зависимостей spring. Картинка про Что такое внедрение зависимостей spring. Фото Что такое внедрение зависимостей spring

Это именно то, что вы хотите.

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

Как писать классы типа Controller

Наконец, мы можем написать наш Controller класс, который обрабатывает запросы /account.

Страница /account будет HTML-страницей с несколькими динамическими переменными, такими как имя пользователя, адрес, информация о подписке и т.д.

В этих двух строчках происходит МНОГО, давайте посмотрим, что именно.

Наш метод Java возвращает строку с именем account. На самом деле это не просто строка, а ссылка на представление (HTML-шаблон). Давайте посмотрим на эти шаблоны на секунду.

Как генерировать HTML представление (view) с помощью Spring Web MVC

Spring MVC по умолчанию предполагает, что вы хотите визуализировать некоторый HTML. И, конечно же, вы не хотите сами отображать HTML-строки с помощью конкатенации строк, скорее вы захотите использовать шаблонную среду, такую как Velocity или Freemarker. Spring интегрируется со всеми этими технологиями.

Итак, вы могли бы иметь следующее представление (шаблон):
classpath:/templates/account.vm

Вот и все, разработка Spring MVC полностью завершена!

Как генерировать JSON / XML (представления) с помощью Spring Web MVC

С веб-сервисами вы не генерируете HTML, а генерируете XML или JSON. Это довольно просто, с Spring MVC также.

Конечно, вам нужна соответствующая библиотека, такая как Jackson, добавленная в ваш проект, но тогда вы можете просто аннотировать свой Controller дополнительной аннотацией, чтобы сигнализировать Spring: пожалуйста, преобразуйте мои объекты Java напрямую в XML / JSON, вместо того, чтобы я давал вам ссылка на вид.

Но как Spring узнает, должен ли он возвращать XML, JSON или что-то еще?

Как работает согласование контента (content negotiation) Spring MVC

Существует множество способов, с помощью которых вы, как клиент, можете указать Spring MVC, какой формат ответа вы хотите при выполнении запроса к приложению Spring MVC.

Если вы хотите узнать больше о согласовании контента, ознакомьтесь с официальной документацией.

Какой тип ввода HTTP-запроса понимает Spring?

Spring MVC понимает в основном все, что предлагает HTTP — с помощью сторонних библиотек.

Это означает, что вы можете выбросить в него тела запросов JSON, XML или HTTP (Multipart) Fileupload, и Spring удобно преобразует этот ввод в объекты Java.

Какие HTTP-ответы может написать Spring MVC?

Spring MVC может записывать все что угодно в HttpServletResponse — с помощью сторонних библиотек.

Будь то HTML, JSON, XML или даже тела ответов WebSocket. Более того, он берет ваши объекты Java и генерирует эти тела ответов для вас.

А как насчет других концепций Spring MVC?

Официальная документация Spring MVC буквально содержит сотни страниц, описывающих, как работает веб-фреймворк.

Поэтому, если вы хотите узнать больше о RequestParams, Моделях, Представлениях, ViewHandlers, RootContexts, Фильтрах, Кэшировании и Безопасности, я приглашаю вас проверить это. Это просто не входит в рамки данного руководства, чтобы охватить все.

Однако в разделе часто задаваемых вопросов данного руководства есть ответы на несколько дополнительных вопросов.

Вы могли бы подумать, что она присуща Spring Boot (как я ошибочно делал слишком долго), но, как правильно заметил Мацей Волковяк, это часть простого старого Spring MVC. Вот его источник:

Это верно, Spring MVC @RestController — это не что иное, как Spring MVC @Controller в сочетании с аннотацией Spring MVC @ResponseBody — хотя вы можете подумать, что это что-то связанное со Spring Boot.

Резюме: Spring MVC

Spring MVC — это старый добрый фреймворк MVC, который позволяет довольно легко писать HTML / JSON / XML веб-сайты или веб-сервисы. Он прекрасно интегрируется с контейнером внедрения зависимостей Spring, включая все его вспомогательные утилиты.

Вкратце: он позволяет вам сосредоточиться на написании Java-классов, вместо того, чтобы иметь дело с сланцевым кодом сервлета, то есть работать с Http-запросами и ответами, и дает вам хорошее разделение проблем между вашей моделью и представлениями.

Дополнительные модули Spring Framework

Мы рассмотрели контейнер IoC Spring, Spring Web MVC и несколько других небольших модулей Spring. Но есть еще много. Что они делают?

О чем дополнительные модули Spring Framework?

Spring Framework состоит из еще большего количества удобных утилит, чем вы видели до сих пор. Давайте назовем их модулями и не путайте эти модули с 20 другими проектами Spring на spring.io. Наоборот, все они являются частью рамочного проекта Spring.

Итак, о каком удобстве идет речь?

Вы должны понимать, что в основном все, что Spring предлагает в этих модулях, также доступно на чистой Java. Предлагается либо JDK, либо сторонней библиотекой. Spring Framework всегда опирается на эти существующие функции.

Вот пример: отправка вложений электронной почты с помощью Java API Почты, безусловно, выполнима, но немного неудобна в использовании. Смотрите здесь примера кода.

Spring предоставляет приятную маленькую API-оболочку поверх Java Mail API, с тем дополнительным преимуществом, что все, что он предлагает, прекрасно вписывается в контейнер внедрения зависимостей Spring.

Итак, подытоживая, цель Spring Framework состоит в том, чтобы «упростить» доступную функциональность Java, подготовить ее к внедрению зависимостей и, следовательно, упростить использование API в контексте Spring.

Есть ли список всех дополнительных модулей Spring Framework?

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

Spring Framework: FAQ

Какую версию Spring я должен использовать?

Выбор версии Spring относительно прост:

В реальности вы можете найти проекты на Spring версии 4.x-5.x, используемые компаниями, хотя имеются и редкие, унаследованные проекты на Spring 3.x (первоначальный выпуск: 2009).

Как часто выпускаются новые версии Spring? Как долго они поддерживаются?

Вот хороший маленький график, показывающий историю версий Spring:

Что такое внедрение зависимостей spring. Смотреть фото Что такое внедрение зависимостей spring. Смотреть картинку Что такое внедрение зависимостей spring. Картинка про Что такое внедрение зависимостей spring. Фото Что такое внедрение зависимостей spring

Вы можете видеть, что первоначальная версия Spring была

17 лет назад, а основные версии платформы выпускались каждые 3-4 года. Это не учитывает филиалы обслуживания, как бы то ни было.

Примечание. Текущий EOL() (больше никаких обновлений и поддержки) для всех версий Spring, кроме 5.2, в настоящее время настроен на 31 декабря 2020 года.

Какие библиотеки вам нужны, чтобы начать работу с Spring?

На самом деле существует только одна зависимость, которая вам нужна, когда вы хотите настроить проект Spring. Это называется spring-context. Это абсолютный минимум, чтобы заставить работать инъекционный контейнер Spring.

Для дополнительных функций Spring (таких как поддержка Spring JDBC или JMS) вам потребуются другие дополнительные библиотеки.

Вы можете найти список всех доступных модулей в официальной документации Spring, хотя с точки зрения зависимостей Maven artifactIds действительно следуют за именем модуля. Вот пример:

Чем отличаются версии Spring?

Подобно JVM, версии Spring безумно обратно совместимы, что означает, что вы можете (по существу) по-прежнему запускать файлы Spring 1.0 xml с последней версией Spring 5.0 (хотя я, по общему признанию, еще не пробовал это). Кроме того, обновление с, скажем, 3 до 5 также возможно с небольшими усилиями (см. Это руководство по миграции).

Таким образом, в целом, новые версии Spring основаны на более старых версиях Spring и имеют минимальные критические изменения (по сравнению, скажем, с Python 2 против 3). Итак, все основные концепции, которые вы изучили для Spring версии 3 или 4, остаются верными для Spring версии 5.

Вы можете получить отличный обзор того, что изменилось за последние 7 лет в отдельных версиях Spring, здесь:

Чтобы дать вам резюме:

Ядро (внедрение зависимостей, управление транзакциями и т.д.) Всегда остается неизменным или расширяется. Однако Spring идет в ногу со временем и предлагает поддержку для новых версий языка Java, улучшений инфраструктуры тестирования, веб-сокетов, реактивного программирования и т.д.

Что сейчас происходит в 20 других проектах Spring.io? Как насчет изменения реального байт-кода?

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

Вывод: все эти библиотеки расширяют Spring Framework и основываются на его основных принципах внедрения зависимостей.

В чем разница между Spring и Spring Boot?

Если вы прочитали это руководство, вы должны понимать, что Spring Boot построен поверх Spring. Несмотря на то, что скоро появится подробное руководство по Spring Boot, вот пример того, что означают «самоуверенные значения по умолчанию» в Spring Boot.

Проблема в том, что вы должны сами написать и настроить все эти отдельные части. Spring Boot, с другой стороны, берет эти отдельные части и связывает их вместе. Пример:

Итак, Spring Boot — это все, что нужно для того, чтобы собрать существующие части Spring Framework, предварительно сконфигурировать и упаковать их с минимальными затратами на разработку.

В чем разница между Spring AOP и AspectJ?

Как упомянуто выше в разделе Нужно ли Spring использовать прокси Cglib?» в Spring по умолчанию используется AOP на основе прокси. Он оборачивает ваши компоненты в прокси для достижения таких вещей, как управление транзакциями. Это имеет несколько ограничений и предостережений, но является довольно простым и понятным способом реализации наиболее распространенных проблем AOP, с которыми сталкиваются разработчики Spring.

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

Однако вы можете настроить Spring на использование AOP AspectJ вместо AOP по умолчанию на основе прокси.

Вот пара ссылок, если вы хотите получить больше информации по этой теме:

В чем разница между Spring и Spring Batch?

Spring Batch — это фреймворк, упрощающий написание пакетных заданий, т.е. «Читать эти 95 CSV-файлов каждую ночь в 3 часа ночи и вызывать внешнюю службу проверки для каждой записи».

Опять же, он построен на основе Spring Framework, но во многом является собственным проектом.

Тем не менее, обратите внимание, что по сути невозможно создать надежное пакетное задание без хорошего понимания общего управления транзакциями в Spring Framework и его отношения к Spring Batch.

В чем разница между Spring и Spring Web MVC?

Если вы прочитали это руководство, вы должны понимать, что Spring Web MVC является частью среды Spring.

Это могут быть RestControllers (где вы отправляете XML или JSON клиенту) или старые добрые HTML-контроллеры, где вы генерируете HTML с такими фреймворками, как Thymeleaf, Velocity или Freemarker.

В чем разница между Spring и Struts?

Вопрос должен быть: чем отличается Spring Web MVC от Struts?

Краткий исторический ответ таков: Spring Web MVC начинал как конкурент Struts, который, как утверждается, был плохо спроектирован разработчиками Spring (см. Википедию).

Современный ответ заключается в том, что, хотя Struts 2, безусловно, все еще используется в странном устаревшем проекте, Spring Web MVC является основой для всего, что связано с сетью во вселенной Spring. От Spring Webflow до RestControllers Spring Boot.

Что лучше? Spring XML или аннотации или Java конфигурация?

Spring начинался только с конфигурации XML. Затем медленно, все больше и больше появлялись аннотации / возможности Java конфигурации.

Сегодня вы заметитите, что конфигурация XML в основном используется в более старых, унаследованных проектах, а в новых проектах используется конфигурации на основе Java или аннотаций.

Обратите внимание на две вещи:

Что лучше? Внедрение зависимостей на основе конструктора или поля?

Как уже упоминалось в разделе о внедрении зависимостей, этот вопрос вызывает много разных мнений. Самое главное, что ваш выбор должен быть одинаковым для всего вашего проекта: не используйте внедрение на основе конструктора для 83% ваших бинов и внедрение на основе поля для остальных 17%.

Разумным подходом будет использование рекомендованного способа в документации Spring: использование внедрения конструктора для обязательных зависимостей, установка / установка поля для необязательных зависимостей, а затем проверка этих дополнительных зависимостей по всему классу на ноль.

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

Есть ли альтернативы контейнеру внедрения зависимостей в Spring?

Да, две популярные в экосистеме Java:

Обратите внимание, что Dagger предлагает только внедрение зависимостей, без дополнительных удобных функций. Guice предлагает внедрение зависимостей и другие функции, такие как управление транзакциями (с помощью Guice Persist).

Заключение

Если вы читали это далеко, то теперь у вас должно быть достаточно глубокое понимание того, что такое Spring Framework.

Вы узнаете, как это связано с другими библиотеками экосистемы Spring (такими как Spring Boot или Spring Data) в последующих руководствах, но сейчас я хочу, чтобы вы помнили эту метафору при попытке ответить на вопрос «Что такое Spring Фреймворк?»

Представьте, что вы хотите отремонтировать дом (

= создать программный проект).

Spring Framework — это ваш DIY-магазин (

= контейнер для инъекций зависимости), который предлагает множество различных инструментов, от горелок Бунзена (

= ресурсы / свойства) до кувалд (

= Web MVC) для вашего обновления. Эти инструменты просто помогут вам быстрее и удобнее отремонтировать ваш дом (создать приложение на Java).

(примечание: не спрашивайте меня, как я придумал эти сравнения;))

Вот и все на сегодня. Если у вас есть какие-либо вопросы или предложения, напишите мне по адресу marco@marcobehler.com или оставьте комментарий ниже. Для практических занятий ознакомьтесь с учебным курсом Spring Framework.

Спасибо за прочтение. Auf Wiedersehen.

Благодарности

(Благодарности автора оригинальной статьи — прим. перев.)

Источник

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

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