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 diff --git a/build.gradle.kts b/build.gradle.kts index fab62014..3343cc03 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -15,6 +15,9 @@ dependencies { testImplementation("org.junit.jupiter:junit-jupiter") testRuntimeOnly("org.junit.platform:junit-platform-launcher") + // 리플렉션 + implementation("org.reflections:reflections:0.10.2") + // ObjectMapper implementation("com.fasterxml.jackson.core:jackson-databind:2.18.3") 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..88b2a909 --- /dev/null +++ b/src/main/java/com/diy/app/Lecture.java @@ -0,0 +1,37 @@ +package com.diy.app; + +import com.diy.framework.web.annotation.MethodOrder; + +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; + } + + @MethodOrder(1) + private String getDisplayName() { + return "lecture"; + } +} 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..e7104f98 --- /dev/null +++ b/src/main/java/com/diy/app/LectureApplication.java @@ -0,0 +1,32 @@ +package com.diy.app; + +import com.diy.framework.web.annotation.Component; +import com.diy.framework.web.beans.factory.BeanFactory; +import com.diy.framework.web.beans.factory.BeanScanner; +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; +import java.util.Set; + +public class LectureApplication { + + public static void main(String[] args) throws Exception { + final BeanScanner beanScanner = new BeanScanner("com.diy.app"); + final Set> beanClasses = beanScanner.scanClassesTypeAnnotatedWith(Component.class); + + final BeanFactory beanFactory = new BeanFactory(); + beanFactory.initialize(beanClasses); + + final LectureService lectureService = (LectureService) beanFactory.getBean(LectureService.class); + + final Map controllerMapping = new HashMap<>(); + controllerMapping.put("/lectures", new LectureController(lectureService)); + + final DispatcherServlet servlet = new DispatcherServlet(controllerMapping); + final TomcatWebServer tomcatWebServer = new TomcatWebServer(servlet); + tomcatWebServer.start(); + } +} \ No newline at end of file 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..8a0459ae --- /dev/null +++ b/src/main/java/com/diy/app/LectureController.java @@ -0,0 +1,55 @@ +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 LectureService lectureService; + + public LectureController(final LectureService lectureService) { + this.lectureService = lectureService; + } + + @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); + lectureService.save(lecture); + + return new ModelAndView("redirect:/lectures"); + } + + private ModelAndView doGet(final HttpServletRequest req, final HttpServletResponse resp) throws Exception { + final Collection lectures = lectureService.findAll(); + 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/LectureRepository.java b/src/main/java/com/diy/app/LectureRepository.java new file mode 100644 index 00000000..0b746e82 --- /dev/null +++ b/src/main/java/com/diy/app/LectureRepository.java @@ -0,0 +1,25 @@ +package com.diy.app; + +import com.diy.framework.web.annotation.Component; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +@Component +public class LectureRepository { + + // 메모리에 Lecture 데이터를 저장하는 임시 저장소 + private final Map lectures = new HashMap<>(); + + public Lecture save(final Lecture lecture) { + final long id = lectures.size(); + lectures.put(id, lecture); + lecture.setId(id); + return lecture; + } + + public Collection findAll() { + return lectures.values(); + } +} \ No newline at end of file diff --git a/src/main/java/com/diy/app/LectureService.java b/src/main/java/com/diy/app/LectureService.java new file mode 100644 index 00000000..dbd12074 --- /dev/null +++ b/src/main/java/com/diy/app/LectureService.java @@ -0,0 +1,25 @@ +package com.diy.app; + +import com.diy.framework.web.annotation.Autowired; +import com.diy.framework.web.annotation.Component; + +import java.util.Collection; + +@Component +public class LectureService { + + private final LectureRepository lectureRepository; + + @Autowired + public LectureService(final LectureRepository lectureRepository) { + this.lectureRepository = lectureRepository; + } + + public Lecture save(final Lecture lecture) { + return lectureRepository.save(lecture); + } + + public Collection findAll() { + return lectureRepository.findAll(); + } +} \ No newline at end of file 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 c81e36ae..00000000 --- a/src/main/java/com/diy/app/Main.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.diy.app; - -public class Main { - public static void main(String[] args) { - } -} diff --git a/src/main/java/com/diy/framework/web/annotation/Autowired.java b/src/main/java/com/diy/framework/web/annotation/Autowired.java new file mode 100644 index 00000000..aebfa0b1 --- /dev/null +++ b/src/main/java/com/diy/framework/web/annotation/Autowired.java @@ -0,0 +1,9 @@ +package com.diy.framework.web.annotation; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +// 의존성 주입에 사용할 생성자를 표시하는 애너테이션 +@Retention(RetentionPolicy.RUNTIME) +public @interface Autowired { +} \ No newline at end of file diff --git a/src/main/java/com/diy/framework/web/annotation/Component.java b/src/main/java/com/diy/framework/web/annotation/Component.java new file mode 100644 index 00000000..535d7e81 --- /dev/null +++ b/src/main/java/com/diy/framework/web/annotation/Component.java @@ -0,0 +1,9 @@ +package com.diy.framework.web.annotation; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +// 런타임에 bean으로 등록할 클래스를 구분하기 위한 애너테이션 +@Retention(RetentionPolicy.RUNTIME) +public @interface Component { +} diff --git a/src/main/java/com/diy/framework/web/annotation/MethodOrder.java b/src/main/java/com/diy/framework/web/annotation/MethodOrder.java new file mode 100644 index 00000000..26ef5845 --- /dev/null +++ b/src/main/java/com/diy/framework/web/annotation/MethodOrder.java @@ -0,0 +1,12 @@ +package com.diy.framework.web.annotation; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +// 실행 중에도 리플렉션으로 읽을 수 있게 설정 +@Retention(RetentionPolicy.RUNTIME) +public @interface MethodOrder { + + // @MethodOrder(1) 처럼 순서 값을 넣기 위한 값 + int value(); +} \ No newline at end of file diff --git a/src/main/java/com/diy/framework/web/beans/factory/BeanFactory.java b/src/main/java/com/diy/framework/web/beans/factory/BeanFactory.java new file mode 100644 index 00000000..779d8b61 --- /dev/null +++ b/src/main/java/com/diy/framework/web/beans/factory/BeanFactory.java @@ -0,0 +1,85 @@ +package com.diy.framework.web.beans.factory; + +import com.diy.framework.web.annotation.Autowired; + +import java.lang.reflect.Constructor; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +public class BeanFactory { + + private final Map, Object> beans = new HashMap<>(); + private Set> beanClasses; + + public void addBean(final Class clazz, final Object bean) { + // 생성한 빈 저장 + beans.put(clazz, bean); + } + + public void initialize(final Set> beanClasses) throws Exception { + this.beanClasses = beanClasses; + + // 빈 클래스들로 객체 생성 후 저장 + for (Class beanClass : beanClasses) { + if (getBean(beanClass) == null) { + Object bean = createBean(beanClass); + addBean(beanClass, bean); + } + } + } + + public Object getBean(final Class clazz) { + // 저장된 빈 조회 + return beans.get(clazz); + } + + private Object createBean(final Class beanClass) throws Exception { + // 사용할 생성자 찾기 + Constructor constructor = findConstructor(beanClass); + + // 생성자 파라미터 타입 가져오기 + Class[] parameterTypes = constructor.getParameterTypes(); + + // 파라미터 없는 생성자면 바로 객체 생성 + if (parameterTypes.length == 0) { + return constructor.newInstance(); + } + + // 생성자 파라미터에 넣을 빈들 찾기 + Object[] parameters = new Object[parameterTypes.length]; + for (int i = 0; i < parameterTypes.length; i++) { + parameters[i] = getOrCreateBean(parameterTypes[i]); + } + + // 찾은 빈들을 생성자에 넣어서 객체 생성 + return constructor.newInstance(parameters); + } + + private Object getOrCreateBean(final Class beanClass) throws Exception { + Object bean = getBean(beanClass); + if (bean != null) { + return bean; + } + + if (!beanClasses.contains(beanClass)) { + throw new IllegalArgumentException("빈 클래스를 찾을 수 없습니다: " + beanClass.getName()); + } + + bean = createBean(beanClass); + addBean(beanClass, bean); + return bean; + } + + private Constructor findConstructor(final Class beanClass) throws NoSuchMethodException { + // Autowired 붙은 생성자 찾기 + for (Constructor constructor : beanClass.getDeclaredConstructors()) { + if (constructor.isAnnotationPresent(Autowired.class)) { + return constructor; + } + } + + // 없으면 기본 생성자 사용 + return beanClass.getDeclaredConstructor(); + } +} \ No newline at end of file diff --git a/src/main/java/com/diy/framework/web/beans/factory/BeanScanner.java b/src/main/java/com/diy/framework/web/beans/factory/BeanScanner.java new file mode 100644 index 00000000..b5276f34 --- /dev/null +++ b/src/main/java/com/diy/framework/web/beans/factory/BeanScanner.java @@ -0,0 +1,25 @@ +package com.diy.framework.web.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/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/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) { 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..e8eb9577 --- /dev/null +++ b/src/main/java/com/diy/framework/web/servlet/DispatcherServlet.java @@ -0,0 +1,84 @@ +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.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; + +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(); + } + } +} diff --git a/src/test/java/com/diy/app/ReflectionTest.java b/src/test/java/com/diy/app/ReflectionTest.java new file mode 100644 index 00000000..6c826dcf --- /dev/null +++ b/src/test/java/com/diy/app/ReflectionTest.java @@ -0,0 +1,87 @@ +package com.diy.app; + +import com.diy.framework.web.annotation.MethodOrder; +import org.junit.jupiter.api.Test; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; + +public class ReflectionTest { + + @Test + void lecture_생성자_찾기() { + // Lecture 생성자들 가져오기 + Constructor[] constructors = Lecture.class.getDeclaredConstructors(); + + // 가져온 생성자 출력 + for (Constructor constructor : constructors) { + System.out.println(constructor); + } + } + + @Test + void lecture_인스턴스_동적_생성() throws Exception { + // Lecture 기본 생성자 가져오기 + Constructor constructor = Lecture.class.getDeclaredConstructor(); + + // 가져온 생성자로 객체 생성 + Lecture lecture = constructor.newInstance(); + + // 생성된 객체 출력 + System.out.println(lecture); + } + + @Test + void private_메서드_찾기() throws Exception { + // private 메서드 찾기 + Method method = Lecture.class.getDeclaredMethod("getDisplayName"); + + // 찾은 메서드 출력 + System.out.println(method); + } + + @Test + void private_메서드_호출() throws Exception { + // Lecture 객체 생성 + Lecture lecture = new Lecture(); + + // private 메서드 찾기 + Method method = Lecture.class.getDeclaredMethod("getDisplayName"); + + // private 메서드 접근 허용 + method.setAccessible(true); + + // private 메서드 호출 + Object result = method.invoke(lecture); + + // 호출 결과 출력 + System.out.println(result); + } + + @Test + void 애너테이션으로_메서드_찾기() { + // Lecture 메서드들 가져오기 + Method[] methods = Lecture.class.getDeclaredMethods(); + + // MethodOrder 붙은 메서드만 출력 + for (Method method : methods) { + if (method.isAnnotationPresent(MethodOrder.class)) { + System.out.println(method); + } + } + } + + @Test + void MethodOrder_애너테이션_정보_조회() throws Exception { + // MethodOrder 붙은 메서드 찾기 + Method method = Lecture.class.getDeclaredMethod("getDisplayName"); + + // MethodOrder 애너테이션 가져오기 + MethodOrder methodOrder = method.getAnnotation(MethodOrder.class); + + // 애너테이션 값 출력 + System.out.println(methodOrder.value()); + } + + +} diff --git a/src/test/java/com/diy/framework/web/beans/factory/BeanFactoryTest.java b/src/test/java/com/diy/framework/web/beans/factory/BeanFactoryTest.java new file mode 100644 index 00000000..fd032476 --- /dev/null +++ b/src/test/java/com/diy/framework/web/beans/factory/BeanFactoryTest.java @@ -0,0 +1,81 @@ +package com.diy.framework.web.beans.factory; + +import com.diy.app.Lecture; +import com.diy.app.LectureService; +import com.diy.framework.web.annotation.Autowired; +import com.diy.framework.web.annotation.Component; +import org.junit.jupiter.api.Test; + +import java.lang.reflect.Constructor; +import java.util.Set; + +public class BeanFactoryTest { + + @Test + void 빈_저장하고_조회하기() { + // BeanFactory 생성 + BeanFactory beanFactory = new BeanFactory(); + + // 저장할 Lecture 객체 생성 + Lecture lecture = new Lecture(); + + // Lecture 빈 저장 + beanFactory.addBean(Lecture.class, lecture); + + // Lecture 빈 조회 + Object bean = beanFactory.getBean(Lecture.class); + + // 조회한 빈 출력 + System.out.println(bean); + } + + @Test + void 빈_클래스로_객체_생성하고_저장하기() throws Exception { + // BeanScanner로 Component 붙은 클래스 찾기 + BeanScanner beanScanner = new BeanScanner("com.diy.app"); + Set> beanClasses = beanScanner.scanClassesTypeAnnotatedWith(Component.class); + + // BeanFactory가 빈 클래스들로 객체 생성 후 저장 + BeanFactory beanFactory = new BeanFactory(); + beanFactory.initialize(beanClasses); + + // 저장된 Lecture 빈 조회 + Object bean = beanFactory.getBean(Lecture.class); + + // 조회한 빈 출력 + System.out.println(bean); + } + + @Test + void Autowired_붙은_생성자_찾기() { + // LectureService 생성자들 가져오기 + Constructor[] constructors = LectureService.class.getDeclaredConstructors(); + + // Autowired 붙은 생성자 출력 + for (Constructor constructor : constructors) { + if (constructor.isAnnotationPresent(Autowired.class)) { + System.out.println(constructor); + } + } + } + + @Test + void Autowired_생성자로_빈_주입하기() throws Exception { + // BeanScanner로 Component 붙은 클래스 찾기 + BeanScanner beanScanner = new BeanScanner("com.diy.app"); + Set> beanClasses = beanScanner.scanClassesTypeAnnotatedWith(Component.class); + + // BeanFactory가 빈 클래스들로 객체 생성 후 저장 + BeanFactory beanFactory = new BeanFactory(); + beanFactory.initialize(beanClasses); + + // LectureService 빈 조회 + LectureService lectureService = (LectureService) beanFactory.getBean(LectureService.class); + + // 주입된 Repository로 Lecture 저장 확인 + Lecture lecture = new Lecture(); + lectureService.save(lecture); + + System.out.println(lectureService.findAll()); + } +} diff --git a/src/test/java/com/diy/framework/web/beans/factory/BeanScannerTest.java b/src/test/java/com/diy/framework/web/beans/factory/BeanScannerTest.java new file mode 100644 index 00000000..be06c486 --- /dev/null +++ b/src/test/java/com/diy/framework/web/beans/factory/BeanScannerTest.java @@ -0,0 +1,21 @@ +package com.diy.framework.web.beans.factory; + +import com.diy.framework.web.annotation.Component; +import org.junit.jupiter.api.Test; + +import java.util.Set; + +public class BeanScannerTest { + + @Test + void Component_붙은_클래스_찾기() { + // com.diy.app 패키지 스캔 준비 + BeanScanner beanScanner = new BeanScanner("com.diy.app"); + + // Component 붙은 클래스 찾기 + Set> classes = beanScanner.scanClassesTypeAnnotatedWith(Component.class); + + // 찾은 클래스 출력 + System.out.println(classes); + } +} \ No newline at end of file