Skip to content

[3주차] 관심사 분리 1 - 신재희#74

Open
yuntasha wants to merge 24 commits into
Loopers-play-dev-lab:yuntashafrom
yuntasha:yuntasha
Open

[3주차] 관심사 분리 1 - 신재희#74
yuntasha wants to merge 24 commits into
Loopers-play-dev-lab:yuntashafrom
yuntasha:yuntasha

Conversation

@yuntasha

@yuntasha yuntasha commented May 5, 2026

Copy link
Copy Markdown
Collaborator

요구사항

빈 생성

  • Component 애너테이션 생성
  • 빈 클래스 조회
  • 빈 객체 생성

빈 주입

  • Autowired 애너테이션 생성
  • 런타임 생성자 검색
  • 빈 주입을 포함한 객체 생성

나만의 추가 요구사항

  • 인터페이스를 통한 조회
    • �순환 참조 조회
    • 2개 이상인 경우 추가 처리
      • 에러를 내기
      • order가 있는 애들이라면 체이닝 처리
  • DataSource로 yml 설정에 따라 다른 DB로 연결되도록 작성해보기
  • 기본 생성자가 아닌 경우도 조회하여 처리

나머지는 다음 기회에 ㅎㅎ....
수요일에 잠시 시간빌때 따로 디벨롭을....!

AOP 실습

test로 시작하는 메소드 실행하기

methods 값 확인

@Test  
@DisplayName("test로 시작하는 메소드 실행하기")  
void executeMethodStartWithTest() {  
    Class<Car> clazz = Car.class;  
    Method[] methods = clazz.getDeclaredMethods();  
    Arrays.stream(methods).forEach(System.out::println);  
}

결과

public void study.reflection.Car.printView()
public java.lang.String study.reflection.Car.testGetName()
public java.lang.String study.reflection.Car.testGetPrice()

toString()이라 내가 사용할만한 값이 아니다.
getName을 발견

getName()값 확인

@Test  
@DisplayName("test로 시작하는 메소드 실행하기")  
void executeMethodStartWithTest() {  
    Class<Car> clazz = Car.class;  
    Method[] methods = clazz.getDeclaredMethods();  
    Arrays.stream(methods).map(method -> method.getName()).forEach(System.out::println);  
}

결과

printView
testGetName
testGetPrice

내가 원하는 결과가 나왔다.

필터링

@Test  
@DisplayName("test로 시작하는 메소드 실행하기")  
void executeMethodStartWithTest() {  
    Class<Car> clazz = Car.class;  
    Method[] methods = clazz.getDeclaredMethods();  
    Arrays.stream(methods)  
            .filter(m -> m.getName().startsWith("test"))  
            .peek(m -> System.out.println(m.getName())).toArray();  
}

결과

testGetName
testGetPrice

인스턴스 만들기

image 위 docs를 보면 deprecate가 된다는 이야기가 있어 아래 내용으로 바꾸면 될 것 같슴다 ```java @test @DisplayName("test로 시작하는 메소드 실행하기") void executeMethodStartWithTest() throws InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException { Class clazz = Car.class; Car car = clazz.getDeclaredConstructor().newInstance();
Method[] methods = clazz.getDeclaredMethods();  
Arrays.stream(methods)  
    .filter(m -> m.getName().startsWith("test"))  
    .peek(m -> System.out.println(m.getName()))  
    .forEach(m -> {  
        try {  
            System.out.println(m.invoke(car));  
        } catch (IllegalAccessException | InvocationTargetException e) {  
            throw new RuntimeException(e);  
        }
	});

}

#### 결과

testGetName
test : null
testGetPrice
test : 0


### 생성자 입력 파라미터 있는 것 가져오기
```java
@Test  
@DisplayName("test로 시작하는 메소드 실행하기")  
void executeMethodStartWithTest() throws InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {  
    Class<Car> clazz = Car.class;  
    Car car = clazz.getDeclaredConstructor(String.class, int.class).newInstance("차 이름", 100000);  
  
    Method[] methods = clazz.getDeclaredMethods();  
    Arrays.stream(methods)  
        .filter(m -> m.getName().startsWith("test"))  
        .peek(m -> System.out.println(m.getName()))  
        .forEach(m -> {  
            try {  
                System.out.println(m.invoke(car));  
            } catch (IllegalAccessException | InvocationTargetException e) {  
                throw new RuntimeException(e);  
            }        
		});
}

int.class가 존재하고 Integer.class랑 다르게 동작함 ㄷㄷ

@PrintView 애노테이션 메소드 실행

@Test  
@DisplayName("@PrintView 애노테이션 메소드 실행하기")  
void executeMethodHaveAnnotation() throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {  
    Class<Car> clazz = Car.class;  
    Car car = clazz.getDeclaredConstructor(String.class, int.class).newInstance("차 이름", 100000);  
  
    Method[] methods = clazz.getMethods();  
    Arrays.stream(methods)  
            .filter(method -> method.isAnnotationPresent(PrintView.class))  
            .forEach(m -> {  
                try {  
                    m.invoke(car);  
                } catch (IllegalAccessException | InvocationTargetException e) {  
                    throw new RuntimeException(e);  
                }            });}

AOP Lecture 요구사항

1. 생성자 찾기

@Test  
@DisplayName("요구사항 1 : Lecture 생성자 찾기")  
void findConstructor() {  
    Constructor<?>[] constructors = Lecture.class.getDeclaredConstructors();  
    Arrays.stream(constructors)  
            .forEach(System.out::println);  
}

결과

public study.reflection.lecture.Lecture(java.lang.String,int,boolean)
public study.reflection.lecture.Lecture(java.lang.String,int)

2. Lecture 인스턴스 동적 생성

@Test  
@DisplayName("요구사항 2 : Lecture 인스턴스 동적 생성2")  
void createConstructor2() throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {  
    final String name = "테스트 이름";  
    final int price = 10000;  
    final boolean visible = false;  
    Lecture lecture = Lecture.class.getDeclaredConstructor(String.class, int.class, boolean.class).newInstance(name, price, visible);  
  
    assertThat(lecture.getName()).isEqualTo(name);  
    assertThat(lecture.getPrice()).isEqualTo(price);  
}

3. private 메서드 찾기

image ㅋㅋ 이런거 발견 ```java @test @DisplayName("요구사항 3 : private 메서드 찾기") void findPrivateMethod() { Class clazz = Lecture.class; Arrays.stream(clazz.getDeclaredMethods()).filter(method -> method.accessFlags().contains(AccessFlag.PRIVATE)).forEach(System.out::println); } ``` ### 결과 ``` private void study.reflection.lecture.Lecture.changeVisible() ``` ## 4. private 메서드 호출 ```java @test @DisplayName("요구사항 4 : private 메서드 호출") void executePrivateMethod() throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException { Class clazz = Lecture.class;
final String name = "테스트 이름";  
final int price = 10000;  
final boolean visible = false;  
Lecture lecture = clazz.getDeclaredConstructor(String.class, int.class, boolean.class).newInstance(name, price, visible);  

Arrays.stream(clazz.getDeclaredMethods()).filter(method -> method.accessFlags().contains(AccessFlag.PRIVATE))  
        .forEach(method -> {  
            try {  
                method.setAccessible(true);  
                method.invoke(lecture);  
            } catch (IllegalAccessException e) {  
                throw new RuntimeException(e);  
            } catch (InvocationTargetException e) {  
                throw new RuntimeException(e);  
            } finally {  
                method.setAccessible(false);  
            }            });  
Field field = clazz.getDeclaredField("visible");  
field.setAccessible(true);  
try {  
    assertThat(field.getBoolean(lecture)).isEqualTo(true);  
} finally {  
    field.setAccessible(false);  
}}
- 필드 값 변경된 것을 확인
## 5. 애너테이션으로 메서드 찾기
```java
@Test  
@DisplayName("애너테이션으로 메서드 찾기")  
void findMethodByAnnotation() {  
    Method[] methods = Lecture.class.getMethods();  
    Arrays.stream(methods)  
            .filter(method -> method.isAnnotationPresent(MethodOrder.class))  
            .forEach(System.out::println);  
}

결과

public int study.reflection.lecture.Lecture.getPrice()
public java.lang.String study.reflection.lecture.Lecture.getName()

6. @MethodOrder 애너테이션 정보 조회

@Test  
@DisplayName("요구사항 6 : @MethodOrder 애너테이션 정보 조회")  
void findMethodParameterByAnnotation() {  
    Method[] methods = Lecture.class.getMethods();  
    Arrays.stream(methods)  
            .filter(method -> method.isAnnotationPresent(MethodOrder.class))  
            .forEach(method -> {  
                System.out.println(method + " : " + method.getAnnotation(MethodOrder.class).value());  
            });}

결과

public int study.reflection.lecture.Lecture.getPrice() : 2
public java.lang.String study.reflection.lecture.Lecture.getName() : 1

빈 조회 및 필드 조회

  1. Component가 달린 모든 클래스를 조회
  2. Component의 클래스에서 Autowired로 설정된 모든 필드를 조회
  3. 위상정렬을 통해 생성 순서 파악
  4. 순서에 따라 생성하며 필드 주입

이슈 및 생각 정리

위상 정렬을 통해 순서를 찾은 이유

  • 순환 참조 예외 처리
  • 생성자로 만드는 경우 위상정렬이 필요하겠다는 생각으로 진행

instanceof가 동작하지 않음

  • instanceof : 컴파일 시점
  • Class.isInstance(Object) : 런타임 시점
    자바 리플렉션의 경우 런타임 시점에서 진행되기 때문에 컴파일 시점에 적용되는 instanceof를 사용할 수 없습니다.
    따라서 아래 Class.isInstance(Object) 메서드를 사용하여 처리하였습니다.

@jude-loopers jude-loopers left a comment

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.

재희님 고생하셨습니다~
궁금한점 몇가지를 남겨봤어요

Comment on lines +29 to +31
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 역할에 알맞을까요?

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.

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


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 매핑을 하는군요 ㅎㅎ

Comment on lines +68 to +86
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 일까요? 그리고 이 메서드는 왜 필요할까요?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants