Hello, Spring Web MVC

Spring Web MVC — веб фреймворк, основанный на Servlet API и являющийся частью Spring framework. Изначально это был только MVC фреймворк, но в настоящий момент поддерживается все разнообразие web. Spring Web MVC является значимой частью Spring framework и, потому, заслуживает отдельного рассмотрения.

Как не сложно догадаться, Spring Web MVC требует наличие Servlet контейнера для запуска и не может быть (за небольшим исключением) использован в standalone приложении. Дабы не загромождать статью подробностями работы с серверами приложений, я использую встраиваемый сервер jetty.

Подготовка

Как обычно, сгенерируем простое web приложение и добавим в него зависимости:

>mvn archetype:generate -DgroupId=ru.easyjava.spring.webmvc -DartifactId=helloservlet -Dversion=1  -DarchetypeArtifactId=maven-archetype-webapp -DinteractiveMode=false
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building Maven Stub Project (No POM) 1
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] >>> maven-archetype-plugin:2.4:generate (default-cli) > generate-sources @ standalone-pom >>>
[INFO]
[INFO] <<< maven-archetype-plugin:2.4:generate (default-cli) < generate-sources @ standalone-pom <<<
[INFO]
[INFO] --- maven-archetype-plugin:2.4:generate (default-cli) @ standalone-pom ---
[INFO] Generating project in Batch mode
[INFO] ----------------------------------------------------------------------------
[INFO] Using following parameters for creating project from Old (1.x) Archetype: maven-archetype-webapp:1.0
[INFO] ----------------------------------------------------------------------------
[INFO] Parameter: basedir, Value: Hello\servlet
[INFO] Parameter: package, Value: ru.easyjava.spring.webmvc
[INFO] Parameter: groupId, Value: ru.easyjava.spring.webmvc
[INFO] Parameter: artifactId, Value: helloservlet
[INFO] Parameter: packageName, Value: ru.easyjava.spring.webmvc
[INFO] Parameter: version, Value: 1
[INFO] project created from Old (1.x) Archetype in dir: Hello\servlet\helloservlet
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2.730 s
[INFO] Finished at: 2018-03-22T19:02:41+01:00
[INFO] Final Memory: 15M/312M
[INFO] ------------------------------------------------------------------------

Из зависимостей понадобятся собственно Spring, Spring Web MVC и всё для тестов.

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <org.springframework.version>5.0.4.RELEASE</org.springframework.version>
        <junit.version>4.12</junit.version>
        <hamcrest.version>1.3</hamcrest.version>
    </properties>


    <dependencies>
        <!-- Spring -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${org.springframework.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${org.springframework.version}</version>
        </dependency>


        <!-- Testing -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>${junit.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.hamcrest</groupId>
            <artifactId>hamcrest-library</artifactId>
            <version>${hamcrest.version}</version>
            <scope>test</scope>
        </dependency>


    </dependencies>

Сам же jetty будет добавлен в виде maven плагина и запускать приложение будет тоже maven.

<plugin>
  <groupId>org.eclipse.jetty</groupId>
  <artifactId>jetty-maven-plugin</artifactId>
  <version>9.4.8.v20171121</version>
  <configuration>
    <scanIntervalSeconds>10</scanIntervalSeconds>
    <webApp>
      <contextPath>/</contextPath>
    </webApp>
  </configuration>
</plugin>

Web приложение

Чтобы сделать из набора java классов настоящее web приложение, надо написать пару xml дескрипторов. Первый из них имеет предопределённое имя и расположение. Это файл src/main/java/webapp/WEB-INF/web.xml Именно этот файл ищет сервлет контейнер в вашем приложении и именно из него берётся конфигурация сервлетов.

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         xsi:schemaLocation="
  http://java.sun.com/xml/ns/javaee
  http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
         id="WebApp_ID" version="3.0">




    <display-name>HEllo Spring Web MVC</display-name>
    <description>Sample Spring WebMVC projet</description>


    <servlet>
        <servlet-name>webmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>2</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>webmvc</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>


</web-app>

В моём случае файл состоит из трёх частей — описания приложения, описания сервлета и параметров сервлета. Описание приложения — вещь интуитивно понятная и рассматривать я её не буду. А вот описание сервлета уже интереснее — вы задаёте имя сервлета ( <servlet-name/>), класс сервлета ( <servlet-class/>) и параметры загрузки. Класс сервлета в случае Spring Web MCV предопределён и всегда будет org.springframework.web.servlet.DispatcherServlet. Наконец, в третьей части сервлету назначается url путь, на который он отображается. Связь пути и сервлета осуществляется по имени сервлета.

web.xml необходим сервлет контейнеру, чтобы знать, что загружать. А вот самому Spring нужна собственная конфигурация контекста. И он её будет искать в файле webmvc-servlet.xml, расположенном в том же каталоге, что и web.xml Имя этого файла зависит от имени сервлета — <servlet name>-servlet.xml, что позволяет иметь несколько контекстов Spring

<?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"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="
  http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
  http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
  http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">
    <context:component-scan base-package="ru.easyjava.spring.webmvc.helloservlet"/>
    <mvc:annotation-driven/>
</beans>

Файл webmvc-servlet.xml представляет собой самую банальную конфигурацию Spring

Наконец, добавим какой-нибудь web метод:

@Controller
public final class HelloController {
    private String formatGreet(final String name) {
        return String.format("Hello, %s", name);
    }
    @GetMapping("/hello")
    @ResponseBody
    public String greet(@RequestParam("name") final String name) {
        if (StringUtils.isEmpty(name)) {
            return formatGreet("Anonymous");
        }
        return formatGreet(name);
    }
}
public class HelloControllerTest {
    @Test
    public void testGreet() {
        HelloController tested = new HelloController();
        assertThat(tested.greet("TEST"), is("Hello, TEST"));
    }


    @Test
    public void testNull() {
        HelloController tested = new HelloController();
        assertThat(tested.greet(null), is("Hello, Anonymous"));
    }
}

Класс, аннотированный @Controller, является web контроллером (в терминах MVC паттерна). В нашем случае достаточно знать, что методы класса, аннотированного @Controller, можно вызывать с помощью веб запросов. Для этого методы должны быть аннотированы @RequestMapping аннотацией. В моём случае это @GetMapping, что означает, что метод будет вызываться при GET запросе по адресу /hello. Адрес или, точнее говоря, путь, задаётся в параметре аннотации. Наконец, метод будет принимать параметр из GET запроса, по имени name и будет ожидать, что этот параметр будет строковым.

Сам же метод возвращает строку и аннотация @ResponseBody говорит Spring, что возвращаемое значение не надо обрабатывать, а надо вернуть пользователю.

Запуск приложения

Как я уже говорил, мы можем собрать пакет с приложением, но для его исполнения нужен servlet контейнер. Поэтому мы используем jetty для запуска:

>mvn jetty:run
[INFO] Mapped "{[/hello],methods=[GET]}" onto public java.lang.String ru.easyjava.spring.webmvc.helloservlet.HelloController.greet(java.lang.String)
[INFO] Looking for @ControllerAdvice: WebApplicationContext for namespace 'webmvc-servlet': startup date [Thu Mar 22 20:35:19 CET 2018]; root of context hierarchy
[INFO] Looking for @ControllerAdvice: WebApplicationContext for namespace 'webmvc-servlet': startup date [Thu Mar 22 20:35:19 CET 2018]; root of context hierarchy
[INFO] FrameworkServlet 'webmvc': initialization completed in 1120 ms
[INFO] Started o.e.j.m.p.JettyWebAppContext@32dbca45{/,file:///servlet/helloservlet/src/main/webapp/,AVAILABLE}{file:///servlet/helloservlet/src/main/webapp/}
[INFO] Started ServerConnector@51566ce0{HTTP/1.1,[http/1.1]}{0.0.0.0:8080}
[INFO] Started @11704ms
[INFO] Started Jetty Server

Jetty сам увидит web.xml, запустит servlet, тот прочитает конфигурацию и найдёт созданный нам контроллер. Давайте немедленного его опробуем:

>curl  http://localhost:8080/hello?name=World
Hello, World


>curl  http://localhost:8080/hello?name=
Hello, Anonymous

Spring Boot

Разумеется, конфигурация на основе xml несколько старомодна и сейчас всё можно сделать в коде. Но зачем что-то делать в коде, если можно этого не делать вообще? Spring boot настраивает Spring Web MVC автоматически и содержит встроенный servlet контейнер.

Для начала сгенерируем Spring boot проект и добавим в него web starter. Если кто-то хочет добавить web starter к существующему проекту, просто добавьте следующую зависимость:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
</dependency>

Теперь, чтобы включить поддержку Spring Web MVC, достаточно добавить аннотацию @EnableWebMVC к классу приложения:

@SpringBootApplication
@EnableWebMvc
public class HellobootApplication {
    public static void main(String[] args) {
        SpringApplication.run(HellobootApplication.class, args);
    }
}

Это заменяет все настройки, которые мы делали для обычного servlet контейнера выше. Контроллер я возьму тот же, что и в примере выше, и использую его совершенно без изменений. Приложение можно собрать и запустить как обычный jar файл:

2018-03-22 20:51:28.611  INFO 8572 --- [           main] r.e.s.w.helloboot.HellobootApplication   : Started HellobootApplication in 2.993 seconds (JVM running for 3.69)
2018-03-22 20:51:42.812  INFO 8572 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring FrameworkServlet 'dispatcherServlet'
2018-03-22 20:51:42.812  INFO 8572 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization started
2018-03-22 20:51:42.826  INFO 8572 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization completed in 14 ms

Простая проверка покажет, что приложение на основе Spring Boot работает точно так же:

>curl  http://localhost:8080/hello?name=Spring+Boot
Hello, Spring Boot

В дальнейшем, при описании остальных возможностей Spring Web MVC, я буду использовать только Spring Boot для сокращения кода и трудоёмкости.

Скачать код примера

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

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