Skip to content
Open
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
9892844
:heavy_plus_sign: 리플렉션 cnrk
yuntasha Apr 30, 2026
ce97712
:sparkles: Car 추가
yuntasha Apr 30, 2026
dc5f9a4
:sparkles: 어노테이션 추가 (컴파일 이슈)
yuntasha Apr 30, 2026
0eea9eb
:sparkles: 요구사항 3까지 구현
yuntasha Apr 30, 2026
026cfae
:recycle: 이름 변경
yuntasha Apr 30, 2026
fc518f2
:heavy_plus_sign: assertThat가져오기
yuntasha Apr 30, 2026
670a24d
:sparkles: getter 추가
yuntasha Apr 30, 2026
a68fe59
:sparkles: 요구사항 4 해결
yuntasha Apr 30, 2026
4402540
:sparkles: 요구사항 5 해결
yuntasha Apr 30, 2026
d69fdfc
:sparkles: Lecture 요구사항 1, 2 해결
yuntasha Apr 30, 2026
9104d4e
:sparkles: Lecture 요구사항 3 해결
yuntasha Apr 30, 2026
081b01b
:sparkles: Lecture 요구사항 4 해결
yuntasha Apr 30, 2026
ba30f34
:sparkles: Lecture 요구사항 5 해결
yuntasha Apr 30, 2026
8b136ae
:sparkles: Lecture 요구사항 6 해결
yuntasha Apr 30, 2026
c482f02
:sparkles: 컴포넌트 어노테이션 생성
yuntasha Apr 30, 2026
aeacaa9
:sparkles: Component 어노테이션 생성
yuntasha May 1, 2026
08b15a4
:sparkles: 스캐너가 실행되도록 작성
yuntasha May 1, 2026
2095347
:recycle: 빈 스토리지로 변경
yuntasha May 1, 2026
da39f2e
:sparkles: 컴포넌트 레포지토리 생성
yuntasha May 1, 2026
39351a7
:sparkles: autowired 적용
yuntasha May 5, 2026
f1dcf50
:sparkles: 위상정렬을 통한 만드는 순서 조정 및 예외처리
yuntasha May 5, 2026
cc636ac
Merge pull request #4 from yuntasha/week3
yuntasha May 5, 2026
d0eacac
:recycle: Autowired 요구사항에 맞게 변경
yuntasha May 16, 2026
7bb520a
Merge pull request #5 from yuntasha/week3-refactor
yuntasha May 16, 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
7 changes: 7 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import org.gradle.internal.impldep.org.junit.Assert.assertThat

plugins {
id("java")
kotlin("jvm")
Expand All @@ -24,11 +26,16 @@ dependencies {

implementation(group = "org.json", name = "json", version = "20160810")

// aop
implementation("org.reflections:reflections:0.10.2")

// 톰캣
implementation("org.apache.tomcat.embed:tomcat-embed-core:8.5.42")
implementation("org.apache.tomcat.embed:tomcat-embed-jasper:8.5.42")

implementation(kotlin("stdlib-jdk8"))

implementation("org.assertj:assertj-core:3.24.2")
}

tasks.test {
Expand Down
3 changes: 3 additions & 0 deletions src/main/java/com/diy/app/Main.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package com.diy.app;

import com.diy.app.business.repository.LectureRepository;
import com.diy.framework.beans.factory.BeanStorage;
import com.diy.framework.web.server.TomcatWebServer;

public class Main {
public static void main(String[] args) {
BeanStorage beanStorage = BeanStorage.getInstance();
TomcatWebServer tomcatWebServer = new TomcatWebServer();
tomcatWebServer.start();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,30 @@
import com.diy.app.infra.dto.ModelAndView;
import com.diy.app.infra.httpSpec.HttpMethod;
import com.diy.app.infra.port.Controller;
import com.diy.framework.beans.annotations.Autowired;
import com.diy.framework.beans.annotations.Component;
import com.fasterxml.jackson.databind.ObjectMapper;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;

@Component
public class LectureController implements Controller {

@Autowired
private LectureService service;
private ObjectMapper objectMapper;
private final ObjectMapper objectMapper;

public LectureController(LectureService service) {
this.service = service;
this.objectMapper = new ObjectMapper();
public LectureController() {
objectMapper = new ObjectMapper();
}

// public LectureController(LectureService service) {
// this.service = service;
// this.objectMapper = new ObjectMapper();
// }

@Override
public ModelAndView handleRequest(HttpServletRequest req, HttpServletResponse resp) throws Exception {
String uri = req.getRequestURI();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
package com.diy.app.business.repository;

import com.diy.app.business.domain.Lecture;
import com.diy.framework.beans.annotations.Component;

import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;

@Component
public class LectureRepository {
static LectureRepository instance;

AtomicLong nextId;
Map<Long, Lecture> lectureDB;
Expand All @@ -19,10 +20,6 @@ private LectureRepository() {
lectureDB = new ConcurrentHashMap<>();
}

public static LectureRepository getInstance() {
return instance == null ? instance = new LectureRepository() : instance;
}

public List<Lecture> getAll() {
return lectureDB.values().stream().toList();
}
Expand Down
15 changes: 5 additions & 10 deletions src/main/java/com/diy/app/business/service/LectureService.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,16 @@

import com.diy.app.business.domain.Lecture;
import com.diy.app.business.repository.LectureRepository;
import com.diy.framework.beans.annotations.Autowired;
import com.diy.framework.beans.annotations.Component;

import java.util.List;

@Component
public class LectureService {
private static LectureService instance = null;

private static LectureRepository repository = null;

private LectureService(LectureRepository repository) {
this.repository = repository;
}

public static LectureService getInstance() {
return instance == null ? instance = new LectureService(LectureRepository.getInstance()) : instance;
}
@Autowired
private static LectureRepository repository;

public List<Lecture> getAllLectures() {
return repository.getAll();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.diy.app.business.service.LectureService;
import com.diy.app.infra.port.Controller;
import com.diy.app.infra.viewRender.ViewResolver;
import com.diy.framework.beans.factory.BeanStorage;

import java.util.Comparator;
import java.util.Map;
Expand All @@ -14,7 +15,7 @@ public class UrlControllerMapper {
private static UrlControllerMapper instance;

public UrlControllerMapper() {
uriToController.put("/lectures", new LectureController(LectureService.getInstance()));
uriToController.put("/lectures", BeanStorage.getInstance().getBeans(LectureController.class).getFirst());

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.

오호 BeanStorage 를 싱글톤으로 활용해서 controller 매핑을 하는군요 ㅎㅎ

}

public Controller findController(String uri) {
Expand Down
3 changes: 3 additions & 0 deletions src/main/java/com/diy/app/infra/viewRender/JspView.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ public void render(final HttpServletRequest req, final HttpServletResponse resp,
.getFile().split("resources/")[1];

final RequestDispatcher requestDispatcher = req.getRequestDispatcher(filePath);
for (String key : modelAndView.getModel().keySet()) {
req.setAttribute(key, modelAndView.getModel().get(key));
}
requestDispatcher.forward(req, resp);
}

Expand Down
11 changes: 11 additions & 0 deletions src/main/java/com/diy/framework/beans/annotations/Autowired.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.diy.framework.beans.annotations;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Autowired {
}
11 changes: 11 additions & 0 deletions src/main/java/com/diy/framework/beans/annotations/Component.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.diy.framework.beans.annotations;

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 {
}
32 changes: 32 additions & 0 deletions src/main/java/com/diy/framework/beans/factory/BeanScanner.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.diy.framework.beans.factory;

import com.diy.framework.beans.annotations.Autowired;
import org.reflections.Reflections;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
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());
}

public Set<Field> scanField(final Class<?> clazz) {
return Arrays.stream(clazz.getDeclaredFields()).filter(field -> Objects.nonNull(field.getDeclaredAnnotation(Autowired.class))).collect(Collectors.toSet());
}

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.

이 메서드가 BeanSacnner 역할에 알맞을까요?

}
123 changes: 123 additions & 0 deletions src/main/java/com/diy/framework/beans/factory/BeanStorage.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package com.diy.framework.beans.factory;

import com.diy.framework.beans.annotations.Component;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.*;

public class BeanStorage {

private static BeanStorage instance;

private final List<Object> beans;
private final BeanScanner bc = new BeanScanner("com.diy.app");

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.

음.. 이걸 하드코딩 해도 괜찮을까요?


private BeanStorage() {
Set<Class<?>> classes = bc.scanClassesTypeAnnotatedWith(Component.class);
Map<Class<?>, Set<Field>> classToFields = getFields(classes);
List<Class<?>> sortedClasses = topologicalSort(classToFields);
beans = makeBeans(sortedClasses, classToFields);
}

public static BeanStorage getInstance() {
if (Objects.isNull(instance)) instance = new BeanStorage();
return instance;
}

private static List<Object> makeBeans(List<Class<?>> classes, Map<Class<?>, Set<Field>> classToFields) {
List<Object> results = new ArrayList<>();

for (Class<?> clazz : classes) {
Constructor<?> constructor = null;
try {
constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(true);
Object bean = constructor.newInstance();
setFields(bean, results, classToFields);
results.add(bean);
} catch (InstantiationException | IllegalAccessException | InvocationTargetException |
NoSuchMethodException e) {
throw new RuntimeException(e);
} finally {
if (constructor != null) {
constructor.setAccessible(false);
}
}
}

return results;
}

public <T> List<T> getBeans(final Class<T> classType) {
return beans.stream()
.filter(classType::isInstance)
.map(classType::cast)
.toList();
}

public Map<Class<?>, Set<Field>> getFields(Set<Class<?>> classes) {
Map<Class<?>, Set<Field>> classToField = new HashMap<>();
for (Class<?> clazz : classes) {
classToField.put(clazz, bc.scanField(clazz));
}
return classToField;
}

public static void setFields(Object target, List<Object> nowBeanList, Map<Class<?>, Set<Field>> classToFields) {
for (Field field : classToFields.get(target.getClass())) {
Object fieldTarget = null;
for (Object targetBean : nowBeanList) {
if (!field.getType().isInstance(targetBean)) continue;
if (Objects.nonNull(fieldTarget)) throw new IllegalArgumentException("2개 이상의 빈이 존재 : " + field.getType().getName());
fieldTarget = targetBean;
}
if (Objects.isNull(fieldTarget)) throw new IllegalArgumentException("존재하지 않는 빈 : " + field.getType().getName());
try {
field.setAccessible(true);
field.set(target, fieldTarget);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} finally {
field.setAccessible(false);
}
}
}

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.

음 이 메서드에 대해서는 궁금한게 많아요.
왜 static 일까요? 그리고 이 메서드는 왜 필요할까요?


public List<Class<?>> topologicalSort(Map<Class<?>, Set<Field>> classMap) {
Map<Class<?>, Set<Class<?>>> map = copyMap(classMap);

List<Class<?>> result = new ArrayList<>();
Set<Class<?>> visited = new HashSet<>();

while (visited.size() < map.size()) {
List<Class<?>> canMake = new ArrayList<>();
for (Map.Entry<Class<?>, Set<Class<?>>> entry : map.entrySet()) {
if (visited.contains(entry.getKey())) continue;
if (visited.containsAll(entry.getValue())) {
canMake.add(entry.getKey());
}
}
if (canMake.isEmpty()) {
throw new IllegalArgumentException("순환 참조 혹은 존재하지 않는 빈을 필드로하는 것이 존재합니다");
}
visited.addAll(canMake);
result.addAll(canMake);
}

return result;
}

private static Map<Class<?>, Set<Class<?>>> copyMap(Map<Class<?>, Set<Field>> classMap) {
Map<Class<?>, Set<Class<?>>> map = new HashMap<>();
for (Map.Entry<Class<?>, Set<Field>> entry : classMap.entrySet()) {
Set<Class<?>> fieldTypes = new HashSet<>();
for (Field field : entry.getValue()) {
fieldTypes.add(field.getType());
}
map.put(entry.getKey(), fieldTypes);
}
return map;
}
}
37 changes: 37 additions & 0 deletions src/test/java/study/reflection/Car.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package study.reflection;

public class Car {

private String name;
private int price;

public String getName() {
return name;
}

public int getPrice() {
return price;
}

public Car() {

}

public Car(String name, int price) {
this.name = name;
this.price = price;
}

@PrintView
public void printView() {
System.out.println("자동차 정보를 출력 합니다.");
}

public String testGetName() {
return "test : " + name;
}

public String testGetPrice() {
return "test : " + price;
}
}
8 changes: 8 additions & 0 deletions src/test/java/study/reflection/PrintView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package study.reflection;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface PrintView {
}
Loading