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=1024×768
Ćwiczenia praktyczne (hands-on) z rozwiązaniami i wskazówkami.
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!
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!
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<Long, User> 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<Long, User> 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<User> findById(Long id) { return Optional.ofNullable(users.get(id)); } public List<User> 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<User> getAllUsers() { return userRepository.findAll(); } @GetMapping("/{id}") public ResponseEntity<User> getUserById(@PathVariable Long id) { return userRepository.findById(id) .map(user -> new ResponseEntity<>(user, HttpStatus.OK)) .orElse(new ResponseEntity<>(HttpStatus.NOT_FOUND)); } @PostMapping public ResponseEntity<User> createUser(@RequestBody User user) { User saved = userRepository.save(user); return new ResponseEntity<>(saved, HttpStatus.CREATED); } @PutMapping("/{id}") public ResponseEntity<User> 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<Void> 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
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<Object> handleResourceNotFound( ResourceNotFoundException ex) { Map<String, Object> 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<Object> handleIllegalArgument( IllegalArgumentException ex) { Map<String, Object> 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<Object> handleGlobalException(Exception ex) { Map<String, Object> 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<User> 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
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:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </dependency>
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<User, Long> { Optional<User> 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<User> findById(Long id) { return userRepository.findById(id); } public List<User> 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
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ę:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
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<Void> 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
Ćwiczenia obejmują:
Po każdym ćwiczeniu: