8000 GitHub - IGIntellectual/MyArticle-SpringFirstSteps
[go: up one dir, main page]

Skip to content

IGIntellectual/MyArticle-SpringFirstSteps

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 

Repository files navigation

Привет всем! Сегодня мы поговорим о Spring Framework, Spring MVC, Spring Boot и о многом другом! Надеемся, что эта статья станет для вас хорошей опорой в освоении современных WEB-технологий.

Что же такое этот «Spring»?

Spring – это целая группа проектов, упрощающих разработку на JAVA. Такие как Spring Framework, Spring Data, Spring Security, Spring Boot и многие другие. Почитать о них вы можете тут. Коротко про основные рассмотренные в нашей статье проекты Spring:

Spring Framework (или Spring Core) Ядро платформы. Основа Spring. Неявно используется всеми остальными проектами. Две его основные особенности: внедрение зависимостей (DI) и аспектно-ориентированное программирование (AOP). Обеспечивает базовую поддержку управления компонентами (ApplicationContext, bean), внедрение зависимостей, управление транзакциями веб-приложений, доступа к данным.

Spring MVC (это часть Spring Framework) Не является отдельным проектом, но стоит отдельного упоминания, так как для темы нашей статьи – это важная часть. Каркас, основанный на HTTP и сервлетах. Spring MVC - это фреймворк для разработки Web-приложений.

Spring Boot Spring Boot — это «вишенка на торте». Это Framework для Framework’а. Он помогает быстро сконфигурировать и поднять приложение через аннотации и использует автоконфигурацию по максимуму (использует стандартные, дефолтные настройки). Он объединяет вместе все остальные проекты Spring и рассчитан на быстрый старт работы с этой технологией. Для того, чтобы упростить жизнь разработчикам и новичкам, которые сходу не могут настроить проект, появился проект Spring Boot. Он имеет заранее подготовленные куски конфигу 8000 ации, которые активируются в зависимости от различных условий.

IoC, DI, Beans

Прежде чем начинать более близкое знакомство со Spring, необходимо разобраться с концепциями, на которых основывается Spring: IoC и DI. Давайте начнем с сухих определений, а потом разберём подробнее:

Inversion of Control (инверсия управления программой)
это достаточно общее понятие, которое отличает библиотеку от фреймворка. Классическая модель подразумевает, что вызывающий код контролирует внешнее окружение и время и порядок вызова библиотечных методов. Однако в случае фреймворка обязанности меняются местами: фреймворк предоставляет некоторые точки расширения, через которые он вызывает определенные методы пользовательского кода. Будет уместно назвать эту концепцию как «вынесение контроля системы на уровень фреймворка».

Dependency Injection (внедрение зависимостей) — это одна из реализаций принципа IoC (помимо этого есть еще Factory Method, Service Locator). Статья к прочтению: Dependency Injection vs. Service Locator.

▪ IoC-контейнер — это какая-то библиотека, фреймворк, программа, если хотите, которая позволит вам упростить и автоматизировать написание кода с использованием данного подхода настолько, насколько это возможно. Статья к прочтению: Inversion of Control Containers and the Dependency Injection pattern

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

Рассмотрим DI на примере.

У нас есть две версии кода. Первая - некорректна относительно концепции DI, а вторая корректна. Давайте сначала посмотрим на некорректную реализацию: как мы с вами обычно реализуем класс – даем ему звучное название, и делаем его ответственным за получение собственных ссылок на объекты, с которыми он взаимодействует. По такому принципу и создан класс “YesNoController”, главная задача которого, как мы узнаем позже, обрабатывать приходящие запросы и возвращать нужный результат гадания (метод “predict”). Он взаимодействует с простым объектом “YesNoTeller”, который случайным образом возвращает либо да, либо нет.

public class YesNoController {
YesNoTeller yesNoTeller = new YesNoTeller();

public String predict(){
    return  yesNoTeller.tell();
}

}

Такая реализация приводит к сильной связанности “YesNoController”-а (главная задача которого, повторюсь, обрабатывать входящие запросы на гадание ) с классом “YesNoTeller”, что в конечном счете приведёт к сложностям при тестировании “YesNoController”-а (тестировать придется сразу их оба). Т.е. написав junit тест для YesNoController’а мы неявно будем тестировать и YesNoTeller (возвращаемый результат YesNoController’а зависит от результата работы YesNoTeller). И так же этот класс становится «одноразовым», т.е. в такой реализации он пригоден только для да/нет гаданий. В противовес же в корректной реализации мы создали класс “FortuneController”.

public class FortuneController {
@Autowired
FortuneTeller fortuneTeller;

public String predict(){
    return  fortuneTeller.tell();
}

}

Во-первых, имя этого класса не привязано к определенному типу гаданий. Во-вторых, этот класс взаимодействует с “YesNoTeller” через интерфейс “FortuneTeller”, что позволяет нам в будущем внедрить в значение этого поля более сложные реализации гаданий – например, гадания на картах Таро или астрологические гадания. А для тестирования мы можем внедрить в поле fortuneTeller любой простой класс реализующий интерфейс Fortune Teller, который бы возвращал бы определённый результат (например только да) и уже более чисто протестировать FortuneController.

Я приведу еще один пример плохого кода, а вы попробуйте найти здесь недостатки, что мы обсуждали выше. Здесь client – это простой класс с двумя полями id и fullName, а eventLogger класс с одним методом logEvent. Класс App логирует какие-то event’ы. В данном случае данные клиента

public class App { Client client; EventLogger eventLogger;
public static void main(String[] args) {
    App app = new App();
    app.client = new Client("1", "JohnSmith");
    app.eventLogger = new EventLogger();

}

public void logEvent(String msg){
    String message = msg.replaceAll(client.getId(), client.fullName);
    eventLogger.logEvent(message);
}

}

Хорошо проанализировав код можно увидеть такие недостатки : • сложно модифицировать, так как данные clienta жестко вшиты в код и при изменения клиента нам придется переписывать код • Сложности при расширении кода • Сложности в тестировании, так как косвенно тестируем и работу EventLogger

А теперь пробежимся по основам Spring Framework

В приложении на основе фреймворка Spring прикладные объекты располагаются внутри контейнера Spring:

Как показано на рисунке выше - контейнер создает объекты (Bean). Это отличная метафорическая аналогия – ведь наши бины (Bean – с англ. «бобы») в контейнере лежат как «бобы в банке». Контейнер связывает их друг с другом, конфигурирует (определяет свойства этого объекта) и управляет их полным жизненным циклом – от зарождения до самой их смерти (или от оператора new до вызова метода finalize) В фреймфорк Spring входит несколько реализаций контейнера, но основной это “Application Context”. Контекст приложений основан на понятии фабрик компонентов и реализует прикладные службы фреймворка - такие как возможность приема текстовых сообщений из файлов свойств, он обеспечивает поддержку упомянутого ранее DI Что бы поднять “ApplicationContext” в Spring Boot вам достаточно использовать данный шаблон: @SpringBootApplication public class Application {

public static void main(String[] args) { SpringApplication.run(Application.class, args); } } Каким образом Spring должен понять, что должен быть создан экземпляр какого-то определенного класса? Все просто по особой аннотации этого класса. Таких аннотаций в Spring Framework четыре:

@Component - универсальная аннотация, указывающая, что класс является компонентом Spring и позволяющая ему находить этот класс

@Controller – указывает, что класс определяет контроллер Spring MVC, об этом дальше

@Repository - указывает, что класс определяет репозиторий данных

@Service – указывает, что класс определяет службу

• Так же вы можете создать свою собственную аннотацию для Spring (читайте в документации)

Любой класс аннотированный одной из этих 4-х аннотаций (или вашей кастомной) будет поднят в Application Context. На примере нашего кода, как вы можете заметить, у нас есть 2 класса помеченных этими особыми аннотациями: @Component public class FortuneController {

@Autowired
FortuneTeller fortuneTeller;

@RequestMapping("/")
@ResponseBody
public String predict(){ return  fortuneTeller.tell();}

} И @Component public class YesNoTeller implements FortuneTeller{ final Random random = new Random();

public String tell(){
  int i =  random.nextInt(2);
    if( i == 0){
        return "No";
    } else {
        return "Yes";
    }
}

}

Значит эти оба класса будут подняты в нашем Application Context. Перейдем, наконец, к самому главному.

А как Spring понимает куда внедрить объект класса, который он поднял и какой именно объект нужно внедрять из всех объектов, что он поднял?

Для того, чтобы Spring понял куда ему внедрить зависимость на объект мы используем аннотацию @Autowired, которую можно поставить в 4 разных места

• Аннотировать ей setter’ы полей класса (или любые другие методы, изменяющие значения поля класса) @Autowired public void setFortuneTeller(FortuneTeller fortuneTeller) { this.fortuneTeller = fortuneTeller; }

• Аннотировать ей конструктор класса @Autowired public FortuneController(FortuneTeller fortuneTeller) { this.fortuneTeller = fortuneTeller; }

• Аннотировать ей само поле класса @Autowired FortuneTeller fortuneTeller;

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

А для того, чтобы Spring понял какой именно объект необходимо внедрять из всех объектов, что он поднял - используется возможность автоматического связывания. Принцип автоматического связывания таков: Если в контексте приложения имеется только один компонент YesNoTeller типа FortuneTeller, тогда любой компонент, имеющий свойство типа FortuneTeller, будет зависеть именно от этого компонента YesNoTeller.

Ещё раз повторим этот принцип с пояснениями для новичков: Если в контексте приложения (Application Context) имеется только один компонент типа FortuneTeller (в нашем примере это YesNoTeller (он реализует интерфейс FortuneTeller, и, следовательно, является компонентом типа FortuneTeller)), тогда любой компонент, имеющий свойство типа FortuneTeller (свойство = поле класса, следовательно, в нашем примере такой компонент это FortuneController), будет зависеть именно от этого компонента YesNoTeller.

А что если в ApplicationContext поднято 2 bean’a c типом FortuneTeller? Ну тогда автоматическое связывание не сработает и Spring не станет за вас решать, какой именно объект внедрить, а просто возбудит исключение NoSuchBeanDefinitionException. Решить эту проблему можно уточнением неоднозначных зависимостей. Для этого используется аннотация @Qualifier(“УникальноеИмя”)

Пример:
Появился новый класс ZodiacTeller, который реализует генерацию гороскопа для всех знаков зодиака. Он тоже, как и YesNoTeller типа FortuneTeller @Qualifier("zodiac") @Component public class ZodiacTeller implements FortuneTeller {
@Override
public String tell() {
    //Здесь ваша реализация 
    return "Horoscope";
}

}

Наш старый класс YesNoTeller в упрощенном виде:

Qualifier("yesno") @Component public class YesNoTeller implements FortuneTeller {
public String tell() {
    //Здесь ваша реализация
    return "YesOrNo";
}

}

И, соответственно, новая реализация FortuneController с уточнением, какой именно bean нам нужен: @Component public class FortuneController {

@Qualifier("yesno")
@Autowired
FortuneTeller fortuneTeller;

@RequestMapping("/")
@ResponseBody
public String predict(){ return  fortuneTeller.tell();}

}

Мы научились внедрять объекты, но что делать если я захочу внедрить простое значение, например String, int или boolean? Для этого применяется анотация @Value. Ее применение заключается в том , что бы добавить аннотацию @Value перед свойством, методом или параметром метода и передать ей строку с выражением, результат которого должен быть внедрен. Например:

@Value("Texnoatom") String coursesName;

Но это еще не все. Все таки внедрять значение прямо над полем, может показаться не очень хорошей идеей. Значение поля мы можем указать в application.properties файле, а уже после внедрять в свойство. Делается это так:

• В application.properties определяем свойство courses = Texnoatom

• И вот так внедряем в его значение в поле: @Value("${courses}") String coursesName;

ТЕПЕРЬ ПРО MVC

Это следующая концепция, которую необходимо понять и уяснить. Здесь мы можем начать с картинки:

Что же мы тут видим? Во-первых, мы можем увидеть расшифровку аббревиатуру MVC - Model View Controller.

Давайте разберемся детально, зачем нужна каждая из этих компонент: • Модель (Model) предоставляет данные и реагирует на команды контроллера, изменяя свое состояние. • Представление (View) отвечает за отображение данных модели пользователю, реагируя на изменения модели. • Контроллер (Controller) интерпретирует действия пользователя, оповещая модель о необходимости изменений.

Как это работает? У нас есть view - это, грубо говоря, web-страничка с “чемоданчиком”. В этом чемоданчике всё, что используется при создании страницы. Пользователь начинает взаимодействовать с тем, что видит. Например, нажимает кнопку. В этот момент происходит запрос в контроллер, который вызывает нужную модель. А та создает новую view и так по кругу. А теперь пойдем от модели к Spring MVC (к практической реализации).

DispatcherServlet принимает и обрабатывает все HTTP-запросы и ответы на них. Сначала он обращается в HandlerMapping, тот по виду запроса (например, /login) определяет какой контроллер (какой метод класса контроллера) обрабатывает этот запрос. Потом DispatcherServlet обращается в метод Controller-а и получает две вещи: имя view и чемоданчик (да-да, тот самый чемоданчик).

Здесь по запросу пользователя “/” DispatcherServlet бежит к HandlerMapping и узнает, что метод main обрабатывает данный запрос. Потом он бежит в этот метод и берёт из него имя view и чемоданчик (в данном случае имя: loginViews/index, а в чемоданчике какой-то список, загружаемый методом getUserNameList, который называется userList и объект LoginUser с именем userJSP).

Что же дальше? Мы продолжаем рассказ о жизни DispatcherServlet. У него есть какое-то имя, теперь он бежит к “viewResolver”-у, который добавляет при необходимости префикс и суффикс к имени view (в данном случае он может добавить префикс WEB-INF/views и суффикс .jsp. В итоге мы получим WEB-INF/views/loginViews/index.jsp). Как только Dispatcher узнал полное имя 74D1 view. Он его находит и отдает вместе с чемоданчиком модели (MVC, Model), которая отправляет всё на клиента и отрисовывает. Cтатья к прочтению

БЫСТРЫЙ СТАРТ
1) Создадим каркас. Для этого можно воспользоваться веб-конструктором:

  1. Создадим Controller и интерфейс для объекта, реализующего логику гадания:
@Controller public class FortuneController {
@Autowired
FortuneTeller fortuneTeller;

@RequestMapping("/")
@ResponseBody
public String predict(){ return  fortuneTeller.tell();}

}

public interface FortuneTeller { public String tell(); }
  1. Реализуем интерфейс FortuneTeller, который будет случайным образом генерировать «да» или «нет»:
@Component public class YesNoTeller implements FortuneTeller{ final Random random = new Random(); public String tell(){ int i = random.nextInt(2); if( i == 0){ return "No"; } else { return "Yes"; } } }
  1. Запускаем приложение.

  2. А теперь загадываем вопрос, который требует ответ «да» или «нет», переходим по адресу “localhost:8080” и смотрим, какой ответ на ваш вопрос нагадал наш предсказатель.

ОСНОВЫ SPRING BOOT

Как мы уже писали ранее Spring Boot - «вишенка на торте». Spring Boot позволяет быстро и легко написать приложение с использованием Spring, которое можно будет запустить одной командой. SpringBoot предоставляет вам Servlet container “из коробки” (по умолчанию используется Tomcat). Обсудим начальный проект, сгенерированный веб-конструктором.

Конфигурация gradle: Spring Boot использует особые зависимости, которые называются Starters (Стартеры).

dependencies { compile('org.springframework.boot:spring-boot-starter-web') testCompile('org.springframework.boot:spring-boot-starter-test') }

Starter’ы – Это целый набор зависимостей Spring Framework и других необходимых зависимостей для быстрого старта проекта, сгруппированные по темам (Web, JPA, Security и т.д.). Список стартеров есть в документации SpringBoot. Все официальные стартеры имеют определенное название “spring-boot-starter-”, где * - это название типа применения этой зависимости (неофициальные стартеры, или же ваши кастомные, в противовес имеют название типа: “- spring-boot-starter”)

Так же особенностью “build.gradle” для приложения на SpringBoot является специальный плагин Spring Boot для Gradle

plugins { id 'org.springframework.boot' version '1.5.8.RELEASE' }

или

buildscript { repositories { mavenCentral() } dependencies { classpath("org.springframework.boot:spring-boot-gradle-plugin:1.5.8.RELEASE") } }

Можно и без него, но этот плагин делает ваши jar и war файлы запускаемыми.

Теперь перейдем к нашим классам. Запускающим классом в SpringBoot является класс типа:

@SpringBootApplication public class FortuneApplication {

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

То есть ваше приложение начинается выполнением класса FortuneApplication. Этот класс аннотирован через @SpringBootApplication. Что это за аннотация и о чём она говорит?

@SpringBootApplication – аннотация, специфичная для SpringBoot и эквивалентна трем аннотациям: @Configuration, @EnableAutoConfiguration и ComponentScan из SpringFramework с их свойствами по умолчанию.

Поясню, что обозначает каждая из этих 3-х аннотаций из SpringFramework, чтобы в конечном счёте понять, что делает аннотация @SpringBootApplication.

@Configuration – она сообщает что это класс конфигураций с помощью которого мы можем сконфигурировать работу Spring контекста. Подсказать ему каким образом он должен работать (вот тут станет понятней)

@ComponentScan. Если конфигурационный класс помечен этой аннотацией, то Spring автоматически поднимает все Spring компоненты, включая @Configuration класс. Если структура вашего кода стандартная (т.е. расположение Application class (FortuneApplication) в корневом пакете), вы можете добавить @ComponentScan без каких-либо аргументов. В противном случае вы должны прописать имя пакета, внутри которого нужно искать Spring компоненты. Все компоненты Spring приложения (@Component, @Service, @Repository, @Controller etc.) будут автоматически зарегистрированы как Spring Beans.

@EnableAutoConfiguration (хватает только одной такой аннотации в любой класс, аннотированный @Configuration). Включает автоконфигурацию твоего Spring приложения основываясь на зависимостях, добавленных в build.gradle.

Некоторые рекомендации, которые стоит соблюдать для корректной работы со SpringBoot: не используйте “default” package и старайтесь расположить main application class в корневом пакете относительно других классов

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published
0