зеркало из
				https://github.com/iharh/notes.git
				synced 2025-10-31 21:56:08 +02:00 
			
		
		
		
	
		
			
				
	
	
		
			381 строка
		
	
	
		
			14 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
			
		
		
	
	
			381 строка
		
	
	
		
			14 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
| 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());
 | |
|         }
 | |
|     }
 | 
