Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Misson 0 #2

Open
wants to merge 5 commits into
base: 24MinJiHong
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,14 @@ git checkout 24YoonByungWook
### DispatcherServlet 구현
`ManualHandlerMapping`과 `AnnotationHandlerMapping` 둘 다 사용할 수 있어야 한다.
- [ ] `Controller`와 `HandlerExecution` 둘 다를 실행할 수 있다.
- [ ] `ModelAndView`를 적절하게 rendering 할 수 있다.
- [ ] `ModelAndView`를 적절하게 rendering 할 수 있다.

## 🚀 2단계 - 점진적인 리팩터링

### 기능 요구사항
> Legacy MVC와 @MVC 통합하기

interface 기반의 컨트롤러와 annotation 기반의 컨트롤러가 공존하는 상태로 정상 동작하도록 구현한다.
- [ ] ControllerScanner 클래스에서 @Controller가 붙은 클래스를 찾을 수 있다.
- [ ] HandlerMappingRegistry 클래스에서 HandlerMapping을 처리하도록 한다.
- [ ] HandlerAdapterRegistry 클래스에서 HandlerAdapter를 처리하도록 한다.
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,18 @@
@WebFilter("/*")
public class CharacterEncodingFilter implements Filter {

private static final String ENCODING = "UTF-8"; // 사용할 문자 인코딩

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
// 요청(Request)와 응답(Response)의 문자 인코딩 설정
request.setCharacterEncoding(ENCODING);
response.setCharacterEncoding(ENCODING);

// 로그 기록
request.getServletContext().log("doFilter() 호출");

// 다음 필터 또는 서블릿으로 요청 전달
chain.doFilter(request, response);
}
}
133 changes: 127 additions & 6 deletions study/src/test/java/di/stage3/context/DIContainer.java
Original file line number Diff line number Diff line change
@@ -1,20 +1,141 @@
package di.stage3.context;

import java.util.Set;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.*;
import java.util.stream.Collectors;

/**
* 스프링의 BeanFactory, ApplicationContext에 해당되는 클래스
* 스프링의 BeanFactory, ApplicationContext에 해당되는 클래스.
* 클래스 간의 의존성을 주입하고 관리하는 역할을 수행한다.
*/
class DIContainer {

private final Set<Object> beans;
// 생성된 빈(인스턴스)을 저장하는 집합
private final Set<Object> beans = new HashSet<>();

// DI 컨테이너가 관리할 클래스의 집합
private final Set<Class<?>> classes;

// 순환 참조를 확인하기 위한 클래스 집합
private final Set<Class<?>> processing = new HashSet<>();

/**
* DIContainer 생성자
* @param classes DI 컨테이너가 관리할 클래스 집합
*/
public DIContainer(final Set<Class<?>> classes) {
this.beans = Set.of();
this.classes = classes; // 관리할 클래스 저장
for (Class<?> clazz : classes) { // 클래스 목록을 순회하며
createBean(clazz); // 각 클래스에 대해 빈 생성
}
}

/**
* 요청한 클래스 타입에 해당하는 빈(인스턴스)을 반환
* @param aClass 요청한 클래스 타입
* @return 요청한 클래스 타입의 빈(인스턴스)
* @throws IllegalArgumentException 요청한 클래스 타입의 빈이 없을 경우 예외 발생
*/
@SuppressWarnings("unchecked")
public <T> T getBean(final Class<T> aClass) {
return null;
return (T) beans.stream() // 빈 목록에서
.filter(aClass::isInstance) // 요청한 타입과 일치하는 빈 필터링
.findFirst() // 첫 번째 매칭된 빈 반환
.orElseThrow(() -> new IllegalArgumentException("bean 없음: " + aClass)); // 없으면 예외 발생
}

/**
* 클래스 타입을 기반으로 빈(인스턴스)을 생성하고 관리
* @param clazz 빈을 생성할 클래스 타입
*/
private void createBean(Class<?> clazz) {
// 이미 빈이 생성되어 있으면 다시 생성하지 않음
if (beans.stream().anyMatch(clazz::isInstance)) {
return;
}

// 순환 참조를 감지하여 무한 재귀를 방지
if (processing.contains(clazz)) {
throw new IllegalStateException("순환참조: " + clazz.getName());
}

// 현재 클래스 타입을 순환 참조 추적 목록에 추가
processing.add(clazz);

// 적절한 생성자를 찾음
Constructor<?> constructor = getSuitableConstructor(clazz);
if (constructor == null) {
throw new IllegalStateException("적절한 생성자 없음: " + clazz.getName());
}

// 생성자 매개변수 정보를 가져옴
Class<?>[] parameterTypes = constructor.getParameterTypes();
Object[] parameters = new Object[parameterTypes.length]; // 매개변수에 맞는 빈을 저장할 배열

try {
// 각 매개변수에 대해 의존성을 해결하여 주입
for (int i = 0; i < parameterTypes.length; i++) {
Class<?> dependencyClass = parameterTypes[i]; // 매개변수 타입
Object dependency;

if (dependencyClass.isInterface()) { // 매개변수 타입이 인터페이스인 경우
// 해당 인터페이스를 구현한 클래스를 찾음
Class<?> implClass = findImplementation(dependencyClass);
if (implClass == null) {
throw new IllegalStateException("인터페이스의 구현체 없음: " + dependencyClass);
}
createBean(implClass); // 구현체의 빈 생성
dependency = beans.stream()
.filter(dependencyClass::isInstance) // 구현체의 빈 필터링
.findFirst()
.orElseThrow(() -> new IllegalStateException("인터페이스 구현체 빈 없음: " + dependencyClass));
} else { // 매개변수 타입이 클래스인 경우
createBean(dependencyClass); // 클래스 타입 빈 생성
dependency = beans.stream()
.filter(dependencyClass::isInstance) // 생성된 클래스 타입의 빈 필터링
.findFirst()
.orElseThrow(() -> new IllegalStateException("클래스 타입의 빈 없음: " + dependencyClass));
}

// 매개변수 배열에 의존성을 추가
parameters[i] = dependency;
}

// 생성자를 호출하여 인스턴스를 생성
Object instance = constructor.newInstance(parameters);
beans.add(instance); // 생성된 인스턴스를 빈 목록에 추가
} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException("빈 생성 중 오류 발생: " + clazz.getName(), e);
}

// 클래스 타입을 순환 참조 추적 목록에서 제거
processing.remove(clazz);
}

/**
* 클래스 타입에서 가장 적합한 생성자를 선택
* @param clazz 생성자를 선택할 클래스 타입
* @return 매개변수 수가 가장 많은 생성자
*/
private Constructor<?> getSuitableConstructor(Class<?> clazz) {
// 생성자 배열을 스트림으로 변환하고, 매개변수 수가 많은 순으로 정렬
return Arrays.stream(clazz.getConstructors()) // 클래스의 생성자 배열을 스트림으로 변환
.sorted((c1, c2) -> Integer.compare(c2.getParameterCount(), c1.getParameterCount())) // 매개변수 수 기준 내림차순 정렬
.findFirst() // 가장 매개변수가 많은 생성자를 반환
.orElse(null); // 생성자가 없으면 null 반환
}

/**
* 특정 인터페이스를 구현한 클래스를 찾아 반환
* @param interfaceClass 찾을 인터페이스 타입
* @return 인터페이스를 구현한 클래스
*/
private Class<?> findImplementation(Class<?> interfaceClass) {
return classes.stream() // 관리 중인 클래스 목록에서
.filter(interfaceClass::isAssignableFrom) // 인터페이스를 구현한 클래스 필터링
.filter(clazz -> !clazz.isInterface()) // 구현체만 필터링
.findFirst() // 첫 번째 구현체 반환
.orElse(null); // 없으면 null 반환
}
}
}
21 changes: 20 additions & 1 deletion study/src/test/java/di/stage4/annotations/ClassPathScanner.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,29 @@
package di.stage4.annotations;

import org.reflections.Reflections;

import java.util.HashSet;
import java.util.Set;

public class ClassPathScanner {

/**
* 패키지 이름을 기반으로 해당 패키지에 포함된 모든 클래스를 반환
* @param packageName 스캔할 패키지 이름
* @return 패키지 내 모든 클래스 집합
*/
public static Set<Class<?>> getAllClassesInPackage(final String packageName) {
return null;
Reflections reflections = new Reflections(packageName);

// 검색할 애너테이션 목록
Set<Class<?>> annotations = Set.of(Service.class, Repository.class);

// 모든 애너테이션을 검색하여 결과 합치기
Set<Class<?>> allClasses = new HashSet<>();
for (Class<?> annotation : annotations) {
allClasses.addAll(reflections.getTypesAnnotatedWith((Class) annotation, true));
}

return allClasses;
}
}
55 changes: 51 additions & 4 deletions study/src/test/java/di/stage4/annotations/DIContainer.java
Original file line number Diff line number Diff line change
@@ -1,24 +1,71 @@
package di.stage4.annotations;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.HashSet;
import java.util.Set;

/**
* 스프링의 BeanFactory, ApplicationContext에 해당되는 클래스
*/
class DIContainer {

private final Set<Object> beans;
private final Set<Object> beans = new HashSet<>();

public DIContainer(final Set<Class<?>> classes) {
this.beans = Set.of();
// 빈 생성
for (Class<?> clazz : classes) {
if (clazz.isAnnotationPresent(Service.class) || clazz.isAnnotationPresent(Repository.class)) {
createBean(clazz);
}
}

// 의존성 주입
for (Object bean : beans) {
injectDependencies(bean);
}
}

public static DIContainer createContainerForPackage(final String rootPackageName) {
return null;
Set<Class<?>> classes = ClassPathScanner.getAllClassesInPackage(rootPackageName); // 패키지 내 클래스 스캔
return new DIContainer(classes); // 스캔된 클래스로 DI 컨테이너 생성
}

@SuppressWarnings("unchecked")
public <T> T getBean(final Class<T> aClass) {
return null;
return (T) beans.stream()
.filter(aClass::isInstance)
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("bean 없음 : " + aClass));
}

private void createBean(Class<?> clazz) {
try {
// 생성자를 가져오고 접근 가능하게 설정
Constructor<?> constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(true); // private 생성자 접근 가능 설정

// 생성자를 통해 인스턴스 생성
Object instance = constructor.newInstance();
beans.add(instance); // 빈으로 등록
} catch (Exception e) {
throw new RuntimeException("빈 생성 중 오류 발생: " + clazz.getName(), e);
}
}

private void injectDependencies(Object bean) {
Field[] fields = bean.getClass().getDeclaredFields(); // 클래스의 모든 필드 가져오기
for (Field field : fields) {
if (field.isAnnotationPresent(Inject.class)) { // @Inject 애너테이션 확인
Class<?> dependencyType = field.getType(); // 필드의 타입
Object dependency = getBean(dependencyType); // 의존성 찾기
try {
field.setAccessible(true); // private 필드 접근 가능하도록 설정
field.set(bean, dependency); // 필드에 의존성 주입
} catch (IllegalAccessException e) {
throw new RuntimeException("의존성 주입 중 오류 발생: " + field.getName(), e);
}
}
}
}
}
11 changes: 11 additions & 0 deletions study/src/test/java/reflection/Junit3TestRunner.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,23 @@

import org.junit.jupiter.api.Test;

import java.lang.reflect.Method;

class Junit3TestRunner {

@Test
void run() throws Exception {
Class<Junit3Test> clazz = Junit3Test.class;

// TODO Junit3Test에서 test로 시작하는 메소드 실행
Junit3Test junit3Test = clazz.getDeclaredConstructor().newInstance();

Method[] methods = clazz.getDeclaredMethods();

for(Method method : methods){
if(method.getName().startsWith("test")){
method.invoke(junit3Test);
}
}
}
}
12 changes: 12 additions & 0 deletions study/src/test/java/reflection/Junit4TestRunner.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,24 @@

import org.junit.jupiter.api.Test;

import java.lang.reflect.Method;

class Junit4TestRunner {

@Test
void run() throws Exception {
Class<Junit4Test> clazz = Junit4Test.class;

// TODO Junit4Test에서 @MyTest 애노테이션이 있는 메소드 실행
Junit4Test junit4Test = clazz.getDeclaredConstructor().newInstance();

Method[] methods = clazz.getDeclaredMethods();

for (Method method : methods) {
if (method.isAnnotationPresent(MyTest.class)) { // 애노테이션 존재 여부 확인
method.invoke(junit4Test); // 메소드 실행
}
}

}
}
Loading