Reflection
• 힙 영역에 로드돼 있는 클래스 타입의 객체를 통해 필드/메소드/생성자를 접근 제어자와 상관 없이 사용할 수 있도록 지원하는 API
• 컴파일 시점이 아닌 런타임 시점에 동적으로 특정 클래스의 정보를 추출해낼 수 있는 프로그래밍 기법
• 주로 프레임워크 또는 라이브러리 개발 시 사용됨
https://www.baeldung.com/reflections-library
Reflection 사용하는 프레임워크/라이브러리 소개
• Spring 프레임워크 (ex. DI)
• Test 프레임워크 (ex. JUnit)
• JSON Serialization/Deserialization 라이브러리 (ex. Jackson)
• 등등
실습
• @Controller 애노테이션이 설정돼 있는 모든 클래스를 찾아서 출력한다.
package org.example;
import org.example.annotation.Controller;
import org.example.annotation.Service;
import org.example.model.User;
import org.junit.jupiter.api.Test;
import org.reflections.Reflections;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import static org.assertj.core.api.Assertions.assertThat;
// @Controller 애노테이션이 설정돼 있는 모든 클래스를 찾아서 출력한다.
public class ReflectionTest {
private static final Logger logger = LoggerFactory.getLogger(ReflectionTest.class);
@Test
void controllerScan() {
Set<Class<?>> beans = getTypesAnnotatedWith(List.of(Controller.class, Service.class));
logger.debug("beans: [{}]", beans);
}
@Test
void showClass() {
Class<User> clazz = User.class;
logger.debug(clazz.getName());
logger.debug("User declared fields: [{}]", Arrays.stream(clazz.getDeclaredFields()).collect(Collectors.toList()));
logger.debug("User declared constructors: [{}]", Arrays.stream(clazz.getDeclaredConstructors()).collect(Collectors.toList()));
logger.debug("user all delared mehods: [{}]", Arrays.stream(clazz.getDeclaredMethods()).collect(Collectors.toList()));
}
@Test
void load() throws ClassNotFoundException { // 힙 영역에 로드되어 있는 클래스 타입의 객체를 가져오는 세가지 방법
// 1
Class<User> clazz = User.class;
// 2
User user = new User("serverwizard", "이창훈");
Class<? extends User> clazz2 = user.getClass();
// 3
Class<?> clazz3 = Class.forName("org.example.model.User");
logger.debug("clazz: [{}]",clazz);
logger.debug("clazz2: [{}]",clazz2);
logger.debug("clazz3: [{}]",clazz3);
assertThat(clazz == clazz2).isTrue();
assertThat(clazz2 == clazz3).isTrue();
assertThat(clazz == clazz3).isTrue();
}
private Set<Class<?>> getTypesAnnotatedWith(List<Class<? extends Annotation>> annotations) {
Reflections reflection = new Reflections("org.example");
Set<Class<?>> beans = new HashSet<>();
annotations.forEach(annotation -> beans.addAll(reflection.getTypesAnnotatedWith(annotation)));
return beans;
}
}
@Test
void controllerScan() {
Set<Class<?>> beans = getTypesAnnotatedWith(List.of(Controller.class, Service.class));
logger.debug("beans: [{}]", beans);
}
1. controllerScan 메소드는 Controller 및 service 어노테이션을 사용하는 클래스를 찾아서 출력한다.
2. getTypeAnnotatedWith 메소드를 호출하여 Controller 및 Service 어노테이션이 설정된 클래스를 검색한다
3. Reflections 를 사용하여 org.example 패키지 내에서 어노테이션 목록에 해당하는 클래스를 검색하고 반환한다.
검색된 클래스들은 Set<Class<?>> 형태로 반환되며 결과를 로그에 출력한다
@Test
void showClass() {
Class<User> clazz = User.class;
logger.debug(clazz.getName());
logger.debug("User declared fields: [{}]", Arrays.stream(clazz.getDeclaredFields()).collect(Collectors.toList()));
logger.debug("User declared constructors: [{}]", Arrays.stream(clazz.getDeclaredConstructors()).collect(Collectors.toList()));
logger.debug("user all delared mehods: [{}]", Arrays.stream(clazz.getDeclaredMethods()).collect(Collectors.toList()));
}
1. showClass 메소드는 User 클래스의 정보를 출력한다.
2. User 클래스의 이름을 출력하고 Class 객체를 사용하여 해당 클래스의 선언된 필드 , 생성자, 메소드를 출력한다.
@Test
void load() throws ClassNotFoundException { // 힙 영역에 로드되어 있는 클래스 타입의 객체를 가져오는 세가지 방법
// 1
Class<User> clazz = User.class;
// 2
User user = new User("serverwizard", "이창훈");
Class<? extends User> clazz2 = user.getClass();
// 3
Class<?> clazz3 = Class.forName("org.example.model.User");
logger.debug("clazz: [{}]",clazz);
logger.debug("clazz2: [{}]",clazz2);
logger.debug("clazz3: [{}]",clazz3);
assertThat(clazz == clazz2).isTrue();
assertThat(clazz2 == clazz3).isTrue();
assertThat(clazz == clazz3).isTrue();
}
1. load 메소드는 클래스를 로드하는 다양한 방법을 보여준다.
2. Class<User> 객체를 가져오는 방법을 세가지로 설명한다.
2-1 : 클래스 리터럴을 사용하여 클래스를 가져온다.
2-2 : 객체를 생성한 후 getClass() 메소드를 사용하여 클래스를 가져온다.
2-3 : 클래스 이름을 문자열로 제공하여 Class.forName() 메소드를 사용하여 클래스를 가져온다.
private Set<Class<?>> getTypesAnnotatedWith(List<Class<? extends Annotation>> annotations) {
Reflections reflection = new Reflections("org.example");
Set<Class<?>> beans = new HashSet<>();
annotations.forEach(annotation -> beans.addAll(reflection.getTypesAnnotatedWith(annotation)));
return beans;
}
1. getTypesAnnotatedWith 메소드는 주어진 어노테이션 목록을 사용하여 클래스를 검색하는 메소드이다
.
2. Reflection 라이브러리를 사용하여 패키지 org.example 내에서 어노테이션 목록에 해당하는 클래스를 검색한다.
3. 검색된 클래스들은 set<Class<?>> 형태로 반환되며 이들은 beans 변수에 저장되여 반환된다.
위 코드들은 주로 리프렉션을 사용하여 어노테이션을 활용하는 방법을 보여준다.
Controller 및 Service 어노테이션이 설정된 클래스를 찾고 출력하는 것이 주요 목적이다.
또한 클래스의 정보를 출력하고 클래스 로딩 방법을 설명하는 부분도 포함된다.
package org.example.annotation;
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 Controller {
}
@Target({ElementType.TYPE}) :
1. @Target 어노테이션은 어노테이션이 적용될 대상을 지정한다.
여기서는 ElementType.TYPE 를 사용하여 어노테이션이 클래스에만 적용될 수 있도록 제한한다. 즉 어노테이션은 클래스에만 적용할 수 있다.
2. @Retention(RetentionPolicy.RUNTIME) :
@Retention 어노테이션은 어노테이션의 보존 정책을 지정한다 RetentionPolicy.RUNTIME 은 어노테이션 정보가
런타임에도 유지되어야 함을 나타낸다 이것은 리플렉션을 통해 어노테이션 정보를 읽을 수 있도록 한다.
어노테이션을 사용 하라면 밑의 클래스 또는 메소드에 해당 어노테이션을 적용할 수 있다
@Controller
public class MyController {
// 클래스 내용
}
위의 예시에서 MyController 클래스에 @Controller 어노테이션을 적용했다
이렇게 어노테이션을 사용하면 리플렉션과 같은 메커니즘을 통해 어노테이션 정보를 사용하여 클래스나 메소드를 식별할거나 특별한 동작을 수행할 수 있다.
예를 들어, 위의 코드에서 정의한 @Controller 애노테이션이 있는 클래스를 ReflectionTest의 controllerScan 메소드에서 찾아서 출력하는 것과 같은 용도로 사용될 수 있다.
package org.example.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestMapping {
String value() default "";
RequestMethod[] method() default {};
}
위 코드는 또 다른 사용자 정의 어노테이션인 @RequestMapping 을 정의하는 예제이다
@RequestMapping어노테이션은 웹 프레임워크에서 주로 사용되며 HTTP 요청에 대한 핸들러 메소드와 URL 매핑을 정의하는 데 사용된다.
1. @Target({ElementType.TYPE, ElementType.METHOD}):
ElementType.TYPE 과 ElementType.METHOD 을 사용 하여 어노테이션의 클래스와 메소드에 모두 적용될 수 있도록 지정 한다 즉, 이 어노테이션은 클래스와 메소드 모두에 적용할 수 있다.
2. @Retention(RetentionPolicy.RUNTIME):
RetentionPolicy.RUNTIME 은 어노테이션 정보가 런타임에도 유지되어야 함을 나타난대 이것은 리플렉션을 통해 어노테이션 정보를 읽을 수 있도록 한다.
3. 어노테이션 멤버 :
@RequestMapping 어노테이션은 두개의 멤버를 정의한다
- value() : 기본값이 빈 문자열 ("")이며, 이멤버는 URL 패턴을 나타낸다 핸들러 메소드나 클래스에 이 어노테이션을 적용 할 때 URL 패턴을 설정할 수 있다.
- method() :
기본값은 빈 배열 ({}) 이며 이 멤버는 HTTP 요청메서드를 지정하는데 사용된다 RequestMethod 배열을 통해 여러 HTTP 메서드를 지정할 수 있다.
어노테이션을 사용할 때 예를 들어 컨트롤러 클래스나 핸들러 메소드에 @ @RequestMapping 어노테이션을 적용하고 URL 패턴 및 HTTP 메소드를 설정할 수 있다 예로
@Controller
public class MyController {
@RequestMapping(value = "/myEndpoint", method = {RequestMethod.GET, RequestMethod.POST})
public String handleRequest() {
// 메소드 내용
return "viewName";
}
}
위 코드에서 @RequestMapping 어노테이션은 클래스와 메소드 모두에 적용되었으며 /myEndpoint URL 패턴과 GET 및 POST HTTP 메소드를 처리하는 핸들러 메소드를 정의한다 . 이러한 어노테이션을 사용하면 웹 어플리에키션에서 URL 매핑과 핸들러 메소드를 효과적으로 관리할 수 있다.
package org.example.annotation;
public enum RequestMethod {
GET, POST, PUT, DELETE
}
이 코드는 HTTP 요청 메서드를 나타내는 열거형 (Enumration) 인 RequestMethod 를 정의한다.
HTTP 요청 메서드는 웹 응용 프로그램에서 클라이언트가 서버에 보내는 요청의 종류를 나타낸다 가장 일반적으로사용되는 HTTP 요청 메서드는 다음과 같다.
- GET: 리소스를 가져오기 위한 요청입니다. 주로 데이터를 요청할 때 사용된다.
- POST: 서버로 데이터를 제출하거나 리소스를 생성하기 위한 요청이다.
- PUT: 서버에 데이터를 업데이트하기 위한 요청이다.
- DELETE: 서버에서 리소스를 삭제하기 위한 요청이다.
이러한 HTTP 요청 메서드는 RESTful 웹 서비스 및 다양한 웹 응용 프로그램에서 중요한 역활을 한다.
RequestMethod 열거형을 사용하면 웹 프레임워크나 웹 애플리케이션에서 요청 메서드를 명시적으로 지정할 때 특히 유용
하다 예를 들어 앞에서 설명한 RequestMapping 어노테이션의 method 멤버는 RequestMethod 열거형을 사용하여 HTTP 메서드를 설정할 수 있다.
예를 들어, 다음은 RequestMethod를 사용하여 @RequestMapping에서 GET 메서드를 지정하는 예제를 보면
@RequestMapping(value = "/myEndpoint", method = RequestMethod.GET)
public String handleGetRequest() {
// GET 요청을 처리하는 로직
return "viewName";
}
이렇게 하면 핸들러 메소드가 GET 요청에만 응답하도록 지정되고
이렇게 RequestMethod 열거형을 사용하면 코드의 가독성을 향상시키고, 잘못된 HTTP 메서드 사용을 방지하는 데 도움이 된다.
package org.example.controller;
import org.example.annotation.Controller;
import org.example.annotation.RequestMapping;
import org.example.annotation.RequestMethod;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Controller
public class HomeController {
@RequestMapping(value = "/", method = RequestMethod.GET)
public String home(HttpServletRequest request, HttpServletResponse response) {
return "home";
}
}
package org.example.controller;
import org.example.annotation.Controller;
import org.example.annotation.RequestMapping;
import org.example.annotation.RequestMethod;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Controller
public class HealthCheckController {
@RequestMapping(value = "/health", method = RequestMethod.GET)
public String home(HttpServletRequest request, HttpServletResponse response) {
return "ok";
}
}
package org.example.controller;
import org.example.annotation.Service;
@Service
public class HomeService {
}
'JAVA > MVC 프레임워크' 카테고리의 다른 글
DI 프레임워크 (1) | 2023.11.24 |
---|---|
프런트 컨트롤러 패턴 실습 (0) | 2023.11.10 |
프런트 컨트롤러 패턴 개념 (0) | 2023.11.09 |
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!