зеркало из
https://github.com/iharh/notes.git
synced 2025-10-29 20:56:06 +02:00
m
Этот коммит содержится в:
родитель
71f35a11de
Коммит
9b7ad5ddc6
@ -1,3 +1,6 @@
|
||||
https://github.com/IsmaelMartinez/teams-for-linux
|
||||
https://flathub.org/apps/com.github.IsmaelMartinez.teams_for_linux
|
||||
|
||||
https://techcommunity.microsoft.com/t5/microsoft-teams-blog/microsoft-teams-progressive-web-app-now-available-on-linux/ba-p/3669846
|
||||
|
||||
chrome://apps
|
||||
|
||||
@ -4,6 +4,8 @@ https://docs.axoniq.io/reference-guide/axon-framework/sagas/implementation
|
||||
https://github.com/eventuate-tram/eventuate-tram-sagas
|
||||
|
||||
2023
|
||||
https://habr.com/ru/articles/744460/
|
||||
axon
|
||||
https://www.baeldung.com/cs/saga-pattern-microservices
|
||||
https://www.vinsguru.com/choreography-saga-pattern-with-spring-boot/
|
||||
https://www.vinsguru.com/orchestration-saga-pattern-with-spring-boot/
|
||||
|
||||
380
pl/java/libfws/spring/boot/mvc/docs/courses/linkedin.txt
Обычный файл
380
pl/java/libfws/spring/boot/mvc/docs/courses/linkedin.txt
Обычный файл
@ -0,0 +1,380 @@
|
||||
2019
|
||||
https://www.linkedin.com/learning/spring-spring-mvc-2
|
||||
Incoming HTTP Request is Processed by
|
||||
DispatcherServlet->HandlerMapping->Controller (Front Controller DP)
|
||||
->Controller->...processing...->back-to-DispatcherServlet
|
||||
->ViewResolver
|
||||
|
||||
Instead of String method at Controller we can use
|
||||
ModelAndView
|
||||
.setViewName
|
||||
.setStatus
|
||||
.addObject(name, value)
|
||||
SearchController {
|
||||
@GetMapping("/search")
|
||||
public String search(@RequestParam("search") String search, Model model) {
|
||||
...
|
||||
List<Product> products = searchRepository.searchByName(search);
|
||||
model.addAttribute("products", products);
|
||||
...
|
||||
}
|
||||
}
|
||||
RegistrationController {
|
||||
@PostMapping("/registeruser")
|
||||
public String registerUser(@ModelAttribute("newuser") User user) { // <form method="post" modelAttribute="newuser" ...>
|
||||
|
||||
}
|
||||
}
|
||||
HomeController {
|
||||
...
|
||||
@ModelAttribute("newuser")
|
||||
public User getDefaultUser() { return new User(); }
|
||||
@ModelAttribute("genderItems")
|
||||
public List<String> getGenderItems() { return List.of("Male", "Female", "Other"); }
|
||||
}
|
||||
|
||||
javax.validation -> hibernate validator
|
||||
annotate with @Valid our beans at controller and add BindingResult param right after the annotated one
|
||||
and add <form:errors path="someAttr".../> to our form elements
|
||||
in order to localize error messages, add resources/ValidationMessages.properties, add msg.key=msg.value
|
||||
and use ... message="{msg.key}"
|
||||
|
||||
binders extend ModelAttribute-s
|
||||
User {
|
||||
|
||||
private Date dateOfBirth; // <form:input path="dateOfBirth" type="date" .../>
|
||||
}
|
||||
RegistrationController {
|
||||
@InitBinder
|
||||
public void initBinder(WebDataBinder binder) {
|
||||
binder.registerCustomEditor(Date.class, "dateOfBirth", new CustomDateEditor(new SimpleDateFormat("yyyy-MM-dd")));
|
||||
}
|
||||
}
|
||||
|
||||
converters extend binders concept, transform request data to desired types
|
||||
built in converters present, but we can provide custom ones
|
||||
|
||||
public enum Gender { MALE, FEMALE, OTHER }
|
||||
User {
|
||||
@Enumerated(EnumType.STRING)
|
||||
private Gender gender;
|
||||
}
|
||||
|
||||
public class StringToEnumConverter implements .core.convert.Converter<String, Gender> {
|
||||
@Override
|
||||
public Gender convert(String s) {
|
||||
... regurn Gender.GGG ...;
|
||||
}
|
||||
}
|
||||
|
||||
ApplicationConfig {
|
||||
...
|
||||
@Override
|
||||
protected void addFormatters(FormatterRegistry registry) {
|
||||
registry.addConverter(new StringToEnumConverter());
|
||||
}
|
||||
}
|
||||
|
||||
HandlerMapping - maps request to handler
|
||||
HandlerAdapter - invokes the handler
|
||||
ViewResolver - helps resolve views
|
||||
LocaleResolver - helps with l10n and i18n support
|
||||
ThemeResolver
|
||||
HandlerExceptionResolver - helps with exception handling
|
||||
ExceptionHandlerExceptionResolver - define ex-handling methods in controllers (annotated by @ExceptionHandler)
|
||||
SimpleMappingExceptionResolver - maps each ex-n class with an error page
|
||||
DefaultHandlerExceptionResolver - default, maps exceptions to error codes
|
||||
ResponseStatusExceptionResolver - resolves custom exceptions using status code defined in @ResponseStatus (for ex-ns)
|
||||
to disable standard error resolver, we need to add property
|
||||
spring.autoconfigure.exclude=ord.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration
|
||||
|
||||
public class Login {
|
||||
private String login;
|
||||
private String password;
|
||||
}
|
||||
HomeController {
|
||||
... @ModelAttribute("login") public Login getDefaultLogin() { return new Login(); }
|
||||
}
|
||||
|
||||
public class ApplicationException extends RuntimeException { ... }
|
||||
|
||||
LoginController {
|
||||
@PostMapping("/login")
|
||||
public String login(@ModelAttribute("login") Login login) {
|
||||
User user = userRepository.searchByName(login.getUsername());
|
||||
if (user == null) {
|
||||
throw new ApplicationException("User not found");
|
||||
}
|
||||
}
|
||||
@ExceptionHandler(ApplicationException.class)
|
||||
public String handleException(ApplicationException ex, WebRequest request) {
|
||||
return "error";
|
||||
}
|
||||
}
|
||||
for global handler
|
||||
@ControllerAdvice
|
||||
public class ApplicationExceptionHandler {
|
||||
|
||||
@ExceptionHandler(ApplicationException.class)
|
||||
public String handleException(ApplicationException ex, WebRequest request) {
|
||||
return "error";
|
||||
}
|
||||
}
|
||||
...
|
||||
@ControllerAdvice
|
||||
public class DefaultModelAttributeController {
|
||||
@ModelAttribute("newuser")
|
||||
public User getDefaultUser() { return new User(); }
|
||||
@ModelAttribute("genderItems")
|
||||
public List<String> getGenderItems() { return List.of("Male", "Female", "Other"); }
|
||||
...
|
||||
}
|
||||
|
||||
@EnableAsync
|
||||
https://stackoverflow.com/questions/36768234/spring-enableasync-breaks-bean-initialization-order
|
||||
https://www.baeldung.com/spring-async
|
||||
...ApplicationAsync {
|
||||
@Override
|
||||
protected void configureAsyncSupport(AsyncSupportConfigurer configurer)
|
||||
configurer.setTaskExecutor(mvcTaskExecutor());
|
||||
configurer.setDefaultTimeout(5000);
|
||||
}
|
||||
@Bean public AsyncTaskExecutor mvcTaskExecutor() { ThreadPoolTaskExecutor... }
|
||||
}
|
||||
... SearchController {
|
||||
public Callable<String> search(..., HttpServletRequest request) {
|
||||
log.info(request.isAsyncSupported(), Thread.currentThread().getName());
|
||||
return () -> {
|
||||
log.info(Thread.currentThread().getName());
|
||||
all the business logic
|
||||
}
|
||||
|
||||
we can also use DeferredResult
|
||||
@Autowired
|
||||
private AsyncTaskExecutor executor;
|
||||
|
||||
|
||||
public DeferredResult<String> search(..., HttpServletRequest request) {
|
||||
DeferredResult<String> defferedResult = new DeferredResult<>();
|
||||
...
|
||||
executor.execute(() -> {
|
||||
...
|
||||
defferedResult.setResult(search);
|
||||
});
|
||||
return defferedResult;
|
||||
}
|
||||
}
|
||||
|
||||
View
|
||||
ViewResolver - resp by resolving views by names
|
||||
InternalResourceViewResolver
|
||||
ResourceBundleViewResolver
|
||||
XmlViewResolver
|
||||
VelocityViewResolver, FreeMakerViewResolver, ...
|
||||
ChainedViewResolver
|
||||
|
||||
DispatcherServlet
|
||||
HandlerMapping, HandlerAdapter (calls interceptors)
|
||||
Pre-process Interceptors 1..n
|
||||
and then (iff all are OK) - calls a Controller
|
||||
after all business-logic -
|
||||
Post-process
|
||||
|
||||
Impl-s of HandlerInterceptorAdapter base class
|
||||
preHandle
|
||||
postHandle
|
||||
afterCompletion - after response is completed to the client
|
||||
ThemeChangeInterceptor
|
||||
LocaleChangeInterceptor
|
||||
|
||||
public class LoggingInterceptor extends HandlerInterceptorAdapter {
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
|
||||
...
|
||||
}
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
|
||||
...
|
||||
}
|
||||
}
|
||||
at ApplicationConfig {
|
||||
@Override
|
||||
protected void addInterceptors(InterceptorRegistry registry) {
|
||||
registry.addInterceptor(new LoggingInterceptor()).addPathPatterns("/*")
|
||||
}
|
||||
}
|
||||
|
||||
... @SessionAttributes, @SessionAttribute
|
||||
@RequestAttribute
|
||||
|
||||
@Controller
|
||||
@SessionAttributes("login") // @ModelAttribute should exist
|
||||
public class LoginController {
|
||||
|
||||
// already exposed by ControllerAdvice
|
||||
// @ModelAttribute("login") public Login getDefaultLogin() { return new Login(); }
|
||||
}
|
||||
|
||||
@Controller
|
||||
public class UserProfileController {
|
||||
@PostMapping("/userprofile")
|
||||
public String getUserProfile(@SessionAttribute("login") Login login, Model model) {
|
||||
// and similar for @RequestAttribute
|
||||
model.setAttribute("username"); // or use ${sessionScope.username} at jsp
|
||||
return "profile"; // use login in jsp
|
||||
}
|
||||
}
|
||||
|
||||
LoginController {
|
||||
...
|
||||
@PostMapping("/login")
|
||||
{
|
||||
session.setMaxInactiveTimeout(...)
|
||||
...
|
||||
... return "forward:/userprofile"
|
||||
}
|
||||
}
|
||||
|
||||
@Controller
|
||||
public class LogoutController {
|
||||
@GetMapping("/logout")
|
||||
public String logout(HttpSession session) {
|
||||
session.invalidate();
|
||||
return "login";
|
||||
}
|
||||
}
|
||||
|
||||
@Controller
|
||||
public class RedirectionController {
|
||||
@GetMapping("/redirectToLinkedIn")
|
||||
public String redirectToLinkedIn() {
|
||||
return "redirect:http://www.linkedin.com"; // absolute URL is needed
|
||||
}
|
||||
}
|
||||
|
||||
ThemeResolver facilitates theme resolution
|
||||
ThemeSource and default impl - ResourceBundleThemeSource
|
||||
CookieThemeResolver
|
||||
SessionThemeResolver
|
||||
FixedThemeResolver
|
||||
|
||||
resources/client-theme1.properties
|
||||
styleSheet=/css/style1.css
|
||||
resources/client-theme2.properties
|
||||
styleSheet=/css/style2.css
|
||||
|
||||
... ApplicationConfig {
|
||||
@Bean
|
||||
public ThemeResolver themeResolver() {
|
||||
CookieThemeResolver cookieThemeResolver = new CookieThemeResolver();
|
||||
cookieThemeResolver.setCookieName("theme");
|
||||
cookieThemeResolver.setDefaultThemeName("client-theme1");
|
||||
return cookieThemeResolver;
|
||||
}
|
||||
... // also it's important to add an interceptor
|
||||
@Override
|
||||
protected void addInterceptors(InterceptorRegistry registry) {
|
||||
registry.addInterceptor(new LoggingInterceptor()).addPathPatterns("/*")
|
||||
registry.addInterceptor(new ThemeChangeInterceptor());
|
||||
}
|
||||
}
|
||||
// next thing - define at our jsp
|
||||
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
|
||||
...
|
||||
<head>
|
||||
<link rel="stylesheet" href="<spring:theme code='styleSheet'/>" type="text/css"/>
|
||||
...
|
||||
</head>
|
||||
// we can check with ...?theme=client-theme2... ... resolver will store this in cookie
|
||||
|
||||
l10n - is to use props for msgs
|
||||
i10n - is more than l10n, including locale treatment
|
||||
|
||||
LocaleResolver
|
||||
AcceptHeaderLocaleResolver - default one
|
||||
CookieLocaleResolver
|
||||
SessionLocaleResolver
|
||||
|
||||
resources/messages.properties
|
||||
label.home=home
|
||||
label.search=search
|
||||
label.login=login
|
||||
label.linkedin=linkedin
|
||||
resources/messages_sv.properties // make sure to have UTF-8
|
||||
label.home=hem
|
||||
label.search=sok
|
||||
label.login=logga in
|
||||
label.linkedin=...
|
||||
|
||||
// at jsp - use <spring:message code="label.home"/>
|
||||
// default resolver takes locale from client settings of browser
|
||||
|
||||
... ApplicationConfig {
|
||||
@Bean
|
||||
public LocaleResolver localeResolver() {
|
||||
CookieLocaleResolver cookieLocaleResolver = new CookieLocaleResolver();
|
||||
cookieLocaleResolver.setCookieName("locale");
|
||||
cookieLocaleResolver.setDefaultLocale(Locale.US);
|
||||
return cookieLocaleResolver;
|
||||
}
|
||||
... // also it's important to add an interceptor
|
||||
@Override
|
||||
protected void addInterceptors(InterceptorRegistry registry) {
|
||||
registry.addInterceptor(new LoggingInterceptor()).addPathPatterns("/*")
|
||||
registry.addInterceptor(new ThemeChangeInterceptor());
|
||||
registry.addInterceptor(new LocaleChangeInterceptor()); // can customize with setParamName if need non-default
|
||||
}
|
||||
}
|
||||
|
||||
REST: spring uses HttpMessageConvertors for JSON (with Jackson), no view name needed
|
||||
|
||||
|
||||
@Controller
|
||||
public class ProductsRestController {
|
||||
...
|
||||
@GetMapping("/rest/products")
|
||||
@ResponseBody
|
||||
public List<Product> getProducts() {
|
||||
return productRepository.findAll();
|
||||
}
|
||||
}
|
||||
@RestController // implies @ResponseBody for every method
|
||||
public class ProductsRestController {
|
||||
...
|
||||
@GetMapping("/rest/products")
|
||||
public ResponseEntity<List<Product>> getProductsByParam(@RequestParam("name") String name) {
|
||||
List<Product> products = productRepository.searchByName(name)
|
||||
return new ResponseEntity<>(products, HttpStatus.OK);
|
||||
}
|
||||
@GetMapping("/rest/products/{id}")
|
||||
public ResponseEntity<List<Product>> getProductsByPathVar(@PathVariable("name") String name) {
|
||||
List<Product> products = productRepository.searchByName(name)
|
||||
return new ResponseEntity<>(products, HttpStatus.OK);
|
||||
}
|
||||
}
|
||||
|
||||
public class LoginFailureException extends Exception {
|
||||
...
|
||||
}
|
||||
|
||||
@RestController
|
||||
public class LoginRestController {
|
||||
...
|
||||
|
||||
@PostMapping("/rest/loginuser")
|
||||
public ResponseEntity<?> loginUser(@RequestBody Login login) {
|
||||
User user = userRepository.searchByName(login.getUsername());
|
||||
if (user == null) {
|
||||
return new ResponseEntity.status(HttpStatus.NOT_FOUND).build();
|
||||
}
|
||||
if (user.getUsername().equals(login.getUsername)) && ... pwd) {
|
||||
return new ResponseEntity<>("Welcome " + user.getUsername(), HttpStatus.OK);
|
||||
} else {
|
||||
throw new LoginFailureException("username or password is incorrect")
|
||||
}
|
||||
}
|
||||
|
||||
@ExceptionHandler(LoginFailureException.class)
|
||||
public ResponseEntity<?> handle Login(LoginFailureException ex) {
|
||||
return ResponseEntity.status(HttpStatus.FORBIDDEN).body(ex.getMessage());
|
||||
}
|
||||
}
|
||||
@ -6,3 +6,53 @@ Udemy - Learn integration testing with Spring Boot
|
||||
Advanced Spring - Effective Integration Testing with Spring Boot
|
||||
magnet:?xt=urn:btih:afd24823c218c0cc876504cb86723cfc44b9ab29
|
||||
! 201.5m, non-complete
|
||||
|
||||
https://www.linkedin.com/learning/spring-boot-test-driven-development
|
||||
AopTestUtils
|
||||
ReflectionTestUtils
|
||||
MvcTestUtils, Mvc test client, MockMvc
|
||||
|
||||
Unit Test
|
||||
@ExtendsWith(MockitoExtension.class)
|
||||
Integration
|
||||
@SpringBootTest(classes=MyConfig.class)
|
||||
@AutoConfigureTestDatabase(replace=Replace.ANY)
|
||||
@AutoConfigureMockMvc
|
||||
@Autowired
|
||||
MockMvc mockMvc;
|
||||
someTestMethod() {
|
||||
mockMvc.perform(get("/someEndpoint"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(content().string(containsString("some-uuid")))
|
||||
}
|
||||
|
||||
https://www.linkedin.com/learning/advanced-spring-effective-integration-testing-with-spring-boot
|
||||
@SpringBootTest
|
||||
@DataJpaTest (without web layer), @DataJdbcTest, ... @DataRedisTest
|
||||
@WebMvcTest (loads web-layer only to the context without persistence layer), @RestClientTest, @WebServiceClientTest, @WebFluxTest
|
||||
@MockBean ? for UT
|
||||
BDDMockito lib
|
||||
instead of when(methodCall).then(doSomething)
|
||||
given(methodCall).will(doSomething)
|
||||
|
||||
TestEntityManager mgr
|
||||
mgr.persistFlushFind(obj) -> to bypass hbm L1 cache
|
||||
|
||||
@ExtendsWith(MockitoExtension.class)
|
||||
@InjectMock - uses constructor or setter injection
|
||||
@Mock - replaces whole class entirely
|
||||
@Spy - takes existing object and replaces only some methods
|
||||
@MockBean and @SpyBean
|
||||
|
||||
@ExceptionHandler+@ResponseStatus()
|
||||
or @ResponseStatus(HttpStatus.NOT_FOUND)
|
||||
public void someMethod(SomeEx-n)
|
||||
mockito given().willThrow(...)
|
||||
|
||||
@RestClientTest (MockRestServiceServer, only for RestTemplate)
|
||||
WireMock is better
|
||||
|
||||
SpringCloudContract (generate tests by contract def-n),
|
||||
StubRunner fetches generated jar and spins stub-server at config-d port
|
||||
@SpringBootTest
|
||||
@AutoConfigureStubRunner(ids="group:artifact:+:8080", stubsMode=StubRunnerProperties.StubsMode.LOCAL)
|
||||
|
||||
@ -0,0 +1,6 @@
|
||||
https://github.com/tkaczmarzyk/specification-arg-resolver
|
||||
https://github.com/tkaczmarzyk/specification-arg-resolver/tree/master/src/main/java/net/kaczmarzyk/spring/data/jpa/web/annotation
|
||||
https://github.com/tkaczmarzyk/specification-arg-resolver/tree/master/src/main/java/net/kaczmarzyk/spring/data/jpa/domain
|
||||
|
||||
2017
|
||||
https://blog.tratif.com/2017/11/23/effective-restful-search-api-in-spring/
|
||||
Загрузка…
x
Ссылка в новой задаче
Block a user