Məzmuna keç
Educora
İrəli25 dəq17 / 18

Spring Boot ilə REST API

Spring Boot layihəsi yarat, kontroller, servis və repozitori qatlarını asılılıq inyeksiyası ilə birləşdir, CRUD API qur və onu curl ilə yoxla.

Özünü yoxla
Bu dərsdə öyrənəcəksən
  • Spring Boot layihəsini yaratmaq və işə salmaq
  • @RestController, @Service, @Repository qatlarını və asılılıq inyeksiyasını izah etmək
  • Düzgün HTTP statusları ilə CRUD API yazmaq və onu curl ilə yoxlamaq

Mobil tətbiq — məsələn, elektron məktəb gündəliyi — qiymətləri serverdən alır: tətbiq HTTP sorğusu göndərir (GET /api/students), server isə JSON qaytarır. Belə serverə REST API deyilir. Java dünyasında onu yazmağın ən populyar yolu Spring Boot-dur: bir neçə annotasiya ilə adi siniflər veb-serverə çevrilir, JSON, verilənlər bazası və konfiqurasiya isə avtomatik qoşulur.

Layihəni yaratmaq

  1. 1
    start.spring.io saytını aç

    Bu, rəsmi layihə generatorudur (IntelliJ IDEA Ultimate-də də eyni sehrbaz var). Project — Maven və ya Gradle, Language — Java, Java — 21 seç.

  2. 2
    Metaməlumatları doldur

    Group — com.educora, Artifact — school, Name — SchoolApi. Paket com.educora.school, əsas sinif isə SchoolApiApplication olacaq.

  3. 3
    Asılılıqları əlavə et

    ADD DEPENDENCIES düyməsi ilə Spring Web seç. Real layihədə adətən Spring Data JPA, bazanın drayveri və Validation da əlavə edilir.

  4. 4
    Yarat və aç

    GENERATE düyməsi ZIP arxivi yükləyir. Faylları arxivdən çıxar, qovluğu IDE-də aç və əsas sinfi işə sal.

Java
package com.educora.school;

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

@SpringBootApplication
public class SchoolApiApplication {
    public static void main(String[] args) {
        SpringApplication.run(SchoolApiApplication.class, args);
    }
}
Başlanğıc nöqtəsi: @SpringBootApplication və bir sətirlik main

@SpringBootApplication üç işi birləşdirir: sinfi konfiqurasiya mənbəyinə çevirir, paketdə komponent axtarışını (component scan) və avtomatik konfiqurasiyanı işə salır. Sonuncu classpath-a baxır: Spring Web varsa, daxili Tomcat serveri 8080 portunda işə düşür, JSON üçün isə Jackson qoşulur. Ayrıca server quraşdırmaq lazım deyil — tətbiq adi main metodu olan JAR-dır.

Terminal
./mvnw spring-boot:run
Gözlənilən nəticə
... Tomcat started on port 8080 (http) with context path '/'
... Started SchoolApiApplication in 1.9 seconds (process running for 2.2)
Nümunə nəticə (qısaldılıb); Gradle-də ./gradlew bootRun

Qatlar və asılılıq inyeksiyası

QatAnnotasiyaVəzifəsi
Kontroller@RestControllerHTTP sorğusunu qəbul edir, JSON qaytarır
Servis@Servicebiznes qaydaları: yoxlamalar, hesablamalar
Repozitori@Repositoryverilənləri saxlayır və oxuyur

Bu annotasiyalarla işarələnmiş sinifləri Spring özü yaradır — onlara bean deyilir — və IoC konteynerində saxlayır (adətən hər sinifdən bir nüsxə). Sinfin konstruktoru başqa bean tələb edirsə, Spring onu avtomatik ötürür: buna asılılıq inyeksiyası (dependency injection) deyilir. Nəticədə siniflər bir-birini new ilə yaratmır, testdə isə asılılığı mock ilə əvəz etmək asanlaşır — JUnit dərsindəki kimi.

Sinif asılılıqlarını özü yaradır
@RestController
public class StudentController {
    // the controller builds its own dependencies:
    // hard to test, and every class gets its own copy
    private final StudentService service =
            new StudentService(new StudentRepository(), 100);
}
Spring asılılığı konstruktora ötürür
@RestController
public class StudentController {
    private final StudentService service;

    // Spring creates StudentService once and passes it in
    public StudentController(StudentService service) {
        this.service = service;
    }
}

Şagirdlər üçün CRUD API

İndi şagirdlər üçün tam CRUD API quraq: yaratmaq (Create), oxumaq (Read), yeniləmək (Update), silmək (Delete). Sadəlik üçün repozitori verilənləri yaddaşda, ConcurrentHashMap-də saxlayır: sorğular paralel thread-lərdə gəldiyi üçün thread-lər üçün təhlükəsiz kolleksiya seçilib.

Java
public record Student(Long id, String name, int score) {}

@Repository
public class StudentRepository {
    private final Map<Long, Student> students = new ConcurrentHashMap<>();
    private final AtomicLong nextId = new AtomicLong(1);

    public List<Student> findAll() {
        return students.values().stream().sorted(Comparator.comparing(Student::id)).toList();
    }

    public Optional<Student> findById(long id) {
        return Optional.ofNullable(students.get(id));
    }

    public Student save(Student s) {
        long id = s.id() != null ? s.id() : nextId.getAndIncrement();
        Student saved = new Student(id, s.name(), s.score());
        students.put(id, saved);
        return saved;
    }

    public boolean deleteById(long id) {
        return students.remove(id) != null;
    }
}
Model və repozitori (importlar qısalıq üçün göstərilməyib)
Java
@Service
public class StudentService {
    private final StudentRepository repository;
    private final int maxScore;

    public StudentService(StudentRepository repository,
                          @Value("${school.max-score:100}") int maxScore) {
        this.repository = repository;
        this.maxScore = maxScore;
    }

    public List<Student> all() { return repository.findAll(); }

    public Student get(long id) {
        return repository.findById(id).orElseThrow(() ->
                new ResponseStatusException(HttpStatus.NOT_FOUND, "Student " + id + " not found"));
    }

    public Student save(Long id, Student s) {
        if (s.score() < 0 || s.score() > maxScore)
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Score must be 0-" + maxScore);
        if (id != null) get(id);                  // 404 if we update a missing student
        return repository.save(new Student(id, s.name(), s.score()));
    }

    public void delete(long id) {
        if (!repository.deleteById(id)) throw new ResponseStatusException(HttpStatus.NOT_FOUND);
    }
}
Servis: yoxlama qaydaları və application.properties-dən gələn school.max-score
Java
@RestController
@RequestMapping("/api/students")
public class StudentController {
    private final StudentService service;

    public StudentController(StudentService service) {
        this.service = service;
    }

    @GetMapping
    public List<Student> all() { return service.all(); }

    @GetMapping("/{id}")
    public Student one(@PathVariable long id) { return service.get(id); }

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public Student create(@RequestBody Student student) { return service.save(null, student); }

    @PutMapping("/{id}")
    public Student update(@PathVariable long id, @RequestBody Student student) {
        return service.save(id, student);
    }

    @DeleteMapping("/{id}")
    @ResponseStatus(HttpStatus.NO_CONTENT)
    public void delete(@PathVariable long id) { service.delete(id); }
}
Kontroller: hər metod bir HTTP əməliyyatına bağlanır
SorğuNə edirUğurlu cavab
GET /api/studentsbütün şagirdlər200 OK
GET /api/students/{id}bir şagird; yoxdursa 404200 OK
POST /api/studentsyeni şagird yaradır201 Created
PUT /api/students/{id}şagirdi yeniləyir200 OK
DELETE /api/students/{id}şagirdi silir204 No Content

Cədvəldə REST-in əsas razılaşmaları görünür: URL resursu cəm halda isimlə adlandırır (/api/students), nə ediləcəyini isə HTTP metodu deyir. Ona görə /api/getStudents və ya /api/deleteStudent?id=2 kimi yollar pis üslub sayılır. GET heç vaxt verilənləri dəyişməməlidir, PUT resursu bütövlükdə əvəz edir, nəticəni isə status kodu bildirir — müştəri cavabın mətnini oxumadan nə baş verdiyini bilir.

@RequestMapping bütün kontroller üçün ümumi yolu təyin edir. @PathVariable URL-dəki {id}-ni parametrə, @RequestBody isə sorğunun JSON gövdəsini Student obyektinə çevirir; cavab obyektini JSON-a Jackson kitabxanası çevirir. ResponseStatusException istənilən yerdən düzgün HTTP statusunu — məsələn, 404 və ya 400 — qaytarmağa imkan verir. @Value("${school.max-score:100}") parametri konfiqurasiyadan oxuyur; iki nöqtədən sonrakı 100 susmaya görə qiymətdir.

Text
spring.application.name=school-api
server.port=8080
school.max-score=100
src/main/resources/application.properties — tətbiqin parametrləri

Parametrləri kodu dəyişmədən dəyişmək olar: java -jar school.jar --server.port=9090 və ya SERVER_PORT mühit dəyişəni ilə. Müxtəlif mühitlər üçün profillər var: məsələn, application-dev.properties faylı --spring.profiles.active=dev ilə aktivləşir.

curl ilə yoxlama

Server işləyərkən ikinci terminal aç. curl HTTP sorğuları göndərən əmr sətri alətidir: -X metodu, -H başlığı, -d isə gövdəni təyin edir. Əvvəlcə iki şagird yaradaq və siyahını oxuyaq:

Terminal
curl -X POST localhost:8080/api/students -H 'Content-Type: application/json' -d '{"name":"Aysel","score":95}'
curl -X POST localhost:8080/api/students -H 'Content-Type: application/json' -d '{"name":"Murad","score":78}'
curl localhost:8080/api/students
curl localhost:8080/api/students/1
Gözlənilən nəticə
{"id":1,"name":"Aysel","score":95}
{"id":2,"name":"Murad","score":78}
[{"id":1,"name":"Aysel","score":95},{"id":2,"name":"Murad","score":78}]
{"id":1,"name":"Aysel","score":95}
Nümunə nəticə: JSON-da sahələr record-dakı sıra ilə gəlir, id-ni server verir

İndi yeniləmə, silmə və səhv halları. -w '%{http_code}' yalnız status kodunu çap edir, -s -o /dev/null isə gövdəni və gedişat məlumatını gizlədir:

Terminal
curl -X PUT localhost:8080/api/students/2 -H 'Content-Type: application/json' -d '{"name":"Murad","score":84}'
curl -s -o /dev/null -w '%{http_code}\n' -X DELETE localhost:8080/api/students/2
curl -s -o /dev/null -w '%{http_code}\n' localhost:8080/api/students/2
curl -s -o /dev/null -w '%{http_code}\n' -X POST localhost:8080/api/students \
     -H 'Content-Type: application/json' -d '{"name":"Elvin","score":120}'
Gözlənilən nəticə
{"id":2,"name":"Murad","score":84}
204
404
400
Nümunə nəticə: 204 — silindi, 404 — artıq yoxdur, 400 — 120 bal qəbul edilmir
Java
@SpringBootTest
@AutoConfigureMockMvc
class StudentControllerTest {

    @Autowired
    MockMvc mvc;

    @Test
    void createsStudent() throws Exception {
        mvc.perform(post("/api/students")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content("{\"name\":\"Aysel\",\"score\":95}"))
                .andExpect(status().isCreated())
                .andExpect(jsonPath("$.name").value("Aysel"));
    }
}
Avtomatik test: MockMvc serveri işə salmadan HTTP sorğusunu təqlid edir (importlar göstərilməyib)

Əsas fikirlər

  • Spring Boot: @SpringBootApplication + avtomatik konfiqurasiya + daxili Tomcat; işə salmaq üçün ./mvnw spring-boot:run.
  • Qatlar: @RestController → @Service → @Repository; Spring bean-ləri yaradır və konstruktorlara ötürür.
  • @GetMapping, @PostMapping, @PutMapping, @DeleteMapping, @PathVariable, @RequestBody; JSON çevrilməsini Jackson edir.
  • Düzgün statuslar: 200, 201, 204, 400, 404 — @ResponseStatus və ResponseStatusException ilə.
  • Parametrlər application.properties-də saxlanılır və @Value ilə oxunur; API curl və ya MockMvc ilə yoxlanılır.

Özünü yoxla

10 sual. Hər düzgün cavab XP qazandırır.

1 / 10
HTTP sorğularını qəbul edib JSON qaytaran sinif hansı annotasiya ilə işarələnir?