Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
560f9e8
init
jude-loopers Apr 12, 2026
5a4f362
1주차 자바 웹 개발 기초
jude-loopers Apr 22, 2026
3cd3989
2주차 자바 웹 요청 추상화
jude-loopers Apr 29, 2026
87473be
Merge pull request #65 from Loopers-play-dev-lab/template-main
jude-loopers Apr 29, 2026
a15d543
2주차 자바 웹 요청 추상화
jude-loopers May 3, 2026
7fb5780
Lecture 리플렉션 실습 추가
MINJOOOONG May 5, 2026
2facee0
MethodOrder 애너테이션 추가
MINJOOOONG May 5, 2026
8c50963
애너테이션으로 메서드 찾기 실습 추가
MINJOOOONG May 5, 2026
3f14fb7
MethodOrder 애너테이션 값 조회 실습 추가
MINJOOOONG May 5, 2026
bdbf604
Component 애너테이션 추가
MINJOOOONG May 5, 2026
f70c8e0
Reflections 의존성 추가
MINJOOOONG May 5, 2026
789a421
Component 붙은 클래스 스캔 실습 추가
MINJOOOONG May 5, 2026
77d3f47
Component 클래스로 빈 객체 생성 실습 추가
MINJOOOONG May 5, 2026
d198014
BeanFactory 빈 저장 조회 실습 추가
MINJOOOONG May 5, 2026
4c345bf
BeanFactory 빈 초기화 실습 추가
MINJOOOONG May 5, 2026
cc8d235
Autowired 애너테이션 추가
MINJOOOONG May 5, 2026
18b574c
Autowired 생성자 찾기 실습 추가
MINJOOOONG May 5, 2026
9a2ffd9
Autowired 생성자로 빈 주입 실습 추가
MINJOOOONG May 5, 2026
36f092f
LectureRepository 추가
MINJOOOONG May 5, 2026
8b21496
LectureService에 Repository 주입 적용
MINJOOOONG May 5, 2026
bdb6a69
LectureController에 Service 주입 적용
MINJOOOONG May 5, 2026
1c8c0f9
Lecture를 빈 대상에서 제외
MINJOOOONG May 5, 2026
ed81986
Bean 생성 순서 문제 수정
MINJOOOONG May 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,6 @@ bin/

### Mac OS ###
.DS_Store\ntomcat.8080/

## tomcat
**/tomcat.8080
3 changes: 3 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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")

Expand Down
37 changes: 37 additions & 0 deletions src/main/java/com/diy/app/Lecture.java
Original file line number Diff line number Diff line change
@@ -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";
}
}
32 changes: 32 additions & 0 deletions src/main/java/com/diy/app/LectureApplication.java
Original file line number Diff line number Diff line change
@@ -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<Class<?>> beanClasses = beanScanner.scanClassesTypeAnnotatedWith(Component.class);

final BeanFactory beanFactory = new BeanFactory();
beanFactory.initialize(beanClasses);

final LectureService lectureService = (LectureService) beanFactory.getBean(LectureService.class);

final Map<String, Controller> controllerMapping = new HashMap<>();
controllerMapping.put("/lectures", new LectureController(lectureService));

final DispatcherServlet servlet = new DispatcherServlet(controllerMapping);
final TomcatWebServer tomcatWebServer = new TomcatWebServer(servlet);
tomcatWebServer.start();
}
}
55 changes: 55 additions & 0 deletions src/main/java/com/diy/app/LectureController.java
Original file line number Diff line number Diff line change
@@ -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<Lecture> lectures = lectureService.findAll();
final Map<String, Object> 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);
}
}
25 changes: 25 additions & 0 deletions src/main/java/com/diy/app/LectureRepository.java
Original file line number Diff line number Diff line change
@@ -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<Long, Lecture> 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<Lecture> findAll() {
return lectures.values();
}
}
25 changes: 25 additions & 0 deletions src/main/java/com/diy/app/LectureService.java
Original file line number Diff line number Diff line change
@@ -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<Lecture> findAll() {
return lectureRepository.findAll();
}
}
6 changes: 0 additions & 6 deletions src/main/java/com/diy/app/Main.java

This file was deleted.

9 changes: 9 additions & 0 deletions src/main/java/com/diy/framework/web/annotation/Autowired.java
Original file line number Diff line number Diff line change
@@ -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 {
}
9 changes: 9 additions & 0 deletions src/main/java/com/diy/framework/web/annotation/Component.java
Original file line number Diff line number Diff line change
@@ -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 {
}
12 changes: 12 additions & 0 deletions src/main/java/com/diy/framework/web/annotation/MethodOrder.java
Original file line number Diff line number Diff line change
@@ -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();
}
85 changes: 85 additions & 0 deletions src/main/java/com/diy/framework/web/beans/factory/BeanFactory.java
Original file line number Diff line number Diff line change
@@ -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<Class<?>, Object> beans = new HashMap<>();
private Set<Class<?>> beanClasses;

public void addBean(final Class<?> clazz, final Object bean) {
// 생성한 빈 저장
beans.put(clazz, bean);
}

public void initialize(final Set<Class<?>> 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());
}
Comment on lines +65 to +67

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오 빈 생성 대상인지 검증! 👍🏻


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();
}
}
25 changes: 25 additions & 0 deletions src/main/java/com/diy/framework/web/beans/factory/BeanScanner.java
Original file line number Diff line number Diff line change
@@ -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<Class<?>> scanClassesTypeAnnotatedWith(final Class<? extends Annotation> annotation) {
// 특정 애너테이션이 붙은 클래스 찾기
return reflections.getTypesAnnotatedWith(annotation)
.stream()
.filter(type -> (!type.isAnnotation() && !type.isInterface()))
.collect(Collectors.toSet());
}
}
11 changes: 11 additions & 0 deletions src/main/java/com/diy/framework/web/mvc/Controller.java
Original file line number Diff line number Diff line change
@@ -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;
}
Loading