Рефактор. Исправление ошибок в Spring Boot приложении

4. Исправить ошибки в Spring Boot приложении

Условие задачи:
Необходимо провести рефакторинг Spring Boot приложения и исправить ошибки в структуре и коде.

Нужно обратить внимание на:

  • расположение классов и пакетов;

  • component scanning Spring Boot;

  • корректные импорты аннотаций;

  • синтаксис @GetMapping;

  • корректную регистрацию контроллера в Spring Context.

Код:

package com.home.app;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class App {

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

package com.home.controller;

@Controller
public class Controller {

    @GetMapping("/")
    public String test() {
        return "test";
    }
}

Спойлеры к решению

Подсказки
💡 В одном .java-файле не может быть нескольких объявлений package.
💡 @Controller находится в org.springframework.stereotype.
💡 @GetMapping находится в org.springframework.web.bind.annotation.
💡 Обрати внимание, от какого пакета Spring Boot начинает поиск компонентов.
💡 Главный класс приложения лучше разместить в общем родительском пакете для остальных компонентов.

Решение

Удобная структура проекта:

com.home
├── App.java
└── controller
    └── HomeController.java

App.java:

package com.home;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class App {

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

HomeController.java:

package com.home.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class HomeController {

    @GetMapping("/")
    public String test() {
        return "test";
    }
}

Каждый файл содержит только одно объявление:

package ...

и находится в директории, соответствующей своему пакету.

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

com.home.app.App
com.home.controller.Controller

@SpringBootApplication включает поиск компонентов начиная с пакета, в котором расположен класс приложения, и его подпакетов.

Если App находится в:

com.home.app

то автоматически сканируются:

com.home.app
com.home.app.*

Но пакет:

com.home.controller

является соседним, а не дочерним. Поэтому контроллер может не попасть в Spring Context.

Размещение App в общем корневом пакете:

com.home

решает эту проблему:

com.home
├── App
└── controller
    └── HomeController

Теперь com.home.controller входит в область component scanning.

Также должны использоваться правильные импорты:

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

Аннотация маршрута записывается как:

@GetMapping("/")

Метод:

public String test() {
    return "test";
}

при использовании @Controller возвращает имя представления test. То есть Spring MVC будет искать соответствующий шаблон, например test.html, если подключён шаблонизатор.

Если задача состоит именно в том, чтобы по запросу / вернуть текст:

test

то контроллер можно сделать REST-контроллером:

package com.home.controller;

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

@RestController
public class HomeController {

    @GetMapping("/")
    public String test() {
        return "test";
    }
}

В этом случае строка "test" будет записана непосредственно в HTTP response body.

Основные исправления:

  • классы разделены по отдельным файлам;

  • главный класс перенесён в общий родительский пакет;

  • контроллер попадает в component scanning;

  • добавлены правильные импорты;

  • исправлен синтаксис @GetMapping;

  • имя Controller заменено на более понятное HomeController;

  • уточнена разница между @Controller и @RestController.