Category: Spring

0

Spring MVC - HttpMessageConverter

목차 Spring MVC - ArgumentResolver Spring MVC - HttpMessageConverter Spring Boot Http 응답 - HTTP API, Message Body에 직접 입력 Spring Boot 응답 - 정적 리소스, 뷰 템플릿 Spring Boot 요청 메시지 - JSON Spring Boot 요청 메시지 - Text Spring Boot 쿼리 파라미터, HTML Form Spring Boot 기본 헤더 조회 Spring Boot API 예시 Spring Boot @RequestMapping Spring Boot Logging Spring MVC - HttpMessageConverterHttpMessageConverter 는 HTTP 요청, 응답 둘다 사용된다. canRead, canWrite : 메시지 컨버터가 해당 클래스, 미디어 타입을 지원하는지 확인한다. read, write : 메시지 컨버터를 통해 메시지르 읽고 쓰는 기능 public interface HttpMessageConverter<T> { boolean canRead(Class<?> clazz, @Nullable MediaType mediaType); boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType); List<MediaType> getSupportedMediaTypes(); default List<MediaType> getSupportedMediaTypes(Class<?> clazz) { return (canRead(clazz, null) || canWrite(clazz, null) ? getSupportedMediaTypes() : Collections.emptyList()); } T read(Class<? extends T> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException; void write(T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException;} 스프링 부트 기본 메시지 컨버터 ByteArrayHttpMessageConverter StringHttpMessageConverter MappingJackson2HttpMessageConverter 스프링 부트는 다양한 메시지 컨버터를 제공하는데, 대상 클래스 타입과 미디어 타입 둘을 체크해 사용여부를 결정한다. 만약 해당되지 않으면 다음 메시지 컨버터로 넘어간다. ByteArrayHttpMessageConverter

0

Spring Boot ReturnValueHandler

목차 Spring MVC - ArgumentResolver Spring MVC - HttpMessageConverter Spring Boot Http 응답 - HTTP API, Message Body에 직접 입력 Spring Boot 응답 - 정적 리소스, 뷰 템플릿 Spring Boot 요청 메시지 - JSON Spring Boot 요청 메시지 - Text Spring Boot 쿼리 파라미터, HTML Form Spring Boot 기본 헤더 조회 Spring Boot API 예시 Spring Boot @RequestMapping Spring Boot Logging Spring Boot ReturnValueHandlerpublic interface HandlerMethodReturnValueHandler { boolean supportsReturnType(MethodParameter returnType); void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception;}

0

Spring Boot RequestMappingHandlerAdapter

목차 Spring MVC - ArgumentResolver Spring MVC - HttpMessageConverter Spring Boot Http 응답 - HTTP API, Message Body에 직접 입력 Spring Boot 응답 - 정적 리소스, 뷰 템플릿 Spring Boot 요청 메시지 - JSON Spring Boot 요청 메시지 - Text Spring Boot 쿼리 파라미터, HTML Form Spring Boot 기본 헤더 조회 Spring Boot API 예시 Spring Boot @RequestMapping Spring Boot Logging public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter implements BeanFactoryAware, InitializingBean { private static final boolean shouldIgnoreXml = SpringProperties.getFlag("spring.xml.ignore"); public static final MethodFilter INIT_BINDER_METHODS = (method) -> { return AnnotatedElementUtils.hasAnnotation(method, InitBinder.class); }; public static final MethodFilter MODEL_ATTRIBUTE_METHODS = (method) -> { return !AnnotatedElementUtils.hasAnnotation(method, RequestMapping.class) && AnnotatedElementUtils.hasAnnotation(method, ModelAttribute.class); }; @Nullable private List<HandlerMethodArgumentResolver> customArgumentResolvers; @Nullable private HandlerMethodArgumentResolverComposite argumentResolvers; @Nullable private HandlerMethodArgumentResolverComposite initBinderArgumentResolvers; @Nullable private List<HandlerMethodReturnValueHandler> customReturnValueHandlers; @Nullable private HandlerMethodReturnValueHandlerComposite returnValueHandlers; @Nullable private List<ModelAndViewResolver> modelAndViewResolvers; private ContentNegotiationManager contentNegotiationManager = new ContentNegotiationManager(); private List<HttpMessageConverter<?>> messageConverters = new ArrayList(4); private final List<Object> requestResponseBodyAdvice = new ArrayList(); @Nullable private WebBindingInitializer webBindingInitializer; private AsyncTaskExecutor taskExecutor = new SimpleAsyncTaskExecutor("MvcAsync"); @Nullable private Long asyncRequestTimeout; private CallableProcessingInterceptor[] callableInterceptors = new CallableProcessingInterceptor[0]; private DeferredResultProcessingInterceptor[] deferredResultInterceptors = new DeferredResultProcessingInterceptor[0]; private ReactiveAdapterRegistry reactiveAdapterRegistry = ReactiveAdapterRegistry.getSharedInstance(); private boolean ignoreDefaultModelOnRedirect = false; private int cacheSecondsForSessionAttributeHandlers = 0; private boolean synchronizeOnSession = false; private SessionAttributeStore sessionAttributeStore = new DefaultSessionAttributeStore(); private ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer(); @Nullable private ConfigurableBeanFactory beanFactory; private final Map<Class<?>, SessionAttributesHandler> sessionAttributesHandlerCache = new ConcurrentHashMap(64); private final Map<Class<?>, Set<Method>> initBinderCache = new ConcurrentHashMap(64); private final Map<ControllerAdviceBean, Set<Method>> initBinderAdviceCache = new LinkedHashMap(); private final Map<Class<?>, Set<Method>> modelAttributeCache = new ConcurrentHashMap(64); private final Map<ControllerAdviceBean, Set<Method>> modelAttributeAdviceCache = new LinkedHashMap(); public RequestMappingHandlerAdapter() { this.messageConverters.add(new ByteArrayHttpMessageConverter()); this.messageConverters.add(new StringHttpMessageConverter()); if (!shouldIgnoreXml) { try { this.messageConverters.add(new SourceHttpMessageConverter()); } catch (Error var2) { } } this.messageConverters.add(new AllEncompassingFormHttpMessageConverter()); } public void setCustomArgumentResolvers(@Nullable List<HandlerMethodArgumentResolver> argumentResolvers) { this.customArgumentResolvers = argumentResolvers; } @Nullable public List<HandlerMethodArgumentResolver> getCustomArgumentResolvers() { return this.customArgumentResolvers; } public void setArgumentResolvers(@Nullable List<HandlerMethodArgumentResolver> argumentResolvers) { if (argumentResolvers == null) { this.argumentResolvers = null; } else { this.argumentResolvers = new HandlerMethodArgumentResolverComposite(); this.argumentResolvers.addResolvers(argumentResolvers); } } @Nullable public List<HandlerMethodArgumentResolver> getArgumentResolvers() { return this.argumentResolvers != null ? this.argumentResolvers.getResolvers() : null; } public void setInitBinderArgumentResolvers(@Nullable List<HandlerMethodArgumentResolver> argumentResolvers) { if (argumentResolvers == null) { this.initBinderArgumentResolvers = null; } else { this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite(); this.initBinderArgumentResolvers.addResolvers(argumentResolvers); } } @Nullable public List<HandlerMethodArgumentResolver> getInitBinderArgumentResolvers() { return this.initBinderArgumentResolvers != null ? this.initBinderArgumentResolvers.getResolvers() : null; } public void setCustomReturnValueHandlers(@Nullable List<HandlerMethodReturnValueHandler> returnValueHandlers) { this.customReturnValueHandlers = returnValueHandlers; } @Nullable public List<HandlerMethodReturnValueHandler> getCustomReturnValueHandlers() { return this.customReturnValueHandlers; } public void setReturnValueHandlers(@Nullable List<HandlerMethodReturnValueHandler> returnValueHandlers) { if (returnValueHandlers == null) { this.returnValueHandlers = null; } else { this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite(); this.returnValueHandlers.addHandlers(returnValueHandlers); } } @Nullable public List<HandlerMethodReturnValueHandler> getReturnValueHandlers() { return this.returnValueHandlers != null ? this.returnValueHandlers.getHandlers() : null; } public void setModelAndViewResolvers(@Nullable List<ModelAndViewResolver> modelAndViewResolvers) { this.modelAndViewResolvers = modelAndViewResolvers; } @Nullable public List<ModelAndViewResolver> getModelAndViewResolvers() { return this.modelAndViewResolvers; } public void setContentNegotiationManager(ContentNegotiationManager contentNegotiationManager) { this.contentNegotiationManager = contentNegotiationManager; } public void setMessageConverters(List<HttpMessageConverter<?>> messageConverters) { this.messageConverters = messageConverters; } public List<HttpMessageConverter<?>> getMessageConverters() { return this.messageConverters; } public void setRequestBodyAdvice(@Nullable List<RequestBodyAdvice> requestBodyAdvice) { if (requestBodyAdvice != null) { this.requestResponseBodyAdvice.addAll(requestBodyAdvice); } } public void setResponseBodyAdvice(@Nullable List<ResponseBodyAdvice<?>> responseBodyAdvice) { if (responseBodyAdvice != null) { this.requestResponseBodyAdvice.addAll(responseBodyAdvice); } } public void setWebBindingInitializer(@Nullable WebBindingInitializer webBindingInitializer) { this.webBindingInitializer = webBindingInitializer; } @Nullable public WebBindingInitializer getWebBindingInitializer() { return this.webBindingInitializer; } public void setTaskExecutor(AsyncTaskExecutor taskExecutor) { this.taskExecutor = taskExecutor; } public void setAsyncRequestTimeout(long timeout) { this.asyncRequestTimeout = timeout; } public void setCallableInterceptors(List<CallableProcessingInterceptor> interceptors) { this.callableInterceptors = (CallableProcessingInterceptor[])interceptors.toArray(new CallableProcessingInterceptor[0]); } public void setDeferredResultInterceptors(List<DeferredResultProcessingInterceptor> interceptors) { this.deferredResultInterceptors = (DeferredResultProcessingInterceptor[])interceptors.toArray(new DeferredResultProcessingInterceptor[0]); } public void setReactiveAdapterRegistry(ReactiveAdapterRegistry reactiveAdapterRegistry) { this.reactiveAdapterRegistry = reactiveAdapterRegistry; } public ReactiveAdapterRegistry getReactiveAdapterRegistry() { return this.reactiveAdapterRegistry; } public void setIgnoreDefaultModelOnRedirect(boolean ignoreDefaultModelOnRedirect) { this.ignoreDefaultModelOnRedirect = ignoreDefaultModelOnRedirect; } public void setSessionAttributeStore(SessionAttributeStore sessionAttributeStore) { this.sessionAttributeStore = sessionAttributeStore; } public void setCacheSecondsForSessionAttributeHandlers(int cacheSecondsForSessionAttributeHandlers) { this.cacheSecondsForSessionAttributeHandlers = cacheSecondsForSessionAttributeHandlers; } public void setSynchronizeOnSession(boolean synchronizeOnSession) { this.synchronizeOnSession = synchronizeOnSession; } public void setParameterNameDiscoverer(ParameterNameDiscoverer parameterNameDiscoverer) { this.parameterNameDiscoverer = parameterNameDiscoverer; } public void setBeanFactory(BeanFactory beanFactory) { if (beanFactory instanceof ConfigurableBeanFactory) { this.beanFactory = (ConfigurableBeanFactory)beanFactory; } } @Nullable protected ConfigurableBeanFactory getBeanFactory() { return this.beanFactory; } public void afterPropertiesSet() { this.initControllerAdviceCache(); List handlers; if (this.argumentResolvers == null) { handlers = this.getDefaultArgumentResolvers(); this.argumentResolvers = (new HandlerMethodArgumentResolverComposite()).addResolvers(handlers); } if (this.initBinderArgumentResolvers == null) { handlers = this.getDefaultInitBinderArgumentResolvers(); this.initBinderArgumentResolvers = (new HandlerMethodArgumentResolverComposite()).addResolvers(handlers); } if (this.returnValueHandlers == null) { handlers = this.getDefaultReturnValueHandlers(); this.returnValueHandlers = (new HandlerMethodReturnValueHandlerComposite()).addHandlers(handlers); } } private void initControllerAdviceCache() { if (this.getApplicationContext() != null) { List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(this.getApplicationContext()); List<Object> requestResponseBodyAdviceBeans = new ArrayList(); Iterator var3 = adviceBeans.iterator(); while(var3.hasNext()) { ControllerAdviceBean adviceBean = (ControllerAdviceBean)var3.next(); Class<?> beanType = adviceBean.getBeanType(); if (beanType == null) { throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean); } Set<Method> attrMethods = MethodIntrospector.selectMethods(beanType, MODEL_ATTRIBUTE_METHODS); if (!attrMethods.isEmpty()) { this.modelAttributeAdviceCache.put(adviceBean, attrMethods); } Set<Method> binderMethods = MethodIntrospector.selectMethods(beanType, INIT_BINDER_METHODS); if (!binderMethods.isEmpty()) { this.initBinderAdviceCache.put(adviceBean, binderMethods); } if (RequestBodyAdvice.class.isAssignableFrom(beanType) || ResponseBodyAdvice.class.isAssignableFrom(beanType)) { requestResponseBodyAdviceBeans.add(adviceBean); } } if (!requestResponseBodyAdviceBeans.isEmpty()) { this.requestResponseBodyAdvice.addAll(0, requestResponseBodyAdviceBeans); } if (this.logger.isDebugEnabled()) { int modelSize = this.modelAttributeAdviceCache.size(); int binderSize = this.initBinderAdviceCache.size(); int reqCount = this.getBodyAdviceCount(RequestBodyAdvice.class); int resCount = this.getBodyAdviceCount(ResponseBodyAdvice.class); if (modelSize == 0 && binderSize == 0 && reqCount == 0 && resCount == 0) { this.logger.debug("ControllerAdvice beans: none"); } else { this.logger.debug("ControllerAdvice beans: " + modelSize + " @ModelAttribute, " + binderSize + " @InitBinder, " + reqCount + " RequestBodyAdvice, " + resCount + " ResponseBodyAdvice"); } } } } private int getBodyAdviceCount(Class<?> adviceType) { List<Object> advice = this.requestResponseBodyAdvice; return RequestBodyAdvice.class.isAssignableFrom(adviceType) ? RequestResponseBodyAdviceChain.getAdviceByType(advice, RequestBodyAdvice.class).size() : RequestResponseBodyAdviceChain.getAdviceByType(advice, ResponseBodyAdvice.class).size(); } private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() { List<HandlerMethodArgumentResolver> resolvers = new ArrayList(30); resolvers.add(new RequestParamMethodArgumentResolver(this.getBeanFactory(), false)); resolvers.add(new RequestParamMapMethodArgumentResolver()); resolvers.add(new PathVariableMethodArgumentResolver()); resolvers.add(new PathVariableMapMethodArgumentResolver()); resolvers.add(new MatrixVariableMethodArgumentResolver()); resolvers.add(new MatrixVariableMapMethodArgumentResolver()); resolvers.add(new ServletModelAttributeMethodProcessor(false)); resolvers.add(new RequestResponseBodyMethodProcessor(this.getMessageConverters(), this.requestResponseBodyAdvice)); resolvers.add(new RequestPartMethodArgumentResolver(this.getMessageConverters(), this.requestResponseBodyAdvice)); resolvers.add(new RequestHeaderMethodArgumentResolver(this.getBeanFactory())); resolvers.add(new RequestHeaderMapMethodArgumentResolver()); resolvers.add(new ServletCookieValueMethodArgumentResolver(this.getBeanFactory())); resolvers.add(new ExpressionValueMethodArgumentResolver(this.getBeanFactory())); resolvers.add(new SessionAttributeMethodArgumentResolver()); resolvers.add(new RequestAttributeMethodArgumentResolver()); resolvers.add(new ServletRequestMethodArgumentResolver()); resolvers.add(new ServletResponseMethodArgumentResolver()); resolvers.add(new HttpEntityMethodProcessor(this.getMessageConverters(), this.requestResponseBodyAdvice)); resolvers.add(new RedirectAttributesMethodArgumentResolver()); resolvers.add(new ModelMethodProcessor()); resolvers.add(new MapMethodProcessor()); resolvers.add(new ErrorsMethodArgumentResolver()); resolvers.add(new SessionStatusMethodArgumentResolver()); resolvers.add(new UriComponentsBuilderMethodArgumentResolver()); if (KotlinDetector.isKotlinPresent()) { resolvers.add(new ContinuationHandlerMethodArgumentResolver()); } if (this.getCustomArgumentResolvers() != null) { resolvers.addAll(this.getCustomArgumentResolvers()); } resolvers.add(new PrincipalMethodArgumentResolver()); resolvers.add(new RequestParamMethodArgumentResolver(this.getBeanFactory(), true)); resolvers.add(new ServletModelAttributeMethodProcessor(true)); return resolvers; } private List<HandlerMethodArgumentResolver> getDefaultInitBinderArgumentResolvers() { List<HandlerMethodArgumentResolver> resolvers = new ArrayList(20); resolvers.add(new RequestParamMethodArgumentResolver(this.getBeanFactory(), false)); resolvers.add(new RequestParamMapMethodArgumentResolver()); resolvers.add(new PathVariableMethodArgumentResolver()); resolvers.add(new PathVariableMapMethodArgumentResolver()); resolvers.add(new MatrixVariableMethodArgumentResolver()); resolvers.add(new MatrixVariableMapMethodArgumentResolver()); resolvers.add(new ExpressionValueMethodArgumentResolver(this.getBeanFactory())); resolvers.add(new SessionAttributeMethodArgumentResolver()); resolvers.add(new RequestAttributeMethodArgumentResolver()); resolvers.add(new ServletRequestMethodArgumentResolver()); resolvers.add(new ServletResponseMethodArgumentResolver()); if (this.getCustomArgumentResolvers() != null) { resolvers.addAll(this.getCustomArgumentResolvers()); } resolvers.add(new PrincipalMethodArgumentResolver()); resolvers.add(new RequestParamMethodArgumentResolver(this.getBeanFactory(), true)); return resolvers; } private List<HandlerMethodReturnValueHandler> getDefaultReturnValueHandlers() { List<HandlerMethodReturnValueHandler> handlers = new ArrayList(20); handlers.add(new ModelAndViewMethodReturnValueHandler()); handlers.add(new ModelMethodProcessor()); handlers.add(new ViewMethodReturnValueHandler()); handlers.add(new ResponseBodyEmitterReturnValueHandler(this.getMessageConverters(), this.reactiveAdapterRegistry, this.taskExecutor, this.contentNegotiationManager)); handlers.add(new StreamingResponseBodyReturnValueHandler()); handlers.add(new HttpEntityMethodProcessor(this.getMessageConverters(), this.contentNegotiationManager, this.requestResponseBodyAdvice)); handlers.add(new HttpHeadersReturnValueHandler()); handlers.add(new CallableMethodReturnValueHandler()); handlers.add(new DeferredResultMethodReturnValueHandler()); handlers.add(new AsyncTaskMethodReturnValueHandler(this.beanFactory)); handlers.add(new ServletModelAttributeMethodProcessor(false)); handlers.add(new RequestResponseBodyMethodProcessor(this.getMessageConverters(), this.contentNegotiationManager, this.requestResponseBodyAdvice)); handlers.add(new ViewNameMethodReturnValueHandler()); handlers.add(new MapMethodProcessor()); if (this.getCustomReturnValueHandlers() != null) { handlers.addAll(this.getCustomReturnValueHandlers()); } if (!CollectionUtils.isEmpty(this.getModelAndViewResolvers())) { handlers.add(new ModelAndViewResolverMethodReturnValueHandler(this.getModelAndViewResolvers())); } else { handlers.add(new ServletModelAttributeMethodProcessor(true)); } return handlers; } protected boolean supportsInternal(HandlerMethod handlerMethod) { return true; } protected ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { this.checkRequest(request); ModelAndView mav; if (this.synchronizeOnSession) { HttpSession session = request.getSession(false); if (session != null) { Object mutex = WebUtils.getSessionMutex(session); synchronized(mutex) { mav = this.invokeHandlerMethod(request, response, handlerMethod); } } else { mav = this.invokeHandlerMethod(request, response, handlerMethod); } } else { mav = this.invokeHandlerMethod(request, response, handlerMethod); } if (!response.containsHeader("Cache-Control")) { if (this.getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) { this.applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers); } else { this.prepareResponse(response); } } return mav; } protected long getLastModifiedInternal(HttpServletRequest request, HandlerMethod handlerMethod) { return -1L; } private SessionAttributesHandler getSessionAttributesHandler(HandlerMethod handlerMethod) { return (SessionAttributesHandler)this.sessionAttributesHandlerCache.computeIfAbsent(handlerMethod.getBeanType(), (type) -> { return new SessionAttributesHandler(type, this.sessionAttributeStore); }); } @Nullable protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { ServletWebRequest webRequest = new ServletWebRequest(request, response); Object result; try { WebDataBinderFactory binderFactory = this.getDataBinderFactory(handlerMethod); ModelFactory modelFactory = this.getModelFactory(handlerMethod, binderFactory); ServletInvocableHandlerMethod invocableMethod = this.createInvocableHandlerMethod(handlerMethod); if (this.argumentResolvers != null) { invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers); } if (this.returnValueHandlers != null) { invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers); } invocableMethod.setDataBinderFactory(binderFactory); invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer); ModelAndViewContainer mavContainer = new ModelAndViewContainer(); mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request)); modelFactory.initModel(webRequest, mavContainer, invocableMethod); mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect); AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response); asyncWebRequest.setTimeout(this.asyncRequestTimeout); WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); asyncManager.setTaskExecutor(this.taskExecutor); asyncManager.setAsyncWebRequest(asyncWebRequest); asyncManager.registerCallableInterceptors(this.callableInterceptors); asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors); if (asyncManager.hasConcurrentResult()) { result = asyncManager.getConcurrentResult(); mavContainer = (ModelAndViewContainer)asyncManager.getConcurrentResultContext()[0]; asyncManager.clearConcurrentResult(); LogFormatUtils.traceDebug(this.logger, (traceOn) -> { String formatted = LogFormatUtils.formatValue(result, !traceOn); return "Resume with async result [" + formatted + "]"; }); invocableMethod = invocableMethod.wrapConcurrentResult(result); } invocableMethod.invokeAndHandle(webRequest, mavContainer, new Object[0]); if (!asyncManager.isConcurrentHandlingStarted()) { ModelAndView var15 = this.getModelAndView(mavContainer, modelFactory, webRequest); return var15; } result = null; } finally { webRequest.requestCompleted(); } return (ModelAndView)result; } protected ServletInvocableHandlerMethod createInvocableHandlerMethod(HandlerMethod handlerMethod) { return new ServletInvocableHandlerMethod(handlerMethod); } private ModelFactory getModelFactory(HandlerMethod handlerMethod, WebDataBinderFactory binderFactory) { SessionAttributesHandler sessionAttrHandler = this.getSessionAttributesHandler(handlerMethod); Class<?> handlerType = handlerMethod.getBeanType(); Set<Method> methods = (Set)this.modelAttributeCache.get(handlerType); if (methods == null) { methods = MethodIntrospector.selectMethods(handlerType, MODEL_ATTRIBUTE_METHODS); this.modelAttributeCache.put(handlerType, methods); } List<InvocableHandlerMethod> attrMethods = new ArrayList(); this.modelAttributeAdviceCache.forEach((controllerAdviceBean, methodSet) -> { if (controllerAdviceBean.isApplicableToBeanType(handlerType)) { Object bean = controllerAdviceBean.resolveBean(); Iterator var7 = methodSet.iterator(); while(var7.hasNext()) { Method method = (Method)var7.next(); attrMethods.add(this.createModelAttributeMethod(binderFactory, bean, method)); } } }); Iterator var7 = methods.iterator(); while(var7.hasNext()) { Method method = (Method)var7.next(); Object bean = handlerMethod.getBean(); attrMethods.add(this.createModelAttributeMethod(binderFactory, bean, method)); } return new ModelFactory(attrMethods, binderFactory, sessionAttrHandler); } private InvocableHandlerMethod createModelAttributeMethod(WebDataBinderFactory factory, Object bean, Method method) { InvocableHandlerMethod attrMethod = new InvocableHandlerMethod(bean, method); if (this.argumentResolvers != null) { attrMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers); } attrMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer); attrMethod.setDataBinderFactory(factory); return attrMethod; } private WebDataBinderFactory getDataBinderFactory(HandlerMethod handlerMethod) throws Exception { Class<?> handlerType = handlerMethod.getBeanType(); Set<Method> methods = (Set)this.initBinderCache.get(handlerType); if (methods == null) { methods = MethodIntrospector.selectMethods(handlerType, INIT_BINDER_METHODS); this.initBinderCache.put(handlerType, methods); } List<InvocableHandlerMethod> initBinderMethods = new ArrayList(); this.initBinderAdviceCache.forEach((controllerAdviceBean, methodSet) -> { if (controllerAdviceBean.isApplicableToBeanType(handlerType)) { Object bean = controllerAdviceBean.resolveBean(); Iterator var6 = methodSet.iterator(); while(var6.hasNext()) { Method method = (Method)var6.next(); initBinderMethods.add(this.createInitBinderMethod(bean, method)); } } }); Iterator var5 = methods.iterator(); while(var5.hasNext()) { Method method = (Method)var5.next(); Object bean = handlerMethod.getBean(); initBinderMethods.add(this.createInitBinderMethod(bean, method)); } return this.createDataBinderFactory(initBinderMethods); } private InvocableHandlerMethod createInitBinderMethod(Object bean, Method method) { InvocableHandlerMethod binderMethod = new InvocableHandlerMethod(bean, method); if (this.initBinderArgumentResolvers != null) { binderMethod.setHandlerMethodArgumentResolvers(this.initBinderArgumentResolvers); } binderMethod.setDataBinderFactory(new DefaultDataBinderFactory(this.webBindingInitializer)); binderMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer); return binderMethod; } protected InitBinderDataBinderFactory createDataBinderFactory(List<InvocableHandlerMethod> binderMethods) throws Exception { return new ServletRequestDataBinderFactory(binderMethods, this.getWebBindingInitializer()); } @Nullable private ModelAndView getModelAndView(ModelAndViewContainer mavContainer, ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception { modelFactory.updateModel(webRequest, mavContainer); if (mavContainer.isRequestHandled()) { return null; } else { ModelMap model = mavContainer.getModel(); ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus()); if (!mavContainer.isViewReference()) { mav.setView((View)mavContainer.getView()); } if (model instanceof RedirectAttributes) { Map<String, ?> flashAttributes = ((RedirectAttributes)model).getFlashAttributes(); HttpServletRequest request = (HttpServletRequest)webRequest.getNativeRequest(HttpServletRequest.class); if (request != null) { RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes); } } return mav; } }}

0

Spring MVC Request Life Cycle - Filter

목차 Spring MVC - ArgumentResolver Spring MVC Request Life Cycle - ViewResolver Spring MVC Request Life Cycle - HandlerAdapter Spring MVC Request Life Cycle - HandlerMapping Spring MVC Request Life Cycle - DispatcherServlet Spring MVC Request Life Cycle - Interceptor Spring MVC Request Life Cycle - Filter Filter Filter는 Spring Context 이전에 실행되기 때문에 Spring자체와는 무관하며 Web Application에 등록이 된다. 요청이 DispatcherServlet에 도착하기 전에 처리를 하거나 요청이 끝나고 응답할 때 마지막으로 처리를 하는 역할을 한다. 주로 인코딩, 요청에 대한 인증, 권한 체크 와 같은 요청에 대한 전역적인 처리를 할 때 사용한다. Filter 인터페이스 init 필터 인스턴스 초기화 doFilter 실제 로직 처리(Filter Chain을 따라 다을 Filter로 움직인다.) destroy 필터 인스턴스 종료 public interface Filter { public default void init(FilterConfig filterConfig) throws ServletException {} public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException; public default void destroy() {}} Filter Chain Filter는 Client와 Servlet사이에 존재해 Client가 보내는 요청정보를 가공해 Servlet으로 보내고 Servelt이 보내는 응답정보를 가공해 Client에게 전달하는 역할을 한다. Filter는 일반적인 경우 1개가 존재하지만 여러개의 필터를 모아 Filter Chain을 형성할 수 있다.

0

Spring Boot 요청 메시지 - Text

목차 Spring MVC - ArgumentResolver Spring MVC - HttpMessageConverter Spring Boot Http 응답 - HTTP API, Message Body에 직접 입력 Spring Boot 응답 - 정적 리소스, 뷰 템플릿 Spring Boot 요청 메시지 - JSON Spring Boot 요청 메시지 - Text Spring Boot 쿼리 파라미터, HTML Form Spring Boot 기본 헤더 조회 Spring Boot API 예시 Spring Boot @RequestMapping Spring Boot Logging Spring Boot 요청 메시지@PostMapping("/request-body-string-v1")public void requestBodyString(HttpServletRequest request, HttpServletResponse response) throws IOException { ServletInputStream inputStream = request.getInputStream(); String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8); log.info("messageBody = {}", messageBody); response.getWriter().write("ok");} @PostMapping("/request-body-string-v2")public void requestBodyStringV2(InputStream inputStream, Writer responseWriter) throws IOException { String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8); log.info("messageBody = {}", messageBody); responseWriter.write("ok");} HttpEntity 사용하기@PostMapping("/request-body-string-v3")public HttpEntity<String> requestBodyStringV3(HttpEntity<String> httpEntity) throws IOException { String messageBody = httpEntity.getBody(); log.info("messageBody = {}", messageBody); return new HttpEntity<>("ok");} RequestBody, ResponseBody 어노테이션 사용하기@ResponseBody@PostMapping("/request-body-string-v4")public String requestBodyStringV4(@RequestBody String messageBody) throws IOException { log.info("messageBody = {}", messageBody); return "ok";}

0

Spring Boot 요청 메시지 - JSON

목차 Spring MVC - ArgumentResolver Spring MVC - HttpMessageConverter Spring Boot Http 응답 - HTTP API, Message Body에 직접 입력 Spring Boot 응답 - 정적 리소스, 뷰 템플릿 Spring Boot 요청 메시지 - JSON Spring Boot 요청 메시지 - Text Spring Boot 쿼리 파라미터, HTML Form Spring Boot 기본 헤더 조회 Spring Boot API 예시 Spring Boot @RequestMapping Spring Boot Logging Spring Boot 요청 메시지 - JSON@PostMapping("/request-body-json-v1")public void requestBodyJsonV1(HttpServletRequest request, HttpServletResponse response) throws IOException { ServletInputStream inputStream = request.getInputStream(); String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8); log.info("messageBody={}", messageBody); HelloData helloData = objectMapper.readValue(messageBody, HelloData.class); log.info("username={}, age={}", helloData.getUsername(), helloData.getAge()); response.getWriter().write("ok");} RequestBody 어노테이션 사용하기RequestParam 어노테이션과 다르게 RequestBody 어노테이션은 생략하면 안된다. @ResponseBody@PostMapping("/request-body-json-v2")public String requestBodyJsonV2(@RequestBody String messageBody) throws IOException { log.info("messageBody={}", messageBody); HelloData helloData = objectMapper.readValue(messageBody, HelloData.class); log.info("username={}, age={}", helloData.getUsername(), helloData.getAge()); return "ok";} DTO 사용하기@ResponseBody@PostMapping("/request-body-json-v3")public String requestBodyJsonV3(@RequestBody HelloData helloData) throws IOException { log.info("username={}, age={}", helloData.getUsername(), helloData.getAge()); return "ok";} @ResponseBody@PostMapping("/request-body-json-v4")public String requestBodyJsonV4(HttpEntity<HelloData> data) throws IOException { HelloData body = data.getBody(); log.info("username={}, age={}", body.getUsername(), body.getAge()); return "ok";}

0

Spring Boot 기본 헤더 조회

목차 Spring MVC - ArgumentResolver Spring MVC - HttpMessageConverter Spring Boot Http 응답 - HTTP API, Message Body에 직접 입력 Spring Boot 응답 - 정적 리소스, 뷰 템플릿 Spring Boot 요청 메시지 - JSON Spring Boot 요청 메시지 - Text Spring Boot 쿼리 파라미터, HTML Form Spring Boot 기본 헤더 조회 Spring Boot API 예시 Spring Boot @RequestMapping Spring Boot Logging 기본 헤더 조회Controller 사용 가능한 파리미터 목록 @Slf4j@RestControllerpublic class RequestHeaderController { @RequestMapping("/headers") public String headers(HttpServletRequest request, HttpServletResponse response, HttpMethod httpMethod, Locale locale, @RequestHeader MultiValueMap<String, String> headerMap, @RequestHeader("host") String host, @CookieValue(value = "myCookie", required = false) String cookie) { log.info("request={}", request); log.info("response={}", response); log.info("httpMethod={}", httpMethod); log.info("locale={}", locale); log.info("headerMap={}", headerMap); log.info("header host={}", host); log.info("myCookie={}", cookie); return "ok"; }}

0

Spring Boot Logging

목차 Spring MVC - ArgumentResolver Spring MVC - HttpMessageConverter Spring Boot Http 응답 - HTTP API, Message Body에 직접 입력 Spring Boot 응답 - 정적 리소스, 뷰 템플릿 Spring Boot 요청 메시지 - JSON Spring Boot 요청 메시지 - Text Spring Boot 쿼리 파라미터, HTML Form Spring Boot 기본 헤더 조회 Spring Boot API 예시 Spring Boot @RequestMapping Spring Boot Logging Spring Boot Logging로그 선언 방법 private final Logger log = LoggerFactory.getLogger(getClass()); private static final Logger log = LoggerFactory.getLogger(xxx.class); @slf4j // Lombok에서 제공하는 Log 기능 사용 log.trace("info log = {}", name);log.debug("info log = {}", name);log.info("info log = {}", name);log.warn("info log = {}", name);log.error("info log = {}", name); logging.level.com.example.springmvc=trace @Slf4j@RestControllerpublic class LogTestController {// private final Logger log = LoggerFactory.getLogger(getClass()); @RequestMapping("/log-test") public String logTest(){ String name = "Spring"; System.out.println("name = " + name); log.trace("info log = {}", name); log.debug("info log = {}", name); log.info("info log = {}", name); log.warn("info log = {}", name); log.error("info log = {}", name); return "ok"; }}

0

Spring Boot API 예시

목차 Spring MVC - ArgumentResolver Spring MVC - HttpMessageConverter Spring Boot Http 응답 - HTTP API, Message Body에 직접 입력 Spring Boot 응답 - 정적 리소스, 뷰 템플릿 Spring Boot 요청 메시지 - JSON Spring Boot 요청 메시지 - Text Spring Boot 쿼리 파라미터, HTML Form Spring Boot 기본 헤더 조회 Spring Boot API 예시 Spring Boot @RequestMapping Spring Boot Logging Spring Boot API 예시@RestController@RequestMapping("/mapping/users")public class MappingClassController { @GetMapping public String user(){ return "get users"; } @PostMapping public String addUser(){ return "post user"; } @GetMapping("/{userId}") public String findUser(@PathVariable String userId){ return "get suerId = " + userId; } @PatchMapping("/{userId}") public String updateUser(@PathVariable String userId){ return "update userId = " + userId; } @DeleteMapping("/{userId}") public String deleteUser(@PathVariable String userId){ return "delete userId = " + userId; }}

0

Spring Boot 쿼리 파라미터, HTML Form

목차 Spring MVC - ArgumentResolver Spring MVC - HttpMessageConverter Spring Boot Http 응답 - HTTP API, Message Body에 직접 입력 Spring Boot 응답 - 정적 리소스, 뷰 템플릿 Spring Boot 요청 메시지 - JSON Spring Boot 요청 메시지 - Text Spring Boot 쿼리 파라미터, HTML Form Spring Boot 기본 헤더 조회 Spring Boot API 예시 Spring Boot @RequestMapping Spring Boot Logging Spring Boot 쿼리 파라미터, HTML Form<!DOCTYPE html><html><head> <meta charset="UTF-8"> <title>Title</title></head><body><form action="/request-param-v1" method="post"> username: <input type="text" name="username"/> age: <input type="text" name="age"/> <button type="submit">전송</button></form></body></html> HttpServletRequest 객체 내 메소드 사용하기@Slf4j@RestControllerpublic class RequestParamController { @RequestMapping("/request-param-v1") public void requestParamV1(HttpServletRequest request, HttpServletResponse response) throws IOException { String username = request.getParameter("username"); int age = Integer.parseInt(request.getParameter("age")); log.info("username={}, age={}", username, age); response.getWriter().write("ok"); }} RequestParam 어노테이션 사용하기@RequestMapping("/request-param-v2")@ResponseBodypublic String requestParamV2( @RequestParam("username") String memberName, @RequestParam("age") int memberAge) { log.info("username={}, age={}", memberName, memberAge); return "ok";} @RequestMapping("/request-param-v3")@ResponseBodypublic String requestParamV3( @RequestParam String username, @RequestParam int age) { log.info("username={}, age={}", username, age ); return "ok";} @RequestMapping("/request-param-v4")@ResponseBodypublic String requestParamV4(String username, int age) { log.info("username={}, age={}", username, age ); return "ok";}

0

Spring Security - AuthenticationProvider

목차 Spring Security 권한 계층 사용하기 - @RoleHierarcy Spring Security - DelegateFilterProxy Spring Security - Remember Me와 관련된 인증 API - RememberMeConfigurer Spring Security - RembmerMeAuthenticationFilter Spring Security - SessionManagementFilter & ConcurrentSessionFilter Spring Security - 인가 API ExpressionUrlAuthorizationConfigurer Spring Security - Security 설정을 위한 WebSecurityConfigurerAdatper Spring Security - AuthenticationProvider Spring Security - AuthenticationManager Spring Security - UsernamePasswordAuthenticationFilter & AbstractAuthenticationProcessingFilter Spring Security - SecurityContextHolder 와 SecurityContext Spring Security - Authentication 객체 참고 https://spring.io/guides/topicals/spring-security-architecture Spring Security - AuthenticationProviderAuthenticationProvider.java AuthenticationManager로 부터 인증을 위임받아 실제로 인증을 진행하는 객체, AuthenticationProvider의 구현체로는 AbstractUserDetailsAuthenticationProvider 와 DaoAuthenticationProvider 가 존재한다. AuthenticationProvider 의 인증 과정 전달받는 authentication 에서 username을 가져온 뒤 DB에서 해당 하는 유저 정보를 가져온다. 인증을 진행하기 전 DB로부터 가져온 UserDetails 객체에 대한 유효성 검사를 진행한다. 전달 받은 Authentication 객체내의 Credential 정보와 UserDetails 객체의 Password와 값이 일치하는지 확인한다. username에 대한하는 UserDetails 객체를 못 찾을 경우 UsernameNotFoundException 예외를 던지고 Credentials가 Password와 다를 경우 BadCredentialsException 예외를 던진다. 인증 후 다시한번 UserDetails 객체의 유효성 검사를 진행하고, 캐시에 UserDetails 객체를 저장한다. 새로운 Authentication 객체를 만들어 반환한다.

0

Spring Security - AuthenticationManager

목차 Spring Security 권한 계층 사용하기 - @RoleHierarcy Spring Security - DelegateFilterProxy Spring Security - Remember Me와 관련된 인증 API - RememberMeConfigurer Spring Security - RembmerMeAuthenticationFilter Spring Security - SessionManagementFilter & ConcurrentSessionFilter Spring Security - 인가 API ExpressionUrlAuthorizationConfigurer Spring Security - Security 설정을 위한 WebSecurityConfigurerAdatper Spring Security - AuthenticationProvider Spring Security - AuthenticationManager Spring Security - UsernamePasswordAuthenticationFilter & AbstractAuthenticationProcessingFilter Spring Security - SecurityContextHolder 와 SecurityContext Spring Security - Authentication 객체 참고 https://spring.io/guides/topicals/spring-security-architecture Spring Security - AuthenticationManager AuthenticationManager 는 인증을 진행하는 AuthenticationProvider 객체들을 관리하는 객체다. AuthenticationManager 는 AuthenticationProvider 에 인증을 위임하는 형태로 인증을 진행한다. public interface AuthenticationManager { Authentication authenticate(Authentication authentication) throws AuthenticationException;} ProviderManager AuthenticationManager의 가장 대표적인 구현체

0

Spring Security - SecurityContextHolder 와 SecurityContext

목차 Spring Security 권한 계층 사용하기 - @RoleHierarcy Spring Security - DelegateFilterProxy Spring Security - Remember Me와 관련된 인증 API - RememberMeConfigurer Spring Security - RembmerMeAuthenticationFilter Spring Security - SessionManagementFilter & ConcurrentSessionFilter Spring Security - 인가 API ExpressionUrlAuthorizationConfigurer Spring Security - Security 설정을 위한 WebSecurityConfigurerAdatper Spring Security - AuthenticationProvider Spring Security - AuthenticationManager Spring Security - UsernamePasswordAuthenticationFilter & AbstractAuthenticationProcessingFilter Spring Security - SecurityContextHolder 와 SecurityContext Spring Security - Authentication 객체 참고 https://spring.io/guides/topicals/spring-security-architecture Spring Security - SecurityContextHolder 와 SecurityContext 인증을 진행하면서 생성된 Authentication 객체는 SecurityContext 객체 내에 저장된다. 저장된 Authentication 객체를 저장하고 가져오기 위해서 SecurityContext는 getAuthentication 메소드와 setAuthentication 메소드를 제공한다. SecurityContext.java public interface SecurityContext extends Serializable { Authentication getAuthentication(); void setAuthentication(Authentication authentication);}

0

Spring Security - Authentication 객체

목차 Spring Security 권한 계층 사용하기 - @RoleHierarcy Spring Security - DelegateFilterProxy Spring Security - Remember Me와 관련된 인증 API - RememberMeConfigurer Spring Security - RembmerMeAuthenticationFilter Spring Security - SessionManagementFilter & ConcurrentSessionFilter Spring Security - 인가 API ExpressionUrlAuthorizationConfigurer Spring Security - Security 설정을 위한 WebSecurityConfigurerAdatper Spring Security - AuthenticationProvider Spring Security - AuthenticationManager Spring Security - UsernamePasswordAuthenticationFilter & AbstractAuthenticationProcessingFilter Spring Security - SecurityContextHolder 와 SecurityContext Spring Security - Authentication 객체 참고 https://spring.io/guides/topicals/spring-security-architecture Spring Security - Authentication 객체 Authentication 객체 내에는 자원에 접근하기 위한 대상의 정보와 권한 정보가 들어가 있다. Authentication 객체의 대표적인 구현 클래스는 AbstractAuthenticationToken 클래스와 UsernamePasswordAuthenticationToken 가 있다. 인증을 진행하면서 생성된 Authentication 객체는 SecurityContext 객체에 저장된다. Authentication 객체 내에 Principal, Credentials, GrantedAuthority 가 있다. Principal Object 타입으로 보통은 User ID가 들어가거나 UserDetails가 들어가게 된다. Credentials Object 타입으로 보통은 User Password가 들어가 있다. GrantedAuthority 접근 권한 목록들을 가지고 있다. public interface Authentication extends Principal, Serializable { Collection<? extends GrantedAuthority> getAuthorities(); Object getCredentials(); Object getDetails(); Object getPrincipal(); boolean isAuthenticated(); void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;}

0

Spring Security - Remember Me와 관련된 인증 API - RememberMeConfigurer

목차 Spring Security 권한 계층 사용하기 - @RoleHierarcy Spring Security - DelegateFilterProxy Spring Security - Remember Me와 관련된 인증 API - RememberMeConfigurer Spring Security - RembmerMeAuthenticationFilter Spring Security - SessionManagementFilter & ConcurrentSessionFilter Spring Security - 인가 API ExpressionUrlAuthorizationConfigurer Spring Security - Security 설정을 위한 WebSecurityConfigurerAdatper Spring Security - AuthenticationProvider Spring Security - AuthenticationManager Spring Security - UsernamePasswordAuthenticationFilter & AbstractAuthenticationProcessingFilter Spring Security - SecurityContextHolder 와 SecurityContext Spring Security - Authentication 객체 Remember Me와 관련된 인증 API rememberMeParameter 쿠키의 파라미터명을 지정한다. 체크박스의 이름(parameter)과 remember-me의 파라미터 명은 일치 시켜줘야 한다. tokenValiditySeconds 쿠키의 만료시간을 설정한다. useSecureCookie Cookie 에 보안 설정을 할 때 사용하는 Flag HTTPS 환경에서만 사용할 수 있다. userDetailsService remember-me 를 이용한 인증 진행시 시스템의 사용자 계정을 조회할 때 사용하는 class 를 지정한다. tokenRepository Remember Me Token 저장소를 설정한다. 기본은 TokenBasedRememberMeServices 를 사용하기 때문에 Cookie 에 저장하는 방식이다. 기본 구현체는 InMemoryTokenRepositoryImpl 와 JdbcTokenRepositoryImpl 로 저장소를 명시해주면 PersistentTokenBasedRememberMeServices 를 사용한다. key Remember Me Authentication 객체 유효성을 확인하기 위한 Key 값을 설정한다. String Hash 비교를 통해 인증 유효성을 확인한다. alwaysRemember Remember Me 기능이 활성화되지 않아도 항상 실행하는 Flag 사용자의 Session이 생성되었고 Session이 인증 객체를 담고 있다는 것이다. RememberMeConfigurerpublic RememberMeConfigurer<H> tokenValiditySeconds(int tokenValiditySeconds) { this.tokenValiditySeconds = tokenValiditySeconds; return this;}public RememberMeConfigurer<H> useSecureCookie(boolean useSecureCookie) { this.useSecureCookie = useSecureCookie; return this;}public RememberMeConfigurer<H> userDetailsService(UserDetailsService userDetailsService) { this.userDetailsService = userDetailsService; return this;}public RememberMeConfigurer<H> tokenRepository(PersistentTokenRepository tokenRepository) { this.tokenRepository = tokenRepository; return this;}public RememberMeConfigurer<H> key(String key) { this.key = key; return this;}public RememberMeConfigurer<H> rememberMeParameter(String rememberMeParameter) { this.rememberMeParameter = rememberMeParameter; return this;}public RememberMeConfigurer<H> rememberMeCookieName(String rememberMeCookieName) { this.rememberMeCookieName = rememberMeCookieName; return this;}public RememberMeConfigurer<H> rememberMeCookieDomain(String rememberMeCookieDomain) { this.rememberMeCookieDomain = rememberMeCookieDomain; return this;}public RememberMeConfigurer<H> authenticationSuccessHandler( AuthenticationSuccessHandler authenticationSuccessHandler) { this.authenticationSuccessHandler = authenticationSuccessHandler; return this;}public RememberMeConfigurer<H> rememberMeServices(RememberMeServices rememberMeServices) { this.rememberMeServices = rememberMeServices; return this;}public RememberMeConfigurer<H> alwaysRemember(boolean alwaysRemember) { this.alwaysRemember = alwaysRemember; return this;}