Spring - rlip/java Wiki

Spring

@Component - przed nazwą klasy - które klasy mają być zainicjowane przez Springa
@Repository - działa jak wyżej, ale żeby programista wiedział że to klasa do operacji crud
@Service - j.w. ale to klasa metody biznesowe dotyczące jakieś logiki
@Scope("prototype") - za każdym razem jest tworzony nowy komponent, domyślnie jest singleton
@Autowired - przed wartością, konstruktorem, bądź seterem, spring tam wsczyknie komponent 

@Qualifier(value="lancelot") - podawany przed zmienną lub przed zmienną w konstruktorze,
dzieki temu jak mamy więcej beenów o takiej samej klasie to wstrzykujemy bean o poddanej nazwie
@Primary jeśli w konfigu to ustawimy dla jakiegoś beena których jest więcej, to wtedy ten będzie wybrany

@PropertySource("classpath:castle.properties") - przed klasą, ustawia źródło wartości
@Value("${my.castle.name:Domyslna nazwa zamku}") - przed wartością - ustawienie jakiejś wartości przez springa w tym przypadku z konfiguracji 

@SpringBootApplication - główna klasa aplikacji
@ImportResource("classpath:config/spring-config.xml") - to jeśli używamy konfiguracji xml
@ComponentScan - można dać przed główną klasą, żeby wypisać miejsca lub klasy, które ma spring zainicjować
@ComponentScan(basePackageClasses = {Starter.class, Castle.class, Quest.class, Knight.class})
@ComponentScan({"com.clockworkjava.kursspring", "com.clockworkjava.component"})

@PostConstruct i @PreDestroy - wywołane po utworzeniu / przed usunięciem komponentu

@Overrride - info dla kompilatora, że nadpisujemy coś

@RunWith(SpringRunner.class)
@SpringBootTest - to i to wyżej nad klasą testującą dodaje kontekst springa, ale wtedy wczytuje się dużo dłużej niż jakby było bez tego

@EnableScheduling - to nad klasą główną
@Scheduled(fixedDelay = 1000) i to nad jakąś metodą pozwoli na wykonywanie tej metody co sekundę, fixRate - czas liczony od momentu uruchomienia, initialDelay - opóźnienie

@Bean(name="lonelyHeartsClubBand")
public CompactDisc sgtPeppers() {
return new SgtPeppers();
} // to w konfiguracji tworzy komponent do springowego kontekstu. Name może zmienić nazwę
//Tak też można od razu coś wstrzyknąć
@Bean
public CDPlayer cdPlayer() {
return new CDPlayer(sgtPeppers());
}

Formularze

Walidacja

import javax.validation.constraints.Digits; 
import javax.validation.constraints.Pattern; 
import org.hibernate.validator.constraints.CreditCardNumber; 
import javax.validation.constraints.NotBlank;

@NotBlank(message="Podanie imienia i nazwiska jest obowiązkowe.")
@CreditCardNumber(message="To nie jest prawidłowy numer karty kredytowej.")
@Pattern(regexp="^(0[1-9]|1[0-2])([\\/])([1-9][0-9])$", message="Wartość musi być w formacie MM/RR.")
@Digits(integer=3, fraction=0, message="Nieprawidłowy kod CVV.")

MVC

Aplikacje Springa mogą używać rest na kilka sposobów:-

  • RestTemplate - prosty synchroniczny klient dostarczny przez Spring
  • Traverson - synchroniczny, obsługujący hiperłącza klient, dostarczany przez Spring Hateoas
  • WebClient - reaktywny, asynchroniczny, wprowadzony w Spring 5

obsługa błędów np.:

// w kontrollerze:
    @ExceptionHandler(AbstractException.class)
    ResponseEntity<ErrorDtoWithDescription> handleException(AbstractException exception) {
        return ErrorDtoWithDescription.from(exception).asHttpResponse();
    }
////
@Getter
public class AbstractException extends RuntimeException implements DigiPayException {

	protected final Integer httpStatus;
	protected final String description;

	public AbstractException(String message, Integer status, String description) {
		super(message);
		this.httpStatus = status;
		this.description = description;
	}
}
////
public class BadRequestException extends AbstractException {

	public BadRequestException(String message) {
		super(HttpStatus.BAD_REQUEST.getReasonPhrase(), HttpStatus.BAD_REQUEST.value(), "The incoming request has a bad syntax: " + message);
	}
}
////
public class ErrorDtoWithDescription {
    @JsonIgnore
    Integer httpStatus;
    String message;
    String description;

    public static ErrorDtoWithDescription from(AbstractException exception) {
        return new ErrorDtoWithDescription(exception.getHttpStatus(),
                exception.getMessage(),
                exception.getDescription());
    }
    public ResponseEntity<ErrorDtoWithDescription> asHttpResponse() {
        return ResponseEntity
                .status(httpStatus)
                .body(this);
    }

Rest template

Ma wiele operacji, w zależności od tego jak i co chcemy wysłać i otrzymać i jakie restowe zapytanie chcemy wywołać.

RestTemplate rest = new RestTemplate(); //lub
@Bean 
public RestTemplate restTemplate() {
   return new RestTemplate();
}
//////////////////
public Ingredient createIngredient(Ingredient ingredient) {
   return rest.postForObject("http://localhost:8080/ingredients", ingredient, Ingredient.class);
}

Pozostałe

@RequestMapping("/api/user")
@SuppressWarnings("unused")
public class UserController {

    @GetMapping("/currentUser")
    public ApiResponse getCurrentUser(@CurrentUser UserPrincipal userPrincipal) {

    @GetMapping("/zipCodeAutocomplete")
    public List<ZipCode> zipCodeAutocomplete(@NotBlank @RequestParam("searchValue") String searchValue) {
        return userService.getZipCodesLikeSearchValueLimited(searchValue);
    }

/////////////////////////////
    @PostMapping("/login")
    public ApiResponse login(@Valid @RequestBody LoginRequest loginRequest) {

/////////////////////////////        
    @RequestMapping("/knight")
    public String getKnight(@RequestParam("id") Integer id, Model model) { // jeśli robimy ?id=

    @RequestMapping("/knight/delete/{id}")
    public String deleteKnight(@PathVariable("id") Integer id, Model model) { // jeśli robimy /id

/////////////////////////////
@RestController @RequestMapping(path="/design", produces="application/json") // Obsługa żądań do /design
@CrossOrigin(origins="*") //Pozwala na żądania między domenami.
public class DesignTacoController {

/////////////////////////////
@PostMapping(consumes="application/json")
@ResponseStatus(HttpStatus.CREATED) // zwraca 201
public Taco postTaco(@RequestBody Taco taco) {
   return tacoRepo.save(taco); 
}
/////////////////////////////
@DeleteMapping(„/{orderId}") 
@ResponseStatus(code=HttpStatus.NO_CONTENT) 
public void deleteOrder(@PathVariable("orderId") Long orderId) {
   try {
     repo.deleteById(orderId);
   } catch (EmptyResultDataAccessException e) {
}

/////////////////////////////
@PatchMapping(path="/{orderId}", consumes="application/json")  // przykład aktualizacji częściowej
public Order patchOrder(@PathVariable("orderId") Long orderId, @RequestBody Order patch) {
Order order = repo.findById(orderId).get();
   if (patch.getDeliveryName() != null) {
     order.setDeliveryName(patch.getDeliveryName());   
   }
   return repo.save(order);
}

Tak można robić obiekt, który będzie zapisany w sesji i będzie dostępny w różnych kontrolerach:

@Controller 
@RequestMapping("/design") 
@SessionAttributes("order") //wskazuje obiekty modelu, które powinny pozostać w sesji i być dostępne w wielu żądaniach.
public class DesignTacoController {
   @ModelAttribute(name = "order")
   public Order order() {
     return new Order();
   }
   @ModelAttribute(name = "taco")
   public Taco taco() {
     return new Taco();
   }
   @PostMapping
   public String processDesign(@Valid Taco design, Errors errors@ModelAttribute Order order) {
// @ModelAttribute określa, że wartość pochodzi z modelu, a Spring MVC nie będzie dołączać do niego parametrów żądania.
     if (errors.hasErrors()) {
       return "design";
     }
     Taco saved = designRepo.save(design);
     order.addDesign(saved);
     return "redirect:/orders/current";
   }
 }
// jak już nie chcemy przechowywać obiektu w sesji to robimy setComplete
   @PostMapping
   public String processOrder(@Valid Order order, Errors errors, SessionStatus sessionStatus) {
     if (errors.hasErrors()) {
       return "orderForm";
     }
     orderRepo.save(order);
     sessionStatus.setComplete();
     return "redirect:/";
   }

Dodanie hiperłączy w rest - HATEOAS. Spring w akcji V - 6.2.1

<dependency>   
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-hateoas</artifactId>
</dependency>

Profile

Można korzystać z profili w konfiguracjach przed nazwą klasy lub metody, s90+. Komponenty bez profili przypisywane są zawsze. Ustawia się je jako spring.profiles.active lub gdy nie ustawiona to spring.profiles.default na wiele sposobów s90. Może być aktywnych wile profili na raz.

@Configuration @Profile("dev")
public class DevelopmentProfileConfig

W testach profil można tak ustawić:

@ActiveProfiles("dev")
public class PersistenceTest {

Usuwanie niejednoznaczności

  • albo @Primary w konfigu

  • albo @Qualifier("iceCream") pod @Autowired. iceCream to domyślny kwalifikator klasy IceCream, ale można (i nawet dobrze) tworzyć własne kwalifikatory też za pomocą tej samej adnotacji @Qualifier("iceCream") pod jedną z adnotacji komponentu np. @Component albo też w konfigu pod @Bean.

  • można użyć adnotacji warunkowej @Conditional(xxx.class) z klasą implementującą Condition posiadającą klasę matches, zwracającą true jak komponent ma zostać utworzony. s93

  • jeśli to nie wystarcza można tworzyć własne adnotacje będące kwalityfikatorami s103

Zasięg

Ustawia się za pomocą @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) stasowanej z @Bean lub @Component s102

  • Singleton — jedna instancja komponentu tworzona dla całej aplikacji;
  • Prototype (prototyp) — jedna instancja komponentu tworzona za każdym razem, gdy komponent jest wstrzykiwany lub pobierany z kontekstu aplikacji Springa;
  • Session (sesja) — w aplikacji internetowej jedna instancja obiektu utworzona dla każdej sesji;
  • Request (żądanie) — w aplikacji internetowej jedna instancja obiektu utworzona dla każdego żądania.
  • application
  • websocket

jak używamy session lub request to trzeba użyć proxy by można te pole tym zainicjalizować przy starcie kontentu, zanim jest sesja czy request s103

@Component
@Scope(
value=WebApplicationContext.SCOPE_SESSION,
proxyMode=ScopedProxyMode.INTERFACES)        // lub ScopedProxyMode.TARGET_CLASS jeśli ShoppingCart  jest klasą
public ShoppingCart cart() {
-----------
@Component
public class StoreService {
@Autowired
public void setShoppingCart(ShoppingCart shoppingCart) {
this.shoppingCart = shoppingCart;
}
...
}

Wiązanie właściwości

@Configuration
@PropertySource("classpath:/com/soundsystem/app.properties")  //deklaracja źródła właściwości
public class ExpressiveConfig {

@Autowired
Environment env;

@Bean
public BlankDisc disc() {
return new BlankDisc(
env.getProperty("disc.title"),   // Pobieranie wartości właściwości
env.getProperty("disc.artist"));
}

Spring security

@Configuration
@EnableGlobalMethodSecurity(
        securedEnabled = true,
        jsr250Enabled = true,
        prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

securedEnabled – enables the spring @Secured annotation. jsr250Enabled – enables the JSR-250 standard java security annotations. prePostEnabled – enables the spring @PreAuthorize and @PostAuthorize annotations.

https://www.baeldung.com/spring-security-method-security

Tabelka do configa - Spring w akcji V str 127 i 128

Aspekty

Można w konfigu deklarować coś jako aspekt, i można to tam podłączyć pod jakąś funkcję i wywołać przed albo po niej s35 Można też aspekterm dodać implementację innego interfejsu jakieś klasie (ten naprawdę to obiektowi ją opakowującemu)

  • Before — Funkcjonalność porady jest wykonywana przed wywołaniem metody z poradą.
  • After — Funkcjonalność porady jest wykonywana po zakończeniu działania metody z poradą, niezależnie od wyniku jej działania.
  • After-returning — Funkcjonalność porady jest wykonywana po prawidłowym zakończeniu metody z poradą.
  • After-throwing — Funkcjonalność porady jest wykonywana po zgłoszeniu wyjątku przez metodę z poradą.
  • Around — Porada realizuje tę samą funkcjonalność zarówno przed wywołaniem, jak i po zakończeniu metody z poradą. s121, s133
package concert;
public interface Performance {
public void perform();
}

execution(* concert.Performance.perform()) and !bean('woodstock')
//porada aspektu zostanie wpleciona do wszystkich komponentów, które mają nazwę różną od woodstock

rysunek 4.4 rysunek 4.5

@Configuration
@EnableAspectJAutoProxy // to potrzebne
@ComponentScan
public class ConcertConfig {
}


@Aspect
public class Audience {
    // Żeby nie powtarzać punktu przecięcia przed każdą klasą to można zrobić tak
    @Pointcut("execution(* concert.Performance.perform(..))")
    public void performance() {
    }

    @Before("performance()")
    public void silenceCellPhones() {
        System.out.println("Widzowie wyciszają telefony komórkowe");
    }

    @Before("performance()")
    public void takeSeats() {
        System.out.println("Widzowie zajmują miejsca");
    }

    @AfterReturning("performance()")
    public void applause() {
        System.out.println("Brawooo! Oklaski!");
    }

    @AfterThrowing("performance()")
    public void demandRefund() {
        System.out.println("Buu! Oddajcie pieniądze za bilety!");
    }
///Albo wszystko w jednej:
    @Around("execution(* concert.Performance.perform(..))")
    public void watchPerformance(ProceedingJoinPoint jp) {
        try {
            System.out.println("Widzowie wyciszają telefony komórkowe.");
            System.out.println("Widzowie zajmują miejsca.");
            jp.proceed();
            System.out.println("Brawooo! Oklaski!");
        } catch (Throwable t) {
            System.out.println("Buu! Oddajcie pieniądze za bilety!");
        }
    }
}

Dodanie implementacji nowego interfejsu klasie

public interface Encoreable {
    void makeBis();
}
----
@Component
public class EncoreableImpl implements Encoreable {
    @Override
    public void makeBis() {
        System.out.println("Robię BIS!!!");
    }
}
----
@Aspect
public class EncoreableIntroducer {
    //value - typ komponentów, które mają implementować interfejs, znak plusa na końcu oznacza,
    // że chodzi o podtyp Performance, a nie o interfejs sam w sobie
    //defaultImpl - identyfikuje klasy, które dostarczą implementacji dla wprowadzenia
    // może tu też być delegate-ref - wtedy podajemy nazwę sprinowego beana
    //Statyczna właściwość opatrzona adnotacją @DeclareParents określa wprowadzany interfejs
    @DeclareParents(value="concert.Performance+", defaultImpl=EncoreableImpl.class)
    public static Encoreable encoreable;
}
----
@Component
public class Starter implements CommandLineRunner {
    @Autowired
    Performance performance;
    @Override
    public void run(String... strings) {
        performance.perform();
        ((Encoreable) performance).makeBis();
    }
}
⚠️ **GitHub.com Fallback** ⚠️