Рубрики
Без рубрики

Весенняя загрузка, часть 3: Внедрение зависимостей и @RestController

(Этот пост был обновлен на blog.hcf.dev более поздней версией Spring Boot.) Эта серия… Помеченный как java, spring.

(Этот пост был обновлен по адресу blog.hcf.dev более поздней версией Spring Boot.)

В этой серии статей будут рассмотрены функции Spring Boot . Эта третья статья основывается на серии, демонстрируя основы Spring Dependency Injection . Для создания наглядного кода в примере также создается реализация @RestController , простой сервер общих свойств, на котором клиенты могут вводить и получать значения свойств.

Полный исходный код для серии и для этой части доступны на Github .

@ComponentScan и внедрение зависимостей

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

Процесс загрузки Spring запускается в методе Launcher::main (без изменений по сравнению с реализацией, описанной в частях 1 и 2 из этой серии) с помощью построения приложения Spring и вызова его run метода. Аннотирование класса с помощью @SpringBootApplication эквивалентно аннотированию с помощью @Configuration , @EnableAutoConfiguration и @ComponentScan .

@SpringBootApplication
@NoArgsConstructor @ToString @Log4j2
public class Launcher {
    public static void main(String[] argv) throws Exception {
        SpringApplication application = new SpringApplication(Launcher.class);

        application.run(argv);
    }
}

Приложение Spring/| начинает свой анализ с помощью приложения . Пусковая установка класс. Параметр @EnableAutoConfiguration указывает, что Spring Boot должен попытаться "угадать" по мере необходимости. Аннотация @ComponentScan указывает, что Spring Boot должен начать сканирование классов в пакете application (содержащем пакет для Launcher ) для классов, помеченных @Component . Примечание: Аннотации -типы, помеченные @Component , также являются компонентами. Например, @Configuration , @Controller и @RestController все @Component s, но не наоборот.

Для каждого класса, помеченного @Component , Spring:

  1. Создает один экземпляр,

  2. Для каждого поля экземпляра, помеченного @Value , вычислите выражение SpEL 1 и инициализируйте поле с результатом,

  3. Для каждого метода, аннотированного @Bean в классе @Configuration , вызовите метод ровно один раз, чтобы получить значение компонента, и,

  4. Для каждого поля, помеченного @Autowired , присвоите соответствующее значение, полученное путем вычисления метода @Bean .

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

Пример кода для этой статьи не требует @Value инъекции, но предыдущая статья предоставляет примеры в своем Конфигурация Mysqld реализация:

    @Value("${mysqld.home}")
    private File home;

    @Value("${mysqld.defaults.file:${mysqld.home}/my.cnf}")
    private File defaults;

    @Value("${mysqld.datadir:${mysqld.home}/data}")
    private File datadir;

    @Value("${mysqld.port}")
    private Integer port;

    @Value("${mysqld.socket:${mysqld.home}/socket}")
    private File socket;

    @Value("${logging.path}/mysqld.log")
    private File console;

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

Простой сервер свойств, реализованный здесь, создает компонент “словарь” внутри Конфигурация словаря :

@Configuration
@NoArgsConstructor @ToString @Log4j2
public class DictionaryConfiguration {
    @Bean
    public Map dictionary() {
        return new ConcurrentSkipListMap<>();
    }
}

И этот компонент подключается к Dictionary RestController следующим образом:

@RestController
...
@NoArgsConstructor @ToString @Log4j2
public class DictionaryRestController {
    @Autowired private Map dictionary = null;
    ...
}

В следующем разделе описывается реализация @RestController .

Реализация @RestController

Реализованный здесь @RestController предоставляет следующий веб-API:

http://localhost:8080/dictionary/get получить Значение, связанное с ключом (может быть нулевым) ключ
http://localhost:8080/dictionary/put ПОЛУЧИТЬ 2 Предыдущее значение, связанное с ключом (может быть нулевым) ключ=значение
http://localhost:8080/dictionary/remove получить Значение, ранее связанное с ключом (может быть нулевым) ключ
http://localhost:8080/dictionary/size получить в никто
http://localhost:8080/dictionary/ Входной набор получить Массив пар ключ-значение никто
http://localhost:8080/dictionary/keySet получить Массив ключевых значений никто

Словарь RestController аннотируется с помощью @RestController и @RequestMapping с значением = { "/dictionary/" } указывающие пути запроса будут иметь префикс /dictionary/ и products , указывающие , что HTTP ответы должны быть закодированы в JSON .

@RestController
@RequestMapping(value = { "/dictionary/" }, produces = MediaType.APPLICATION_JSON_VALUE)
@NoArgsConstructor @ToString @Log4j2
public class DictionaryRestController {
    @Autowired private Map dictionary = null;
    ...
}

Карта словаря имеет вид @Autowired , как описано в предыдущем разделе.

Реализация метода /dictionary/put заключается в:

    @RequestMapping(method = { RequestMethod.GET }, value = { "put" })
    public Optional put(@RequestParam Map parameters) {
        if (parameters.size() != 1) {
            throw new IllegalArgumentException();
        }

        Map.Entry entry = parameters.entrySet().iterator().next();
        String result = dictionary.put(entry.getKey(), entry.getValue());

        return Optional.ofNullable(result);
    }

Spring введет параметры запроса запроса в вызов метода как parameters . Метод проверяет, что указан ровно один параметр запроса, помещает это значение ключа в словарь и возвращает результат (предыдущее значение для этого ключа на карте). Spring интерпретирует String как литерал JSON таким образом, метод оборачивает результат в Необязательный , чтобы заставить Spring кодировать в JSON .

Реализация метода /dictionary/get заключается в:

    @RequestMapping(method = { RequestMethod.GET }, value = { "get" })
    public Optional get(@RequestParam Map parameters) {
        if (parameters.size() != 1) {
            throw new IllegalArgumentException();
        }

        Map.Entry entry = parameters.entrySet().iterator().next();
        String result = dictionary.get(entry.getKey());

        return Optional.ofNullable(result);
    }

Опять же, должен быть ровно один параметр запроса, и результат будет заключен в Необязательный . Реализация запроса /dictionary/remove практически идентична.

Реализация метода /dictionary/size заключается в:

    @RequestMapping(method = { RequestMethod.GET }, value = { "size" })
    public int size(@RequestParam Map parameters) {
        if (! parameters.isEmpty()) {
            throw new IllegalArgumentException();
        }

        return dictionary.size();
    }

Параметры запроса указывать не следует. Реализация /словаря/entrySet почти идентичен типу, возвращаемому методом Set Запись<Строка,Строка>> :

    @RequestMapping(method = { RequestMethod.GET }, value = { "entrySet" })
    public Set> entrySet(@RequestParam Map parameters) {
        if (! parameters.isEmpty()) {
            throw new IllegalArgumentException();
        }

        return dictionary.entrySet();
    }

И реализация /dictionary/key Set следует тому же шаблону.

Maven project POM предоставляет spring-boot:run профиль, описанный в первой статье этой серии, и сервер может быть запущен с помощью mvn -B -Pspring-boot:run . При запуске с этим профилем доступен привод пружинного загрузчика . Сопоставления @RestController обработчика могут быть проверены с помощью следующего запроса:

$ curl -X GET http://localhost:8081/actuator/mappings \
> | jq '.contexts.application.mappings.dispatcherServlets[][]
        | {handler: .handler, predicate: .predicate}'
{
  "handler": "application.DictionaryRestController#remove(Map)",
  "predicate": "{GET /dictionary/remove, produces [application/json]}"
}
{
  "handler": "application.DictionaryRestController#get(Map)",
  "predicate": "{GET /dictionary/get, produces [application/json]}"
}
{
  "handler": "application.DictionaryRestController#put(Map)",
  "predicate": "{GET /dictionary/put, produces [application/json]}"
}
{
  "handler": "application.DictionaryRestController#size(Map)",
  "predicate": "{GET /dictionary/size, produces [application/json]}"
}
{
  "handler": "application.DictionaryRestController#entrySet(Map)",
  "predicate": "{GET /dictionary/entrySet, produces [application/json]}"
}
{
  "handler": "application.DictionaryRestController#keySet(Map)",
  "predicate": "{GET /dictionary/keySet, produces [application/json]}"
}
{
  "handler": "org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController#errorHtml(HttpServletRequest, HttpServletResponse)",
  "predicate": "{ /error, produces [text/html]}"
}
{
  "handler": "org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController#error(HttpServletRequest)",
  "predicate": "{ /error}"
}
{
  "handler": "ResourceHttpRequestHandler [\"classpath:/META-INF/resources/webjars/\"]",
  "predicate": "/webjars/**"
}
{
  "handler": "ResourceHttpRequestHandler [\"classpath:/META-INF/resources/\", \"classpath:/resources/\", \"classpath:/static/\", \"classpath:/public/\", \"/\"]",
  "predicate": "/**"
}

Использование curl для проверки операции put (обратите внимание на разницу в возвращаемых значениях при первом и втором вызовах):

$ curl -X GET -i http://localhost:8080/dictionary/put?foo=bar
HTTP/1.1 200
Content-Type: application/json
Transfer-Encoding: chunked
Date: Wed, 11 Dec 2019 19:56:43 GMT

null

$ curl -X GET -i http://localhost:8080/dictionary/put?foo=bar
HTTP/1.1 200
Content-Type: application/json
Transfer-Encoding: chunked
Date: Wed, 11 Dec 2019 19:56:44 GMT

"bar"

А затем проверьте предыдущий put с помощью операции get:

$ curl -X GET -i http://localhost:8080/dictionary/get?foo
HTTP/1.1 200
Content-Type: application/json
Transfer-Encoding: chunked
Date: Wed, 11 Dec 2019 19:59:22 GMT

"bar"

Извлечение набора словарных записей демонстрирует сложную кодировку JSON:

$ curl -X GET -i http://localhost:8080/dictionary/entrySet
HTTP/1.1 200
Content-Type: application/json
Transfer-Encoding: chunked
Date: Wed, 11 Dec 2019 20:00:24 GMT

[ {
  "foo" : "bar"
} ]

И предоставление параметра запроса для size демонстрирует обработку ошибок:

$ curl -X GET -i http://localhost:8080/dictionary/size?foo
HTTP/1.1 500
Content-Type: application/json
Transfer-Encoding: chunked
Date: Wed, 11 Dec 2019 20:03:42 GMT
Connection: close

{
  "timestamp" : "2019-12-11T20:03:42.110+0000",
  "status" : 500,
  "error" : "Internal Server Error",
  "message" : "No message available",
  "trace" : "java.lang.IllegalArgumentException\n\tat application.DictionaryRestController.size(DictionaryRestController.java:65)\n...",
  "path" : "/dictionary/size"
}

Резюме

В этой статье демонстрируется базовая инъекция зависимостей Spring, показывая, как можно вычислять и вводить ” @Value s ” и ” @Bean s ” может быть создан и ” @Autowired ” в реализации @RestController .

В части 4 этой серии обсуждается Spring MVC и в качестве примера реализуется простое интернационализированное приложение clock.

[1] Spell также предоставляет доступ к свойствам, определенным в application.properties resources. ↩

[2] К сожалению, метод GET HTTP в сочетании с методом Map put может вызвать путаницу, и более сложное определение API может разумно использовать пост или ПОМЕСТИТЕ методы для их семантического значения. ↩

Оригинал: “https://dev.to/allenball/spring-boot-part-3-dependency-injection-and-restcontroller-3374”