1. 리플렉션 (Reflection)
1. 개념
- 동적 클래스 정보: 실행 중에 클래스, 메서드, 필드 등의 정보를 동적으로 조회.
- 런타임 메타데이터: 클래스, 인터페이스, 메서드, 생성자 등의 정보를 런타임에 얻을 수 있음.
- 코드 유연성: 코드의 유연성을 높여 다양한 동적 작업 가능.
2. 주요 특징
- 클래스 객체 조회: 클래스명으로 객체를 생성하거나 메서드를 호출.
- 필드와 메서드 접근: 비공개(private) 멤버에도 접근 가능.
- 컴파일 시 알 수 없는 정보 처리: 런타임에만 알 수 있는 정보 처리 가능.
3. 사용 예시
- 프레임워크: 스프링, 하이버네이트 등에서 빈(bean) 생성, 의존성 주입에 사용.
- 테스트 도구: 테스트 자동화 도구에서 테스트 메서드나 필드에 대한 정보를 동적으로 처리.
- 동적 프록시: 런타임에 동적으로 프록시 객체를 생성.
4. 장점
- 동적 처리: 실행 중에 객체를 다룰 수 있음.
- 유연성: 코드 변경 없이 런타임에 동적으로 동작을 변경.
5. 단점
- 성능 저하: 리플렉션을 사용하면 성능이 저하될 수 있음.
- 타입 안전성 부족: 컴파일 타임에 오류를 잡기 어려움.
2. 어노테이션 (Annotation)
1. 개념
- 메타데이터: 코드에 추가적인 정보를 제공하는 메타데이터.
- 클래스, 메서드, 필드에 부착: 코드에 정보를 제공하거나 처리 방식 지정.
- 런타임, 컴파일 타임 처리: 주석처럼 코드를 설명하거나, 컴파일 타임 또는 런타임에서 처리됨.
2. 주요 특징
- 어노테이션 프로세서: 어노테이션을 분석하고 처리하는 도구.
- 타입 안전성: 컴파일 시 검증을 통해 안정성을 높임.
- 직관적: 코드의 의도를 명확하게 표현.
3. 사용 예시
- Spring Framework:
@Autowired
,@Service
,@Controller
등.
- JUnit:
@Test
,@Before
,@After
등.
- 직접 정의: 사용자가 원하는 방식으로 어노테이션을 정의하여 사용.
4. 장점
- 코드 가독성 향상: 코드의 의도를 명확하게 표현.
- 플러그인 처리: 외부 라이브러리나 프레임워크에서 동적으로 어노테이션을 처리.
- 유연성: 코드에 변화를 주지 않고 동작을 제어.
5. 단점
- 어노테이션 의존성: 코드가 어노테이션에 의존적일 수 있음.
- 런타임 처리 오버헤드: 런타임에 어노테이션을 처리해야 하므로 성능에 영향을 줄 수 있음.
3. 리플렉션과 어노테이션의 결합
1. 개념
- 어노테이션: 코드에 메타데이터를 추가, 클래스, 메서드, 필드에 정보를 제공.
- 리플렉션: 런타임에 클래스, 메서드, 필드 등 정보를 동적으로 조회하고 처리.
2. 함께 동작하는 방식
- 어노테이션으로 메타데이터 제공
- 코드에서 어노테이션을 사용해 클래스, 메서드, 필드에 특별한 의미를 부여.
- 예:
@Service
,@Autowired
등.
- 리플렉션으로 어노테이션 처리
- 런타임에 리플렉션을 통해 어노테이션을 읽고, 이에 따라 동작 처리.
- 예:
@Autowired
필드에 의존성 자동 주입,@Service
클래스를 자동으로 등록.
3. 사용 예시
- 스프링 프레임워크:
@Autowired
: 의존성 주입을 위한 어노테이션.@Service
: 서비스 클래스에 대한 메타데이터 제공.- 리플렉션: 어노테이션이 붙은 클래스나 메서드를 런타임에 찾아 처리.
4. 코드예시
1. 어노테이션이 없을 때
/**
* 1차 개발자가 작성하는 코드
*/
public class Router {
UserController uc;
public Router(UserController uc) {
this.uc = uc;
}
public void routing(String path) {
if (path.equals("/login")) {
uc.login();
} else if (path.equals("/join")) {
uc.join();
}
}
}
/**
* 2차 개발자가 작성하는 코드
*/
public class UserController {
public void login() {
System.out.println("로그인");
}
public void join() {
System.out.println("회원가입");
}
public void logout() {
System.out.println("로그아웃");
}
}
기능을 확장하려면 1차 개발자에게 라이브러리 수정 요청을 해야하는 한계가 존재
public class App {
public static void main(String[] args) {
Router router = new Router(new UserController());
Scanner scan = new Scanner(System.in);
String path = scan.nextLine();
router.routing(path);
}
}
“/logout”을 콘솔창에 입력하면 아무것도 출력 X
2. 어노테이션이 있을때
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestMapping {
String value(); // value만 존재할 경우 어노테이션 파라미터 작성시 생략가능
}
public class Router {
UserController uc;
public Router(UserController uc) {
this.uc = uc;
}
public void routing(String path) {
// 어노테이션 구현 순서
// 1. 메소드 찾아내기
Method[] methods = uc.getClass().getMethods(); // uc의 클래스의 모든 메소드를 가져옴
// 2. 어노테이션 체크
for (Method m : methods) {
RequestMapping rm = m.getAnnotation(RequestMapping.class);
// 3. value와 path 일치 확인해서 일치하면 invoke 하기
if (rm == null) {
break;
}
if (rm.value().equals(path)) {
try {
m.invoke(uc);
} catch (Exception e) {
throw new RuntimeException("메소드 실행 중 오류 발생");
}
}
}
}
}
public class UserController {
@RequestMapping("/login")
public void login() {
System.out.println("로그인");
}
@RequestMapping("/join")
public void join() {
System.out.println("회원가입");
}
@RequestMapping("/logout")
public void logout() {
System.out.println("로그아웃");
}
@RequestMapping("/userinfo")
public void userinfo() {
System.out.println("유저인포");
}
}
@RequestMapping(value) 만 잘 붙인다면 제한 없이 메소드 구현 가능
App.java 동일.
Share article