- Spring Boot layihəsini yaratmaq və işə salmaq
@RestController,@Service,@Repositoryqatları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
- 1start.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ç.
- 2Metaməlumatları doldur
Group —
com.educora, Artifact —school, Name —SchoolApi. Paketcom.educora.school, əsas sinif isəSchoolApiApplicationolacaq. - 3Ası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.
- 4Yarat 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.
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);
}
}@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.
./mvnw spring-boot:run... Tomcat started on port 8080 (http) with context path '/' ... Started SchoolApiApplication in 1.9 seconds (process running for 2.2)
./gradlew bootRunQatlar və asılılıq inyeksiyası
| Qat | Annotasiya | Vəzifəsi |
|---|---|---|
| Kontroller | @RestController | HTTP sorğusunu qəbul edir, JSON qaytarır |
| Servis | @Service | biznes qaydaları: yoxlamalar, hesablamalar |
| Repozitori | @Repository | verilə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.
@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);
}@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.
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;
}
}@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);
}
}application.properties-dən gələn school.max-score@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); }
}| Sorğu | Nə edir | Uğurlu cavab |
|---|---|---|
GET /api/students | bütün şagirdlər | 200 OK |
GET /api/students/{id} | bir şagird; yoxdursa 404 | 200 OK |
POST /api/students | yeni şagird yaradır | 201 Created |
PUT /api/students/{id} | şagirdi yeniləyir | 200 OK |
DELETE /api/students/{id} | şagirdi silir | 204 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.
spring.application.name=school-api
server.port=8080
school.max-score=100src/main/resources/application.properties — tətbiqin parametrləriParametrlə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:
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{"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}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:
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}'{"id":2,"name":"Murad","score":84}
204
404
400@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"));
}
}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 —
@ResponseStatusvəResponseStatusExceptionilə. - Parametrlər
application.properties-də saxlanılır və@Valueilə oxunur; API curl və yaMockMvcilə yoxlanılır.
Özünü yoxla
10 sual. Hər düzgün cavab XP qazandırır.