~~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
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.bootspring-boot-starter-data-jpacom.h2databaseh2runtime
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.bootspring-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