~~NOCACHE~~ ~~REVEAL theme=simple&disableLayout=0&transition=none&controls=1&show_progress_bar=1&build_all_lists=0&show_image_borders=0&horizontal_slide_level=2&enlarge_vertical_slide_headers=0&show_slide_details=1&open_in_new_window=1&size=1024x768~~ ===== Ćwiczenia demonstracyjne - Spring Framework ===== Ćwiczenia praktyczne (hands-on) z rozwiązaniami i wskazówkami. ===== Ćwiczenie 1: Hello World w Spring Boot ===== **Opis:** Utwórz prostą aplikację Spring Boot, która wyświetla "Hello Spring Boot!" w logach i obsługuje żądanie HTTP. **Kroki wykonania:** 1. Stwórz nowy projekt Spring Boot (File > New > Project > Spring Boot) 2. Nazwa projektu: ''HelloSpring'' 3. Dodaj dependencję: ''Spring Web'' 4. Utwórz główną klasę aplikacji 5. Utwórz prosty kontroler REST 6. Uruchom aplikację **Wskazówki:** - Użyj Spring Initializr (https://start.spring.io) do wygenerowania projektu - Główna klasa musi być w głównym pakiecie projektu - Kontroler powinien być w podpakiecie ''controller'' - Aplikacja domyślnie uruchamia się na porcie 8080 **Na co zwrócić uwagę:** - Adnotacja ''@SpringBootApplication'' jest wymagana w głównej klasie - ''SpringApplication.run()'' uruchamia aplikację - Metoda w kontrolerze powinna być dostępna spoza pakietu **Rozwiązanie:** Główna klasa aplikacji (''src/main/java/com/example/HelloSpring/Application.java''): package com.example.HelloSpring; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); System.out.println("Hello Spring Boot!"); } } Kontroler (''src/main/java/com/example/HelloSpring/controller/GreetController.java''): package com.example.HelloSpring.controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class GreetController { @GetMapping("/hello") public String hello() { return "Hello Spring Boot!"; } @GetMapping("/greet") public String greet() { return "Witaj w Spring Boot!"; } } **Testowanie:** - Uruchom aplikację (Shift + F10 w IntelliJ IDEA) - Otwórz przeglądarkę i wejdź na: http://localhost:8080/hello - Powinna wyświetlić się odpowiedź: ''Hello Spring Boot!'' ===== Ćwiczenie 2: Wstrzykiwanie zależności (Dependency Injection) ===== **Opis:** Utwórz aplikację demonstrującą wstrzykiwanie zależności - serwis biznesowy będzie wstrzykiwany do kontrolera. **Kroki wykonania:** 1. Stwórz interfejs ''UserService'' i jego implementację 2. Oznacz implementację adnotacją ''@Service'' 3. Wstrzyknij serwis do kontrolera przez konstruktor 4. Utwórz endpoint, który korzysta z serwisu **Wskazówki:** - Wstrzykiwanie przez konstruktor jest najbezpieczniejsze - Interfejsy pomagają w testowaniu i rozszerzaniu kodu - Spring automatycznie tworzy instancję i wstrzykuje zależność **Na co zwrócić uwagę:** - Klasa implementująca interfejs musi być zaadnotowana ''@Service'' - Konstruktor musi być publiczny - Spring szuka beanów oznaczonych ''@Service'', ''@Component'', ''@Repository'' **Rozwiązanie:** Interfejs (''service/UserService.java''): package com.example.HelloSpring.service; public interface UserService { String getUserInfo(String username); String getGreeting(String name); } Implementacja (''service/UserServiceImpl.java''): package com.example.HelloSpring.service; import org.springframework.stereotype.Service; @Service public class UserServiceImpl implements UserService { @Override public String getUserInfo(String username) { return "Informacje o użytkowniku: " + username; } @Override public String getGreeting(String name) { return "Cześć " + name + ", witaj w aplikacji!"; } } Kontroler (''controller/UserController.java''): package com.example.HelloSpring.controller; import com.example.HelloSpring.service.UserService; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; @RestController public class UserController { private final UserService userService; // Wstrzykiwanie zależności przez konstruktor public UserController(UserService userService) { this.userService = userService; } @GetMapping("/user/{username}") public String getUserInfo(@PathVariable String username) { return userService.getUserInfo(username); } @GetMapping("/greet/{name}") public String greet(@PathVariable String name) { return userService.getGreeting(name); } } **Testowanie:** - http://localhost:8080/user/john → ''Informacje o użytkowniku: john'' - http://localhost:8080/greet/Anna → ''Cześć Anna, witaj w aplikacji!'' ===== Ćwiczenie 3: REST API - CRUD dla zasobu User ===== **Opis:** Utwórz REST API do zarządzania użytkownikami (Create, Read, Update, Delete). Dane przechowuj w pamięci (HashMap). **Kroki wykonania:** 1. Utwórz klasę ''User'' (model danych) 2. Utwórz serwis ''UserRepository'' (zarządzanie użytkownikami) 3. Utwórz kontroler REST z metodami CRUD 4. Mapuj żądania HTTP na operacje CRUD **Wskazówki:** - Użyj ''HashMap'' do przechowywania użytkowników - Każdy użytkownik powinien mieć unikalny ID - Zwracaj odpowiednie kody HTTP (200, 201, 404, 204) **Na co zwrócić uwagę:** - ''@PostMapping'' tworzy zasób - ''@GetMapping'' pobiera zasób - ''@PutMapping'' aktualizuje zasób - ''@DeleteMapping'' usuwa zasób - ''@PathVariable'' pobiera parametr ze ścieżki **Rozwiązanie:** Model (''model/User.java''): package com.example.HelloSpring.model; public class User { private Long id; private String name; private String email; public User() {} public User(Long id, String name, String email) { this.id = id; this.name = name; this.email = email; } // Gettery i settery public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } } Repozytorium (''service/UserRepository.java''): package com.example.HelloSpring.service; import com.example.HelloSpring.model.User; import org.springframework.stereotype.Repository; import java.util.*; @Repository public class UserRepository { private final Map users = new HashMap<>(); private Long nextId = 1L; public User save(User user) { if (user.getId() == null) { user.setId(nextId++); } users.put(user.getId(), user); return user; } public Optional findById(Long id) { return Optional.ofNullable(users.get(id)); } public List findAll() { return new ArrayList<>(users.values()); } public void delete(Long id) { users.remove(id); } } Kontroler REST (''controller/UserRestController.java''): package com.example.HelloSpring.controller; import com.example.HelloSpring.model.User; import com.example.HelloSpring.service.UserRepository; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import java.util.List; @RestController @RequestMapping("/api/users") public class UserRestController { private final UserRepository userRepository; public UserRestController(UserRepository userRepository) { this.userRepository = userRepository; } @GetMapping public List getAllUsers() { return userRepository.findAll(); } @GetMapping("/{id}") public ResponseEntity getUserById(@PathVariable Long id) { return userRepository.findById(id) .map(user -> new ResponseEntity<>(user, HttpStatus.OK)) .orElse(new ResponseEntity<>(HttpStatus.NOT_FOUND)); } @PostMapping public ResponseEntity createUser(@RequestBody User user) { User saved = userRepository.save(user); return new ResponseEntity<>(saved, HttpStatus.CREATED); } @PutMapping("/{id}") public ResponseEntity updateUser( @PathVariable Long id, @RequestBody User userDetails) { return userRepository.findById(id) .map(user -> { user.setName(userDetails.getName()); user.setEmail(userDetails.getEmail()); userRepository.save(user); return new ResponseEntity<>(user, HttpStatus.OK); }) .orElse(new ResponseEntity<>(HttpStatus.NOT_FOUND)); } @DeleteMapping("/{id}") public ResponseEntity deleteUser(@PathVariable Long id) { userRepository.delete(id); return new ResponseEntity<>(HttpStatus.NO_CONTENT); } } **Testowanie (przy użyciu Postman lub curl):** Tworzenie nowego użytkownika: POST /api/users Content-Type: application/json { "name": "Jan Kowalski", "email": "jan@example.com" } Pobranie wszystkich użytkowników: GET /api/users Pobranie konkretnego użytkownika: GET /api/users/1 Aktualizacja użytkownika: PUT /api/users/1 Content-Type: application/json { "name": "Jan Nowak", "email": "jan.nowak@example.com" } Usunięcie użytkownika: DELETE /api/users/1 ===== Ćwiczenie 4: Obsługa błędów - Global Exception Handler ===== **Opis:** Utwórz globalny handler wyjątków, aby przechwytywać błędy i zwracać jednolite odpowiedzi błędów. **Kroki wykonania:** 1. Utwórz wyjątek ''ResourceNotFoundException'' 2. Utwórz klasę ``@ControllerAdvice`` do globalnej obsługi wyjątków 3. Definiuj metody ''@ExceptionHandler'' dla różnych typów wyjątków 4. Zwracaj odpowiednie kody HTTP i wiadomości błędów **Wskazówki:** - ''@ControllerAdvice'' obsługuje wyjątki z całej aplikacji - ''@ExceptionHandler'' mapuje konkretny typ wyjątku na metodę obsługi - Zwracaj ''ResponseEntity'' z odpowiednim kodem HTTP **Na co zwrócić uwagę:** - Ustal porządek handlersów (od bardziej szczegółowych do ogólnych) - Zwracaj informacyjne wiadomości błędów - Obsługuj wyjątki walidacji i wyjątki biznesowe oddzielnie **Rozwiązanie:** Wyjątek (''exception/ResourceNotFoundException.java''): package com.example.HelloSpring.exception; public class ResourceNotFoundException extends RuntimeException { public ResourceNotFoundException(String message) { super(message); } } Global Exception Handler (''exception/GlobalExceptionHandler.java''): package com.example.HelloSpring.exception; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import java.time.LocalDateTime; import java.util.LinkedHashMap; import java.util.Map; @ControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(ResourceNotFoundException.class) public ResponseEntity handleResourceNotFound( ResourceNotFoundException ex) { Map body = new LinkedHashMap<>(); body.put("timestamp", LocalDateTime.now()); body.put("status", HttpStatus.NOT_FOUND.value()); body.put("error", "Zasób nie znaleziony"); body.put("message", ex.getMessage()); return new ResponseEntity<>(body, HttpStatus.NOT_FOUND); } @ExceptionHandler(IllegalArgumentException.class) public ResponseEntity handleIllegalArgument( IllegalArgumentException ex) { Map body = new LinkedHashMap<>(); body.put("timestamp", LocalDateTime.now()); body.put("status", HttpStatus.BAD_REQUEST.value()); body.put("error", "Nieprawidłowy argument"); body.put("message", ex.getMessage()); return new ResponseEntity<>(body, HttpStatus.BAD_REQUEST); } @ExceptionHandler(Exception.class) public ResponseEntity handleGlobalException(Exception ex) { Map body = new LinkedHashMap<>(); body.put("timestamp", LocalDateTime.now()); body.put("status", HttpStatus.INTERNAL_SERVER_ERROR.value()); body.put("error", "Błąd serwera"); body.put("message", "Wewnętrzny błąd serwera"); return new ResponseEntity<>(body, HttpStatus.INTERNAL_SERVER_ERROR); } } Modyfikacja repozytorium do rzucania wyjątków: @GetMapping("/{id}") public ResponseEntity getUserById(@PathVariable Long id) { return userRepository.findById(id) .map(user -> new ResponseEntity<>(user, HttpStatus.OK)) .orElseThrow(() -> new ResourceNotFoundException("Użytkownik o ID " + id + " nie znaleziony") ); } **Testowanie:** - Pobierz użytkownika z nieistniejącym ID: GET /api/users/999 - Powinien zwrócić kod 404 i szczegółową wiadomość błędu ===== Ćwiczenie 5: Spring Data JPA - Praca z bazą danych ===== **Opis:** Zintegruj aplikację z bazą danych H2 (w pamięci) i użyj Spring Data JPA do operacji CRUD. **Kroki wykonania:** 1. Dodaj dependencje: ''Spring Data JPA'' i ''H2 Database'' 2. Utwórz encję ''User'' 3. Utwórz repozytorium rozszerzające ''JpaRepository'' 4. Skonfiguruj połączenie do bazy danych 5. Użyj repozytorium w serwisie **Wskazówki:** - H2 to baza danych w pamięci, idealna do testów - ''@Entity'' oznacza klasę jako encję - ''@Id'' i ''@GeneratedValue'' obsługują klucz główny - Spring Data JPA generuje SQL automatycznie **Na co zwrócić uwagę:** - Encja musi mieć konstruktor bezargumentowy - Klucz główny musi być oznaczony ''@Id'' - Nazwy pól powinny odpowiadać kolumnom w bazie **Rozwiązanie:** Plik ''pom.xml'' - dodaj dependencje: org.springframework.boot spring-boot-starter-data-jpa com.h2database h2 runtime Encja (''entity/User.java''): package com.example.HelloSpring.entity; import jakarta.persistence.*; @Entity @Table(name = "users") public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(nullable = false) private String name; @Column(nullable = false, unique = true) private String email; public User() {} public User(String name, String email) { this.name = name; this.email = email; } // Gettery i settery public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } } Repozytorium (''repository/UserRepository.java''): package com.example.HelloSpring.repository; import com.example.HelloSpring.entity.User; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; import java.util.Optional; @Repository public interface UserRepository extends JpaRepository { Optional findByEmail(String email); } Plik ''application.properties'': spring.datasource.url=jdbc:h2:mem:testdb spring.datasource.driverClassName=org.h2.Driver spring.datasource.username=sa spring.datasource.password= spring.h2.console.enabled=true spring.jpa.database-platform=org.hibernate.dialect.H2Dialect spring.jpa.hibernate.ddl-auto=create-drop spring.jpa.show-sql=true Serwis (''service/UserService.java''): package com.example.HelloSpring.service; import com.example.HelloSpring.entity.User; import com.example.HelloSpring.repository.UserRepository; import org.springframework.stereotype.Service; import java.util.List; import java.util.Optional; @Service public class UserService { private final UserRepository userRepository; public UserService(UserRepository userRepository) { this.userRepository = userRepository; } public User save(User user) { return userRepository.save(user); } public Optional findById(Long id) { return userRepository.findById(id); } public List findAll() { return userRepository.findAll(); } public void delete(Long id) { userRepository.deleteById(id); } } **Testowanie:** - Uruchom aplikację - Wejdź na http://localhost:8080/h2-console (konsola H2) - Utwórz nowych użytkowników: POST /api/users - Sprawdź w konsoli H2, czy dane zostały zapisane w bazie ===== Ćwiczenie 6: Spring Security - Autentykacja i autoryzacja ===== **Opis:** Dodaj zabezpieczenia do aplikacji - autentykacja użytkowników i kontrola dostępu do endpointów. **Kroki wykonania:** 1. Dodaj dependencję ''Spring Security'' 2. Utwórz klasę konfiguracji zabezpieczeń 3. Zdefiniuj domyślnego użytkownika 4. Zabezpiecz endpointy na podstawie ról 5. Przetestuj dostęp z autentykacją **Wskazówki:** - Spring Security domyślnie chroni wszystkie endpointy - Definiuj reguły w metodzie ''configure()'' lub ''filterChain()'' - Użyj ''@PreAuthorize'' do weryfikacji uprawnień na metodzie - Domyślny użytkownik to ''user'' **Na co zwrócić uwagę:** - Hasło generowane jest losowo przy uruchomieniu (sprawdź w logach) - Można definiować własnych użytkowników w kodzie - CSRF jest domyślnie włączony dla POST/PUT/DELETE **Rozwiązanie:** Plik ''pom.xml'' - dodaj dependencję: org.springframework.boot spring-boot-starter-security Konfiguracja (''config/SecurityConfig.java''): package com.example.HelloSpring.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.web.SecurityFilterChain; @Configuration @EnableWebSecurity public class SecurityConfig { @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean public UserDetailsService userDetailsService(PasswordEncoder encoder) { UserDetails admin = User.builder() .username("admin") .password(encoder.encode("admin123")) .roles("ADMIN") .build(); UserDetails user = User.builder() .username("user") .password(encoder.encode("user123")) .roles("USER") .build(); return new InMemoryUserDetailsManager(admin, user); } @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(auth -> auth .requestMatchers("/api/admin/**") .hasRole("ADMIN") .requestMatchers("/api/users/**") .hasAnyRole("USER", "ADMIN") .requestMatchers("/", "/hello") .permitAll() .anyRequest() .authenticated()) .httpBasic() .and() .csrf().disable(); return http.build(); } } Modyfikacja kontrolera z @PreAuthorize: @DeleteMapping("/{id}") @PreAuthorize("hasRole('ADMIN')") public ResponseEntity deleteUser(@PathVariable Long id) { userRepository.deleteById(id); return new ResponseEntity<>(HttpStatus.NO_CONTENT); } **Testowanie:** - Bez autentykacji: dostęp do GET /api/users zwróci 401 Unauthorized - Z autentykacją Basic (user:user123): dostęp będzie pozwolony - DELETE /api/users/{id} dostęp tylko dla admin:admin123 ===== Podsumowanie ćwiczeń ===== Ćwiczenia obejmują: * Podstawową konfigurację aplikacji Spring Boot * Wstrzykiwanie zależności i zarządzanie beanami * REST API i operacje CRUD * Obsługę błędów i wyjątków * Integrację z bazą danych (JPA) * Zabezpieczenia (Spring Security) Po każdym ćwiczeniu: * Eksperymentuj z kodem * Dodaj logowanie do debugowania * Przetestuj różne scenariusze * Obserwuj komunikaty błędów