From 2de338cbb8403582f4a25dc3117a35bea8478224 Mon Sep 17 00:00:00 2001 From: DaejunBae Date: Tue, 8 Oct 2019 15:56:09 +0900 Subject: [PATCH 01/29] feat: JsonView --- .../java/nextstep/mvc/tobe/view/JsonView.java | 26 ++++++++++++++++++- .../main/java/nextstep/utils/StringUtils.java | 5 ++++ .../nextstep/mvc/tobe/view/JsonViewTest.java | 26 ++++++++++++++++--- 3 files changed, 52 insertions(+), 5 deletions(-) create mode 100644 nextstep-mvc/src/main/java/nextstep/utils/StringUtils.java diff --git a/nextstep-mvc/src/main/java/nextstep/mvc/tobe/view/JsonView.java b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/view/JsonView.java index b7827fd9..5c50d360 100644 --- a/nextstep-mvc/src/main/java/nextstep/mvc/tobe/view/JsonView.java +++ b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/view/JsonView.java @@ -1,12 +1,36 @@ package nextstep.mvc.tobe.view; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import nextstep.utils.StringUtils; +import org.springframework.http.MediaType; + import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import java.io.PrintWriter; import java.util.Map; public class JsonView implements View { + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + @Override - public void render(Map model, HttpServletRequest request, HttpServletResponse response) throws Exception { + public void render(final Map model, final HttpServletRequest request, final HttpServletResponse response) throws Exception { + response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE); + + final PrintWriter writer = response.getWriter(); + writer.println(convert(model)); + writer.flush(); + } + + private Object convert(final Map model) throws JsonProcessingException { + if (model.size() == 0) { + return StringUtils.EMPTY; + } + + if (model.size() == 1) { + return OBJECT_MAPPER.writeValueAsString(model.values().toArray()[0]); + } + return OBJECT_MAPPER.writeValueAsString(model); } } diff --git a/nextstep-mvc/src/main/java/nextstep/utils/StringUtils.java b/nextstep-mvc/src/main/java/nextstep/utils/StringUtils.java new file mode 100644 index 00000000..ef05346f --- /dev/null +++ b/nextstep-mvc/src/main/java/nextstep/utils/StringUtils.java @@ -0,0 +1,5 @@ +package nextstep.utils; + +public class StringUtils { + public static final String EMPTY = ""; +} diff --git a/nextstep-mvc/src/test/java/nextstep/mvc/tobe/view/JsonViewTest.java b/nextstep-mvc/src/test/java/nextstep/mvc/tobe/view/JsonViewTest.java index 58c97ba2..3bfb6dfb 100644 --- a/nextstep-mvc/src/test/java/nextstep/mvc/tobe/view/JsonViewTest.java +++ b/nextstep-mvc/src/test/java/nextstep/mvc/tobe/view/JsonViewTest.java @@ -49,15 +49,33 @@ void render_one_element() throws Exception { } @Test - void render_over_two_element() throws Exception { + void render_one_primitive() throws Exception { Map model = new HashMap<>(); - Car expected = new Car("Black", "Sonata"); - model.put("car", expected); - model.put("name", "포비"); + int expected = 1; + model.put("number", expected); view.render(model, request, response); + int actual = JsonUtils.toObject(response.getContentAsString(), int.class); assertThat(response.getContentType()).isEqualTo(MediaType.APPLICATION_JSON_UTF8_VALUE); + assertThat(actual).isEqualTo(expected); + } + + @Test + void render_over_two_element() throws Exception { + final Map model = new HashMap<>(); + final Car car = new Car("Black", "Sonata"); + final String name = "포비"; + model.put("car", car); + model.put("name", name); + + view.render(model, request, response); + + Map actual = JsonUtils.toObject(response.getContentAsString(), Map.class); + logger.debug("response body : {}", response.getContentAsString()); + assertThat(response.getContentType()).isEqualTo(MediaType.APPLICATION_JSON_UTF8_VALUE); + assertThat(actual.get("name")).isEqualTo(name); + assertThat(actual.get("car").toString()).contains(car.getColor(), car.getType()); } } From ce08dea1953fd7945346e28236d8a875bc1a214f Mon Sep 17 00:00:00 2001 From: DaejunBae Date: Tue, 8 Oct 2019 16:15:23 +0900 Subject: [PATCH 02/29] =?UTF-8?q?feat:=20jackson=20=ED=95=99=EC=8A=B5?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20Object=20to=20Map?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- study/src/test/java/reflection/Student.java | 21 ++++++++++++++++- study/src/test/java/spring/JacksonTest.java | 26 +++++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 study/src/test/java/spring/JacksonTest.java diff --git a/study/src/test/java/reflection/Student.java b/study/src/test/java/reflection/Student.java index 85526b74..593ec1a0 100644 --- a/study/src/test/java/reflection/Student.java +++ b/study/src/test/java/reflection/Student.java @@ -1,18 +1,37 @@ package reflection; +import java.util.List; + public class Student { private String name; - private int age; + private List skills; + public String getName() { return name; } + public void setName(final String name) { + this.name = name; + } + public int getAge() { return age; } + public void setAge(final int age) { + this.age = age; + } + + public List getSkills() { + return skills; + } + + public void setSkills(final List skills) { + this.skills = skills; + } + @Override public String toString() { return "Student{" + diff --git a/study/src/test/java/spring/JacksonTest.java b/study/src/test/java/spring/JacksonTest.java new file mode 100644 index 00000000..8778c2ea --- /dev/null +++ b/study/src/test/java/spring/JacksonTest.java @@ -0,0 +1,26 @@ +package spring; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Test; +import reflection.Student; + +import java.util.Arrays; +import java.util.Map; + +public class JacksonTest { + @Test + void object_to_map() { + ObjectMapper oMapper = new ObjectMapper(); + + Student obj = new Student(); + obj.setName("mkyong"); + obj.setAge(34); + obj.setSkills(Arrays.asList("java","node")); + + // object -> Map + Map map = oMapper.convertValue(obj, Map.class); + System.out.println(map); + + } +} + From 53bef859834eb9b9dcf1b239072512d37d480acf Mon Sep 17 00:00:00 2001 From: DaejunBae Date: Tue, 8 Oct 2019 17:33:09 +0900 Subject: [PATCH 03/29] =?UTF-8?q?feat:=20WebRequest=20-=20request,=20respo?= =?UTF-8?q?nse=20=EA=B4=80=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/nextstep/mvc/tobe/WebRequest.java | 12 ++++++++ .../nextstep/mvc/tobe/WebRequestContext.java | 29 +++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 nextstep-mvc/src/main/java/nextstep/mvc/tobe/WebRequest.java create mode 100644 nextstep-mvc/src/main/java/nextstep/mvc/tobe/WebRequestContext.java diff --git a/nextstep-mvc/src/main/java/nextstep/mvc/tobe/WebRequest.java b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/WebRequest.java new file mode 100644 index 00000000..4d963132 --- /dev/null +++ b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/WebRequest.java @@ -0,0 +1,12 @@ +package nextstep.mvc.tobe; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +public interface WebRequest { + HttpServletRequest getRequest(); + + HttpServletResponse getResponse(); + + String getRequestURI(); +} diff --git a/nextstep-mvc/src/main/java/nextstep/mvc/tobe/WebRequestContext.java b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/WebRequestContext.java new file mode 100644 index 00000000..211b1b32 --- /dev/null +++ b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/WebRequestContext.java @@ -0,0 +1,29 @@ +package nextstep.mvc.tobe; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +public class WebRequestContext implements WebRequest { + private final HttpServletRequest req; + private final HttpServletResponse resp; + + public WebRequestContext(final HttpServletRequest req, final HttpServletResponse resp) { + this.req = req; + this.resp = resp; + } + + @Override + public HttpServletRequest getRequest() { + return req; + } + + @Override + public HttpServletResponse getResponse() { + return resp; + } + + @Override + public String getRequestURI() { + return req.getRequestURI(); + } +} From 067f43b8873a141888e541a645e9f233c7e072c5 Mon Sep 17 00:00:00 2001 From: DaejunBae Date: Tue, 8 Oct 2019 17:33:53 +0900 Subject: [PATCH 04/29] =?UTF-8?q?refactor:=20resolver,=20adapter=20-=20Req?= =?UTF-8?q?uest=20=EB=8C=80=EC=8B=A0=20WebRequest=20=EA=B0=80=EC=A7=80?= =?UTF-8?q?=EA=B2=8C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/nextstep/mvc/DispatcherServlet.java | 20 +++++++++++++---- .../mvc/tobe/adapter/HandlerAdapter.java | 6 ++--- .../tobe/adapter/HandlerExecutionAdapter.java | 18 ++++----------- .../tobe/adapter/SimpleControllerAdapter.java | 8 +++---- .../DefaultHandlerMethodArgumentResolver.java | 10 +++++++-- .../HandlerMethodArgumentResolver.java | 4 ++-- ...andlerMethodArgumentResolverComposite.java | 21 +++++++++++++----- ...pServletHandlerMethodArgumentResolver.java | 22 +++++++++++++++++++ ...VariableHandlerMethodArgumentResolver.java | 6 ++--- .../adapter/HandlerExecutionAdapterTest.java | 12 +++++----- ...aultHandlerMethodArgumentResolverTest.java | 21 +++++++++++------- ...erMethodArgumentResolverCompositeTest.java | 11 +++++----- ...ableHandlerMethodArgumentResolverTest.java | 13 ++++++++--- 13 files changed, 112 insertions(+), 60 deletions(-) create mode 100644 nextstep-mvc/src/main/java/nextstep/mvc/tobe/resolver/HttpServletHandlerMethodArgumentResolver.java diff --git a/nextstep-mvc/src/main/java/nextstep/mvc/DispatcherServlet.java b/nextstep-mvc/src/main/java/nextstep/mvc/DispatcherServlet.java index 5b35fd63..23b7e620 100644 --- a/nextstep-mvc/src/main/java/nextstep/mvc/DispatcherServlet.java +++ b/nextstep-mvc/src/main/java/nextstep/mvc/DispatcherServlet.java @@ -1,11 +1,13 @@ package nextstep.mvc; -import nextstep.mvc.tobe.exception.HandlerAdapterNotSupportedException; -import nextstep.mvc.tobe.exception.HandlerNotFoundException; +import nextstep.mvc.tobe.ModelAndView; +import nextstep.mvc.tobe.WebRequest; +import nextstep.mvc.tobe.WebRequestContext; import nextstep.mvc.tobe.adapter.HandlerAdapter; import nextstep.mvc.tobe.adapter.HandlerExecutionAdapter; -import nextstep.mvc.tobe.ModelAndView; import nextstep.mvc.tobe.adapter.SimpleControllerAdapter; +import nextstep.mvc.tobe.exception.HandlerAdapterNotSupportedException; +import nextstep.mvc.tobe.exception.HandlerNotFoundException; import nextstep.mvc.tobe.mapping.HandlerMapping; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -47,14 +49,16 @@ public void init() throws ServletException { protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { try { logger.debug("Method : {}, Request URI : {}", req.getMethod(), req.getRequestURI()); + final WebRequest webRequest = new WebRequestContext(req, resp); final Object handler = getHandler(req); final HandlerAdapter handlerAdapter = getHandlerAdapter(handler); - final ModelAndView mav = handlerAdapter.handle(req, resp, handler); + final ModelAndView mav = handlerAdapter.handle(webRequest, handler); // TODO ViewResolver (2단계) + move(mav, req, resp); } catch (HandlerNotFoundException e) { @@ -90,4 +94,12 @@ private void move(ModelAndView mav, HttpServletRequest req, HttpServletResponse RequestDispatcher rd = req.getRequestDispatcher(viewName); rd.forward(req, resp); } + + public void addHandlerMapping(final HandlerMapping handlerMapping) { + handlerMappings.add(handlerMapping); + } + + public void addHandlerAdapter(final HandlerAdapter handlerAdapter) { + handlerAdapters.add(handlerAdapter); + } } diff --git a/nextstep-mvc/src/main/java/nextstep/mvc/tobe/adapter/HandlerAdapter.java b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/adapter/HandlerAdapter.java index f7761033..72fa61f8 100644 --- a/nextstep-mvc/src/main/java/nextstep/mvc/tobe/adapter/HandlerAdapter.java +++ b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/adapter/HandlerAdapter.java @@ -1,12 +1,10 @@ package nextstep.mvc.tobe.adapter; import nextstep.mvc.tobe.ModelAndView; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import nextstep.mvc.tobe.WebRequest; public interface HandlerAdapter { - ModelAndView handle(final HttpServletRequest req, final HttpServletResponse resp, final Object handler) throws Exception; + ModelAndView handle(final WebRequest webRequest, final Object handler) throws Exception; boolean supports(final Object handler); } \ No newline at end of file diff --git a/nextstep-mvc/src/main/java/nextstep/mvc/tobe/adapter/HandlerExecutionAdapter.java b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/adapter/HandlerExecutionAdapter.java index 290faffa..aaf97c4b 100644 --- a/nextstep-mvc/src/main/java/nextstep/mvc/tobe/adapter/HandlerExecutionAdapter.java +++ b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/adapter/HandlerExecutionAdapter.java @@ -1,35 +1,25 @@ package nextstep.mvc.tobe.adapter; import nextstep.mvc.tobe.ModelAndView; +import nextstep.mvc.tobe.WebRequest; import nextstep.mvc.tobe.handler.HandlerExecution; import nextstep.mvc.tobe.resolver.HandlerMethodArgumentResolverComposite; import nextstep.mvc.tobe.resolver.MethodParameters; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - public class HandlerExecutionAdapter implements HandlerAdapter { private HandlerMethodArgumentResolverComposite argumentResolvers; public HandlerExecutionAdapter() { - argumentResolvers = new HandlerMethodArgumentResolverComposite(); + argumentResolvers = HandlerMethodArgumentResolverComposite.getInstance(); } @Override - public ModelAndView handle(final HttpServletRequest req, final HttpServletResponse resp, final Object handler) throws Exception { + public ModelAndView handle(final WebRequest webRequest, final Object handler) throws Exception { final HandlerExecution handlerExecution = (HandlerExecution) handler; final MethodParameters methodParameters = new MethodParameters(handlerExecution.getMethod()); final Object[] values = methodParameters.getMethodParams().stream() - .map(parameter -> { - if (parameter.isSameType(HttpServletRequest.class)) { - return req; - } - if (parameter.isSameType(HttpServletResponse.class)) { - return resp; - } - return argumentResolvers.resolveArgument(req, parameter); - }) + .map(parameter -> argumentResolvers.resolveArgument(webRequest, parameter)) .toArray(); final Object result = handlerExecution.execute(values); diff --git a/nextstep-mvc/src/main/java/nextstep/mvc/tobe/adapter/SimpleControllerAdapter.java b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/adapter/SimpleControllerAdapter.java index 9e5d2af8..1a610e2b 100644 --- a/nextstep-mvc/src/main/java/nextstep/mvc/tobe/adapter/SimpleControllerAdapter.java +++ b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/adapter/SimpleControllerAdapter.java @@ -2,16 +2,14 @@ import nextstep.mvc.asis.Controller; import nextstep.mvc.tobe.ModelAndView; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import nextstep.mvc.tobe.WebRequest; public class SimpleControllerAdapter implements HandlerAdapter { @Override - public ModelAndView handle(final HttpServletRequest req, final HttpServletResponse resp, final Object handler) throws Exception { + public ModelAndView handle(final WebRequest webRequest, final Object handler) throws Exception { final Controller controller = (Controller) handler; - final String viewName = String.valueOf((controller.execute(req, resp))); + final String viewName = String.valueOf((controller.execute(webRequest.getRequest(), webRequest.getResponse()))); return new ModelAndView(viewName); } diff --git a/nextstep-mvc/src/main/java/nextstep/mvc/tobe/resolver/DefaultHandlerMethodArgumentResolver.java b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/resolver/DefaultHandlerMethodArgumentResolver.java index c900eb1b..fde0215f 100644 --- a/nextstep-mvc/src/main/java/nextstep/mvc/tobe/resolver/DefaultHandlerMethodArgumentResolver.java +++ b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/resolver/DefaultHandlerMethodArgumentResolver.java @@ -1,14 +1,19 @@ package nextstep.mvc.tobe.resolver; +import nextstep.mvc.tobe.WebRequest; import nextstep.mvc.tobe.exception.HandlerMethodArgumentResolverException; import nextstep.utils.TypeConverter; import javax.servlet.http.HttpServletRequest; -import java.lang.reflect.*; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Parameter; import java.util.Optional; import java.util.stream.Stream; +// todo primitive - javabean 나누기 public class DefaultHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver { @Override @@ -17,7 +22,8 @@ public boolean supports(final MethodParameter methodParameter) { } @Override - public Object resolveArgument(final HttpServletRequest request, final MethodParameter methodParameter) { + public Object resolveArgument(final WebRequest webRequest, final MethodParameter methodParameter) { + final HttpServletRequest request = webRequest.getRequest(); final String value = request.getParameter(methodParameter.getName()); final Parameter parameter = methodParameter.getParameter(); diff --git a/nextstep-mvc/src/main/java/nextstep/mvc/tobe/resolver/HandlerMethodArgumentResolver.java b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/resolver/HandlerMethodArgumentResolver.java index 10032c11..92bbb832 100644 --- a/nextstep-mvc/src/main/java/nextstep/mvc/tobe/resolver/HandlerMethodArgumentResolver.java +++ b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/resolver/HandlerMethodArgumentResolver.java @@ -1,9 +1,9 @@ package nextstep.mvc.tobe.resolver; -import javax.servlet.http.HttpServletRequest; +import nextstep.mvc.tobe.WebRequest; public interface HandlerMethodArgumentResolver { boolean supports(final MethodParameter methodParameter); - Object resolveArgument(final HttpServletRequest request, final MethodParameter methodParameter); + Object resolveArgument(final WebRequest webRequest, final MethodParameter methodParameter); } diff --git a/nextstep-mvc/src/main/java/nextstep/mvc/tobe/resolver/HandlerMethodArgumentResolverComposite.java b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/resolver/HandlerMethodArgumentResolverComposite.java index 4b88a533..3a59b677 100644 --- a/nextstep-mvc/src/main/java/nextstep/mvc/tobe/resolver/HandlerMethodArgumentResolverComposite.java +++ b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/resolver/HandlerMethodArgumentResolverComposite.java @@ -1,6 +1,7 @@ package nextstep.mvc.tobe.resolver; -import javax.servlet.http.HttpServletRequest; +import nextstep.mvc.tobe.WebRequest; + import java.util.ArrayList; import java.util.List; @@ -8,19 +9,19 @@ public class HandlerMethodArgumentResolverComposite implements HandlerMethodArgu private final List resolvers = new ArrayList<>(); private final HandlerMethodArgumentResolver defaultResolver = new DefaultHandlerMethodArgumentResolver(); - public HandlerMethodArgumentResolverComposite() { + private HandlerMethodArgumentResolverComposite() { resolvers.add(new PathVariableHandlerMethodArgumentResolver()); - + resolvers.add(new HttpServletHandlerMethodArgumentResolver()); } @Override - public Object resolveArgument(final HttpServletRequest request, final MethodParameter methodParameter) { + public Object resolveArgument(final WebRequest webRequest, final MethodParameter methodParameter) { final HandlerMethodArgumentResolver resolver = resolvers.stream() .filter(x -> x.supports(methodParameter)) .findAny() .orElse(defaultResolver); - return resolver.resolveArgument(request, methodParameter); + return resolver.resolveArgument(webRequest, methodParameter); } @Override @@ -31,4 +32,14 @@ public boolean supports(final MethodParameter methodParameter) { public void addHandlerMethodArgumentResolver(final HandlerMethodArgumentResolver resolver) { resolvers.add(resolver); } + + public static HandlerMethodArgumentResolverComposite getInstance(){ + return LazyHolder.INSTANCE; + } + + // todo DI or Container 만들면 싱글턴 말고 빈으로 관리하기. + // 현재는 사용 편의성을 위해서 싱글턴으로 관리함. (매번 생성해서 직접 di 해주려면 테스트가 힘들어져서) + private static class LazyHolder { + public static final HandlerMethodArgumentResolverComposite INSTANCE = new HandlerMethodArgumentResolverComposite(); + } } diff --git a/nextstep-mvc/src/main/java/nextstep/mvc/tobe/resolver/HttpServletHandlerMethodArgumentResolver.java b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/resolver/HttpServletHandlerMethodArgumentResolver.java new file mode 100644 index 00000000..90cbd00f --- /dev/null +++ b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/resolver/HttpServletHandlerMethodArgumentResolver.java @@ -0,0 +1,22 @@ +package nextstep.mvc.tobe.resolver; + +import nextstep.mvc.tobe.WebRequest; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +public class HttpServletHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver { + @Override + public boolean supports(final MethodParameter methodParameter) { + return methodParameter.isSameType(HttpServletRequest.class) + || methodParameter.isSameType(HttpServletResponse.class); + } + + @Override + public Object resolveArgument(final WebRequest webRequest, final MethodParameter methodParameter) { + if (methodParameter.isSameType(HttpServletRequest.class)) { + return webRequest.getRequest(); + } + return webRequest.getResponse(); + } +} diff --git a/nextstep-mvc/src/main/java/nextstep/mvc/tobe/resolver/PathVariableHandlerMethodArgumentResolver.java b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/resolver/PathVariableHandlerMethodArgumentResolver.java index 4f1ac1ee..835c3294 100644 --- a/nextstep-mvc/src/main/java/nextstep/mvc/tobe/resolver/PathVariableHandlerMethodArgumentResolver.java +++ b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/resolver/PathVariableHandlerMethodArgumentResolver.java @@ -1,12 +1,12 @@ package nextstep.mvc.tobe.resolver; +import nextstep.mvc.tobe.WebRequest; import nextstep.utils.PathPatternUtils; import nextstep.utils.TypeConverter; import nextstep.web.annotation.PathVariable; import nextstep.web.annotation.RequestMapping; import org.springframework.web.util.pattern.PathPattern; -import javax.servlet.http.HttpServletRequest; import java.util.Objects; public class PathVariableHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver { @@ -16,8 +16,8 @@ public boolean supports(final MethodParameter methodParameter) { } @Override - public Object resolveArgument(final HttpServletRequest request, final MethodParameter methodParameter) { - final String uri = request.getRequestURI(); + public Object resolveArgument(final WebRequest webRequest, final MethodParameter methodParameter) { + final String uri = webRequest.getRequestURI(); final String path = methodParameter.getMethod().getAnnotation(RequestMapping.class).value(); final PathPattern pathPattern = PathPatternUtils.parse(path); diff --git a/nextstep-mvc/src/test/java/nextstep/mvc/tobe/adapter/HandlerExecutionAdapterTest.java b/nextstep-mvc/src/test/java/nextstep/mvc/tobe/adapter/HandlerExecutionAdapterTest.java index 185da64e..4ac9859a 100644 --- a/nextstep-mvc/src/test/java/nextstep/mvc/tobe/adapter/HandlerExecutionAdapterTest.java +++ b/nextstep-mvc/src/test/java/nextstep/mvc/tobe/adapter/HandlerExecutionAdapterTest.java @@ -1,6 +1,8 @@ package nextstep.mvc.tobe.adapter; +import nextstep.mvc.tobe.WebRequest; +import nextstep.mvc.tobe.WebRequestContext; import nextstep.mvc.tobe.handler.HandlerExecution; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -8,8 +10,6 @@ import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; -import java.lang.reflect.InvocationTargetException; - import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -22,12 +22,14 @@ class HandlerExecutionAdapterTest { private final HandlerExecution handler = mock(HandlerExecution.class); private MockHttpServletResponse response; private MockHttpServletRequest request; + private WebRequest webRequest; @BeforeEach void setUp() throws NoSuchMethodException { when(handler.getMethod()).thenReturn(this.getClass().getDeclaredMethod("StringToModelAndView")); - response = new MockHttpServletResponse(); request = new MockHttpServletRequest(); + response = new MockHttpServletResponse(); + webRequest = new WebRequestContext(request, response); } @Test @@ -44,7 +46,7 @@ void StringToModelAndView() throws Exception { when(handler.execute(any())).thenReturn("view"); // when & then - assertDoesNotThrow(() -> handlerAdapter.handle(request, response, handler)); + assertDoesNotThrow(() -> handlerAdapter.handle(webRequest, handler)); } @Test @@ -54,6 +56,6 @@ void handleTest() throws Exception { when(handler.execute(any())).thenReturn(123); // when & then - assertThrows(ClassCastException.class, () -> handlerAdapter.handle(request, response, handler)); + assertThrows(ClassCastException.class, () -> handlerAdapter.handle(webRequest, handler)); } } diff --git a/nextstep-mvc/src/test/java/nextstep/mvc/tobe/resolver/DefaultHandlerMethodArgumentResolverTest.java b/nextstep-mvc/src/test/java/nextstep/mvc/tobe/resolver/DefaultHandlerMethodArgumentResolverTest.java index 1b11988c..78a44540 100644 --- a/nextstep-mvc/src/test/java/nextstep/mvc/tobe/resolver/DefaultHandlerMethodArgumentResolverTest.java +++ b/nextstep-mvc/src/test/java/nextstep/mvc/tobe/resolver/DefaultHandlerMethodArgumentResolverTest.java @@ -1,6 +1,8 @@ package nextstep.mvc.tobe.resolver; import nextstep.mvc.tobe.TestUser; +import nextstep.mvc.tobe.WebRequest; +import nextstep.mvc.tobe.WebRequestContext; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.mock.web.MockHttpServletRequest; @@ -20,6 +22,7 @@ class DefaultHandlerMethodArgumentResolverTest { private Class clazz = DefaultHandlerMethodArgumentResolverTest.class; private Map, Parameter> params; private MockHttpServletRequest request; + private WebRequest webRequest; private Method method; private String id = "1"; @@ -44,6 +47,8 @@ void setUp() throws NoSuchMethodException { request.addParameter("id", id); request.addParameter("age", age); request.addParameter("bool", bool); + + webRequest = new WebRequestContext(request, null); } @Test @@ -52,7 +57,7 @@ void setUp() throws NoSuchMethodException { final MethodParameter methodParameter = new MethodParameter(params.get(TestUser.class), "testUser", 1, method); // when - final TestUser actual = (TestUser) resolver.resolveArgument(request, methodParameter); + final TestUser actual = (TestUser) resolver.resolveArgument(webRequest, methodParameter); // then assertThat(actual.getUserId()).isEqualTo(userId); @@ -66,7 +71,7 @@ void setUp() throws NoSuchMethodException { final MethodParameter methodParameter = new MethodParameter(params.get(OnlyDefaultConstructorJavaBean.class), "javaBean", 1, method); // when - final OnlyDefaultConstructorJavaBean actual = (OnlyDefaultConstructorJavaBean) resolver.resolveArgument(request, methodParameter); + final OnlyDefaultConstructorJavaBean actual = (OnlyDefaultConstructorJavaBean) resolver.resolveArgument(webRequest, methodParameter); // then assertThat(actual.getUserId()).isEqualTo(userId); @@ -80,7 +85,7 @@ void setUp() throws NoSuchMethodException { final MethodParameter methodParameter = new MethodParameter(params.get(long.class), "id", 1, method); // when - final Object actual = resolver.resolveArgument(request, methodParameter); + final Object actual = resolver.resolveArgument(webRequest, methodParameter); // then assertThat(actual).isEqualTo(Long.parseLong(id)); @@ -92,7 +97,7 @@ void setUp() throws NoSuchMethodException { final MethodParameter methodParameter = new MethodParameter(params.get(int.class), "age", 1, method); // when - final Object actual = resolver.resolveArgument(request, methodParameter); + final Object actual = resolver.resolveArgument(webRequest, methodParameter); // then assertThat(actual).isEqualTo(Integer.parseInt(age)); @@ -103,7 +108,7 @@ void setUp() throws NoSuchMethodException { // given final MethodParameter methodParameter = new MethodParameter(params.get(boolean.class), "bool", 1, method); // when - final Object actual = resolver.resolveArgument(request, methodParameter); + final Object actual = resolver.resolveArgument(webRequest, methodParameter); // then assertThat(actual).isEqualTo(Boolean.parseBoolean(bool)); @@ -115,7 +120,7 @@ void setUp() throws NoSuchMethodException { final MethodParameter methodParameter = new MethodParameter(params.get(Long.class), "id", 1, method); // when - final Object actual = resolver.resolveArgument(request, methodParameter); + final Object actual = resolver.resolveArgument(webRequest, methodParameter); // then assertThat(actual).isEqualTo(Long.parseLong(id)); @@ -127,7 +132,7 @@ void setUp() throws NoSuchMethodException { final MethodParameter methodParameter = new MethodParameter(params.get(Integer.class), "id", 1, method); // when - final Object actual = resolver.resolveArgument(request, methodParameter); + final Object actual = resolver.resolveArgument(webRequest, methodParameter); // then assertThat(actual).isEqualTo(Integer.parseInt(id)); @@ -139,7 +144,7 @@ void setUp() throws NoSuchMethodException { final MethodParameter methodParameter = new MethodParameter(params.get(String.class), "userId", 1, method); // when - final Object actual = resolver.resolveArgument(request, methodParameter); + final Object actual = resolver.resolveArgument(webRequest, methodParameter); // then assertThat(actual).isEqualTo(userId); diff --git a/nextstep-mvc/src/test/java/nextstep/mvc/tobe/resolver/HandlerMethodArgumentResolverCompositeTest.java b/nextstep-mvc/src/test/java/nextstep/mvc/tobe/resolver/HandlerMethodArgumentResolverCompositeTest.java index bcc00e07..8f456057 100644 --- a/nextstep-mvc/src/test/java/nextstep/mvc/tobe/resolver/HandlerMethodArgumentResolverCompositeTest.java +++ b/nextstep-mvc/src/test/java/nextstep/mvc/tobe/resolver/HandlerMethodArgumentResolverCompositeTest.java @@ -1,8 +1,6 @@ package nextstep.mvc.tobe.resolver; -import nextstep.mvc.tobe.ModelAndView; -import nextstep.mvc.tobe.TestUser; -import nextstep.mvc.tobe.TestUserController; +import nextstep.mvc.tobe.*; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.mock.web.MockHttpServletRequest; @@ -14,8 +12,9 @@ import static org.assertj.core.api.Assertions.assertThat; class HandlerMethodArgumentResolverCompositeTest { - private HandlerMethodArgumentResolver resolver = new HandlerMethodArgumentResolverComposite(); + private HandlerMethodArgumentResolver resolver = HandlerMethodArgumentResolverComposite.getInstance(); private MockHttpServletRequest request; + private WebRequest webRequest; private Class clazz = TestUserController.class; private String id = "1"; @@ -30,6 +29,8 @@ void setUp() { request.addParameter("password", password); request.addParameter("id", id); request.addParameter("age", age); + + webRequest = new WebRequestContext(request, null); } @Test @@ -91,7 +92,7 @@ private ModelAndView invokeMethod(final Method method) throws IllegalAccessExcep final Object[] values = methodParameters.getMethodParams() .stream() - .map(methodParameter -> resolver.resolveArgument(request, methodParameter)) + .map(methodParameter -> resolver.resolveArgument(webRequest, methodParameter)) .toArray(); return (ModelAndView) method.invoke(clazz.newInstance(), values); } diff --git a/nextstep-mvc/src/test/java/nextstep/mvc/tobe/resolver/PathVariableHandlerMethodArgumentResolverTest.java b/nextstep-mvc/src/test/java/nextstep/mvc/tobe/resolver/PathVariableHandlerMethodArgumentResolverTest.java index 11f7cc62..aec127e9 100644 --- a/nextstep-mvc/src/test/java/nextstep/mvc/tobe/resolver/PathVariableHandlerMethodArgumentResolverTest.java +++ b/nextstep-mvc/src/test/java/nextstep/mvc/tobe/resolver/PathVariableHandlerMethodArgumentResolverTest.java @@ -1,11 +1,14 @@ package nextstep.mvc.tobe.resolver; +import nextstep.mvc.tobe.WebRequest; +import nextstep.mvc.tobe.WebRequestContext; import nextstep.web.annotation.PathVariable; import nextstep.web.annotation.RequestMapping; import nextstep.web.annotation.RequestMethod; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; import java.lang.reflect.Method; import java.lang.reflect.Parameter; @@ -18,6 +21,8 @@ public class PathVariableHandlerMethodArgumentResolverTest { private Class clazz = PathVariableHandlerMethodArgumentResolverTest.class; private Parameter[] parameters; private MockHttpServletRequest request; + private MockHttpServletResponse response; + private WebRequest webRequest; private String id = "1"; private Method method; @@ -31,6 +36,8 @@ void setUp() { request = new MockHttpServletRequest(); request.addParameter("id", id); + + webRequest = new WebRequestContext(request, response); } @Test @@ -58,7 +65,7 @@ void setUp() { request.setRequestURI("/users/10"); // when - final Object actual = resolver.resolveArgument(request, methodParameter); + final Object actual = resolver.resolveArgument(webRequest, methodParameter); // then assertThat(actual).isEqualTo(10L); @@ -78,8 +85,8 @@ void setUp() { request.setRequestURI("/users/10/12"); // when - final Object id = resolver.resolveArgument(request, methodParameter); - final Object userId = resolver.resolveArgument(request, methodParameter2); + final Object id = resolver.resolveArgument(webRequest, methodParameter); + final Object userId = resolver.resolveArgument(webRequest, methodParameter2); // then assertThat(id).isEqualTo(10L); From d45916a2e6c8d8346a124a58f37c8552ae4fa5e1 Mon Sep 17 00:00:00 2001 From: DaejunBae Date: Tue, 8 Oct 2019 18:21:24 +0900 Subject: [PATCH 05/29] =?UTF-8?q?feat:=20json=20=EC=B2=98=EB=A6=AC?= =?UTF-8?q?=EC=9A=A9=20ResponseBody=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/nextstep/mvc/tobe/view/JsonView.java | 11 +++++++++++ .../java/nextstep/web/annotation/ResponseBody.java | 10 ++++++++++ 2 files changed, 21 insertions(+) create mode 100644 nextstep-mvc/src/main/java/nextstep/web/annotation/ResponseBody.java diff --git a/nextstep-mvc/src/main/java/nextstep/mvc/tobe/view/JsonView.java b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/view/JsonView.java index 5c50d360..1cd0b86b 100644 --- a/nextstep-mvc/src/main/java/nextstep/mvc/tobe/view/JsonView.java +++ b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/view/JsonView.java @@ -8,15 +8,26 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.PrintWriter; +import java.util.HashMap; import java.util.Map; public class JsonView implements View { private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + private final Map model; + + public JsonView() { + model = new HashMap<>(); + } + + public JsonView(final Object body) { + model = OBJECT_MAPPER.convertValue(body, Map.class); + } @Override public void render(final Map model, final HttpServletRequest request, final HttpServletResponse response) throws Exception { response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE); + this.model.putAll(model); final PrintWriter writer = response.getWriter(); writer.println(convert(model)); writer.flush(); diff --git a/nextstep-mvc/src/main/java/nextstep/web/annotation/ResponseBody.java b/nextstep-mvc/src/main/java/nextstep/web/annotation/ResponseBody.java new file mode 100644 index 00000000..3f71dbfd --- /dev/null +++ b/nextstep-mvc/src/main/java/nextstep/web/annotation/ResponseBody.java @@ -0,0 +1,10 @@ +package nextstep.web.annotation; + +import java.lang.annotation.*; + +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface ResponseBody { + +} \ No newline at end of file From 7090de7908454c9a188174b05860f8a5c625ba77 Mon Sep 17 00:00:00 2001 From: DaejunBae Date: Tue, 8 Oct 2019 18:21:48 +0900 Subject: [PATCH 06/29] feat: RequestBodyAdapter --- .../java/nextstep/mvc/DispatcherServlet.java | 11 ++++-- .../mvc/tobe/adapter/ResponseBodyAdapter.java | 37 ++++++++++++++++++ .../mvc/tobe/handler/HandlerExecution.java | 5 +++ .../tobe/adapter/ResponseBodyAdapterTest.java | 38 +++++++++++++++++++ 4 files changed, 88 insertions(+), 3 deletions(-) create mode 100644 nextstep-mvc/src/main/java/nextstep/mvc/tobe/adapter/ResponseBodyAdapter.java create mode 100644 nextstep-mvc/src/test/java/nextstep/mvc/tobe/adapter/ResponseBodyAdapterTest.java diff --git a/nextstep-mvc/src/main/java/nextstep/mvc/DispatcherServlet.java b/nextstep-mvc/src/main/java/nextstep/mvc/DispatcherServlet.java index 23b7e620..5084d87a 100644 --- a/nextstep-mvc/src/main/java/nextstep/mvc/DispatcherServlet.java +++ b/nextstep-mvc/src/main/java/nextstep/mvc/DispatcherServlet.java @@ -5,6 +5,7 @@ import nextstep.mvc.tobe.WebRequestContext; import nextstep.mvc.tobe.adapter.HandlerAdapter; import nextstep.mvc.tobe.adapter.HandlerExecutionAdapter; +import nextstep.mvc.tobe.adapter.ResponseBodyAdapter; import nextstep.mvc.tobe.adapter.SimpleControllerAdapter; import nextstep.mvc.tobe.exception.HandlerAdapterNotSupportedException; import nextstep.mvc.tobe.exception.HandlerNotFoundException; @@ -37,7 +38,7 @@ public class DispatcherServlet extends HttpServlet { public DispatcherServlet(HandlerMapping... handlerMappings) { this.handlerMappings = Arrays.asList(handlerMappings); - this.handlerAdapters = Arrays.asList(new SimpleControllerAdapter(), new HandlerExecutionAdapter()); + this.handlerAdapters = Arrays.asList(new SimpleControllerAdapter(), new ResponseBodyAdapter(), new HandlerExecutionAdapter()); } @Override @@ -65,7 +66,7 @@ protected void service(HttpServletRequest req, HttpServletResponse resp) throws logger.error("not support uri: {} ", req.getRequestURI()); resp.sendError(SC_NOT_FOUND); } catch (Exception e) { - logger.error(e.getMessage()); + logger.error("Exception: {}", e.getMessage()); resp.sendError(SC_INTERNAL_SERVER_ERROR, e.getMessage()); } } @@ -85,8 +86,12 @@ private HandlerAdapter getHandlerAdapter(final Object handler) { .orElseThrow(HandlerAdapterNotSupportedException::new); } - private void move(ModelAndView mav, HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + private void move(ModelAndView mav, HttpServletRequest req, HttpServletResponse resp) throws Exception { final String viewName = mav.getViewName(); + if (viewName == null) { + mav.getView().render(mav.getModel(), req, resp); + return; + } if (viewName.startsWith(DEFAULT_REDIRECT_PREFIX)) { resp.sendRedirect(viewName.substring(DEFAULT_REDIRECT_PREFIX.length())); return; diff --git a/nextstep-mvc/src/main/java/nextstep/mvc/tobe/adapter/ResponseBodyAdapter.java b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/adapter/ResponseBodyAdapter.java new file mode 100644 index 00000000..401ec533 --- /dev/null +++ b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/adapter/ResponseBodyAdapter.java @@ -0,0 +1,37 @@ +package nextstep.mvc.tobe.adapter; + +import nextstep.mvc.tobe.ModelAndView; +import nextstep.mvc.tobe.WebRequest; +import nextstep.mvc.tobe.handler.HandlerExecution; +import nextstep.mvc.tobe.resolver.HandlerMethodArgumentResolverComposite; +import nextstep.mvc.tobe.resolver.MethodParameters; +import nextstep.mvc.tobe.view.JsonView; +import nextstep.web.annotation.ResponseBody; + +public class ResponseBodyAdapter implements HandlerAdapter { + private HandlerMethodArgumentResolverComposite argumentResolvers; + + public ResponseBodyAdapter() { + argumentResolvers = HandlerMethodArgumentResolverComposite.getInstance(); + } + + // todo 추상화로 중복 제거 or 합쳐야하나? + @Override + public ModelAndView handle(final WebRequest webRequest, final Object handler) throws Exception { + final HandlerExecution handlerExecution = (HandlerExecution) handler; + final MethodParameters methodParameters = new MethodParameters(handlerExecution.getMethod()); + + final Object[] values = methodParameters.getMethodParams().stream() + .map(parameter -> argumentResolvers.resolveArgument(webRequest, parameter)) + .toArray(); + + final Object result = handlerExecution.execute(values); + + return new ModelAndView(new JsonView(result)); + } + + @Override + public boolean supports(final Object handler) { + return ((HandlerExecution) handler).isAnnotationPresent(ResponseBody.class); + } +} diff --git a/nextstep-mvc/src/main/java/nextstep/mvc/tobe/handler/HandlerExecution.java b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/handler/HandlerExecution.java index 9698112a..7848c574 100644 --- a/nextstep-mvc/src/main/java/nextstep/mvc/tobe/handler/HandlerExecution.java +++ b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/handler/HandlerExecution.java @@ -1,5 +1,6 @@ package nextstep.mvc.tobe.handler; +import java.lang.annotation.Annotation; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Parameter; @@ -24,4 +25,8 @@ public Parameter[] getParameters() { public Method getMethod() { return method; } + + public boolean isAnnotationPresent(final Class annotation){ + return method.isAnnotationPresent(annotation); + } } diff --git a/nextstep-mvc/src/test/java/nextstep/mvc/tobe/adapter/ResponseBodyAdapterTest.java b/nextstep-mvc/src/test/java/nextstep/mvc/tobe/adapter/ResponseBodyAdapterTest.java new file mode 100644 index 00000000..0cfd8dca --- /dev/null +++ b/nextstep-mvc/src/test/java/nextstep/mvc/tobe/adapter/ResponseBodyAdapterTest.java @@ -0,0 +1,38 @@ +package nextstep.mvc.tobe.adapter; + +import nextstep.mvc.tobe.handler.HandlerExecution; +import nextstep.web.annotation.RequestMapping; +import nextstep.web.annotation.ResponseBody; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; +import static org.assertj.core.api.Assertions.assertThat; + +class ResponseBodyAdapterTest { + private ResponseBodyAdapter adapter = new ResponseBodyAdapter(); + + @Test + void supports_ResponseBody_포함_true() throws NoSuchMethodException { + final HandlerExecution handler = new HandlerExecution(null, Foo.class.getMethod("foo")); + + assertThat(adapter.supports(handler)).isTrue(); + } + + @Test + void supports_ResponseBody_미포함_false() throws NoSuchMethodException { + final HandlerExecution handler = new HandlerExecution(null, Foo2.class.getMethod("foo")); + + assertThat(adapter.supports(handler)).isFalse(); + } + + class Foo{ + @ResponseBody + @RequestMapping + public void foo(){}; + } + + class Foo2{ + @RequestMapping + public void foo(){}; + } +} \ No newline at end of file From 77a8905e71b5fd98225ef748d8fd17cea0283658 Mon Sep 17 00:00:00 2001 From: DaejunBae Date: Tue, 8 Oct 2019 23:22:34 +0900 Subject: [PATCH 07/29] feat: RequestBodyMethodArgumentResolver --- ...stBodyMethodArgumentResolverException.java | 7 ++ ...andlerMethodArgumentResolverComposite.java | 1 + .../RequestBodyMethodArgumentResolver.java | 31 ++++++++ .../java/nextstep/mvc/tobe/view/JsonView.java | 42 +++++++++-- .../main/java/nextstep/utils/JsonUtils.java | 16 +++- .../src/main/java/nextstep/utils/Objects.java | 14 ++++ .../nextstep/web/annotation/RequestBody.java | 9 +++ ...RequestBodyMethodArgumentResolverTest.java | 73 +++++++++++++++++++ .../nextstep/mvc/tobe/view/JsonViewTest.java | 13 ++++ 9 files changed, 196 insertions(+), 10 deletions(-) create mode 100644 nextstep-mvc/src/main/java/nextstep/mvc/tobe/exception/RequestBodyMethodArgumentResolverException.java create mode 100644 nextstep-mvc/src/main/java/nextstep/mvc/tobe/resolver/RequestBodyMethodArgumentResolver.java create mode 100644 nextstep-mvc/src/main/java/nextstep/utils/Objects.java create mode 100644 nextstep-mvc/src/main/java/nextstep/web/annotation/RequestBody.java create mode 100644 nextstep-mvc/src/test/java/nextstep/mvc/tobe/resolver/RequestBodyMethodArgumentResolverTest.java diff --git a/nextstep-mvc/src/main/java/nextstep/mvc/tobe/exception/RequestBodyMethodArgumentResolverException.java b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/exception/RequestBodyMethodArgumentResolverException.java new file mode 100644 index 00000000..cdeb0edb --- /dev/null +++ b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/exception/RequestBodyMethodArgumentResolverException.java @@ -0,0 +1,7 @@ +package nextstep.mvc.tobe.exception; + +public class RequestBodyMethodArgumentResolverException extends RuntimeException { + public RequestBodyMethodArgumentResolverException(final String message) { + super(message); + } +} diff --git a/nextstep-mvc/src/main/java/nextstep/mvc/tobe/resolver/HandlerMethodArgumentResolverComposite.java b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/resolver/HandlerMethodArgumentResolverComposite.java index 3a59b677..809d0742 100644 --- a/nextstep-mvc/src/main/java/nextstep/mvc/tobe/resolver/HandlerMethodArgumentResolverComposite.java +++ b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/resolver/HandlerMethodArgumentResolverComposite.java @@ -12,6 +12,7 @@ public class HandlerMethodArgumentResolverComposite implements HandlerMethodArgu private HandlerMethodArgumentResolverComposite() { resolvers.add(new PathVariableHandlerMethodArgumentResolver()); resolvers.add(new HttpServletHandlerMethodArgumentResolver()); + resolvers.add(new RequestBodyMethodArgumentResolver()); } @Override diff --git a/nextstep-mvc/src/main/java/nextstep/mvc/tobe/resolver/RequestBodyMethodArgumentResolver.java b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/resolver/RequestBodyMethodArgumentResolver.java new file mode 100644 index 00000000..fab59b5d --- /dev/null +++ b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/resolver/RequestBodyMethodArgumentResolver.java @@ -0,0 +1,31 @@ +package nextstep.mvc.tobe.resolver; + +import nextstep.mvc.tobe.WebRequest; +import nextstep.mvc.tobe.exception.RequestBodyMethodArgumentResolverException; +import nextstep.utils.JsonUtils; +import nextstep.web.annotation.RequestBody; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; + +public class RequestBodyMethodArgumentResolver implements HandlerMethodArgumentResolver { + private static final Logger logger = LoggerFactory.getLogger(RequestBodyMethodArgumentResolver.class); + + @Override + public boolean supports(final MethodParameter methodParameter) { + return methodParameter.isAnnotationPresent(RequestBody.class); + } + + @Override + public Object resolveArgument(final WebRequest webRequest, final MethodParameter methodParameter) { + final HttpServletRequest request = webRequest.getRequest(); + try { + return JsonUtils.toObject(request.getInputStream(), methodParameter.getType()); + } catch (IOException e) { + logger.error("IOException: {}", e); + throw new RequestBodyMethodArgumentResolverException(e.getMessage()); + } + } +} diff --git a/nextstep-mvc/src/main/java/nextstep/mvc/tobe/view/JsonView.java b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/view/JsonView.java index 1cd0b86b..554ca3d0 100644 --- a/nextstep-mvc/src/main/java/nextstep/mvc/tobe/view/JsonView.java +++ b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/view/JsonView.java @@ -2,39 +2,67 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import nextstep.utils.Objects; import nextstep.utils.StringUtils; +import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import java.io.IOException; import java.io.PrintWriter; -import java.util.HashMap; import java.util.Map; public class JsonView implements View { private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); - private final Map model; + private Object object; public JsonView() { - model = new HashMap<>(); } - public JsonView(final Object body) { - model = OBJECT_MAPPER.convertValue(body, Map.class); + public JsonView(final Object object) { + this.object = object; } + // todo 리팩토링 @Override public void render(final Map model, final HttpServletRequest request, final HttpServletResponse response) throws Exception { response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE); - this.model.putAll(model); + if (object instanceof ResponseEntity) { + final ResponseEntity responseEntity = (ResponseEntity) this.object; + setStatusLine(responseEntity, response); + setHeaders(responseEntity, response); + setBody(responseEntity.getBody(), model, response); + return; + } + setBody(object, model, response); + } + + private void setStatusLine(final ResponseEntity responseEntity, final HttpServletResponse response) { + response.setStatus(responseEntity.getStatusCodeValue()); + } + + private void setHeaders(final ResponseEntity responseEntity, final HttpServletResponse response) { + final HttpHeaders headers = responseEntity.getHeaders(); + // todo First 말고 전체 리스트 담아주기 ( ResponseEntity, HttpHeaders 직접 구현했을 때) + headers.forEach((key, value) -> response.addHeader(key, headers.getFirst(key))); + } + + private void setBody(final Object body, final Map model, final HttpServletResponse response) throws IOException { final PrintWriter writer = response.getWriter(); + + if (Objects.isNotNull(body)) { + final String json = OBJECT_MAPPER.writeValueAsString(body); + writer.println(json); + } writer.println(convert(model)); writer.flush(); } private Object convert(final Map model) throws JsonProcessingException { - if (model.size() == 0) { + if (Objects.isNull(model) || model.size() == 0) { return StringUtils.EMPTY; } diff --git a/nextstep-mvc/src/main/java/nextstep/utils/JsonUtils.java b/nextstep-mvc/src/main/java/nextstep/utils/JsonUtils.java index 77d3e94b..745f4157 100644 --- a/nextstep-mvc/src/main/java/nextstep/utils/JsonUtils.java +++ b/nextstep-mvc/src/main/java/nextstep/utils/JsonUtils.java @@ -4,17 +4,27 @@ import com.fasterxml.jackson.databind.ObjectMapper; import nextstep.mvc.tobe.exception.ObjectMapperException; +import javax.servlet.ServletInputStream; import java.io.IOException; public class JsonUtils { + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + public static T toObject(String json, Class clazz) throws ObjectMapperException { try { - ObjectMapper objectMapper = new ObjectMapper(); - objectMapper.setVisibility(objectMapper.getSerializationConfig().getDefaultVisibilityChecker() + OBJECT_MAPPER.setVisibility(OBJECT_MAPPER.getSerializationConfig().getDefaultVisibilityChecker() .withFieldVisibility(JsonAutoDetect.Visibility.ANY) .withGetterVisibility(JsonAutoDetect.Visibility.ANY) .withSetterVisibility(JsonAutoDetect.Visibility.NONE)); - return objectMapper.readValue(json, clazz); + return OBJECT_MAPPER.readValue(json, clazz); + } catch (IOException e) { + throw new ObjectMapperException(e); + } + } + + public static T toObject(final ServletInputStream servletInputStream, Class clazz) { + try { + return OBJECT_MAPPER.readValue(servletInputStream, clazz); } catch (IOException e) { throw new ObjectMapperException(e); } diff --git a/nextstep-mvc/src/main/java/nextstep/utils/Objects.java b/nextstep-mvc/src/main/java/nextstep/utils/Objects.java new file mode 100644 index 00000000..0f1cc688 --- /dev/null +++ b/nextstep-mvc/src/main/java/nextstep/utils/Objects.java @@ -0,0 +1,14 @@ +package nextstep.utils; + +public class Objects { + private Objects() { + } + + public static boolean isNull(final Object object) { + return object == null; + } + + public static boolean isNotNull(final Object object) { + return !isNull(object); + } +} diff --git a/nextstep-mvc/src/main/java/nextstep/web/annotation/RequestBody.java b/nextstep-mvc/src/main/java/nextstep/web/annotation/RequestBody.java new file mode 100644 index 00000000..e8507201 --- /dev/null +++ b/nextstep-mvc/src/main/java/nextstep/web/annotation/RequestBody.java @@ -0,0 +1,9 @@ +package nextstep.web.annotation; + +import java.lang.annotation.*; + +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface RequestBody { +} diff --git a/nextstep-mvc/src/test/java/nextstep/mvc/tobe/resolver/RequestBodyMethodArgumentResolverTest.java b/nextstep-mvc/src/test/java/nextstep/mvc/tobe/resolver/RequestBodyMethodArgumentResolverTest.java new file mode 100644 index 00000000..886c4942 --- /dev/null +++ b/nextstep-mvc/src/test/java/nextstep/mvc/tobe/resolver/RequestBodyMethodArgumentResolverTest.java @@ -0,0 +1,73 @@ +package nextstep.mvc.tobe.resolver; + +import nextstep.mvc.tobe.Car; +import nextstep.mvc.tobe.WebRequest; +import nextstep.mvc.tobe.WebRequestContext; +import nextstep.utils.JsonUtils; +import nextstep.web.annotation.PathVariable; +import nextstep.web.annotation.RequestBody; +import nextstep.web.annotation.RequestMapping; +import nextstep.web.annotation.RequestMethod; +import nextstep.web.support.MediaType; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.mock.web.MockHttpServletRequest; + +import javax.servlet.ServletInputStream; +import java.lang.reflect.Method; +import java.util.List; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; + +class RequestBodyMethodArgumentResolverTest { + private RequestBodyMethodArgumentResolver resolver = new RequestBodyMethodArgumentResolver(); + private WebRequest webRequest; + private MockHttpServletRequest request; + private Method method; + private List methodParams; + + @BeforeEach + void setUp() { + method = Stream.of(this.getClass().getDeclaredMethods()) + .filter(method -> method.getName().equals("foo")) + .findAny() + .get(); + + methodParams = new MethodParameters(method).getMethodParams(); + + request = new MockHttpServletRequest(); + webRequest = new WebRequestContext(request, null); + } + + @Test + void supports_RequestBody_인경우_true() { + assertThat(resolver.supports(methodParams.get(0))).isTrue(); + } + + @Test + void supports_RequestBody_아닌경우_false() { + assertThat(resolver.supports(methodParams.get(1))).isFalse(); + } + + @Test + void resolveArgument_to_json_매핑_확인() { + // given + final String json = "{ \"color\" : \"Black\", \"type\" : \"BMW\" }"; + final Car expected = JsonUtils.toObject(json, Car.class); + request.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE); + request.setContent(json.getBytes()); + + // when + final Object actual = resolver.resolveArgument(webRequest, methodParams.get(0)); + + // then + assertThat(actual).isEqualTo(expected); + + } + + @RequestMapping(value = "/users/{id}/{userId}", method = RequestMethod.GET) + public Car foo(@RequestBody Car car, @PathVariable long userId) { + return car; + } +} \ No newline at end of file diff --git a/nextstep-mvc/src/test/java/nextstep/mvc/tobe/view/JsonViewTest.java b/nextstep-mvc/src/test/java/nextstep/mvc/tobe/view/JsonViewTest.java index 3bfb6dfb..2ea3f472 100644 --- a/nextstep-mvc/src/test/java/nextstep/mvc/tobe/view/JsonViewTest.java +++ b/nextstep-mvc/src/test/java/nextstep/mvc/tobe/view/JsonViewTest.java @@ -7,6 +7,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; @@ -78,4 +79,16 @@ void render_over_two_element() throws Exception { assertThat(actual.get("name")).isEqualTo(name); assertThat(actual.get("car").toString()).contains(car.getColor(), car.getType()); } + + @Test + void render_with_ResponseEntity() throws Exception { + final Car expected = new Car("Black", "Sonata"); + final JsonView jsonView = new JsonView(ResponseEntity.ok(expected)); + + jsonView.render(null, request, response); + + Car actual = JsonUtils.toObject(response.getContentAsString(), Car.class); + + assertThat(actual).isEqualTo(expected); + } } From 7b65385adc609dff51cf30b1dee0175e9b788150 Mon Sep 17 00:00:00 2001 From: DaejunBae Date: Tue, 8 Oct 2019 23:24:44 +0900 Subject: [PATCH 08/29] =?UTF-8?q?feat:=20UserApiController=20(json?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=ED=9A=8C=EC=9B=90=EA=B0=80=EC=9E=85,=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C,=20=EC=88=98=EC=A0=95)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit UserAcceptanceTest 통과 (요구사항1 완료) NsWebTestClient 로그인 후 jsessionId 얻는 기능 추가 --- .../java/slipp/api/UserApiController.java | 57 +++++++++++++++++++ slipp/src/main/java/slipp/domain/User.java | 3 + .../main/java/slipp/dto/UserCreatedDto.java | 6 ++ .../main/java/slipp/dto/UserUpdatedDto.java | 6 ++ .../slipp/controller/UserAcceptanceTest.java | 5 +- .../java/support/test/NsWebTestClient.java | 20 ++++++- 6 files changed, 95 insertions(+), 2 deletions(-) create mode 100644 slipp/src/main/java/slipp/api/UserApiController.java diff --git a/slipp/src/main/java/slipp/api/UserApiController.java b/slipp/src/main/java/slipp/api/UserApiController.java new file mode 100644 index 00000000..52eb1ad8 --- /dev/null +++ b/slipp/src/main/java/slipp/api/UserApiController.java @@ -0,0 +1,57 @@ +package slipp.api; + +import nextstep.web.annotation.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.ResponseEntity; +import slipp.controller.UserSessionUtils; +import slipp.domain.User; +import slipp.dto.UserCreatedDto; +import slipp.dto.UserUpdatedDto; +import slipp.support.db.DataBase; + +import javax.servlet.http.HttpServletRequest; +import java.net.URI; + +@Controller +public class UserApiController { + private static final Logger logger = LoggerFactory.getLogger(UserApiController.class); + + @ResponseBody + @RequestMapping(value = "/api/users", method = RequestMethod.POST) + public ResponseEntity create(@RequestBody UserCreatedDto userCreatedDto) { + logger.debug("UserCreatedDto : {}", userCreatedDto); + + final User user = userCreatedDto.toEntity(); + DataBase.addUser(user); + + final URI uri = URI.create("/api/users/" + user.getUserId()); + return ResponseEntity.created(uri).body(user); + } + @ResponseBody + @RequestMapping(value = "/api/users/{userId}", method = RequestMethod.GET) + public ResponseEntity show(@PathVariable String userId) { + final User user = DataBase.findUserById(userId); + if (user == null) { + throw new NullPointerException("사용자를 찾을 수 없습니다."); + } + return ResponseEntity.ok(user); + } + + @ResponseBody + @RequestMapping(value = "/api/users/{userId}", method = RequestMethod.PUT) + public ResponseEntity update(@PathVariable String userId, + @RequestBody UserUpdatedDto userUpdatedDto, + HttpServletRequest req) { + final User user = DataBase.findUserById(userId); + + if (!UserSessionUtils.isSameUser(req.getSession(), user)) { + throw new IllegalStateException("다른 사용자의 정보를 수정할 수 없습니다."); + } + + final User updateUser = userUpdatedDto.toEntity(); + user.update(updateUser); + logger.debug("Update User : {}", updateUser); + return ResponseEntity.ok(user); + } +} diff --git a/slipp/src/main/java/slipp/domain/User.java b/slipp/src/main/java/slipp/domain/User.java index cd1ea758..c6a6578a 100644 --- a/slipp/src/main/java/slipp/domain/User.java +++ b/slipp/src/main/java/slipp/domain/User.java @@ -6,6 +6,9 @@ public class User { private String name; private String email; + public User() { + } + public User(String userId, String password, String name, String email) { this.userId = userId; this.password = password; diff --git a/slipp/src/main/java/slipp/dto/UserCreatedDto.java b/slipp/src/main/java/slipp/dto/UserCreatedDto.java index d0696819..ff53ab67 100644 --- a/slipp/src/main/java/slipp/dto/UserCreatedDto.java +++ b/slipp/src/main/java/slipp/dto/UserCreatedDto.java @@ -1,5 +1,7 @@ package slipp.dto; +import slipp.domain.User; + public class UserCreatedDto { private String userId; private String password; @@ -31,4 +33,8 @@ public String getName() { public String getEmail() { return email; } + + public User toEntity() { + return new User(userId, password, name, email); + } } diff --git a/slipp/src/main/java/slipp/dto/UserUpdatedDto.java b/slipp/src/main/java/slipp/dto/UserUpdatedDto.java index 9a3b425c..18706f8b 100644 --- a/slipp/src/main/java/slipp/dto/UserUpdatedDto.java +++ b/slipp/src/main/java/slipp/dto/UserUpdatedDto.java @@ -1,5 +1,7 @@ package slipp.dto; +import slipp.domain.User; + public class UserUpdatedDto { private String password; private String name; @@ -33,4 +35,8 @@ public String toString() { ", email='" + email + '\'' + '}'; } + + public User toEntity() { + return new User(null, password, name, email); + } } diff --git a/slipp/src/test/java/slipp/controller/UserAcceptanceTest.java b/slipp/src/test/java/slipp/controller/UserAcceptanceTest.java index 1620fa25..a5ce4e44 100644 --- a/slipp/src/test/java/slipp/controller/UserAcceptanceTest.java +++ b/slipp/src/test/java/slipp/controller/UserAcceptanceTest.java @@ -39,9 +39,12 @@ void crud() { assertThat(actual.getName()).isEqualTo(expected.getName()); assertThat(actual.getEmail()).isEqualTo(expected.getEmail()); + // 로그인 후 세션 얻어오기 + final String jSessionId = client.getJSessionId("pobi", "password"); + // 수정 UserUpdatedDto updateUser = new UserUpdatedDto("password2", "코난", "conan@nextstep.camp"); - client.updateResource(location, updateUser, UserUpdatedDto.class); + client.updateResource(location, updateUser, UserUpdatedDto.class, jSessionId); actual = client.getResource(location, User.class); assertThat(actual.getPassword()).isEqualTo(updateUser.getPassword()); diff --git a/slipp/src/test/java/support/test/NsWebTestClient.java b/slipp/src/test/java/support/test/NsWebTestClient.java index 5ace6527..00338712 100644 --- a/slipp/src/test/java/support/test/NsWebTestClient.java +++ b/slipp/src/test/java/support/test/NsWebTestClient.java @@ -2,6 +2,7 @@ import org.springframework.test.web.reactive.server.EntityExchangeResult; import org.springframework.test.web.reactive.server.WebTestClient; +import org.springframework.web.reactive.function.BodyInserters; import reactor.core.publisher.Mono; import java.net.URI; @@ -10,6 +11,7 @@ public class NsWebTestClient { private static final String BASE_URL = "http://localhost"; + private static final String JSESSIONID = "JSESSIONID"; private String baseUrl = BASE_URL; private int port; @@ -40,10 +42,11 @@ public URI createResource(String url, T body, Class clazz) { return response.getResponseHeaders().getLocation(); } - public void updateResource(URI location, T body, Class clazz) { + public void updateResource(URI location, T body, Class clazz, final String jSessionId) { testClientBuilder.build() .put() .uri(location.toString()) + .cookie(JSESSIONID, jSessionId) .body(Mono.just(body), clazz) .exchange() .expectStatus().isOk(); @@ -59,6 +62,21 @@ public T getResource(URI location, Class clazz) { .returnResult().getResponseBody(); } + + public String getJSessionId(final String userId, final String password) { + return testClientBuilder.build() + .post() + .uri("/users/login") + .body(BodyInserters + .fromFormData("userId", userId) + .with("password", password)) + .exchange() + .expectStatus().is3xxRedirection() + .expectBody() + .returnResult() + .getResponseCookies().get(JSESSIONID).get(0).getValue(); + } + public static NsWebTestClient of(int port) { return of(BASE_URL, port); } From 9b5b650b44bb0e8404b203b5d15b4e3e4ca1ad35 Mon Sep 17 00:00:00 2001 From: DaejunBae Date: Wed, 9 Oct 2019 00:12:12 +0900 Subject: [PATCH 09/29] =?UTF-8?q?feat:=20=EC=9A=94=EA=B5=AC=EC=82=AC?= =?UTF-8?q?=ED=95=AD=202=EB=8B=A8=EA=B3=84=20=EC=99=84=EB=A3=8C=20(?= =?UTF-8?q?=EB=A0=88=EA=B1=B0=EC=8B=9C=20=EC=BD=94=EB=93=9C=20=EC=97=86?= =?UTF-8?q?=EC=9D=B4=EB=8F=84=20=EB=8F=8C=EC=95=84=EA=B0=80=EA=B2=8C=20?= =?UTF-8?q?=ED=95=98=EA=B8=B0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../slipp/SlippWebApplicationInitializer.java | 4 +- .../java/slipp/api/UserApiController.java | 2 +- .../slipp/controller/ListUserController.java | 1 + .../slipp/controller/LoginController.java | 1 + .../slipp/controller/LogoutController.java | 1 + .../controller/UpdateFormUserController.java | 1 + .../controller/UpdateUserController.java | 1 + .../slipp/controller2/HomeControllerr.java | 19 +++++ .../slipp/controller2/LoginController.java | 1 - .../slipp/controller2/UserController.java | 81 +++++++++++++++++++ .../UserSessionUtils.java | 2 +- .../controller2/UserControllerTests.java | 44 ++++++++++ .../java/support/test/NsWebTestClient.java | 18 ++++- 13 files changed, 169 insertions(+), 7 deletions(-) create mode 100644 slipp/src/main/java/slipp/controller2/HomeControllerr.java create mode 100644 slipp/src/main/java/slipp/controller2/UserController.java rename slipp/src/main/java/slipp/{controller => controller2}/UserSessionUtils.java (96%) create mode 100644 slipp/src/test/java/slipp/controller2/UserControllerTests.java diff --git a/slipp/src/main/java/slipp/SlippWebApplicationInitializer.java b/slipp/src/main/java/slipp/SlippWebApplicationInitializer.java index 46357aa8..e06a873c 100644 --- a/slipp/src/main/java/slipp/SlippWebApplicationInitializer.java +++ b/slipp/src/main/java/slipp/SlippWebApplicationInitializer.java @@ -15,9 +15,7 @@ public class SlippWebApplicationInitializer implements WebApplicationInitializer @Override public void onStartup(ServletContext servletContext) throws ServletException { - DispatcherServlet dispatcherServlet = new DispatcherServlet( - new AnnotationHandlerMapping("slipp"), - new ManualHandlerMapping()); + DispatcherServlet dispatcherServlet = new DispatcherServlet(new AnnotationHandlerMapping("slipp")); ServletRegistration.Dynamic dispatcher = servletContext.addServlet("dispatcher", dispatcherServlet); dispatcher.setLoadOnStartup(1); diff --git a/slipp/src/main/java/slipp/api/UserApiController.java b/slipp/src/main/java/slipp/api/UserApiController.java index 52eb1ad8..a4588409 100644 --- a/slipp/src/main/java/slipp/api/UserApiController.java +++ b/slipp/src/main/java/slipp/api/UserApiController.java @@ -4,7 +4,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.ResponseEntity; -import slipp.controller.UserSessionUtils; +import slipp.controller2.UserSessionUtils; import slipp.domain.User; import slipp.dto.UserCreatedDto; import slipp.dto.UserUpdatedDto; diff --git a/slipp/src/main/java/slipp/controller/ListUserController.java b/slipp/src/main/java/slipp/controller/ListUserController.java index 3de028cb..351d4047 100644 --- a/slipp/src/main/java/slipp/controller/ListUserController.java +++ b/slipp/src/main/java/slipp/controller/ListUserController.java @@ -1,5 +1,6 @@ package slipp.controller; +import slipp.controller2.UserSessionUtils; import slipp.support.db.DataBase; import nextstep.mvc.asis.Controller; diff --git a/slipp/src/main/java/slipp/controller/LoginController.java b/slipp/src/main/java/slipp/controller/LoginController.java index e619ba4f..83852caa 100644 --- a/slipp/src/main/java/slipp/controller/LoginController.java +++ b/slipp/src/main/java/slipp/controller/LoginController.java @@ -1,5 +1,6 @@ package slipp.controller; +import slipp.controller2.UserSessionUtils; import slipp.domain.User; import slipp.support.db.DataBase; import nextstep.mvc.asis.Controller; diff --git a/slipp/src/main/java/slipp/controller/LogoutController.java b/slipp/src/main/java/slipp/controller/LogoutController.java index 7bcd0004..7bf7d574 100644 --- a/slipp/src/main/java/slipp/controller/LogoutController.java +++ b/slipp/src/main/java/slipp/controller/LogoutController.java @@ -1,6 +1,7 @@ package slipp.controller; import nextstep.mvc.asis.Controller; +import slipp.controller2.UserSessionUtils; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; diff --git a/slipp/src/main/java/slipp/controller/UpdateFormUserController.java b/slipp/src/main/java/slipp/controller/UpdateFormUserController.java index 33d982a0..bcf3fa8d 100644 --- a/slipp/src/main/java/slipp/controller/UpdateFormUserController.java +++ b/slipp/src/main/java/slipp/controller/UpdateFormUserController.java @@ -1,5 +1,6 @@ package slipp.controller; +import slipp.controller2.UserSessionUtils; import slipp.domain.User; import slipp.support.db.DataBase; import nextstep.mvc.asis.Controller; diff --git a/slipp/src/main/java/slipp/controller/UpdateUserController.java b/slipp/src/main/java/slipp/controller/UpdateUserController.java index 3c8bae78..702f6733 100644 --- a/slipp/src/main/java/slipp/controller/UpdateUserController.java +++ b/slipp/src/main/java/slipp/controller/UpdateUserController.java @@ -1,5 +1,6 @@ package slipp.controller; +import slipp.controller2.UserSessionUtils; import slipp.domain.User; import slipp.support.db.DataBase; import nextstep.mvc.asis.Controller; diff --git a/slipp/src/main/java/slipp/controller2/HomeControllerr.java b/slipp/src/main/java/slipp/controller2/HomeControllerr.java new file mode 100644 index 00000000..871616cf --- /dev/null +++ b/slipp/src/main/java/slipp/controller2/HomeControllerr.java @@ -0,0 +1,19 @@ +package slipp.controller2; + +import nextstep.web.annotation.Controller; +import nextstep.web.annotation.RequestMapping; +import nextstep.web.annotation.RequestMethod; +import slipp.support.db.DataBase; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +@Controller +public class HomeControllerr { + + @RequestMapping(value = "/", method = RequestMethod.GET) + public String index(HttpServletRequest req, HttpServletResponse resp) throws Exception { + req.setAttribute("users", DataBase.findAll()); + return "home.jsp"; + } +} diff --git a/slipp/src/main/java/slipp/controller2/LoginController.java b/slipp/src/main/java/slipp/controller2/LoginController.java index a034031b..be07eacd 100644 --- a/slipp/src/main/java/slipp/controller2/LoginController.java +++ b/slipp/src/main/java/slipp/controller2/LoginController.java @@ -4,7 +4,6 @@ import nextstep.web.annotation.Controller; import nextstep.web.annotation.RequestMapping; import nextstep.web.annotation.RequestMethod; -import slipp.controller.UserSessionUtils; import slipp.domain.User; import slipp.support.db.DataBase; diff --git a/slipp/src/main/java/slipp/controller2/UserController.java b/slipp/src/main/java/slipp/controller2/UserController.java new file mode 100644 index 00000000..64406ec0 --- /dev/null +++ b/slipp/src/main/java/slipp/controller2/UserController.java @@ -0,0 +1,81 @@ +package slipp.controller2; + + +import nextstep.mvc.tobe.ModelAndView; +import nextstep.web.annotation.Controller; +import nextstep.web.annotation.PathVariable; +import nextstep.web.annotation.RequestMapping; +import nextstep.web.annotation.RequestMethod; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import slipp.domain.User; +import slipp.dto.UserCreatedDto; +import slipp.dto.UserUpdatedDto; +import slipp.support.db.DataBase; + +import javax.servlet.http.HttpServletRequest; +import java.util.Collection; + +@Controller +public class UserController { + private static final Logger logger = LoggerFactory.getLogger(UserController.class); + + @RequestMapping(value = "/users/form", method = RequestMethod.GET) + public String createForm() { + return "/user/form.jsp"; + } + + @RequestMapping(value = "/users/create", method = RequestMethod.POST) + public String create(UserCreatedDto userCreatedDto) { + logger.debug("UserCreatedDto : {}", userCreatedDto); + + final User user = userCreatedDto.toEntity(); + DataBase.addUser(user); + + return "redirect:/"; + } + + @RequestMapping(value = "/users/profile", method = RequestMethod.GET) + public ModelAndView show(@PathVariable String userId) { + User user = DataBase.findUserById(userId); + if (user == null) { + throw new NullPointerException("사용자를 찾을 수 없습니다."); + } + return new ModelAndView("/user/profile.jsp").addObject("user", user); + } + + @RequestMapping(value = "/users", method = RequestMethod.GET) + public ModelAndView list(HttpServletRequest req) throws Exception { + if (!UserSessionUtils.isLogined(req.getSession())) { + return new ModelAndView("redirect:/users/loginForm"); + } + + final Collection users = DataBase.findAll(); + return new ModelAndView("/user/list.jsp").addObject("users", users); + } + + @RequestMapping(value = "/users/updateForm", method = RequestMethod.GET) + public ModelAndView updateForm(HttpServletRequest req, String userId) throws Exception { + User user = DataBase.findUserById(userId); + if (!UserSessionUtils.isSameUser(req.getSession(), user)) { + throw new IllegalStateException("다른 사용자의 정보를 수정할 수 없습니다."); + } + return new ModelAndView("/user/updateForm.jsp").addObject("user", user); + } + + @RequestMapping(value = "/users/update", method = RequestMethod.PUT) + public String update(@PathVariable String userId, + UserUpdatedDto userUpdatedDto, + HttpServletRequest req) { + final User user = DataBase.findUserById(userId); + + if (!UserSessionUtils.isSameUser(req.getSession(), user)) { + throw new IllegalStateException("다른 사용자의 정보를 수정할 수 없습니다."); + } + + final User updateUser = userUpdatedDto.toEntity(); + user.update(updateUser); + logger.debug("Update User : {}", updateUser); + return "redirect:/"; + } +} diff --git a/slipp/src/main/java/slipp/controller/UserSessionUtils.java b/slipp/src/main/java/slipp/controller2/UserSessionUtils.java similarity index 96% rename from slipp/src/main/java/slipp/controller/UserSessionUtils.java rename to slipp/src/main/java/slipp/controller2/UserSessionUtils.java index 0ba77127..45b534f2 100644 --- a/slipp/src/main/java/slipp/controller/UserSessionUtils.java +++ b/slipp/src/main/java/slipp/controller2/UserSessionUtils.java @@ -1,4 +1,4 @@ -package slipp.controller; +package slipp.controller2; import slipp.domain.User; diff --git a/slipp/src/test/java/slipp/controller2/UserControllerTests.java b/slipp/src/test/java/slipp/controller2/UserControllerTests.java new file mode 100644 index 00000000..4a2933cf --- /dev/null +++ b/slipp/src/test/java/slipp/controller2/UserControllerTests.java @@ -0,0 +1,44 @@ +package slipp.controller2; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import slipp.dto.UserCreatedDto; +import support.test.NsWebTestClient; + +import java.util.List; + +import static support.test.NsWebTestClient.JSESSIONID; + +class UserControllerTests { + private NsWebTestClient client; + private String jSessionId; + + @BeforeEach + void setUp() { + client = NsWebTestClient.of(8080); + + // 회원가입 + UserCreatedDto createdDto = + new UserCreatedDto("pobi", "password", "포비", "pobi@nextstep.camp"); + client.createResource("/api/users", createdDto, UserCreatedDto.class); + + // 로그인 + jSessionId = client.getJSessionId("pobi", "password"); + } + + @Test + void 로그인_X_유저목록_조회_redirect() { + client.get().uri("/users") + .exchange() + .expectStatus().is3xxRedirection() + .expectHeader().valueMatches("Location", "/users/loginForm"); + } + + @Test + void 로그인_O_유저목록_조회_성공() { + client.get().uri("/users") + .cookie(JSESSIONID, jSessionId) + .exchange() + .expectStatus().isOk(); + } +} \ No newline at end of file diff --git a/slipp/src/test/java/support/test/NsWebTestClient.java b/slipp/src/test/java/support/test/NsWebTestClient.java index 00338712..21d0169e 100644 --- a/slipp/src/test/java/support/test/NsWebTestClient.java +++ b/slipp/src/test/java/support/test/NsWebTestClient.java @@ -11,7 +11,7 @@ public class NsWebTestClient { private static final String BASE_URL = "http://localhost"; - private static final String JSESSIONID = "JSESSIONID"; + public static final String JSESSIONID = "JSESSIONID"; private String baseUrl = BASE_URL; private int port; @@ -77,6 +77,22 @@ public String getJSessionId(final String userId, final String password) { .getResponseCookies().get(JSESSIONID).get(0).getValue(); } + public WebTestClient.RequestBodyUriSpec post() { + return testClientBuilder.build().post(); + } + + public WebTestClient.RequestHeadersUriSpec get() { + return testClientBuilder.build().get(); + } + + public WebTestClient.RequestBodyUriSpec put() { + return testClientBuilder.build().put(); + } + + public WebTestClient.RequestHeadersUriSpec delete() { + return testClientBuilder.build().delete(); + } + public static NsWebTestClient of(int port) { return of(BASE_URL, port); } From 5c9bed2e604fd7f4e0c331c2304e732af5255fb0 Mon Sep 17 00:00:00 2001 From: DaejunBae Date: Wed, 9 Oct 2019 00:57:55 +0900 Subject: [PATCH 10/29] refactor: rename HttpServletHandlerMethodArgumentResolver -> HttpServletMethodArgumentResolver --- .../tobe/resolver/HandlerMethodArgumentResolverComposite.java | 2 +- ...mentResolver.java => HttpServletMethodArgumentResolver.java} | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename nextstep-mvc/src/main/java/nextstep/mvc/tobe/resolver/{HttpServletHandlerMethodArgumentResolver.java => HttpServletMethodArgumentResolver.java} (87%) diff --git a/nextstep-mvc/src/main/java/nextstep/mvc/tobe/resolver/HandlerMethodArgumentResolverComposite.java b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/resolver/HandlerMethodArgumentResolverComposite.java index 809d0742..5a08fdf0 100644 --- a/nextstep-mvc/src/main/java/nextstep/mvc/tobe/resolver/HandlerMethodArgumentResolverComposite.java +++ b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/resolver/HandlerMethodArgumentResolverComposite.java @@ -11,7 +11,7 @@ public class HandlerMethodArgumentResolverComposite implements HandlerMethodArgu private HandlerMethodArgumentResolverComposite() { resolvers.add(new PathVariableHandlerMethodArgumentResolver()); - resolvers.add(new HttpServletHandlerMethodArgumentResolver()); + resolvers.add(new HttpServletMethodArgumentResolver()); resolvers.add(new RequestBodyMethodArgumentResolver()); } diff --git a/nextstep-mvc/src/main/java/nextstep/mvc/tobe/resolver/HttpServletHandlerMethodArgumentResolver.java b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/resolver/HttpServletMethodArgumentResolver.java similarity index 87% rename from nextstep-mvc/src/main/java/nextstep/mvc/tobe/resolver/HttpServletHandlerMethodArgumentResolver.java rename to nextstep-mvc/src/main/java/nextstep/mvc/tobe/resolver/HttpServletMethodArgumentResolver.java index 90cbd00f..7bf1183e 100644 --- a/nextstep-mvc/src/main/java/nextstep/mvc/tobe/resolver/HttpServletHandlerMethodArgumentResolver.java +++ b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/resolver/HttpServletMethodArgumentResolver.java @@ -5,7 +5,7 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -public class HttpServletHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver { +public class HttpServletMethodArgumentResolver implements HandlerMethodArgumentResolver { @Override public boolean supports(final MethodParameter methodParameter) { return methodParameter.isSameType(HttpServletRequest.class) From 66cf7b360320b0ad1dcf3f04031d8c3c78b527f4 Mon Sep 17 00:00:00 2001 From: DaejunBae Date: Wed, 9 Oct 2019 01:52:58 +0900 Subject: [PATCH 11/29] =?UTF-8?q?feat:=20ViewResolver,=20View=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20=EB=B0=8F=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/nextstep/mvc/DispatcherServlet.java | 30 +++++++-------- .../java/nextstep/mvc/tobe/ModelAndView.java | 4 +- .../java/nextstep/mvc/tobe/view/JspView.java | 34 +++++++++++++++++ .../nextstep/mvc/tobe/view/RedirectView.java | 19 ++++++++++ .../InternalResourceViewResolver.java | 11 ++++++ .../tobe/viewresolver/JsonViewResolver.java | 16 ++++++++ .../tobe/viewresolver/JspViewResolver.java | 30 +++++++++++++++ .../viewresolver/RedirectViewResolver.java | 28 ++++++++++++++ .../mvc/tobe/viewresolver/ViewResolver.java | 9 +++++ .../viewresolver/ViewResolverManager.java | 38 +++++++++++++++++++ .../viewresolver/JsonViewResolverTest.java | 22 +++++++++++ .../viewresolver/JspViewResolverTest.java | 28 ++++++++++++++ .../RedirectViewResolverTest.java | 33 ++++++++++++++++ 13 files changed, 284 insertions(+), 18 deletions(-) create mode 100644 nextstep-mvc/src/main/java/nextstep/mvc/tobe/view/JspView.java create mode 100644 nextstep-mvc/src/main/java/nextstep/mvc/tobe/view/RedirectView.java create mode 100644 nextstep-mvc/src/main/java/nextstep/mvc/tobe/viewresolver/InternalResourceViewResolver.java create mode 100644 nextstep-mvc/src/main/java/nextstep/mvc/tobe/viewresolver/JsonViewResolver.java create mode 100644 nextstep-mvc/src/main/java/nextstep/mvc/tobe/viewresolver/JspViewResolver.java create mode 100644 nextstep-mvc/src/main/java/nextstep/mvc/tobe/viewresolver/RedirectViewResolver.java create mode 100644 nextstep-mvc/src/main/java/nextstep/mvc/tobe/viewresolver/ViewResolver.java create mode 100644 nextstep-mvc/src/main/java/nextstep/mvc/tobe/viewresolver/ViewResolverManager.java create mode 100644 nextstep-mvc/src/test/java/nextstep/mvc/tobe/viewresolver/JsonViewResolverTest.java create mode 100644 nextstep-mvc/src/test/java/nextstep/mvc/tobe/viewresolver/JspViewResolverTest.java create mode 100644 nextstep-mvc/src/test/java/nextstep/mvc/tobe/viewresolver/RedirectViewResolverTest.java diff --git a/nextstep-mvc/src/main/java/nextstep/mvc/DispatcherServlet.java b/nextstep-mvc/src/main/java/nextstep/mvc/DispatcherServlet.java index 5084d87a..5363157a 100644 --- a/nextstep-mvc/src/main/java/nextstep/mvc/DispatcherServlet.java +++ b/nextstep-mvc/src/main/java/nextstep/mvc/DispatcherServlet.java @@ -10,10 +10,12 @@ import nextstep.mvc.tobe.exception.HandlerAdapterNotSupportedException; import nextstep.mvc.tobe.exception.HandlerNotFoundException; import nextstep.mvc.tobe.mapping.HandlerMapping; +import nextstep.mvc.tobe.view.View; +import nextstep.mvc.tobe.viewresolver.ViewResolver; +import nextstep.mvc.tobe.viewresolver.ViewResolverManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.servlet.RequestDispatcher; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; @@ -35,14 +37,19 @@ public class DispatcherServlet extends HttpServlet { private List handlerMappings; private List handlerAdapters; + private ViewResolverManager viewResolverManager; public DispatcherServlet(HandlerMapping... handlerMappings) { this.handlerMappings = Arrays.asList(handlerMappings); this.handlerAdapters = Arrays.asList(new SimpleControllerAdapter(), new ResponseBodyAdapter(), new HandlerExecutionAdapter()); + this.viewResolverManager = new ViewResolverManager(); } @Override public void init() throws ServletException { + if (handlerMappings.isEmpty()) { + throw new IllegalArgumentException("HandlerMapping 은 최소한 하나는 있어야 합니다."); + } handlerMappings.forEach(HandlerMapping::initialize); } @@ -59,8 +66,9 @@ protected void service(HttpServletRequest req, HttpServletResponse resp) throws final ModelAndView mav = handlerAdapter.handle(webRequest, handler); // TODO ViewResolver (2단계) + final View view = viewResolverManager.resolveView(mav.getView()); - move(mav, req, resp); + view.render(mav.getModel(), req, resp); } catch (HandlerNotFoundException e) { logger.error("not support uri: {} ", req.getRequestURI()); @@ -86,20 +94,6 @@ private HandlerAdapter getHandlerAdapter(final Object handler) { .orElseThrow(HandlerAdapterNotSupportedException::new); } - private void move(ModelAndView mav, HttpServletRequest req, HttpServletResponse resp) throws Exception { - final String viewName = mav.getViewName(); - if (viewName == null) { - mav.getView().render(mav.getModel(), req, resp); - return; - } - if (viewName.startsWith(DEFAULT_REDIRECT_PREFIX)) { - resp.sendRedirect(viewName.substring(DEFAULT_REDIRECT_PREFIX.length())); - return; - } - RequestDispatcher rd = req.getRequestDispatcher(viewName); - rd.forward(req, resp); - } - public void addHandlerMapping(final HandlerMapping handlerMapping) { handlerMappings.add(handlerMapping); } @@ -107,4 +101,8 @@ public void addHandlerMapping(final HandlerMapping handlerMapping) { public void addHandlerAdapter(final HandlerAdapter handlerAdapter) { handlerAdapters.add(handlerAdapter); } + + public void addViewResolver(final ViewResolver viewResolver) { + viewResolverManager.addViewResolver(viewResolver); + } } diff --git a/nextstep-mvc/src/main/java/nextstep/mvc/tobe/ModelAndView.java b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/ModelAndView.java index 033ba58d..3c50c414 100644 --- a/nextstep-mvc/src/main/java/nextstep/mvc/tobe/ModelAndView.java +++ b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/ModelAndView.java @@ -34,8 +34,8 @@ public Map getModel() { return Collections.unmodifiableMap(model); } - public View getView() { - return view instanceof View ? (View) view : null; + public Object getView() { + return view; } public String getViewName() { diff --git a/nextstep-mvc/src/main/java/nextstep/mvc/tobe/view/JspView.java b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/view/JspView.java new file mode 100644 index 00000000..1ab9988b --- /dev/null +++ b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/view/JspView.java @@ -0,0 +1,34 @@ +package nextstep.mvc.tobe.view; + +import javax.servlet.RequestDispatcher; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.Map; +import java.util.Objects; + +public class JspView implements View { + private final String viewName; + + public JspView(final String viewName) { + this.viewName = viewName; + } + + @Override + public void render(final Map model, final HttpServletRequest request, final HttpServletResponse response) throws Exception { + RequestDispatcher rd = request.getRequestDispatcher(viewName); + rd.forward(request, response); + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + final JspView jspView = (JspView) o; + return Objects.equals(viewName, jspView.viewName); + } + + @Override + public int hashCode() { + return Objects.hash(viewName); + } +} diff --git a/nextstep-mvc/src/main/java/nextstep/mvc/tobe/view/RedirectView.java b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/view/RedirectView.java new file mode 100644 index 00000000..49b23704 --- /dev/null +++ b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/view/RedirectView.java @@ -0,0 +1,19 @@ +package nextstep.mvc.tobe.view; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.Map; + +public class RedirectView implements View { + + private final String viewName; + + public RedirectView(final String viewName) { + this.viewName = viewName; + } + + @Override + public void render(final Map model, final HttpServletRequest request, final HttpServletResponse response) throws Exception { + response.sendRedirect(viewName); + } +} diff --git a/nextstep-mvc/src/main/java/nextstep/mvc/tobe/viewresolver/InternalResourceViewResolver.java b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/viewresolver/InternalResourceViewResolver.java new file mode 100644 index 00000000..6e40d6fa --- /dev/null +++ b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/viewresolver/InternalResourceViewResolver.java @@ -0,0 +1,11 @@ +package nextstep.mvc.tobe.viewresolver; + +public abstract class InternalResourceViewResolver implements ViewResolver { + private final String prefix; + private final String suffix; + + public InternalResourceViewResolver(final String prefix, final String suffix) { + this.prefix = prefix; + this.suffix = suffix; + } +} diff --git a/nextstep-mvc/src/main/java/nextstep/mvc/tobe/viewresolver/JsonViewResolver.java b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/viewresolver/JsonViewResolver.java new file mode 100644 index 00000000..aea80c75 --- /dev/null +++ b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/viewresolver/JsonViewResolver.java @@ -0,0 +1,16 @@ +package nextstep.mvc.tobe.viewresolver; + +import nextstep.mvc.tobe.view.JsonView; +import nextstep.mvc.tobe.view.View; + +public class JsonViewResolver implements ViewResolver { + @Override + public boolean supports(final Object view) { + return view instanceof JsonView; + } + + @Override + public View resolveView(final Object view) { + return (JsonView) view; + } +} diff --git a/nextstep-mvc/src/main/java/nextstep/mvc/tobe/viewresolver/JspViewResolver.java b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/viewresolver/JspViewResolver.java new file mode 100644 index 00000000..06642af9 --- /dev/null +++ b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/viewresolver/JspViewResolver.java @@ -0,0 +1,30 @@ +package nextstep.mvc.tobe.viewresolver; + +import nextstep.mvc.tobe.view.JspView; +import nextstep.mvc.tobe.view.View; + +import static nextstep.mvc.tobe.viewresolver.RedirectViewResolver.DEFAULT_REDIRECT_PREFIX; + +public class JspViewResolver extends InternalResourceViewResolver { + private static final String DEFAULT_PREFIX = ""; + private static final String DEFAULT_SUFFIX = ".jsp"; + + public JspViewResolver() { + this(DEFAULT_PREFIX, DEFAULT_SUFFIX); + } + + public JspViewResolver(final String prefix, final String suffix) { + super(prefix, suffix); + } + + @Override + public boolean supports(final Object view) { + return view instanceof String && !((String) view).startsWith(DEFAULT_REDIRECT_PREFIX); + } + + @Override + public View resolveView(final Object view) { + final String viewName = DEFAULT_PREFIX + view + DEFAULT_SUFFIX; + return new JspView(viewName); + } +} diff --git a/nextstep-mvc/src/main/java/nextstep/mvc/tobe/viewresolver/RedirectViewResolver.java b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/viewresolver/RedirectViewResolver.java new file mode 100644 index 00000000..b858bcdd --- /dev/null +++ b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/viewresolver/RedirectViewResolver.java @@ -0,0 +1,28 @@ +package nextstep.mvc.tobe.viewresolver; + +import nextstep.mvc.tobe.view.RedirectView; +import nextstep.mvc.tobe.view.View; + +public class RedirectViewResolver implements ViewResolver { + static final String DEFAULT_REDIRECT_PREFIX = "redirect:"; + + @Override + public boolean supports(final Object view) { + if (view instanceof RedirectView) { + return true; + } + if (view instanceof String) { + return String.valueOf(view).startsWith(DEFAULT_REDIRECT_PREFIX); + } + return false; + } + + @Override + public View resolveView(final Object view) { + if (view instanceof RedirectView) { + return (RedirectView) view; + } + final String viewName = String.valueOf(view).substring(DEFAULT_REDIRECT_PREFIX.length()); + return new RedirectView(viewName); + } +} diff --git a/nextstep-mvc/src/main/java/nextstep/mvc/tobe/viewresolver/ViewResolver.java b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/viewresolver/ViewResolver.java new file mode 100644 index 00000000..d7ab736a --- /dev/null +++ b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/viewresolver/ViewResolver.java @@ -0,0 +1,9 @@ +package nextstep.mvc.tobe.viewresolver; + +import nextstep.mvc.tobe.view.View; + +public interface ViewResolver { + boolean supports(final Object view); + + View resolveView(final Object view); +} diff --git a/nextstep-mvc/src/main/java/nextstep/mvc/tobe/viewresolver/ViewResolverManager.java b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/viewresolver/ViewResolverManager.java new file mode 100644 index 00000000..191da4d4 --- /dev/null +++ b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/viewresolver/ViewResolverManager.java @@ -0,0 +1,38 @@ +package nextstep.mvc.tobe.viewresolver; + +import nextstep.mvc.tobe.view.View; + +import java.util.Arrays; +import java.util.List; + +public class ViewResolverManager implements ViewResolver { + private List viewResolvers; + private InternalResourceViewResolver internalResourceViewResolver; + + public ViewResolverManager() { + this.viewResolvers = Arrays.asList(new JsonViewResolver(), new RedirectViewResolver()); + this.internalResourceViewResolver = new JspViewResolver(); + } + + @Override + public boolean supports(final Object view) { + return true; + } + + @Override + public View resolveView(final Object view) { + return viewResolvers.stream() + .filter(viewResolver -> viewResolver.supports(view)) + .findFirst() + .map(viewResolver -> viewResolver.resolveView(view)) + .orElse(internalResourceViewResolver.resolveView(view)); + } + + public void addViewResolver(final ViewResolver viewResolver) { + viewResolvers.add(viewResolver); + } + + public void changeInternalResourceResolver(final InternalResourceViewResolver internalResourceResolver) { + this.internalResourceViewResolver = internalResourceResolver; + } +} diff --git a/nextstep-mvc/src/test/java/nextstep/mvc/tobe/viewresolver/JsonViewResolverTest.java b/nextstep-mvc/src/test/java/nextstep/mvc/tobe/viewresolver/JsonViewResolverTest.java new file mode 100644 index 00000000..a1de5d7d --- /dev/null +++ b/nextstep-mvc/src/test/java/nextstep/mvc/tobe/viewresolver/JsonViewResolverTest.java @@ -0,0 +1,22 @@ +package nextstep.mvc.tobe.viewresolver; + +import nextstep.mvc.tobe.view.JsonView; +import nextstep.mvc.tobe.view.RedirectView; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; +import static org.assertj.core.api.Assertions.assertThat; + +class JsonViewResolverTest { + private JsonViewResolver viewResolver = new JsonViewResolver(); + + @Test + void supports_JsonView_경우_true() { + assertThat(viewResolver.supports(new JsonView())).isTrue(); + } + + @Test + void supports_JsonView_아닌_경우_false() { + assertThat(viewResolver.supports(new RedirectView("asd"))).isFalse(); + } +} \ No newline at end of file diff --git a/nextstep-mvc/src/test/java/nextstep/mvc/tobe/viewresolver/JspViewResolverTest.java b/nextstep-mvc/src/test/java/nextstep/mvc/tobe/viewresolver/JspViewResolverTest.java new file mode 100644 index 00000000..67fa9f95 --- /dev/null +++ b/nextstep-mvc/src/test/java/nextstep/mvc/tobe/viewresolver/JspViewResolverTest.java @@ -0,0 +1,28 @@ +package nextstep.mvc.tobe.viewresolver; + +import nextstep.mvc.tobe.view.JsonView; +import nextstep.mvc.tobe.view.JspView; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; +import static org.assertj.core.api.Assertions.assertThat; + +class JspViewResolverTest { + private JspViewResolver viewResolver = new JspViewResolver(); + + @Test + void supports_string_이면_true() { + assertThat(viewResolver.supports("foo")).isTrue(); + } + + @Test + void supports_jspView_아닌경우_false() { + assertThat(viewResolver.supports(new JsonView())).isFalse(); + } + + @Test + void prefix_suffix_적용_확인() { + assertThat(viewResolver.resolveView("foo")).isEqualTo(new JspView("foo.jsp")); + } +} \ No newline at end of file diff --git a/nextstep-mvc/src/test/java/nextstep/mvc/tobe/viewresolver/RedirectViewResolverTest.java b/nextstep-mvc/src/test/java/nextstep/mvc/tobe/viewresolver/RedirectViewResolverTest.java new file mode 100644 index 00000000..0468933c --- /dev/null +++ b/nextstep-mvc/src/test/java/nextstep/mvc/tobe/viewresolver/RedirectViewResolverTest.java @@ -0,0 +1,33 @@ +package nextstep.mvc.tobe.viewresolver; + +import nextstep.mvc.tobe.view.JsonView; +import nextstep.mvc.tobe.view.RedirectView; +import org.junit.jupiter.api.Test; + +import static nextstep.mvc.tobe.viewresolver.RedirectViewResolver.DEFAULT_REDIRECT_PREFIX; +import static org.assertj.core.api.Assertions.assertThat; + +class RedirectViewResolverTest { + private RedirectViewResolver viewResolver = new RedirectViewResolver(); + + @Test + void supports_RedirectView_이면_true() { + assertThat(viewResolver.supports(new RedirectView("foo"))).isTrue(); + } + + @Test + void supports_prefix가_redirect_이면_true() { + assertThat(viewResolver.supports(DEFAULT_REDIRECT_PREFIX + "/")).isTrue(); + } + + @Test + void supports_유효_하지않은_타입_false() { + assertThat(viewResolver.supports(new JsonView())).isFalse(); + } + + @Test + void resolveView_String_이면_Redirect_반환() { + assertThat(viewResolver.resolveView(DEFAULT_REDIRECT_PREFIX + "/")).isInstanceOf(RedirectView.class); + + } +} \ No newline at end of file From dad9448a338d9bb6d1535244deffcaabe04e5446 Mon Sep 17 00:00:00 2001 From: DaejunBae Date: Wed, 9 Oct 2019 01:53:33 +0900 Subject: [PATCH 12/29] =?UTF-8?q?refactor:=20ViewResolver=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=EC=97=90=20=EB=94=B0=EB=A5=B8=20Controller=20viewName?= =?UTF-8?q?=20suffix=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/slipp/controller2/HomeControllerr.java | 2 +- .../src/main/java/slipp/controller2/LoginController.java | 6 +++--- slipp/src/main/java/slipp/controller2/UserController.java | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/slipp/src/main/java/slipp/controller2/HomeControllerr.java b/slipp/src/main/java/slipp/controller2/HomeControllerr.java index 871616cf..54ef4a20 100644 --- a/slipp/src/main/java/slipp/controller2/HomeControllerr.java +++ b/slipp/src/main/java/slipp/controller2/HomeControllerr.java @@ -14,6 +14,6 @@ public class HomeControllerr { @RequestMapping(value = "/", method = RequestMethod.GET) public String index(HttpServletRequest req, HttpServletResponse resp) throws Exception { req.setAttribute("users", DataBase.findAll()); - return "home.jsp"; + return "home"; } } diff --git a/slipp/src/main/java/slipp/controller2/LoginController.java b/slipp/src/main/java/slipp/controller2/LoginController.java index be07eacd..1019795d 100644 --- a/slipp/src/main/java/slipp/controller2/LoginController.java +++ b/slipp/src/main/java/slipp/controller2/LoginController.java @@ -19,7 +19,7 @@ public ModelAndView login(final HttpServletRequest req, final String userId, fin User user = DataBase.findUserById(userId); if (user == null) { req.setAttribute("loginFailed", true); - return new ModelAndView("/user/login.jsp"); + return new ModelAndView("/user/login"); } if (user.matchPassword(password)) { HttpSession session = req.getSession(); @@ -27,7 +27,7 @@ public ModelAndView login(final HttpServletRequest req, final String userId, fin return new ModelAndView("redirect:/"); } else { req.setAttribute("loginFailed", true); - return new ModelAndView("/user/login.jsp"); + return new ModelAndView("/user/login"); } } @RequestMapping(value = "/users/logout", method = RequestMethod.GET) @@ -39,6 +39,6 @@ public ModelAndView logout(HttpServletRequest req, HttpServletResponse resp) thr @RequestMapping(value = "/users/loginForm", method = RequestMethod.GET) public String form() { - return "/user/login.jsp"; + return "/user/login"; } } diff --git a/slipp/src/main/java/slipp/controller2/UserController.java b/slipp/src/main/java/slipp/controller2/UserController.java index 64406ec0..c080da45 100644 --- a/slipp/src/main/java/slipp/controller2/UserController.java +++ b/slipp/src/main/java/slipp/controller2/UserController.java @@ -22,7 +22,7 @@ public class UserController { @RequestMapping(value = "/users/form", method = RequestMethod.GET) public String createForm() { - return "/user/form.jsp"; + return "/user/form"; } @RequestMapping(value = "/users/create", method = RequestMethod.POST) @@ -41,7 +41,7 @@ public ModelAndView show(@PathVariable String userId) { if (user == null) { throw new NullPointerException("사용자를 찾을 수 없습니다."); } - return new ModelAndView("/user/profile.jsp").addObject("user", user); + return new ModelAndView("/user/profile").addObject("user", user); } @RequestMapping(value = "/users", method = RequestMethod.GET) @@ -51,7 +51,7 @@ public ModelAndView list(HttpServletRequest req) throws Exception { } final Collection users = DataBase.findAll(); - return new ModelAndView("/user/list.jsp").addObject("users", users); + return new ModelAndView("/user/list").addObject("users", users); } @RequestMapping(value = "/users/updateForm", method = RequestMethod.GET) @@ -60,7 +60,7 @@ public ModelAndView updateForm(HttpServletRequest req, String userId) throws Exc if (!UserSessionUtils.isSameUser(req.getSession(), user)) { throw new IllegalStateException("다른 사용자의 정보를 수정할 수 없습니다."); } - return new ModelAndView("/user/updateForm.jsp").addObject("user", user); + return new ModelAndView("/user/updateForm").addObject("user", user); } @RequestMapping(value = "/users/update", method = RequestMethod.PUT) From ac2a4fde5b18b3045fd6bc1132d2caaaa54da28f Mon Sep 17 00:00:00 2001 From: DaejunBae Date: Wed, 9 Oct 2019 02:05:48 +0900 Subject: [PATCH 13/29] =?UTF-8?q?refactor:=20MethodParameter=20getAnnotati?= =?UTF-8?q?on()=20=EC=B6=94=EA=B0=80=20(=EC=BA=A1=EC=8A=90=ED=99=94)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/nextstep/mvc/tobe/resolver/MethodParameter.java | 5 +++++ .../resolver/PathVariableHandlerMethodArgumentResolver.java | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/nextstep-mvc/src/main/java/nextstep/mvc/tobe/resolver/MethodParameter.java b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/resolver/MethodParameter.java index 82ba55fb..72327086 100644 --- a/nextstep-mvc/src/main/java/nextstep/mvc/tobe/resolver/MethodParameter.java +++ b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/resolver/MethodParameter.java @@ -22,6 +22,11 @@ public boolean isAnnotationPresent(final Class annotation) return parameter.isAnnotationPresent(annotation); } + + public T getAnnotation(final Class annotation) { + return method.getAnnotation(annotation); + } + public Parameter getParameter() { return parameter; } diff --git a/nextstep-mvc/src/main/java/nextstep/mvc/tobe/resolver/PathVariableHandlerMethodArgumentResolver.java b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/resolver/PathVariableHandlerMethodArgumentResolver.java index 835c3294..eb5111d2 100644 --- a/nextstep-mvc/src/main/java/nextstep/mvc/tobe/resolver/PathVariableHandlerMethodArgumentResolver.java +++ b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/resolver/PathVariableHandlerMethodArgumentResolver.java @@ -18,7 +18,7 @@ public boolean supports(final MethodParameter methodParameter) { @Override public Object resolveArgument(final WebRequest webRequest, final MethodParameter methodParameter) { final String uri = webRequest.getRequestURI(); - final String path = methodParameter.getMethod().getAnnotation(RequestMapping.class).value(); + final String path = methodParameter.getAnnotation(RequestMapping.class).value(); final PathPattern pathPattern = PathPatternUtils.parse(path); final String value = Objects.requireNonNull(pathPattern From c1e086f5e0b45906d4226977c5ec0f1c3939a991 Mon Sep 17 00:00:00 2001 From: DaejunBae Date: Wed, 9 Oct 2019 02:17:07 +0900 Subject: [PATCH 14/29] =?UTF-8?q?refactor:=20DefaultHandlerMethodArgumentR?= =?UTF-8?q?esolver=20-=20=EB=91=90=EA=B0=9C=EB=A1=9C=20=EB=82=98=EB=88=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. Object 전용 ObjectMethodArgumentResolver 2. Primitive, Wrapper 전용 PrimitiveMethodArgumentResolver (적절한 이름이 생각이 안났다..) --- ...andlerMethodArgumentResolverComposite.java | 5 +- ...java => ObjectMethodArgumentResolver.java} | 41 +++---- .../PrimitiveMethodArgumentResolver.java | 21 ++++ .../ObjectMethodArgumentResolverTest.java | 106 ++++++++++++++++++ ... PrimitiveMethodArgumentResolverTest.java} | 76 ++----------- 5 files changed, 157 insertions(+), 92 deletions(-) rename nextstep-mvc/src/main/java/nextstep/mvc/tobe/resolver/{DefaultHandlerMethodArgumentResolver.java => ObjectMethodArgumentResolver.java} (53%) create mode 100644 nextstep-mvc/src/main/java/nextstep/mvc/tobe/resolver/PrimitiveMethodArgumentResolver.java create mode 100644 nextstep-mvc/src/test/java/nextstep/mvc/tobe/resolver/ObjectMethodArgumentResolverTest.java rename nextstep-mvc/src/test/java/nextstep/mvc/tobe/resolver/{DefaultHandlerMethodArgumentResolverTest.java => PrimitiveMethodArgumentResolverTest.java} (58%) diff --git a/nextstep-mvc/src/main/java/nextstep/mvc/tobe/resolver/HandlerMethodArgumentResolverComposite.java b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/resolver/HandlerMethodArgumentResolverComposite.java index 5a08fdf0..2ce3f32e 100644 --- a/nextstep-mvc/src/main/java/nextstep/mvc/tobe/resolver/HandlerMethodArgumentResolverComposite.java +++ b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/resolver/HandlerMethodArgumentResolverComposite.java @@ -7,12 +7,13 @@ public class HandlerMethodArgumentResolverComposite implements HandlerMethodArgumentResolver { private final List resolvers = new ArrayList<>(); - private final HandlerMethodArgumentResolver defaultResolver = new DefaultHandlerMethodArgumentResolver(); + private final HandlerMethodArgumentResolver defaultResolver = new ObjectMethodArgumentResolver(); private HandlerMethodArgumentResolverComposite() { resolvers.add(new PathVariableHandlerMethodArgumentResolver()); resolvers.add(new HttpServletMethodArgumentResolver()); resolvers.add(new RequestBodyMethodArgumentResolver()); + resolvers.add(new PrimitiveMethodArgumentResolver()); } @Override @@ -34,7 +35,7 @@ public void addHandlerMethodArgumentResolver(final HandlerMethodArgumentResolver resolvers.add(resolver); } - public static HandlerMethodArgumentResolverComposite getInstance(){ + public static HandlerMethodArgumentResolverComposite getInstance() { return LazyHolder.INSTANCE; } diff --git a/nextstep-mvc/src/main/java/nextstep/mvc/tobe/resolver/DefaultHandlerMethodArgumentResolver.java b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/resolver/ObjectMethodArgumentResolver.java similarity index 53% rename from nextstep-mvc/src/main/java/nextstep/mvc/tobe/resolver/DefaultHandlerMethodArgumentResolver.java rename to nextstep-mvc/src/main/java/nextstep/mvc/tobe/resolver/ObjectMethodArgumentResolver.java index fde0215f..2fa24e94 100644 --- a/nextstep-mvc/src/main/java/nextstep/mvc/tobe/resolver/DefaultHandlerMethodArgumentResolver.java +++ b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/resolver/ObjectMethodArgumentResolver.java @@ -9,55 +9,46 @@ import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Parameter; import java.util.Optional; import java.util.stream.Stream; -// todo primitive - javabean 나누기 -public class DefaultHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver { +public class ObjectMethodArgumentResolver implements HandlerMethodArgumentResolver { @Override public boolean supports(final MethodParameter methodParameter) { - return true; + return methodParameter.isSameType(Object.class); } @Override public Object resolveArgument(final WebRequest webRequest, final MethodParameter methodParameter) { - final HttpServletRequest request = webRequest.getRequest(); - final String value = request.getParameter(methodParameter.getName()); - final Parameter parameter = methodParameter.getParameter(); - - if (TypeConverter.contains(parameter.getType())) { - return TypeConverter.to(parameter.getType()).apply(value); - } - - return javaBean(request, methodParameter); - } - - private Object javaBean(final HttpServletRequest request, final MethodParameter methodParameter) { try { + final HttpServletRequest request = webRequest.getRequest(); final Field[] fields = methodParameter.getType().getDeclaredFields(); + final Optional> allArgsConstructor = getAllArgsConstructor(methodParameter, fields); - final Optional> allArgsConstructor = Stream.of(methodParameter.getType().getConstructors()) - .filter(x -> x.getParameterCount() == fields.length) - .findAny(); - - return allArgsConstructor.isPresent() ? - createAllArgsConstructor(request, fields, allArgsConstructor.get()) - : createDefaultConstructor(request, fields, methodParameter); + if (allArgsConstructor.isPresent()) { + return createByAllArgsConstructor(request, fields, allArgsConstructor.get()); + } + return createByDefaultConstructor(request, fields, methodParameter); } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { throw new HandlerMethodArgumentResolverException(e.getMessage()); } } - private Object createAllArgsConstructor(final HttpServletRequest request, final Field[] fields, final Constructor constructor) throws IllegalAccessException, InvocationTargetException, InstantiationException { + private Optional> getAllArgsConstructor(final MethodParameter methodParameter, final Field[] fields) { + return Stream.of(methodParameter.getType().getConstructors()) + .filter(x -> x.getParameterCount() == fields.length) + .findAny(); + } + + private Object createByAllArgsConstructor(final HttpServletRequest request, final Field[] fields, final Constructor constructor) throws IllegalAccessException, InvocationTargetException, InstantiationException { final Object[] values = Stream.of(fields) .map(field -> parseValue(field, request)) .toArray(); return constructor.newInstance(values); } - private Object createDefaultConstructor(final HttpServletRequest request, final Field[] fields, final MethodParameter methodParameter) throws InstantiationException, IllegalAccessException { + private Object createByDefaultConstructor(final HttpServletRequest request, final Field[] fields, final MethodParameter methodParameter) throws InstantiationException, IllegalAccessException { final Object instance = methodParameter.getType().newInstance(); for (final Field field : fields) { final Object value = parseValue(field, request); diff --git a/nextstep-mvc/src/main/java/nextstep/mvc/tobe/resolver/PrimitiveMethodArgumentResolver.java b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/resolver/PrimitiveMethodArgumentResolver.java new file mode 100644 index 00000000..5fdd4123 --- /dev/null +++ b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/resolver/PrimitiveMethodArgumentResolver.java @@ -0,0 +1,21 @@ +package nextstep.mvc.tobe.resolver; + +import nextstep.mvc.tobe.WebRequest; +import nextstep.utils.TypeConverter; + +import javax.servlet.http.HttpServletRequest; + +public class PrimitiveMethodArgumentResolver implements HandlerMethodArgumentResolver { + + @Override + public boolean supports(final MethodParameter methodParameter) { + return TypeConverter.contains(methodParameter.getType()); + } + + @Override + public Object resolveArgument(final WebRequest webRequest, final MethodParameter methodParameter) { + final HttpServletRequest request = webRequest.getRequest(); + final String value = methodParameter.getName(); + return TypeConverter.to(methodParameter.getType()).apply(request.getParameter(value)); + } +} diff --git a/nextstep-mvc/src/test/java/nextstep/mvc/tobe/resolver/ObjectMethodArgumentResolverTest.java b/nextstep-mvc/src/test/java/nextstep/mvc/tobe/resolver/ObjectMethodArgumentResolverTest.java new file mode 100644 index 00000000..3f8d1540 --- /dev/null +++ b/nextstep-mvc/src/test/java/nextstep/mvc/tobe/resolver/ObjectMethodArgumentResolverTest.java @@ -0,0 +1,106 @@ +package nextstep.mvc.tobe.resolver; + +import nextstep.mvc.tobe.TestUser; +import nextstep.mvc.tobe.WebRequest; +import nextstep.mvc.tobe.WebRequestContext; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.mock.web.MockHttpServletRequest; + +import java.lang.reflect.Executable; +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.util.Arrays; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static org.assertj.core.api.Java6Assertions.assertThat; + +class ObjectMethodArgumentResolverTest { + private ObjectMethodArgumentResolver resolver = new ObjectMethodArgumentResolver(); + private Class clazz = this.getClass(); + private Map, Parameter> params; + private MockHttpServletRequest request; + private WebRequest webRequest; + private Method method; + + private String id = "1"; + private String age = "20"; + private String userId = "javajigi"; + private String password = "password"; + private String bool = "true"; + + @BeforeEach + void setUp() throws NoSuchMethodException { + method = clazz.getDeclaredMethod("sampleJavaBean", TestUser.class, OnlyDefaultConstructorJavaBean.class); + + params = Stream.of(clazz.getDeclaredMethods()) + .filter(method -> method.getName().startsWith("sample")) + .map(Executable::getParameters) + .flatMap(Arrays::stream) + .collect(Collectors.toMap(Parameter::getType, x -> x)); + + request = new MockHttpServletRequest(); + request.addParameter("userId", userId); + request.addParameter("password", password); + request.addParameter("id", id); + request.addParameter("age", age); + request.addParameter("bool", bool); + + webRequest = new WebRequestContext(request, null); + } + + @Test + void 모든_필드_생성자_javaBean_매핑() { + // given + final MethodParameter methodParameter = new MethodParameter(params.get(TestUser.class), "testUser", 1, method); + + // when + final TestUser actual = (TestUser) resolver.resolveArgument(webRequest, methodParameter); + + // then + assertThat(actual.getUserId()).isEqualTo(userId); + assertThat(actual.getPassword()).isEqualTo(password); + assertThat(actual.getAge()).isEqualTo(Long.parseLong(age)); + } + + @Test + void 기본생성자만_있는_javaBean_매핑() { + // given + final MethodParameter methodParameter = new MethodParameter(params.get(OnlyDefaultConstructorJavaBean.class), "javaBean", 1, method); + + // when + final OnlyDefaultConstructorJavaBean actual = (OnlyDefaultConstructorJavaBean) resolver.resolveArgument(webRequest, methodParameter); + + // then + assertThat(actual.getUserId()).isEqualTo(userId); + assertThat(actual.getPassword()).isEqualTo(password); + assertThat(actual.getAge()).isEqualTo(Long.parseLong(age)); + } + + void sampleJavaBean(final TestUser testUser, final OnlyDefaultConstructorJavaBean javaBean) { + } + +} + +class OnlyDefaultConstructorJavaBean { + private String userId; + private String password; + private int age; + + public OnlyDefaultConstructorJavaBean() { + } + + public String getUserId() { + return userId; + } + + public String getPassword() { + return password; + } + + public int getAge() { + return age; + } +} \ No newline at end of file diff --git a/nextstep-mvc/src/test/java/nextstep/mvc/tobe/resolver/DefaultHandlerMethodArgumentResolverTest.java b/nextstep-mvc/src/test/java/nextstep/mvc/tobe/resolver/PrimitiveMethodArgumentResolverTest.java similarity index 58% rename from nextstep-mvc/src/test/java/nextstep/mvc/tobe/resolver/DefaultHandlerMethodArgumentResolverTest.java rename to nextstep-mvc/src/test/java/nextstep/mvc/tobe/resolver/PrimitiveMethodArgumentResolverTest.java index 78a44540..0a8cbb54 100644 --- a/nextstep-mvc/src/test/java/nextstep/mvc/tobe/resolver/DefaultHandlerMethodArgumentResolverTest.java +++ b/nextstep-mvc/src/test/java/nextstep/mvc/tobe/resolver/PrimitiveMethodArgumentResolverTest.java @@ -1,8 +1,8 @@ package nextstep.mvc.tobe.resolver; -import nextstep.mvc.tobe.TestUser; import nextstep.mvc.tobe.WebRequest; import nextstep.mvc.tobe.WebRequestContext; +import org.assertj.core.api.Java6Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.mock.web.MockHttpServletRequest; @@ -15,11 +15,8 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import static org.assertj.core.api.Java6Assertions.assertThat; - -class DefaultHandlerMethodArgumentResolverTest { - private DefaultHandlerMethodArgumentResolver resolver = new DefaultHandlerMethodArgumentResolver(); - private Class clazz = DefaultHandlerMethodArgumentResolverTest.class; +class PrimitiveMethodArgumentResolverTest { + private PrimitiveMethodArgumentResolver resolver = new PrimitiveMethodArgumentResolver(); private Map, Parameter> params; private MockHttpServletRequest request; private WebRequest webRequest; @@ -33,9 +30,9 @@ class DefaultHandlerMethodArgumentResolverTest { @BeforeEach void setUp() throws NoSuchMethodException { - method = clazz.getDeclaredMethod("sampleWrappers", Long.class, Integer.class, String.class); + method = this.getClass().getDeclaredMethod("sampleWrappers", Long.class, Integer.class, String.class); - params = Stream.of(clazz.getDeclaredMethods()) + params = Stream.of(this.getClass().getDeclaredMethods()) .filter(method -> method.getName().startsWith("sample")) .map(Executable::getParameters) .flatMap(Arrays::stream) @@ -51,34 +48,6 @@ void setUp() throws NoSuchMethodException { webRequest = new WebRequestContext(request, null); } - @Test - void 모든_필드_생성자_javaBean_매핑() { - // given - final MethodParameter methodParameter = new MethodParameter(params.get(TestUser.class), "testUser", 1, method); - - // when - final TestUser actual = (TestUser) resolver.resolveArgument(webRequest, methodParameter); - - // then - assertThat(actual.getUserId()).isEqualTo(userId); - assertThat(actual.getPassword()).isEqualTo(password); - assertThat(actual.getAge()).isEqualTo(Long.parseLong(age)); - } - - @Test - void 기본생성자만_있는_javaBean_매핑() { - // given - final MethodParameter methodParameter = new MethodParameter(params.get(OnlyDefaultConstructorJavaBean.class), "javaBean", 1, method); - - // when - final OnlyDefaultConstructorJavaBean actual = (OnlyDefaultConstructorJavaBean) resolver.resolveArgument(webRequest, methodParameter); - - // then - assertThat(actual.getUserId()).isEqualTo(userId); - assertThat(actual.getPassword()).isEqualTo(password); - assertThat(actual.getAge()).isEqualTo(Long.parseLong(age)); - } - @Test void long_매핑() { // given @@ -88,7 +57,7 @@ void setUp() throws NoSuchMethodException { final Object actual = resolver.resolveArgument(webRequest, methodParameter); // then - assertThat(actual).isEqualTo(Long.parseLong(id)); + Java6Assertions.assertThat(actual).isEqualTo(Long.parseLong(id)); } @Test @@ -100,7 +69,7 @@ void setUp() throws NoSuchMethodException { final Object actual = resolver.resolveArgument(webRequest, methodParameter); // then - assertThat(actual).isEqualTo(Integer.parseInt(age)); + Java6Assertions.assertThat(actual).isEqualTo(Integer.parseInt(age)); } @Test @@ -111,7 +80,7 @@ void setUp() throws NoSuchMethodException { final Object actual = resolver.resolveArgument(webRequest, methodParameter); // then - assertThat(actual).isEqualTo(Boolean.parseBoolean(bool)); + Java6Assertions.assertThat(actual).isEqualTo(Boolean.parseBoolean(bool)); } @Test @@ -123,7 +92,7 @@ void setUp() throws NoSuchMethodException { final Object actual = resolver.resolveArgument(webRequest, methodParameter); // then - assertThat(actual).isEqualTo(Long.parseLong(id)); + Java6Assertions.assertThat(actual).isEqualTo(Long.parseLong(id)); } @Test @@ -135,7 +104,7 @@ void setUp() throws NoSuchMethodException { final Object actual = resolver.resolveArgument(webRequest, methodParameter); // then - assertThat(actual).isEqualTo(Integer.parseInt(id)); + Java6Assertions.assertThat(actual).isEqualTo(Integer.parseInt(id)); } @Test @@ -147,36 +116,13 @@ void setUp() throws NoSuchMethodException { final Object actual = resolver.resolveArgument(webRequest, methodParameter); // then - assertThat(actual).isEqualTo(userId); + Java6Assertions.assertThat(actual).isEqualTo(userId); } void samplePrimitive(final long id, final int age, final boolean bool) { } - void sampleJavaBean(final TestUser testUser, final OnlyDefaultConstructorJavaBean javaBean) { - } void sampleWrappers(final Long id, final Integer age, final String userId) { } -} - -class OnlyDefaultConstructorJavaBean { - private String userId; - private String password; - private int age; - - public OnlyDefaultConstructorJavaBean() { - } - - public String getUserId() { - return userId; - } - - public String getPassword() { - return password; - } - - public int getAge() { - return age; - } } \ No newline at end of file From d88b0f9987913b18c9489cba4d360860a8b8f297 Mon Sep 17 00:00:00 2001 From: DaejunBae Date: Wed, 9 Oct 2019 02:21:07 +0900 Subject: [PATCH 15/29] =?UTF-8?q?style:=20=ED=8F=AC=EB=A7=B7=ED=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/nextstep/mvc/DispatcherServlet.java | 2 -- .../mvc/tobe/handler/HandlerExecution.java | 2 +- .../main/java/slipp/ManualHandlerMapping.java | 32 +++++++++---------- .../java/slipp/api/UserApiController.java | 1 + .../slipp/controller2/LoginController.java | 1 + .../slipp/controller2/UserController.java | 4 +-- 6 files changed, 21 insertions(+), 21 deletions(-) diff --git a/nextstep-mvc/src/main/java/nextstep/mvc/DispatcherServlet.java b/nextstep-mvc/src/main/java/nextstep/mvc/DispatcherServlet.java index 5363157a..69e7d0b3 100644 --- a/nextstep-mvc/src/main/java/nextstep/mvc/DispatcherServlet.java +++ b/nextstep-mvc/src/main/java/nextstep/mvc/DispatcherServlet.java @@ -33,7 +33,6 @@ public class DispatcherServlet extends HttpServlet { private static final long serialVersionUID = 1L; private static final Logger logger = LoggerFactory.getLogger(DispatcherServlet.class); - private static final String DEFAULT_REDIRECT_PREFIX = "redirect:"; private List handlerMappings; private List handlerAdapters; @@ -65,7 +64,6 @@ protected void service(HttpServletRequest req, HttpServletResponse resp) throws final ModelAndView mav = handlerAdapter.handle(webRequest, handler); - // TODO ViewResolver (2단계) final View view = viewResolverManager.resolveView(mav.getView()); view.render(mav.getModel(), req, resp); diff --git a/nextstep-mvc/src/main/java/nextstep/mvc/tobe/handler/HandlerExecution.java b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/handler/HandlerExecution.java index 7848c574..b9480b03 100644 --- a/nextstep-mvc/src/main/java/nextstep/mvc/tobe/handler/HandlerExecution.java +++ b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/handler/HandlerExecution.java @@ -26,7 +26,7 @@ public Method getMethod() { return method; } - public boolean isAnnotationPresent(final Class annotation){ + public boolean isAnnotationPresent(final Class annotation) { return method.isAnnotationPresent(annotation); } } diff --git a/slipp/src/main/java/slipp/ManualHandlerMapping.java b/slipp/src/main/java/slipp/ManualHandlerMapping.java index 93318a0a..3b574f0f 100644 --- a/slipp/src/main/java/slipp/ManualHandlerMapping.java +++ b/slipp/src/main/java/slipp/ManualHandlerMapping.java @@ -1,9 +1,9 @@ package slipp; import nextstep.mvc.DispatcherServlet; -import nextstep.mvc.tobe.mapping.HandlerMapping; import nextstep.mvc.asis.Controller; import nextstep.mvc.asis.ForwardController; +import nextstep.mvc.tobe.mapping.HandlerMapping; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import slipp.controller.*; @@ -18,21 +18,21 @@ public class ManualHandlerMapping implements HandlerMapping { @Override public void initialize() { - mappings.put("/", new HomeController()); - mappings.put("/users/form", new ForwardController("/user/form.jsp")); - mappings.put("/users/loginForm", new ForwardController("/user/login.jsp")); - mappings.put("/users", new ListUserController()); - mappings.put("/users/login", new LoginController()); - mappings.put("/users/profile", new ProfileController()); - mappings.put("/users/logout", new LogoutController()); - mappings.put("/users/create", new CreateUserController()); - mappings.put("/users/updateForm", new UpdateFormUserController()); - mappings.put("/users/update", new UpdateUserController()); - - logger.info("Initialized Request Mapping!"); - mappings.keySet().forEach(path -> { - logger.info("Path : {}, Controller : {}", path, mappings.get(path).getClass()); - }); +// mappings.put("/", new HomeController()); +// mappings.put("/users/form", new ForwardController("/user/form.jsp")); +// mappings.put("/users/loginForm", new ForwardController("/user/login.jsp")); +// mappings.put("/users", new ListUserController()); +// mappings.put("/users/login", new LoginController()); +// mappings.put("/users/profile", new ProfileController()); +// mappings.put("/users/logout", new LogoutController()); +// mappings.put("/users/create", new CreateUserController()); +// mappings.put("/users/updateForm", new UpdateFormUserController()); +// mappings.put("/users/update", new UpdateUserController()); +// +// logger.info("Initialized Request Mapping!"); +// mappings.keySet().forEach(path -> { +// logger.info("Path : {}, Controller : {}", path, mappings.get(path).getClass()); +// }); } @Override diff --git a/slipp/src/main/java/slipp/api/UserApiController.java b/slipp/src/main/java/slipp/api/UserApiController.java index a4588409..4d9040f9 100644 --- a/slipp/src/main/java/slipp/api/UserApiController.java +++ b/slipp/src/main/java/slipp/api/UserApiController.java @@ -28,6 +28,7 @@ public ResponseEntity create(@RequestBody UserCreatedDto userCreatedDto) { final URI uri = URI.create("/api/users/" + user.getUserId()); return ResponseEntity.created(uri).body(user); } + @ResponseBody @RequestMapping(value = "/api/users/{userId}", method = RequestMethod.GET) public ResponseEntity show(@PathVariable String userId) { diff --git a/slipp/src/main/java/slipp/controller2/LoginController.java b/slipp/src/main/java/slipp/controller2/LoginController.java index 1019795d..cc5ee0f0 100644 --- a/slipp/src/main/java/slipp/controller2/LoginController.java +++ b/slipp/src/main/java/slipp/controller2/LoginController.java @@ -30,6 +30,7 @@ public ModelAndView login(final HttpServletRequest req, final String userId, fin return new ModelAndView("/user/login"); } } + @RequestMapping(value = "/users/logout", method = RequestMethod.GET) public ModelAndView logout(HttpServletRequest req, HttpServletResponse resp) throws Exception { HttpSession session = req.getSession(); diff --git a/slipp/src/main/java/slipp/controller2/UserController.java b/slipp/src/main/java/slipp/controller2/UserController.java index c080da45..33cd1731 100644 --- a/slipp/src/main/java/slipp/controller2/UserController.java +++ b/slipp/src/main/java/slipp/controller2/UserController.java @@ -65,8 +65,8 @@ public ModelAndView updateForm(HttpServletRequest req, String userId) throws Exc @RequestMapping(value = "/users/update", method = RequestMethod.PUT) public String update(@PathVariable String userId, - UserUpdatedDto userUpdatedDto, - HttpServletRequest req) { + UserUpdatedDto userUpdatedDto, + HttpServletRequest req) { final User user = DataBase.findUserById(userId); if (!UserSessionUtils.isSameUser(req.getSession(), user)) { From aedf5789c85cb5ac7f4efb5dd5d982fd103c1759 Mon Sep 17 00:00:00 2001 From: DaejunBae Date: Wed, 9 Oct 2019 02:21:20 +0900 Subject: [PATCH 16/29] docs: update README.md --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e042f67b..af015c75 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,9 @@ - [x] DispatcherServlet 에 적용하기 - [ ] 성능 측정 ServletFilter 구현해보기 - [x] /users/{id} - url 요청 가능하게 하기 - +- [ ] handlerMappings, adapters 등.. 포장하기 +- [x] 모든 클래스 이름 점검 (ex. HandlerMethodArgumentResolver) +- [ ] ResponseEntity 직접 구현하기 ## 추가 미션 - [ ] 다른 템플릿 엔진 지원하기 - [ ] 인터셉터 구현하기 From 67106944ca359381b3d62a9271ca7c246060eea7 Mon Sep 17 00:00:00 2001 From: DaejunBae Date: Wed, 9 Oct 2019 02:30:48 +0900 Subject: [PATCH 17/29] feat: AbstractRequestMappingAdapter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ResponseBodyAdapter, HandlerExecutionAdapter 중복제거를 위해서 템플릿 메소드 패턴 적용 --- .../AbstractRequestMappingAdapter.java | 33 +++++++++++++++++++ .../tobe/adapter/HandlerExecutionAdapter.java | 25 ++------------ .../tobe/adapter/RequestMappingAdapter.java | 4 +++ .../mvc/tobe/adapter/ResponseBodyAdapter.java | 22 ++----------- 4 files changed, 41 insertions(+), 43 deletions(-) create mode 100644 nextstep-mvc/src/main/java/nextstep/mvc/tobe/adapter/AbstractRequestMappingAdapter.java create mode 100644 nextstep-mvc/src/main/java/nextstep/mvc/tobe/adapter/RequestMappingAdapter.java diff --git a/nextstep-mvc/src/main/java/nextstep/mvc/tobe/adapter/AbstractRequestMappingAdapter.java b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/adapter/AbstractRequestMappingAdapter.java new file mode 100644 index 00000000..1e951332 --- /dev/null +++ b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/adapter/AbstractRequestMappingAdapter.java @@ -0,0 +1,33 @@ +package nextstep.mvc.tobe.adapter; + +import nextstep.mvc.tobe.ModelAndView; +import nextstep.mvc.tobe.WebRequest; +import nextstep.mvc.tobe.handler.HandlerExecution; +import nextstep.mvc.tobe.resolver.HandlerMethodArgumentResolverComposite; +import nextstep.mvc.tobe.resolver.MethodParameters; + +public abstract class AbstractRequestMappingAdapter implements HandlerAdapter { + private HandlerMethodArgumentResolverComposite argumentResolvers; + + public AbstractRequestMappingAdapter() { + argumentResolvers = HandlerMethodArgumentResolverComposite.getInstance(); + } + + @Override + public ModelAndView handle(final WebRequest webRequest, final Object handler) throws Exception { + final HandlerExecution handlerExecution = (HandlerExecution) handler; + final MethodParameters methodParameters = new MethodParameters(handlerExecution.getMethod()); + + final Object[] values = methodParameters.getMethodParams().stream() + .map(parameter -> argumentResolvers.resolveArgument(webRequest, parameter)) + .toArray(); + + final Object result = handlerExecution.execute(values); + + return resolveReturn(result); + } + + protected abstract ModelAndView resolveReturn(final Object result); + + +} diff --git a/nextstep-mvc/src/main/java/nextstep/mvc/tobe/adapter/HandlerExecutionAdapter.java b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/adapter/HandlerExecutionAdapter.java index aaf97c4b..309784aa 100644 --- a/nextstep-mvc/src/main/java/nextstep/mvc/tobe/adapter/HandlerExecutionAdapter.java +++ b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/adapter/HandlerExecutionAdapter.java @@ -1,33 +1,12 @@ package nextstep.mvc.tobe.adapter; import nextstep.mvc.tobe.ModelAndView; -import nextstep.mvc.tobe.WebRequest; import nextstep.mvc.tobe.handler.HandlerExecution; -import nextstep.mvc.tobe.resolver.HandlerMethodArgumentResolverComposite; -import nextstep.mvc.tobe.resolver.MethodParameters; -public class HandlerExecutionAdapter implements HandlerAdapter { - private HandlerMethodArgumentResolverComposite argumentResolvers; - - public HandlerExecutionAdapter() { - argumentResolvers = HandlerMethodArgumentResolverComposite.getInstance(); - } +public class HandlerExecutionAdapter extends AbstractRequestMappingAdapter { @Override - public ModelAndView handle(final WebRequest webRequest, final Object handler) throws Exception { - final HandlerExecution handlerExecution = (HandlerExecution) handler; - final MethodParameters methodParameters = new MethodParameters(handlerExecution.getMethod()); - - final Object[] values = methodParameters.getMethodParams().stream() - .map(parameter -> argumentResolvers.resolveArgument(webRequest, parameter)) - .toArray(); - - final Object result = handlerExecution.execute(values); - - return parseModelAndView(result); - } - - private ModelAndView parseModelAndView(final Object result) { + protected ModelAndView resolveReturn(final Object result) { if (result instanceof String) { return new ModelAndView(String.valueOf(result)); } diff --git a/nextstep-mvc/src/main/java/nextstep/mvc/tobe/adapter/RequestMappingAdapter.java b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/adapter/RequestMappingAdapter.java new file mode 100644 index 00000000..f338d072 --- /dev/null +++ b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/adapter/RequestMappingAdapter.java @@ -0,0 +1,4 @@ +package nextstep.mvc.tobe.adapter; + +public interface RequestMappingAdapter extends HandlerAdapter { +} diff --git a/nextstep-mvc/src/main/java/nextstep/mvc/tobe/adapter/ResponseBodyAdapter.java b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/adapter/ResponseBodyAdapter.java index 401ec533..0b7285df 100644 --- a/nextstep-mvc/src/main/java/nextstep/mvc/tobe/adapter/ResponseBodyAdapter.java +++ b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/adapter/ResponseBodyAdapter.java @@ -1,32 +1,14 @@ package nextstep.mvc.tobe.adapter; import nextstep.mvc.tobe.ModelAndView; -import nextstep.mvc.tobe.WebRequest; import nextstep.mvc.tobe.handler.HandlerExecution; -import nextstep.mvc.tobe.resolver.HandlerMethodArgumentResolverComposite; -import nextstep.mvc.tobe.resolver.MethodParameters; import nextstep.mvc.tobe.view.JsonView; import nextstep.web.annotation.ResponseBody; -public class ResponseBodyAdapter implements HandlerAdapter { - private HandlerMethodArgumentResolverComposite argumentResolvers; +public class ResponseBodyAdapter extends AbstractRequestMappingAdapter { - public ResponseBodyAdapter() { - argumentResolvers = HandlerMethodArgumentResolverComposite.getInstance(); - } - - // todo 추상화로 중복 제거 or 합쳐야하나? @Override - public ModelAndView handle(final WebRequest webRequest, final Object handler) throws Exception { - final HandlerExecution handlerExecution = (HandlerExecution) handler; - final MethodParameters methodParameters = new MethodParameters(handlerExecution.getMethod()); - - final Object[] values = methodParameters.getMethodParams().stream() - .map(parameter -> argumentResolvers.resolveArgument(webRequest, parameter)) - .toArray(); - - final Object result = handlerExecution.execute(values); - + protected ModelAndView resolveReturn(final Object result) { return new ModelAndView(new JsonView(result)); } From 2f6a6025be56af5bc5b16197b097ab5b736e8f2e Mon Sep 17 00:00:00 2001 From: DaejunBae Date: Wed, 9 Oct 2019 02:31:24 +0900 Subject: [PATCH 18/29] =?UTF-8?q?style:=20JsonView=20=EB=B6=88=ED=95=84?= =?UTF-8?q?=EC=9A=94=ED=95=9C=20=EC=A3=BC=EC=84=9D=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/nextstep/mvc/tobe/view/JsonView.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/nextstep-mvc/src/main/java/nextstep/mvc/tobe/view/JsonView.java b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/view/JsonView.java index 554ca3d0..02987721 100644 --- a/nextstep-mvc/src/main/java/nextstep/mvc/tobe/view/JsonView.java +++ b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/view/JsonView.java @@ -25,7 +25,6 @@ public JsonView(final Object object) { this.object = object; } - // todo 리팩토링 @Override public void render(final Map model, final HttpServletRequest request, final HttpServletResponse response) throws Exception { response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE); @@ -54,8 +53,7 @@ private void setBody(final Object body, final Map model, final HttpSe final PrintWriter writer = response.getWriter(); if (Objects.isNotNull(body)) { - final String json = OBJECT_MAPPER.writeValueAsString(body); - writer.println(json); + writer.println(OBJECT_MAPPER.writeValueAsString(body)); } writer.println(convert(model)); writer.flush(); From 44342f42c50821aec4dbac6f4df4e2ed0c45c128 Mon Sep 17 00:00:00 2001 From: DaejunBae Date: Wed, 9 Oct 2019 23:45:59 +0900 Subject: [PATCH 19/29] =?UTF-8?q?refactor:=20ObjectMethodArgumentResolver?= =?UTF-8?q?=20=EA=B8=B0=EB=B3=B8=20=EC=83=9D=EC=84=B1=EC=9E=90=EB=A1=9C?= =?UTF-8?q?=EB=A7=8C=20=EB=A7=A4=ED=95=91=ED=95=98=EA=B2=8C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ObjectMethodArgumentResolver.java | 45 +++++-------------- .../test/java/nextstep/mvc/tobe/TestUser.java | 3 ++ ...erMethodArgumentResolverCompositeTest.java | 2 +- .../ObjectMethodArgumentResolverTest.java | 18 +------- 4 files changed, 17 insertions(+), 51 deletions(-) diff --git a/nextstep-mvc/src/main/java/nextstep/mvc/tobe/resolver/ObjectMethodArgumentResolver.java b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/resolver/ObjectMethodArgumentResolver.java index 2fa24e94..5dc6631a 100644 --- a/nextstep-mvc/src/main/java/nextstep/mvc/tobe/resolver/ObjectMethodArgumentResolver.java +++ b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/resolver/ObjectMethodArgumentResolver.java @@ -6,11 +6,7 @@ import nextstep.utils.TypeConverter; import javax.servlet.http.HttpServletRequest; -import java.lang.reflect.Constructor; import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.util.Optional; -import java.util.stream.Stream; public class ObjectMethodArgumentResolver implements HandlerMethodArgumentResolver { @@ -23,39 +19,20 @@ public boolean supports(final MethodParameter methodParameter) { public Object resolveArgument(final WebRequest webRequest, final MethodParameter methodParameter) { try { final HttpServletRequest request = webRequest.getRequest(); - final Field[] fields = methodParameter.getType().getDeclaredFields(); - final Optional> allArgsConstructor = getAllArgsConstructor(methodParameter, fields); - - if (allArgsConstructor.isPresent()) { - return createByAllArgsConstructor(request, fields, allArgsConstructor.get()); + final Class parameterType = methodParameter.getType(); + final Field[] fields = parameterType.getDeclaredFields(); + final Object instance = parameterType.newInstance(); + + for (final Field field : fields) { + final Object value = parseValue(field, request); + field.setAccessible(true); + field.set(instance, value); } - return createByDefaultConstructor(request, fields, methodParameter); - } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { - throw new HandlerMethodArgumentResolverException(e.getMessage()); - } - } - private Optional> getAllArgsConstructor(final MethodParameter methodParameter, final Field[] fields) { - return Stream.of(methodParameter.getType().getConstructors()) - .filter(x -> x.getParameterCount() == fields.length) - .findAny(); - } - - private Object createByAllArgsConstructor(final HttpServletRequest request, final Field[] fields, final Constructor constructor) throws IllegalAccessException, InvocationTargetException, InstantiationException { - final Object[] values = Stream.of(fields) - .map(field -> parseValue(field, request)) - .toArray(); - return constructor.newInstance(values); - } - - private Object createByDefaultConstructor(final HttpServletRequest request, final Field[] fields, final MethodParameter methodParameter) throws InstantiationException, IllegalAccessException { - final Object instance = methodParameter.getType().newInstance(); - for (final Field field : fields) { - final Object value = parseValue(field, request); - field.setAccessible(true); - field.set(instance, value); + return instance; + } catch (InstantiationException | IllegalAccessException e) { + throw new HandlerMethodArgumentResolverException(e.getMessage()); } - return instance; } private Object parseValue(final Field field, final HttpServletRequest request) { diff --git a/nextstep-mvc/src/test/java/nextstep/mvc/tobe/TestUser.java b/nextstep-mvc/src/test/java/nextstep/mvc/tobe/TestUser.java index 5976e062..9c22e48d 100644 --- a/nextstep-mvc/src/test/java/nextstep/mvc/tobe/TestUser.java +++ b/nextstep-mvc/src/test/java/nextstep/mvc/tobe/TestUser.java @@ -5,6 +5,9 @@ public class TestUser { private String password; private int age; + public TestUser() { + } + public TestUser(String userId, String password, int age) { this.userId = userId; this.password = password; diff --git a/nextstep-mvc/src/test/java/nextstep/mvc/tobe/resolver/HandlerMethodArgumentResolverCompositeTest.java b/nextstep-mvc/src/test/java/nextstep/mvc/tobe/resolver/HandlerMethodArgumentResolverCompositeTest.java index 8f456057..512545c7 100644 --- a/nextstep-mvc/src/test/java/nextstep/mvc/tobe/resolver/HandlerMethodArgumentResolverCompositeTest.java +++ b/nextstep-mvc/src/test/java/nextstep/mvc/tobe/resolver/HandlerMethodArgumentResolverCompositeTest.java @@ -75,7 +75,7 @@ void create_javabean() throws Exception { } @Test - void show_pathvariable() throws Exception { + void show_pathVariable() throws Exception { // given final Method method = getMethod("show_pathvariable", clazz.getMethods()); request.setRequestURI("/users/10"); diff --git a/nextstep-mvc/src/test/java/nextstep/mvc/tobe/resolver/ObjectMethodArgumentResolverTest.java b/nextstep-mvc/src/test/java/nextstep/mvc/tobe/resolver/ObjectMethodArgumentResolverTest.java index 3f8d1540..0e2d045c 100644 --- a/nextstep-mvc/src/test/java/nextstep/mvc/tobe/resolver/ObjectMethodArgumentResolverTest.java +++ b/nextstep-mvc/src/test/java/nextstep/mvc/tobe/resolver/ObjectMethodArgumentResolverTest.java @@ -33,7 +33,7 @@ class ObjectMethodArgumentResolverTest { @BeforeEach void setUp() throws NoSuchMethodException { - method = clazz.getDeclaredMethod("sampleJavaBean", TestUser.class, OnlyDefaultConstructorJavaBean.class); + method = clazz.getDeclaredMethod("sampleJavaBean", OnlyDefaultConstructorJavaBean.class); params = Stream.of(clazz.getDeclaredMethods()) .filter(method -> method.getName().startsWith("sample")) @@ -51,20 +51,6 @@ void setUp() throws NoSuchMethodException { webRequest = new WebRequestContext(request, null); } - @Test - void 모든_필드_생성자_javaBean_매핑() { - // given - final MethodParameter methodParameter = new MethodParameter(params.get(TestUser.class), "testUser", 1, method); - - // when - final TestUser actual = (TestUser) resolver.resolveArgument(webRequest, methodParameter); - - // then - assertThat(actual.getUserId()).isEqualTo(userId); - assertThat(actual.getPassword()).isEqualTo(password); - assertThat(actual.getAge()).isEqualTo(Long.parseLong(age)); - } - @Test void 기본생성자만_있는_javaBean_매핑() { // given @@ -79,7 +65,7 @@ void setUp() throws NoSuchMethodException { assertThat(actual.getAge()).isEqualTo(Long.parseLong(age)); } - void sampleJavaBean(final TestUser testUser, final OnlyDefaultConstructorJavaBean javaBean) { + void sampleJavaBean(final OnlyDefaultConstructorJavaBean javaBean) { } } From 09c5dcc6b6d0dc7b2fd7cf4e1b7c8361889b2638 Mon Sep 17 00:00:00 2001 From: DaejunBae Date: Wed, 9 Oct 2019 23:47:07 +0900 Subject: [PATCH 20/29] =?UTF-8?q?refactor:=20AbstractRequestMappingAdapter?= =?UTF-8?q?=20-=20RequestMappingAdapter=20=EC=83=81=EC=86=8D=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mvc/tobe/adapter/AbstractRequestMappingAdapter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nextstep-mvc/src/main/java/nextstep/mvc/tobe/adapter/AbstractRequestMappingAdapter.java b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/adapter/AbstractRequestMappingAdapter.java index 1e951332..8bd9a462 100644 --- a/nextstep-mvc/src/main/java/nextstep/mvc/tobe/adapter/AbstractRequestMappingAdapter.java +++ b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/adapter/AbstractRequestMappingAdapter.java @@ -6,7 +6,7 @@ import nextstep.mvc.tobe.resolver.HandlerMethodArgumentResolverComposite; import nextstep.mvc.tobe.resolver.MethodParameters; -public abstract class AbstractRequestMappingAdapter implements HandlerAdapter { +public abstract class AbstractRequestMappingAdapter implements RequestMappingAdapter { private HandlerMethodArgumentResolverComposite argumentResolvers; public AbstractRequestMappingAdapter() { From 52c24cd9d991c29ad49c70beb750e338ba317951 Mon Sep 17 00:00:00 2001 From: DaejunBae Date: Thu, 10 Oct 2019 00:17:15 +0900 Subject: [PATCH 21/29] feat: HandlerInterceptor --- .../mvc/tobe/interceptor/HandlerInterceptor.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 nextstep-mvc/src/main/java/nextstep/mvc/tobe/interceptor/HandlerInterceptor.java diff --git a/nextstep-mvc/src/main/java/nextstep/mvc/tobe/interceptor/HandlerInterceptor.java b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/interceptor/HandlerInterceptor.java new file mode 100644 index 00000000..c8b1129c --- /dev/null +++ b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/interceptor/HandlerInterceptor.java @@ -0,0 +1,16 @@ +package nextstep.mvc.tobe.interceptor; + +import nextstep.mvc.tobe.ModelAndView; +import org.springframework.lang.Nullable; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +public interface HandlerInterceptor { + default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + return true; + } + + default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception { + } +} From ac3adfd3d7b32411a894f4443de6ee959a132069 Mon Sep 17 00:00:00 2001 From: DaejunBae Date: Thu, 10 Oct 2019 00:40:18 +0900 Subject: [PATCH 22/29] feat: InterceptorRegistration --- .../interceptor/InterceptorRegistration.java | 54 ++++++++++++++++ .../java/nextstep/utils/PathPatternUtils.java | 14 ++++- .../InterceptorRegistrationTest.java | 61 +++++++++++++++++++ 3 files changed, 126 insertions(+), 3 deletions(-) create mode 100644 nextstep-mvc/src/main/java/nextstep/mvc/tobe/interceptor/InterceptorRegistration.java create mode 100644 nextstep-mvc/src/test/java/nextstep/mvc/tobe/interceptor/InterceptorRegistrationTest.java diff --git a/nextstep-mvc/src/main/java/nextstep/mvc/tobe/interceptor/InterceptorRegistration.java b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/interceptor/InterceptorRegistration.java new file mode 100644 index 00000000..695f12fb --- /dev/null +++ b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/interceptor/InterceptorRegistration.java @@ -0,0 +1,54 @@ +package nextstep.mvc.tobe.interceptor; + +import nextstep.utils.PathPatternUtils; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class InterceptorRegistration { + private final HandlerInterceptor interceptor; + private List includePatters = new ArrayList<>(); + private List excludePatterns = new ArrayList<>(); + + private InterceptorRegistration(final HandlerInterceptor interceptor) { + this.interceptor = interceptor; + } + + public static InterceptorRegistration from(final HandlerInterceptor interceptor) { + return new InterceptorRegistration(interceptor); + } + + public InterceptorRegistration addPathPatterns(final String... patterns) { + return addPathPatterns(Arrays.asList(patterns)); + } + + public InterceptorRegistration addPathPatterns(final List patterns) { + includePatters.addAll(patterns); + return this; + } + + public InterceptorRegistration excludePathPatterns(final String... patterns) { + return excludePathPatterns(Arrays.asList(patterns)); + } + + public InterceptorRegistration excludePathPatterns(final List patterns) { + excludePatterns.addAll(patterns); + return this; + } + + public boolean match(final String path) { + final boolean isNotIncludePath = includePatters.stream() + .noneMatch(pattern -> PathPatternUtils.match(pattern, path)); + + if (isNotIncludePath) { + return false; + } + return excludePatterns.stream() + .noneMatch(pattern -> PathPatternUtils.match(pattern, path)); + } + + public HandlerInterceptor getInterceptor(){ + return interceptor; + } +} diff --git a/nextstep-mvc/src/main/java/nextstep/utils/PathPatternUtils.java b/nextstep-mvc/src/main/java/nextstep/utils/PathPatternUtils.java index ad9a848c..00a18dae 100644 --- a/nextstep-mvc/src/main/java/nextstep/utils/PathPatternUtils.java +++ b/nextstep-mvc/src/main/java/nextstep/utils/PathPatternUtils.java @@ -1,24 +1,32 @@ package nextstep.utils; import org.springframework.http.server.PathContainer; +import org.springframework.util.AntPathMatcher; +import org.springframework.util.PathMatcher; import org.springframework.web.util.pattern.PathPattern; import org.springframework.web.util.pattern.PathPatternParser; public class PathPatternUtils { - public static final PathPatternParser PATH_PATTERN_PARSER = new PathPatternParser(); + private static final PathPatternParser PATH_PATTERN_PARSER = new PathPatternParser(); + private static final PathMatcher PATH_MATCHER = new AntPathMatcher(); private PathPatternUtils() { } - public static PathPattern parse(String path) { + public static PathPattern parse(final String path) { PATH_PATTERN_PARSER.setMatchOptionalTrailingSeparator(true); return PATH_PATTERN_PARSER.parse(path); } - public static PathContainer toPathContainer(String path) { + public static PathContainer toPathContainer(final String path) { if (path == null) { return null; } return PathContainer.parsePath(path); } + + public static boolean match(String pattern, String path){ + final boolean match = PATH_MATCHER.match(pattern, path); + return match; + } } diff --git a/nextstep-mvc/src/test/java/nextstep/mvc/tobe/interceptor/InterceptorRegistrationTest.java b/nextstep-mvc/src/test/java/nextstep/mvc/tobe/interceptor/InterceptorRegistrationTest.java new file mode 100644 index 00000000..3ab8b8bb --- /dev/null +++ b/nextstep-mvc/src/test/java/nextstep/mvc/tobe/interceptor/InterceptorRegistrationTest.java @@ -0,0 +1,61 @@ +package nextstep.mvc.tobe.interceptor; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +class InterceptorRegistrationTest { + private HandlerInterceptor interceptor; + private List includePatters; + private List excludePatterns; + private InterceptorRegistration registration; + + @BeforeEach + void setUp() { + interceptor = new HandlerInterceptor() { + }; + includePatters = Arrays.asList("/users/**", "/user/**/edit"); + excludePatterns = Arrays.asList("/users/login"); + + registration = InterceptorRegistration.from(interceptor) + .addPathPatterns(includePatters) + .excludePathPatterns(excludePatterns); + } + + @Test + @DisplayName("includePattern만 일치하는 경우 true") + void matchTest01() { + final String url = "/users/id"; + + assertThat(registration.match(url)).isTrue(); + } + + @Test + @DisplayName("includePattern '**' 중앙에 있는 경우 true 확인") + void matchTest02() { + final String url = "/user/1/edit"; + + assertThat(registration.match(url)).isTrue(); + } + + @Test + @DisplayName("includePattern만 일치하지 않으면 false") + void matchTest03() { + final String url = "/user"; + + assertThat(registration.match(url)).isFalse(); + } + + @Test + @DisplayName("includePattern, excludePattern 둘 다 일치 false") + void matchTest04() { + final String url = "/users/login"; + + assertThat(registration.match(url)).isFalse(); + } +} \ No newline at end of file From 6f370a3b4a62b2dec68f03d9ca6ada41de74122d Mon Sep 17 00:00:00 2001 From: DaejunBae Date: Thu, 10 Oct 2019 00:58:46 +0900 Subject: [PATCH 23/29] feat: InterceptorRegistry --- .../tobe/interceptor/InterceptorRegistry.java | 22 ++++++++ .../interceptor/InterceptorRegistryTest.java | 55 +++++++++++++++++++ 2 files changed, 77 insertions(+) create mode 100644 nextstep-mvc/src/main/java/nextstep/mvc/tobe/interceptor/InterceptorRegistry.java create mode 100644 nextstep-mvc/src/test/java/nextstep/mvc/tobe/interceptor/InterceptorRegistryTest.java diff --git a/nextstep-mvc/src/main/java/nextstep/mvc/tobe/interceptor/InterceptorRegistry.java b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/interceptor/InterceptorRegistry.java new file mode 100644 index 00000000..031938f2 --- /dev/null +++ b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/interceptor/InterceptorRegistry.java @@ -0,0 +1,22 @@ +package nextstep.mvc.tobe.interceptor; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +public class InterceptorRegistry { + private List registrations = new ArrayList<>(); + + public InterceptorRegistration addInterceptor(final HandlerInterceptor interceptor) { + final InterceptorRegistration registration = InterceptorRegistration.from(interceptor); + registrations.add(registration); + return registration; + } + + public List getHandlerInterceptors(final String path){ + return registrations.stream() + .filter(registration -> registration.match(path)) + .map(InterceptorRegistration::getInterceptor) + .collect(Collectors.toList()); + } +} diff --git a/nextstep-mvc/src/test/java/nextstep/mvc/tobe/interceptor/InterceptorRegistryTest.java b/nextstep-mvc/src/test/java/nextstep/mvc/tobe/interceptor/InterceptorRegistryTest.java new file mode 100644 index 00000000..7fc2c405 --- /dev/null +++ b/nextstep-mvc/src/test/java/nextstep/mvc/tobe/interceptor/InterceptorRegistryTest.java @@ -0,0 +1,55 @@ +package nextstep.mvc.tobe.interceptor; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +class InterceptorRegistryTest { + private InterceptorRegistry registry = new InterceptorRegistry(); + private HandlerInterceptor interceptor1; + private HandlerInterceptor interceptor2; + + @BeforeEach + void setUp() { + interceptor1 = new HandlerInterceptor() { + }; + interceptor2 = new HandlerInterceptor() { + }; + + registry.addInterceptor(interceptor1) + .addPathPatterns("/users") + .addPathPatterns("/users/**") + .excludePathPatterns("/users/login"); + + registry.addInterceptor(interceptor2) + .addPathPatterns("/users") + .addPathPatterns("/users/**"); + } + + @Test + @DisplayName("path 일치하는 인터셉터 여러개 가져오는 테스트") + void test01() { + final List actual = registry.getHandlerInterceptors("/users"); + + assertThat(actual) + .hasSize(2) + .contains(interceptor1) + .contains(interceptor2); + } + + @Test + @DisplayName("PathPatterns 은 모두 일치하지만 excludePathPatterns 도 일치하는 인터셉터가 있는 경우") + void test02() { + final List actual = registry.getHandlerInterceptors("/users/login"); + + assertThat(actual) + .hasSize(1) + .contains(interceptor2); + } + + +} \ No newline at end of file From 1de15eb9919c17de3202a22c66e3b9dddbed53b7 Mon Sep 17 00:00:00 2001 From: DaejunBae Date: Thu, 10 Oct 2019 01:32:41 +0900 Subject: [PATCH 24/29] =?UTF-8?q?fix:=20ObjectMethodArgumentResolver=20pri?= =?UTF-8?q?vate=20=EA=B8=B0=EB=B3=B8=EC=83=9D=EC=84=B1=EC=9E=90=EB=8F=84?= =?UTF-8?q?=20=EC=83=9D=EC=84=B1=EA=B0=80=EB=8A=A5=ED=95=98=EA=B2=8C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ObjectMethodArgumentResolver.java | 9 ++++- .../ObjectMethodArgumentResolverTest.java | 40 +++++++++++++++++-- 2 files changed, 44 insertions(+), 5 deletions(-) diff --git a/nextstep-mvc/src/main/java/nextstep/mvc/tobe/resolver/ObjectMethodArgumentResolver.java b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/resolver/ObjectMethodArgumentResolver.java index 5dc6631a..ffc75fff 100644 --- a/nextstep-mvc/src/main/java/nextstep/mvc/tobe/resolver/ObjectMethodArgumentResolver.java +++ b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/resolver/ObjectMethodArgumentResolver.java @@ -6,7 +6,9 @@ import nextstep.utils.TypeConverter; import javax.servlet.http.HttpServletRequest; +import java.lang.reflect.Constructor; import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; public class ObjectMethodArgumentResolver implements HandlerMethodArgumentResolver { @@ -21,7 +23,10 @@ public Object resolveArgument(final WebRequest webRequest, final MethodParameter final HttpServletRequest request = webRequest.getRequest(); final Class parameterType = methodParameter.getType(); final Field[] fields = parameterType.getDeclaredFields(); - final Object instance = parameterType.newInstance(); + + final Constructor constructor = parameterType.getDeclaredConstructor(); + constructor.setAccessible(true); + final Object instance = constructor.newInstance(); for (final Field field : fields) { final Object value = parseValue(field, request); @@ -30,7 +35,7 @@ public Object resolveArgument(final WebRequest webRequest, final MethodParameter } return instance; - } catch (InstantiationException | IllegalAccessException e) { + } catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) { throw new HandlerMethodArgumentResolverException(e.getMessage()); } } diff --git a/nextstep-mvc/src/test/java/nextstep/mvc/tobe/resolver/ObjectMethodArgumentResolverTest.java b/nextstep-mvc/src/test/java/nextstep/mvc/tobe/resolver/ObjectMethodArgumentResolverTest.java index 0e2d045c..2e9143e7 100644 --- a/nextstep-mvc/src/test/java/nextstep/mvc/tobe/resolver/ObjectMethodArgumentResolverTest.java +++ b/nextstep-mvc/src/test/java/nextstep/mvc/tobe/resolver/ObjectMethodArgumentResolverTest.java @@ -1,6 +1,5 @@ package nextstep.mvc.tobe.resolver; -import nextstep.mvc.tobe.TestUser; import nextstep.mvc.tobe.WebRequest; import nextstep.mvc.tobe.WebRequestContext; import org.junit.jupiter.api.BeforeEach; @@ -33,7 +32,7 @@ class ObjectMethodArgumentResolverTest { @BeforeEach void setUp() throws NoSuchMethodException { - method = clazz.getDeclaredMethod("sampleJavaBean", OnlyDefaultConstructorJavaBean.class); + method = clazz.getDeclaredMethod("sampleJavaBean", OnlyDefaultConstructorJavaBean.class, PrivateConstructorJavaBean.class); params = Stream.of(clazz.getDeclaredMethods()) .filter(method -> method.getName().startsWith("sample")) @@ -65,7 +64,21 @@ void setUp() throws NoSuchMethodException { assertThat(actual.getAge()).isEqualTo(Long.parseLong(age)); } - void sampleJavaBean(final OnlyDefaultConstructorJavaBean javaBean) { + @Test + void 기본생성자_접근자_private_javaBean_매핑() { + // given + final MethodParameter methodParameter = new MethodParameter(params.get(PrivateConstructorJavaBean.class), "javaBean", 1, method); + + // when + final PrivateConstructorJavaBean actual = (PrivateConstructorJavaBean) resolver.resolveArgument(webRequest, methodParameter); + + // then + assertThat(actual.getUserId()).isEqualTo(userId); + assertThat(actual.getPassword()).isEqualTo(password); + assertThat(actual.getAge()).isEqualTo(Long.parseLong(age)); + } + + void sampleJavaBean(final OnlyDefaultConstructorJavaBean javaBean, final PrivateConstructorJavaBean bean) { } } @@ -86,6 +99,27 @@ public String getPassword() { return password; } + public int getAge() { + return age; + } +} + +class PrivateConstructorJavaBean { + private String userId; + private String password; + private int age; + + private PrivateConstructorJavaBean() { + } + + public String getUserId() { + return userId; + } + + public String getPassword() { + return password; + } + public int getAge() { return age; } From 585649bc565374a0d4d03dbf8c0ddfcc63077541 Mon Sep 17 00:00:00 2001 From: DaejunBae Date: Thu, 10 Oct 2019 01:40:54 +0900 Subject: [PATCH 25/29] =?UTF-8?q?fix:=20JspView.render()=20model=20?= =?UTF-8?q?=EA=B0=92=20setAttribute=EC=97=90=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nextstep-mvc/src/main/java/nextstep/mvc/tobe/view/JspView.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nextstep-mvc/src/main/java/nextstep/mvc/tobe/view/JspView.java b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/view/JspView.java index 1ab9988b..5f0ef7cf 100644 --- a/nextstep-mvc/src/main/java/nextstep/mvc/tobe/view/JspView.java +++ b/nextstep-mvc/src/main/java/nextstep/mvc/tobe/view/JspView.java @@ -16,6 +16,8 @@ public JspView(final String viewName) { @Override public void render(final Map model, final HttpServletRequest request, final HttpServletResponse response) throws Exception { RequestDispatcher rd = request.getRequestDispatcher(viewName); + + model.forEach(request::setAttribute); rd.forward(request, response); } From 050131eb2495ef4be6307d57e94444ef787a6d4b Mon Sep 17 00:00:00 2001 From: DaejunBae Date: Thu, 10 Oct 2019 01:41:40 +0900 Subject: [PATCH 26/29] =?UTF-8?q?feat:=20Interceptor=20DispatcherServlet?= =?UTF-8?q?=20=EC=97=90=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/nextstep/mvc/DispatcherServlet.java | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/nextstep-mvc/src/main/java/nextstep/mvc/DispatcherServlet.java b/nextstep-mvc/src/main/java/nextstep/mvc/DispatcherServlet.java index 69e7d0b3..66c7243c 100644 --- a/nextstep-mvc/src/main/java/nextstep/mvc/DispatcherServlet.java +++ b/nextstep-mvc/src/main/java/nextstep/mvc/DispatcherServlet.java @@ -9,6 +9,8 @@ import nextstep.mvc.tobe.adapter.SimpleControllerAdapter; import nextstep.mvc.tobe.exception.HandlerAdapterNotSupportedException; import nextstep.mvc.tobe.exception.HandlerNotFoundException; +import nextstep.mvc.tobe.interceptor.HandlerInterceptor; +import nextstep.mvc.tobe.interceptor.InterceptorRegistry; import nextstep.mvc.tobe.mapping.HandlerMapping; import nextstep.mvc.tobe.view.View; import nextstep.mvc.tobe.viewresolver.ViewResolver; @@ -37,8 +39,10 @@ public class DispatcherServlet extends HttpServlet { private List handlerMappings; private List handlerAdapters; private ViewResolverManager viewResolverManager; + private InterceptorRegistry interceptorRegistry; - public DispatcherServlet(HandlerMapping... handlerMappings) { + public DispatcherServlet(InterceptorRegistry interceptorRegistry, HandlerMapping... handlerMappings) { + this.interceptorRegistry = interceptorRegistry; this.handlerMappings = Arrays.asList(handlerMappings); this.handlerAdapters = Arrays.asList(new SimpleControllerAdapter(), new ResponseBodyAdapter(), new HandlerExecutionAdapter()); this.viewResolverManager = new ViewResolverManager(); @@ -60,10 +64,18 @@ protected void service(HttpServletRequest req, HttpServletResponse resp) throws final Object handler = getHandler(req); + final List interceptors = interceptorRegistry.getHandlerInterceptors(req.getRequestURI()); + + if (!preHandle(req, resp, handler, interceptors)) { + return; + } + final HandlerAdapter handlerAdapter = getHandlerAdapter(handler); final ModelAndView mav = handlerAdapter.handle(webRequest, handler); + postHandle(req, resp, handler, interceptors, mav); + final View view = viewResolverManager.resolveView(mav.getView()); view.render(mav.getModel(), req, resp); @@ -77,6 +89,21 @@ protected void service(HttpServletRequest req, HttpServletResponse resp) throws } } + private boolean preHandle(final HttpServletRequest req, final HttpServletResponse resp, final Object handler, final List interceptors) throws Exception { + for (final HandlerInterceptor interceptor : interceptors) { + if (!interceptor.preHandle(req, resp, handler)) { + return false; + } + } + return true; + } + + private void postHandle(final HttpServletRequest req, final HttpServletResponse resp, final Object handler, final List interceptors, final ModelAndView mav) throws Exception { + for (final HandlerInterceptor interceptor : interceptors) { + interceptor.postHandle(req, resp, handler, mav); + } + } + private Object getHandler(final HttpServletRequest req) { return handlerMappings.stream() .map(handlerMapping -> handlerMapping.getHandler(req)) From bbdb6e35263b7a76e64e36b2f7e0a0b72b9091fa Mon Sep 17 00:00:00 2001 From: DaejunBae Date: Thu, 10 Oct 2019 01:42:14 +0900 Subject: [PATCH 27/29] =?UTF-8?q?feat:=20MeasuringInterceptor=20-=20Contro?= =?UTF-8?q?ller=20=EC=84=B1=EB=8A=A5=EC=B8=A1=EC=A0=81=20=EB=AF=B8?= =?UTF-8?q?=EC=85=98=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../support/web/MeasuringInterceptor.java | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 slipp/src/main/java/slipp/support/web/MeasuringInterceptor.java diff --git a/slipp/src/main/java/slipp/support/web/MeasuringInterceptor.java b/slipp/src/main/java/slipp/support/web/MeasuringInterceptor.java new file mode 100644 index 00000000..b1531a7c --- /dev/null +++ b/slipp/src/main/java/slipp/support/web/MeasuringInterceptor.java @@ -0,0 +1,30 @@ +package slipp.support.web; + +import nextstep.mvc.tobe.ModelAndView; +import nextstep.mvc.tobe.interceptor.HandlerInterceptor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +public class MeasuringInterceptor implements HandlerInterceptor { + private static final Logger logger = LoggerFactory.getLogger(MeasuringInterceptor.class); + private static final String BEGIN_TIME = "beginTime"; + + @Override + public boolean preHandle(final HttpServletRequest request, final HttpServletResponse response, final Object handler) throws Exception { + final long currentTime = System.currentTimeMillis(); + request.setAttribute(BEGIN_TIME, currentTime); + return true; + } + + @Override + public void postHandle(final HttpServletRequest request, final HttpServletResponse response, final Object handler, final ModelAndView modelAndView) throws Exception { + final long currentTime = System.currentTimeMillis(); + final long beginTime = (long) request.getAttribute(BEGIN_TIME); + final long processedTime = currentTime - beginTime; + + logger.debug("processed time: {} Millis", processedTime); + } +} From 607c519112e504b8ad5ad773fb7864d6887511db Mon Sep 17 00:00:00 2001 From: DaejunBae Date: Thu, 10 Oct 2019 01:42:49 +0900 Subject: [PATCH 28/29] feat: LoginInterceptor --- .../slipp/SlippWebApplicationInitializer.java | 19 ++++++++++++++++++- .../slipp/controller2/UserController.java | 4 ---- .../slipp/support/web/LoginInterceptor.java | 18 ++++++++++++++++++ 3 files changed, 36 insertions(+), 5 deletions(-) create mode 100644 slipp/src/main/java/slipp/support/web/LoginInterceptor.java diff --git a/slipp/src/main/java/slipp/SlippWebApplicationInitializer.java b/slipp/src/main/java/slipp/SlippWebApplicationInitializer.java index e06a873c..908c183a 100644 --- a/slipp/src/main/java/slipp/SlippWebApplicationInitializer.java +++ b/slipp/src/main/java/slipp/SlippWebApplicationInitializer.java @@ -1,10 +1,13 @@ package slipp; import nextstep.mvc.DispatcherServlet; +import nextstep.mvc.tobe.interceptor.InterceptorRegistry; import nextstep.mvc.tobe.mapping.AnnotationHandlerMapping; import nextstep.web.WebApplicationInitializer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import slipp.support.web.LoginInterceptor; +import slipp.support.web.MeasuringInterceptor; import javax.servlet.ServletContext; import javax.servlet.ServletException; @@ -15,7 +18,9 @@ public class SlippWebApplicationInitializer implements WebApplicationInitializer @Override public void onStartup(ServletContext servletContext) throws ServletException { - DispatcherServlet dispatcherServlet = new DispatcherServlet(new AnnotationHandlerMapping("slipp")); + InterceptorRegistry interceptorRegistry = initInterceptorRegistry(); + + DispatcherServlet dispatcherServlet = new DispatcherServlet(interceptorRegistry, new AnnotationHandlerMapping("slipp")); ServletRegistration.Dynamic dispatcher = servletContext.addServlet("dispatcher", dispatcherServlet); dispatcher.setLoadOnStartup(1); @@ -23,4 +28,16 @@ public void onStartup(ServletContext servletContext) throws ServletException { log.info("Start MyWebApplication Initializer"); } + + private InterceptorRegistry initInterceptorRegistry() { + final InterceptorRegistry registry = new InterceptorRegistry(); + + registry.addInterceptor(new MeasuringInterceptor()) + .addPathPatterns("/**"); + + registry.addInterceptor(new LoginInterceptor()) + .addPathPatterns("/users"); + + return registry; + } } \ No newline at end of file diff --git a/slipp/src/main/java/slipp/controller2/UserController.java b/slipp/src/main/java/slipp/controller2/UserController.java index 33cd1731..2e38eb49 100644 --- a/slipp/src/main/java/slipp/controller2/UserController.java +++ b/slipp/src/main/java/slipp/controller2/UserController.java @@ -46,10 +46,6 @@ public ModelAndView show(@PathVariable String userId) { @RequestMapping(value = "/users", method = RequestMethod.GET) public ModelAndView list(HttpServletRequest req) throws Exception { - if (!UserSessionUtils.isLogined(req.getSession())) { - return new ModelAndView("redirect:/users/loginForm"); - } - final Collection users = DataBase.findAll(); return new ModelAndView("/user/list").addObject("users", users); } diff --git a/slipp/src/main/java/slipp/support/web/LoginInterceptor.java b/slipp/src/main/java/slipp/support/web/LoginInterceptor.java new file mode 100644 index 00000000..4c8229da --- /dev/null +++ b/slipp/src/main/java/slipp/support/web/LoginInterceptor.java @@ -0,0 +1,18 @@ +package slipp.support.web; + +import nextstep.mvc.tobe.interceptor.HandlerInterceptor; +import slipp.controller2.UserSessionUtils; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +public class LoginInterceptor implements HandlerInterceptor { + @Override + public boolean preHandle(final HttpServletRequest request, final HttpServletResponse response, final Object handler) throws Exception { + if (!UserSessionUtils.isLogined(request.getSession())) { + response.sendRedirect("/users/loginForm"); + return false; + } + return true; + } +} From 147976899661e1c6b2de6725e327a8124bf44d17 Mon Sep 17 00:00:00 2001 From: DaejunBae Date: Thu, 10 Oct 2019 01:45:43 +0900 Subject: [PATCH 29/29] docs: update README.md --- README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/README.md b/README.md index af015c75..28728a5d 100644 --- a/README.md +++ b/README.md @@ -40,9 +40,20 @@ - [ ] handlerMappings, adapters 등.. 포장하기 - [x] 모든 클래스 이름 점검 (ex. HandlerMethodArgumentResolver) - [ ] ResponseEntity 직접 구현하기 +- [ ] interceptor HttpMethod 비교기능 추가 ## 추가 미션 - [ ] 다른 템플릿 엔진 지원하기 - [ ] 인터셉터 구현하기 + - HandlerInterceptor 구현 + - InterceptorRegistry 에 등록 + - InterceptorRegistration 은 HandlerInterceptor, includePatterns, excludePatterns 를 갖는다. + + - 흐름 + - url-> filter includePatterns = true, excludePatterns = false 확인 후 + 해당하는 모든 HandlerInterceptor 반환 + - HandlerAdapter 실행 전에 interceptor.preHandle() + - HandlerAdapter 실행 후에 interceptor.postHandle() + 새로운 컨트롤러 (RequestMapping 스캔 기반)은 controller2 패키지에 위치