From 560f9e8bd31444afb77f30511a209def62cef752 Mon Sep 17 00:00:00 2001 From: jude-loopers Date: Sun, 12 Apr 2026 23:32:44 +0900 Subject: [PATCH 1/6] init --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 3fefc442..29d7114a 100644 --- a/.gitignore +++ b/.gitignore @@ -42,3 +42,6 @@ bin/ ### Mac OS ### .DS_Store\ntomcat.8080/ + +## tomcat +**/tomcat.8080 From 5a4f362b183e28008b269e6f2eed4bbaa6386260 Mon Sep 17 00:00:00 2001 From: jude-loopers Date: Thu, 23 Apr 2026 03:03:02 +0900 Subject: [PATCH 2/6] =?UTF-8?q?1=EC=A3=BC=EC=B0=A8=20=EC=9E=90=EB=B0=94=20?= =?UTF-8?q?=EC=9B=B9=20=EA=B0=9C=EB=B0=9C=20=EA=B8=B0=EC=B4=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/diy/app/Lecture.java | 29 ++++++++++ src/main/java/com/diy/app/LectureServlet.java | 58 +++++++++++++++++++ src/main/java/com/diy/app/Main.java | 4 ++ 3 files changed, 91 insertions(+) create mode 100644 src/main/java/com/diy/app/Lecture.java create mode 100644 src/main/java/com/diy/app/LectureServlet.java diff --git a/src/main/java/com/diy/app/Lecture.java b/src/main/java/com/diy/app/Lecture.java new file mode 100644 index 00000000..2fc4f9d5 --- /dev/null +++ b/src/main/java/com/diy/app/Lecture.java @@ -0,0 +1,29 @@ +package com.diy.app; + +import java.math.BigDecimal; + +public class Lecture { + + private Long id; + private String name; + private BigDecimal price; + + public Lecture() { + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public BigDecimal getPrice() { + return price; + } + + public void setId(Long id) { + this.id = id; + } +} diff --git a/src/main/java/com/diy/app/LectureServlet.java b/src/main/java/com/diy/app/LectureServlet.java new file mode 100644 index 00000000..3353cbc2 --- /dev/null +++ b/src/main/java/com/diy/app/LectureServlet.java @@ -0,0 +1,58 @@ +package com.diy.app; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import javax.servlet.ServletException; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +@WebServlet("/lectures") +public class LectureServlet extends HttpServlet { + + public static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + private final Map repository = new HashMap<>(); + + @Override + protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { + final Collection lectures = repository.values(); + + req.setAttribute("lectures", lectures); + req.getRequestDispatcher("lecture-list.jsp").forward(req, resp); + } + + @Override + protected void doPost(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { + final String body = new String(req.getInputStream().readAllBytes()); + final Lecture lecture = OBJECT_MAPPER.readValue(body, Lecture.class); + + final long id = repository.size() + 1L; + lecture.setId(id); + repository.put(lecture.getId(), lecture); + + resp.sendRedirect("/lectures"); + } + + @Override + protected void doDelete(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { + final Long id = Long.valueOf(req.getParameter("id")); + repository.remove(id); + + resp.sendRedirect("/lectures"); + } + + @Override + protected void doPut(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { + final String body = new String(req.getInputStream().readAllBytes()); + final Lecture lecture = OBJECT_MAPPER.readValue(body, Lecture.class); + + repository.put(lecture.getId(), lecture); + + resp.sendRedirect("/lectures"); + } +} diff --git a/src/main/java/com/diy/app/Main.java b/src/main/java/com/diy/app/Main.java index c81e36ae..eab53ef7 100644 --- a/src/main/java/com/diy/app/Main.java +++ b/src/main/java/com/diy/app/Main.java @@ -1,6 +1,10 @@ package com.diy.app; +import com.diy.framework.web.server.TomcatWebServer; + public class Main { public static void main(String[] args) { + final TomcatWebServer tomcatWebServer = new TomcatWebServer(); + tomcatWebServer.start(); } } From 3cd398998d374963b72015393e01e8c4c6fac4e3 Mon Sep 17 00:00:00 2001 From: jude-loopers Date: Thu, 30 Apr 2026 01:04:11 +0900 Subject: [PATCH 3/6] =?UTF-8?q?2=EC=A3=BC=EC=B0=A8=20=EC=9E=90=EB=B0=94=20?= =?UTF-8?q?=EC=9B=B9=20=EC=9A=94=EC=B2=AD=20=EC=B6=94=EC=83=81=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/diy/app/LectureApplication.java | 19 ++++ .../java/com/diy/app/LectureController.java | 52 +++++++++++ src/main/java/com/diy/app/LectureServlet.java | 58 ------------- src/main/java/com/diy/app/Main.java | 10 --- .../com/diy/framework/web/mvc/Controller.java | 11 +++ .../diy/framework/web/mvc/view/HtmlView.java | 79 +++++++++++++++++ .../web/mvc/view/HtmlViewResolver.java | 10 +++ .../diy/framework/web/mvc/view/JspView.java | 26 ++++++ .../web/mvc/view/JspViewResolver.java | 9 ++ .../framework/web/mvc/view/ModelAndView.java | 27 ++++++ .../framework/web/mvc/view/RedirectView.java | 19 ++++ .../web/mvc/view/UrlBasedViewResolver.java | 13 +++ .../com/diy/framework/web/mvc/view/View.java | 9 ++ .../framework/web/mvc/view/ViewResolver.java | 5 ++ .../web/servlet/DispatcherServlet.java | 86 +++++++++++++++++++ 15 files changed, 365 insertions(+), 68 deletions(-) create mode 100644 src/main/java/com/diy/app/LectureApplication.java create mode 100644 src/main/java/com/diy/app/LectureController.java delete mode 100644 src/main/java/com/diy/app/LectureServlet.java delete mode 100644 src/main/java/com/diy/app/Main.java create mode 100644 src/main/java/com/diy/framework/web/mvc/Controller.java create mode 100644 src/main/java/com/diy/framework/web/mvc/view/HtmlView.java create mode 100644 src/main/java/com/diy/framework/web/mvc/view/HtmlViewResolver.java create mode 100644 src/main/java/com/diy/framework/web/mvc/view/JspView.java create mode 100644 src/main/java/com/diy/framework/web/mvc/view/JspViewResolver.java create mode 100644 src/main/java/com/diy/framework/web/mvc/view/ModelAndView.java create mode 100644 src/main/java/com/diy/framework/web/mvc/view/RedirectView.java create mode 100644 src/main/java/com/diy/framework/web/mvc/view/UrlBasedViewResolver.java create mode 100644 src/main/java/com/diy/framework/web/mvc/view/View.java create mode 100644 src/main/java/com/diy/framework/web/mvc/view/ViewResolver.java create mode 100644 src/main/java/com/diy/framework/web/servlet/DispatcherServlet.java diff --git a/src/main/java/com/diy/app/LectureApplication.java b/src/main/java/com/diy/app/LectureApplication.java new file mode 100644 index 00000000..b7c6b609 --- /dev/null +++ b/src/main/java/com/diy/app/LectureApplication.java @@ -0,0 +1,19 @@ +package com.diy.app; + +import com.diy.framework.web.mvc.Controller; +import com.diy.framework.web.server.TomcatWebServer; +import com.diy.framework.web.servlet.DispatcherServlet; + +import java.util.HashMap; +import java.util.Map; + +public class LectureApplication { + public static void main(String[] args) { + final Map controllerMapping = new HashMap<>(); + controllerMapping.put("/lectures", new LectureController()); + + final DispatcherServlet servlet = new DispatcherServlet(controllerMapping); + final TomcatWebServer tomcatWebServer = new TomcatWebServer(); + tomcatWebServer.start(); + } +} diff --git a/src/main/java/com/diy/app/LectureController.java b/src/main/java/com/diy/app/LectureController.java new file mode 100644 index 00000000..8fba29af --- /dev/null +++ b/src/main/java/com/diy/app/LectureController.java @@ -0,0 +1,52 @@ +package com.diy.app; + +import com.diy.framework.web.mvc.Controller; +import com.diy.framework.web.mvc.view.ModelAndView; +import com.fasterxml.jackson.databind.ObjectMapper; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +public class LectureController implements Controller { + + private final Map lectureRepository = new HashMap<>(); + + @Override + public ModelAndView handleRequest(final HttpServletRequest request, final HttpServletResponse response) throws Exception { + if ("POST".equals(request.getMethod())) { + return doPost(request, response); + } else if ("GET".equals(request.getMethod())) { + return doGet(request, response); + } + + throw new RuntimeException("404 Not Found"); + } + + private ModelAndView doPost(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { + final byte[] bodyBytes = req.getInputStream().readAllBytes(); + final String body = new String(bodyBytes, StandardCharsets.UTF_8); + + final Lecture lecture = new ObjectMapper().readValue(body, Lecture.class); + + final long id = lectureRepository.size(); + lectureRepository.put(id, lecture); + lecture.setId(id); + + return new ModelAndView("redirect:/lectures"); + } + + private ModelAndView doGet(final HttpServletRequest req, final HttpServletResponse resp) throws Exception { + final Collection lectures = lectureRepository.values(); + final Map model = new HashMap<>(); + final Object lectureModels = lectures.stream().map(lecture -> Map.of("id", lecture.getId(), "name", lecture.getName(), "price", lecture.getPrice())).toList(); + model.put("lectures", lectureModels); + + return new ModelAndView("lecture-list", model); + } +} diff --git a/src/main/java/com/diy/app/LectureServlet.java b/src/main/java/com/diy/app/LectureServlet.java deleted file mode 100644 index 3353cbc2..00000000 --- a/src/main/java/com/diy/app/LectureServlet.java +++ /dev/null @@ -1,58 +0,0 @@ -package com.diy.app; - -import com.fasterxml.jackson.databind.ObjectMapper; - -import javax.servlet.ServletException; -import javax.servlet.annotation.WebServlet; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; - -@WebServlet("/lectures") -public class LectureServlet extends HttpServlet { - - public static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); - private final Map repository = new HashMap<>(); - - @Override - protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { - final Collection lectures = repository.values(); - - req.setAttribute("lectures", lectures); - req.getRequestDispatcher("lecture-list.jsp").forward(req, resp); - } - - @Override - protected void doPost(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { - final String body = new String(req.getInputStream().readAllBytes()); - final Lecture lecture = OBJECT_MAPPER.readValue(body, Lecture.class); - - final long id = repository.size() + 1L; - lecture.setId(id); - repository.put(lecture.getId(), lecture); - - resp.sendRedirect("/lectures"); - } - - @Override - protected void doDelete(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { - final Long id = Long.valueOf(req.getParameter("id")); - repository.remove(id); - - resp.sendRedirect("/lectures"); - } - - @Override - protected void doPut(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { - final String body = new String(req.getInputStream().readAllBytes()); - final Lecture lecture = OBJECT_MAPPER.readValue(body, Lecture.class); - - repository.put(lecture.getId(), lecture); - - resp.sendRedirect("/lectures"); - } -} diff --git a/src/main/java/com/diy/app/Main.java b/src/main/java/com/diy/app/Main.java deleted file mode 100644 index eab53ef7..00000000 --- a/src/main/java/com/diy/app/Main.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.diy.app; - -import com.diy.framework.web.server.TomcatWebServer; - -public class Main { - public static void main(String[] args) { - final TomcatWebServer tomcatWebServer = new TomcatWebServer(); - tomcatWebServer.start(); - } -} diff --git a/src/main/java/com/diy/framework/web/mvc/Controller.java b/src/main/java/com/diy/framework/web/mvc/Controller.java new file mode 100644 index 00000000..cf802167 --- /dev/null +++ b/src/main/java/com/diy/framework/web/mvc/Controller.java @@ -0,0 +1,11 @@ +package com.diy.framework.web.mvc; + +import com.diy.framework.web.mvc.view.ModelAndView; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +@FunctionalInterface +public interface Controller { + ModelAndView handleRequest(final HttpServletRequest request, final HttpServletResponse response) throws Exception; +} diff --git a/src/main/java/com/diy/framework/web/mvc/view/HtmlView.java b/src/main/java/com/diy/framework/web/mvc/view/HtmlView.java new file mode 100644 index 00000000..916bab9f --- /dev/null +++ b/src/main/java/com/diy/framework/web/mvc/view/HtmlView.java @@ -0,0 +1,79 @@ +package com.diy.framework.web.mvc.view; + +import javax.servlet.ServletContext; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.BufferedReader; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.PrintWriter; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class HtmlView implements View { + + final Pattern pattern = Pattern.compile(""); + + private final String viewName; + + public HtmlView(final String viewName) { + this.viewName = viewName; + } + + @Override + public void render(final Map model, final HttpServletRequest req, final HttpServletResponse res) throws IOException { + final String viewFile = readViewFile(req, model); + + res.setContentType("text/html;charset=utf-8"); + final PrintWriter writer = res.getWriter(); + writer.print(viewFile); + } + + private String readViewFile(final HttpServletRequest req, final Map model) { + final StringBuilder content = new StringBuilder(); + + final String viewPath = getViewPath(req); + try (final BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(viewPath), StandardCharsets.UTF_8))) { + String line; + + + while ((line = reader.readLine()) != null) { + final Matcher matcher = pattern.matcher(line); + + if (matcher.find()) { + final String modelKey = matcher.group(1); + final List> lectures = (List>) model.get(modelKey); + + lectures.forEach(lecture -> { + lecture.forEach((key, value) -> + content.append("
  • ") + .append(key) + .append(": ") + .append(value) + .append("
  • ") + .append("\n")); + + content.append("
    ").append("\n"); + }); + + continue; + } + + content.append(line).append("\n"); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + + return content.toString(); + } + + private String getViewPath(final HttpServletRequest req) { + final ServletContext sc = req.getServletContext(); + return sc.getRealPath(viewName); + } +} diff --git a/src/main/java/com/diy/framework/web/mvc/view/HtmlViewResolver.java b/src/main/java/com/diy/framework/web/mvc/view/HtmlViewResolver.java new file mode 100644 index 00000000..f03de524 --- /dev/null +++ b/src/main/java/com/diy/framework/web/mvc/view/HtmlViewResolver.java @@ -0,0 +1,10 @@ +package com.diy.framework.web.mvc.view; + +public class HtmlViewResolver implements ViewResolver { + + @Override + public View resolveViewName(final String viewName) { + final String resolvedViewName = "/templates/" + viewName + ".html"; + return new HtmlView(resolvedViewName); + } +} diff --git a/src/main/java/com/diy/framework/web/mvc/view/JspView.java b/src/main/java/com/diy/framework/web/mvc/view/JspView.java new file mode 100644 index 00000000..81aec511 --- /dev/null +++ b/src/main/java/com/diy/framework/web/mvc/view/JspView.java @@ -0,0 +1,26 @@ +package com.diy.framework.web.mvc.view; + +import javax.servlet.RequestDispatcher; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.Map; + +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 req, final HttpServletResponse res) throws ServletException, IOException { + for (final Map.Entry entry : model.entrySet()) { + req.setAttribute(entry.getKey(), entry.getValue()); + } + + final RequestDispatcher requestDispatcher = req.getRequestDispatcher(viewName); + requestDispatcher.forward(req, res); + } +} diff --git a/src/main/java/com/diy/framework/web/mvc/view/JspViewResolver.java b/src/main/java/com/diy/framework/web/mvc/view/JspViewResolver.java new file mode 100644 index 00000000..4c024917 --- /dev/null +++ b/src/main/java/com/diy/framework/web/mvc/view/JspViewResolver.java @@ -0,0 +1,9 @@ +package com.diy.framework.web.mvc.view; + +public class JspViewResolver implements ViewResolver { + + @Override + public View resolveViewName(final String viewName) { + return new JspView("/" + viewName + ".jsp"); + } +} diff --git a/src/main/java/com/diy/framework/web/mvc/view/ModelAndView.java b/src/main/java/com/diy/framework/web/mvc/view/ModelAndView.java new file mode 100644 index 00000000..bff5ecf3 --- /dev/null +++ b/src/main/java/com/diy/framework/web/mvc/view/ModelAndView.java @@ -0,0 +1,27 @@ +package com.diy.framework.web.mvc.view; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +public class ModelAndView { + private final String viewName; + private final Map model = new HashMap<>(); + + public ModelAndView(final String viewName) { + this.viewName = viewName; + } + + public ModelAndView(final String viewName, final Map model) { + this.viewName = viewName; + this.model.putAll(model); + } + + public String getViewName() { + return viewName; + } + + public Map getModel() { + return Collections.unmodifiableMap(this.model); + } +} diff --git a/src/main/java/com/diy/framework/web/mvc/view/RedirectView.java b/src/main/java/com/diy/framework/web/mvc/view/RedirectView.java new file mode 100644 index 00000000..680298e0 --- /dev/null +++ b/src/main/java/com/diy/framework/web/mvc/view/RedirectView.java @@ -0,0 +1,19 @@ +package com.diy.framework.web.mvc.view; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.Map; + +public class RedirectView implements View { + + private final String redirectUrl; + + public RedirectView(final String redirectUrl) { + this.redirectUrl = redirectUrl; + } + + @Override + public void render(final Map model, final HttpServletRequest req, final HttpServletResponse res) throws Exception { + res.sendRedirect(redirectUrl); + } +} diff --git a/src/main/java/com/diy/framework/web/mvc/view/UrlBasedViewResolver.java b/src/main/java/com/diy/framework/web/mvc/view/UrlBasedViewResolver.java new file mode 100644 index 00000000..32950bb6 --- /dev/null +++ b/src/main/java/com/diy/framework/web/mvc/view/UrlBasedViewResolver.java @@ -0,0 +1,13 @@ +package com.diy.framework.web.mvc.view; + +public class UrlBasedViewResolver implements ViewResolver { + @Override + public View resolveViewName(final String viewName) { + if (!viewName.startsWith("redirect:")) { + return null; + } + + final String redirectUrl = viewName.substring("redirect:".length()); + return new RedirectView(redirectUrl); + } +} diff --git a/src/main/java/com/diy/framework/web/mvc/view/View.java b/src/main/java/com/diy/framework/web/mvc/view/View.java new file mode 100644 index 00000000..5a4e1287 --- /dev/null +++ b/src/main/java/com/diy/framework/web/mvc/view/View.java @@ -0,0 +1,9 @@ +package com.diy.framework.web.mvc.view; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.Map; + +public interface View { + void render(final Map model, final HttpServletRequest req, final HttpServletResponse res) throws Exception; +} diff --git a/src/main/java/com/diy/framework/web/mvc/view/ViewResolver.java b/src/main/java/com/diy/framework/web/mvc/view/ViewResolver.java new file mode 100644 index 00000000..c7ecd211 --- /dev/null +++ b/src/main/java/com/diy/framework/web/mvc/view/ViewResolver.java @@ -0,0 +1,5 @@ +package com.diy.framework.web.mvc.view; + +public interface ViewResolver { + View resolveViewName(final String viewName); +} diff --git a/src/main/java/com/diy/framework/web/servlet/DispatcherServlet.java b/src/main/java/com/diy/framework/web/servlet/DispatcherServlet.java new file mode 100644 index 00000000..cbe989b8 --- /dev/null +++ b/src/main/java/com/diy/framework/web/servlet/DispatcherServlet.java @@ -0,0 +1,86 @@ +package com.diy.framework.web.servlet; + +import com.diy.framework.web.mvc.Controller; +import com.diy.framework.web.mvc.view.JspViewResolver; +import com.diy.framework.web.mvc.view.ModelAndView; +import com.diy.framework.web.mvc.view.UrlBasedViewResolver; +import com.diy.framework.web.mvc.view.View; +import com.diy.framework.web.mvc.view.ViewResolver; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; + +import javax.servlet.ServletException; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +@WebServlet("/") +public class DispatcherServlet extends HttpServlet { + + private final Map controllersMapping; + private final List viewResolvers = new ArrayList<>(); + + public DispatcherServlet(final Map controllersMapping) { + this.controllersMapping = controllersMapping; + this.viewResolvers.add(new UrlBasedViewResolver()); + this.viewResolvers.add(new JspViewResolver()); + } + + @Override + protected void service(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { + final String uri = req.getRequestURI(); + + final Controller controller = controllersMapping.get(uri); + + if (controller == null) { + return; + } + + try { + final ModelAndView mav = controller.handleRequest(req, resp); + render(mav, req, resp); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private void render(final ModelAndView mav, final HttpServletRequest req, final HttpServletResponse resp) throws Exception { + final String viewName = mav.getViewName(); + + final View view = resolveViewName(viewName); + + if (view == null) { + throw new RuntimeException("View not found: " + viewName); + } + + view.render(mav.getModel(), req, resp); + } + + private View resolveViewName(final String viewName) { + for (final ViewResolver viewResolver : this.viewResolvers) { + final View view = viewResolver.resolveViewName(viewName); + if (view != null) { + return view; + } + } + + return null; + } + + private Map parseParams(final HttpServletRequest req) throws IOException { + if ("application/json".equals(req.getHeader("Content-Type"))) { + final byte[] bodyBytes = req.getInputStream().readAllBytes(); + final String body = new String(bodyBytes, StandardCharsets.UTF_8); + + return new ObjectMapper().readValue(body, new TypeReference>() {}); + } else { + return req.getParameterMap(); + } + } +} From a15d54301bd4ac6de66f018f67428ecfc6facdfc Mon Sep 17 00:00:00 2001 From: jude-loopers Date: Mon, 4 May 2026 00:38:47 +0900 Subject: [PATCH 4/6] =?UTF-8?q?2=EC=A3=BC=EC=B0=A8=20=EC=9E=90=EB=B0=94=20?= =?UTF-8?q?=EC=9B=B9=20=EC=9A=94=EC=B2=AD=20=EC=B6=94=EC=83=81=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/diy/app/LectureApplication.java | 2 +- .../diy/framework/web/server/TomcatWebServer.java | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/diy/app/LectureApplication.java b/src/main/java/com/diy/app/LectureApplication.java index b7c6b609..9fe646d7 100644 --- a/src/main/java/com/diy/app/LectureApplication.java +++ b/src/main/java/com/diy/app/LectureApplication.java @@ -13,7 +13,7 @@ public static void main(String[] args) { controllerMapping.put("/lectures", new LectureController()); final DispatcherServlet servlet = new DispatcherServlet(controllerMapping); - final TomcatWebServer tomcatWebServer = new TomcatWebServer(); + final TomcatWebServer tomcatWebServer = new TomcatWebServer(servlet); tomcatWebServer.start(); } } diff --git a/src/main/java/com/diy/framework/web/server/TomcatWebServer.java b/src/main/java/com/diy/framework/web/server/TomcatWebServer.java index 47dbb0e7..fc0663ae 100644 --- a/src/main/java/com/diy/framework/web/server/TomcatWebServer.java +++ b/src/main/java/com/diy/framework/web/server/TomcatWebServer.java @@ -2,10 +2,12 @@ import org.apache.catalina.Context; import org.apache.catalina.LifecycleException; +import org.apache.catalina.Wrapper; import org.apache.catalina.startup.Tomcat; import org.apache.catalina.webresources.DirResourceSet; import org.apache.catalina.webresources.StandardRoot; +import javax.servlet.http.HttpServlet; import java.io.File; import java.net.URISyntaxException; import java.nio.file.Paths; @@ -16,6 +18,11 @@ public class TomcatWebServer { private final Tomcat tomcat = new Tomcat(); private final int port = 8080; + private final HttpServlet servlet; + + public TomcatWebServer(final HttpServlet servlet) { + this.servlet = servlet; + } public void start() { setServerContext(); @@ -44,6 +51,12 @@ private void setServerContext() { context.setResponseCharacterEncoding("UTF-8"); setServerResources(context); + setDispatcherServlet(context); + } + + private void setDispatcherServlet(final Context context) { + final Wrapper sw = this.tomcat.addServlet(context.getPath(), "dispatcherServlet", servlet); + sw.addMapping("/"); } private void setServerResources(final Context context) { From 1a7081a6cedfed23c1f98da462fc4e5fc6a100cd Mon Sep 17 00:00:00 2001 From: jude-loopers Date: Thu, 7 May 2026 01:30:28 +0900 Subject: [PATCH 5/6] =?UTF-8?q?3=EC=A3=BC=EC=B0=A8=20=EB=B9=88=EC=83=9D?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle.kts | 3 + .../java/com/diy/app/LectureApplication.java | 4 + .../framework/beans/factory/BeanScanner.java | 23 ++++ .../framework/context/ApplicationContext.java | 101 ++++++++++++++++++ .../context/annotation/Autowired.java | 11 ++ .../context/annotation/Component.java | 11 ++ 6 files changed, 153 insertions(+) create mode 100644 src/main/java/com/diy/framework/beans/factory/BeanScanner.java create mode 100644 src/main/java/com/diy/framework/context/ApplicationContext.java create mode 100644 src/main/java/com/diy/framework/context/annotation/Autowired.java create mode 100644 src/main/java/com/diy/framework/context/annotation/Component.java diff --git a/build.gradle.kts b/build.gradle.kts index fab62014..d870a9ed 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -26,6 +26,9 @@ dependencies { implementation("org.apache.tomcat.embed:tomcat-embed-core:8.5.42") implementation("org.apache.tomcat.embed:tomcat-embed-jasper:8.5.42") + // 리플렉션 의존성 주입 + implementation("org.reflections:reflections:0.10.2") + implementation(kotlin("stdlib-jdk8")) } diff --git a/src/main/java/com/diy/app/LectureApplication.java b/src/main/java/com/diy/app/LectureApplication.java index 9fe646d7..de34980f 100644 --- a/src/main/java/com/diy/app/LectureApplication.java +++ b/src/main/java/com/diy/app/LectureApplication.java @@ -1,5 +1,6 @@ package com.diy.app; +import com.diy.framework.context.ApplicationContext; import com.diy.framework.web.mvc.Controller; import com.diy.framework.web.server.TomcatWebServer; import com.diy.framework.web.servlet.DispatcherServlet; @@ -12,6 +13,9 @@ public static void main(String[] args) { final Map controllerMapping = new HashMap<>(); controllerMapping.put("/lectures", new LectureController()); + final ApplicationContext ac = new ApplicationContext(LectureApplication.class.getPackageName()); + ac.initialize(); + final DispatcherServlet servlet = new DispatcherServlet(controllerMapping); final TomcatWebServer tomcatWebServer = new TomcatWebServer(servlet); tomcatWebServer.start(); diff --git a/src/main/java/com/diy/framework/beans/factory/BeanScanner.java b/src/main/java/com/diy/framework/beans/factory/BeanScanner.java new file mode 100644 index 00000000..fd2c6b2b --- /dev/null +++ b/src/main/java/com/diy/framework/beans/factory/BeanScanner.java @@ -0,0 +1,23 @@ +package com.diy.framework.beans.factory; + +import org.reflections.Reflections; + +import java.lang.annotation.Annotation; +import java.util.Set; +import java.util.stream.Collectors; + +public class BeanScanner { + + private final Reflections reflections; + + public BeanScanner(final String... basePackages) { + reflections = new Reflections(basePackages); + } + + public Set> scanClassesTypeAnnotatedWith(final Class annotation) { + return reflections.getTypesAnnotatedWith(annotation) + .stream() + .filter(type -> (!type.isAnnotation() && !type.isInterface())) + .collect(Collectors.toSet()); + } +} \ No newline at end of file diff --git a/src/main/java/com/diy/framework/context/ApplicationContext.java b/src/main/java/com/diy/framework/context/ApplicationContext.java new file mode 100644 index 00000000..f7c5a1f9 --- /dev/null +++ b/src/main/java/com/diy/framework/context/ApplicationContext.java @@ -0,0 +1,101 @@ +package com.diy.framework.context; + +import com.diy.framework.beans.factory.BeanScanner; +import com.diy.framework.context.annotation.Autowired; +import com.diy.framework.context.annotation.Component; + +import java.lang.reflect.Constructor; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class ApplicationContext { + + private final String basePackage; + private final Set> beanClasses = new HashSet<>(); + private final List beans = new ArrayList<>(); + + public ApplicationContext(final String basePackage) { + this.basePackage = basePackage; + } + + public void initialize() { + final BeanScanner beanScanner = new BeanScanner(basePackage); + beanClasses.addAll(beanScanner.scanClassesTypeAnnotatedWith(Component.class)); + + beanClasses.forEach(clazz -> { + if (isBeanInitialized(clazz)) { + return; + } + + final Object bean = createInstance(clazz); + saveBean(bean); + }); + } + + private Object createInstance(final Class clazz) { + final Constructor constructor = findConstructor(clazz); + + try { + constructor.setAccessible(true); + final Object[] parameters = getConstructorParameters(constructor); + + return constructor.newInstance(parameters); + } catch (Exception e) { + throw new RuntimeException(e); + } finally { + constructor.setAccessible(false); + } + } + + private Constructor findConstructor(final Class clazz) { + final Constructor[] constructors = clazz.getDeclaredConstructors(); + + if (constructors.length == 1) { + return constructors[0]; + } + + return findAutowiredConstructor(constructors); + } + + private Constructor findAutowiredConstructor(final Constructor[] constructors) { + final Constructor[] autowiredConstructors = Arrays.stream(constructors) + .filter(constructor -> constructor.isAnnotationPresent(Autowired.class)) + .toArray(Constructor[]::new); + + if (autowiredConstructors.length != 1) { + throw new RuntimeException("Autowired constructor not found"); + } + + return autowiredConstructors[0]; + } + + private Object[] getConstructorParameters(final Constructor constructor) { + final List> parameterTypes = Arrays.stream(constructor.getParameterTypes()).toList(); + + if (!beanClasses.containsAll(parameterTypes)) { + throw new RuntimeException("parameter is not bean"); + } + + return parameterTypes.stream().map(parameterType -> { + if (isBeanInitialized(parameterType)) { + return beans.stream().findFirst().get(); + } + + final Object bean = createInstance(parameterType); + saveBean(bean); + + return bean; + }).toArray(); + } + + private boolean isBeanInitialized(final Class parameterType) { + return beans.stream().anyMatch(bean -> bean.getClass().equals(parameterType)); + } + + private void saveBean(final Object bean) { + beans.add(bean); + } +} diff --git a/src/main/java/com/diy/framework/context/annotation/Autowired.java b/src/main/java/com/diy/framework/context/annotation/Autowired.java new file mode 100644 index 00000000..e38e425f --- /dev/null +++ b/src/main/java/com/diy/framework/context/annotation/Autowired.java @@ -0,0 +1,11 @@ +package com.diy.framework.context.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.CONSTRUCTOR}) +@Retention(RetentionPolicy.RUNTIME) +public @interface Autowired { +} diff --git a/src/main/java/com/diy/framework/context/annotation/Component.java b/src/main/java/com/diy/framework/context/annotation/Component.java new file mode 100644 index 00000000..22a0c9a3 --- /dev/null +++ b/src/main/java/com/diy/framework/context/annotation/Component.java @@ -0,0 +1,11 @@ +package com.diy.framework.context.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface Component { +} From 183266dd55ee25a05179d4dc4e8980651e69ce5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A0=95=EC=9D=B8=EC=B2=A0?= Date: Wed, 13 May 2026 19:33:27 +0900 Subject: [PATCH 6/6] =?UTF-8?q?4=EC=A3=BC=EC=B0=A8=20Bean=20=EC=9D=B4?= =?UTF-8?q?=EB=A6=84=20=EA=B4=80=EB=A6=AC=20&=20@Bean=20&=20Controller=20?= =?UTF-8?q?=EB=B9=88=20=EB=93=B1=EB=A1=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ApplicationContext: beans를 Map으로 변경하여 이름 기반 빈 관리 - @Bean 애노테이션 추가 및 registerBeanMethods() 구현 - @RequestMapping 애노테이션 추가 - LectureController: @Component, @RequestMapping("/lectures") 적용 - LectureApplication: 수동 컨트롤러 매핑 제거, getControllerMapping() 자동화 --- .../java/com/diy/app/LectureApplication.java | 5 +- .../java/com/diy/app/LectureController.java | 4 + .../framework/context/ApplicationContext.java | 80 ++++++++++++++++--- .../framework/context/annotation/Bean.java | 12 +++ .../context/annotation/RequestMapping.java | 12 +++ 5 files changed, 96 insertions(+), 17 deletions(-) create mode 100644 src/main/java/com/diy/framework/context/annotation/Bean.java create mode 100644 src/main/java/com/diy/framework/context/annotation/RequestMapping.java diff --git a/src/main/java/com/diy/app/LectureApplication.java b/src/main/java/com/diy/app/LectureApplication.java index de34980f..57e1381f 100644 --- a/src/main/java/com/diy/app/LectureApplication.java +++ b/src/main/java/com/diy/app/LectureApplication.java @@ -5,17 +5,14 @@ import com.diy.framework.web.server.TomcatWebServer; import com.diy.framework.web.servlet.DispatcherServlet; -import java.util.HashMap; import java.util.Map; public class LectureApplication { public static void main(String[] args) { - final Map controllerMapping = new HashMap<>(); - controllerMapping.put("/lectures", new LectureController()); - final ApplicationContext ac = new ApplicationContext(LectureApplication.class.getPackageName()); ac.initialize(); + final Map controllerMapping = ac.getControllerMapping(); final DispatcherServlet servlet = new DispatcherServlet(controllerMapping); final TomcatWebServer tomcatWebServer = new TomcatWebServer(servlet); tomcatWebServer.start(); diff --git a/src/main/java/com/diy/app/LectureController.java b/src/main/java/com/diy/app/LectureController.java index 8fba29af..4f129b51 100644 --- a/src/main/java/com/diy/app/LectureController.java +++ b/src/main/java/com/diy/app/LectureController.java @@ -1,5 +1,7 @@ package com.diy.app; +import com.diy.framework.context.annotation.Component; +import com.diy.framework.context.annotation.RequestMapping; import com.diy.framework.web.mvc.Controller; import com.diy.framework.web.mvc.view.ModelAndView; import com.fasterxml.jackson.databind.ObjectMapper; @@ -13,6 +15,8 @@ import java.util.HashMap; import java.util.Map; +@Component +@RequestMapping("/lectures") public class LectureController implements Controller { private final Map lectureRepository = new HashMap<>(); diff --git a/src/main/java/com/diy/framework/context/ApplicationContext.java b/src/main/java/com/diy/framework/context/ApplicationContext.java index f7c5a1f9..08c67a02 100644 --- a/src/main/java/com/diy/framework/context/ApplicationContext.java +++ b/src/main/java/com/diy/framework/context/ApplicationContext.java @@ -2,20 +2,20 @@ import com.diy.framework.beans.factory.BeanScanner; import com.diy.framework.context.annotation.Autowired; +import com.diy.framework.context.annotation.Bean; import com.diy.framework.context.annotation.Component; +import com.diy.framework.context.annotation.RequestMapping; +import com.diy.framework.web.mvc.Controller; import java.lang.reflect.Constructor; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashSet; -import java.util.List; -import java.util.Set; +import java.lang.reflect.Method; +import java.util.*; public class ApplicationContext { private final String basePackage; private final Set> beanClasses = new HashSet<>(); - private final List beans = new ArrayList<>(); + private final Map beans = new HashMap<>(); public ApplicationContext(final String basePackage) { this.basePackage = basePackage; @@ -31,8 +31,39 @@ public void initialize() { } final Object bean = createInstance(clazz); - saveBean(bean); + saveBean(generateBeanName(clazz), bean); }); + + registerBeanMethods(); + } + + private void registerBeanMethods() { + // 이미 등록된 @Component 빈들을 순회 + new ArrayList<>(beans.values()).forEach(componentBean -> { + final Method[] methods = componentBean.getClass().getDeclaredMethods(); + Arrays.stream(methods) + .filter(method -> method.isAnnotationPresent(Bean.class)) + .forEach(method -> registerBeanFromMethod(componentBean, method)); + }); + } + + private void registerBeanFromMethod(final Object componentBean, final Method method) { + try { + final Bean beanAnnotation = method.getAnnotation(Bean.class); + + // 이름: @Bean("name")이면 name, 없으면 메서드명 + final String beanName = beanAnnotation.value().isEmpty() + ? method.getName() + : beanAnnotation.value(); + + method.setAccessible(true); + final Object bean = method.invoke(componentBean); + method.setAccessible(false); + + saveBean(beanName, bean); + } catch (Exception e) { + throw new RuntimeException("@Bean 메서드 실행 실패", e); + } } private Object createInstance(final Class clazz) { @@ -81,21 +112,44 @@ private Object[] getConstructorParameters(final Constructor constructor) { return parameterTypes.stream().map(parameterType -> { if (isBeanInitialized(parameterType)) { - return beans.stream().findFirst().get(); + return beans.values().stream() + .filter(bean -> bean.getClass().equals(parameterType)) + .findFirst().get(); } final Object bean = createInstance(parameterType); - saveBean(bean); + saveBean(generateBeanName(parameterType), bean); return bean; }).toArray(); } - private boolean isBeanInitialized(final Class parameterType) { - return beans.stream().anyMatch(bean -> bean.getClass().equals(parameterType)); + public Map getControllerMapping() { + final Map mapping = new HashMap<>(); + + beans.values().stream() + .filter(bean -> bean instanceof Controller) + .forEach(bean -> { + final RequestMapping annotation = + bean.getClass().getAnnotation(RequestMapping.class); + if (annotation != null) { + mapping.put(annotation.value(), (Controller) bean); + } + }); + + return mapping; + } + + private boolean isBeanInitialized(final Class clazz) { + return beans.values().stream().anyMatch(bean -> bean.getClass().equals(clazz)); + } + + private void saveBean(final String beanName, final Object bean) { + beans.put(beanName, bean); } - private void saveBean(final Object bean) { - beans.add(bean); + private String generateBeanName(final Class clazz) { + final String simpleName = clazz.getSimpleName(); + return Character.toLowerCase(simpleName.charAt(0)) + simpleName.substring(1); } } diff --git a/src/main/java/com/diy/framework/context/annotation/Bean.java b/src/main/java/com/diy/framework/context/annotation/Bean.java new file mode 100644 index 00000000..658e12eb --- /dev/null +++ b/src/main/java/com/diy/framework/context/annotation/Bean.java @@ -0,0 +1,12 @@ +package com.diy.framework.context.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface Bean { + String value() default ""; +} diff --git a/src/main/java/com/diy/framework/context/annotation/RequestMapping.java b/src/main/java/com/diy/framework/context/annotation/RequestMapping.java new file mode 100644 index 00000000..fe455f68 --- /dev/null +++ b/src/main/java/com/diy/framework/context/annotation/RequestMapping.java @@ -0,0 +1,12 @@ +package com.diy.framework.context.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface RequestMapping { + String value(); +}