일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- 덕배
- 모바일 앱
- Reflection
- 에노테이션
- java program
- apache #start_warning #ServerName #ubuntu
- 자바스크립트
- 주인님
- 생성자
- 객체
- 리액트 네이티브
- 어노테이션
- aws #ubuntu #root계정
- 어플
- class
- annotation
- 고양이
- 어플리케이션
- React Native
- Feild
- 리플렉션
- Java
- constructor
- Today
- Total
코딩하는 곰
[Java] Reflection이란? 본문
리플렉션(Reflection)이란?
리플렉션은 자바가 가진 가장 큰 특징이자 장점이라고 할 수 있습니다.
동적으로 로드 되는 자바의 모든 구성원을 동적으로 조작하거나 조회할 수 있는 개념인데요.
개인적으로 리플렉션 개념이 있었기에 스프링이 존재할 수 있지 않았나 하고 생각하고 있습니다.
스프링 컨테이너가 실행 될 때 BeanFactory를 통해서 Bean들을 로딩하는데,
Bean을 생성하는 과정에서 컨트롤러에 에노테이션으로 선언되어 있는 핸들러매핑이나, Bean의 생성자를 찾는 과정, 그리고 Bean 을 주입받는 과정까지 모두 리플렉션 개념이 있었기 때문이라고 생각합니다.
1. 생성자를 갖고 놀아봅시다
먼저 클래스를 바탕으로 인스턴스를 생성해야 동작 중에 뭔가를 할 수 있겠죠?
그렇다면 임의로 Memeber 클래스를 만들어 보겠습니다.
Member.java package reflection; public class Member { private int idx; private String id; private String password; private String email; private String address; private String tel; private String mobile; private boolean block; public Member() { super(); } public Member(int idx) { super(); this.idx = idx; this.id = "test_"+idx; this.password = "password_" + idx; this.email = "eamil" + idx + "@google.com"; this.address = "경기도 " + idx + "시"; this.tel = "000-000-000"+idx; this.mobile = "010-000-000"+idx; this.block = false; } @Override public String toString() { return String.format("%s (%s) : %s / isBlock(%s)", id, email, address, block); } }
기본 생성자와 파라미터 하나를 더 받도록 오버라이딩 된 생성자 두가지를 선언하고,
메인 클래스에서 데이터 생성 확인용으로 사용하기 위해 toString 메소드를 오버라이딩 한 Member 클래스를 생성합니다.
다음은 메인 클래스입니다
ConstructorClazz.java package reflection; import java.lang.reflect.Constructor; public class ConstructorClazz { public static void main(String[] args) { // 생성자의 배열을 얻어옵니다. Constructor[] cstrArr = (Constructor []) new Member().getClass().getConstructors(); for(Constructor cnstr : cstrArr) { ] // 생성자의 파라미터 개수를 얻어옵니다. int paramCount = cnstr.getParameterCount(); System.out.print(String.format("파라미터 개수 (%s)", paramCount)); if(paramCount == 0) { // 파라미터 개수가 0개이면 기본생성자 System.out.println(String.format(" : 기본 생성자 %s()", cnstr.getName())); } else { // 파리미터 개수가 0개가 아니면 오버로딩 생성자 String paramNames = " "; // 생성자의 파라미터를 가지고 옵니다. Class[] params = cnstr.getParameterTypes(); for(Class c : params) { // 파라미터 타입명 출력을 위해, 파라미터의 타입을 더해 둡니다. paramNames += c.getTypeName() + " "; } System.out.println(String.format(" : 오버로딩 생성자 %s(%s)", cnstr.getName(), paramNames)); } } } }
메인 클래스를 실행했을 때 결과는 아래와 같습니다.
그러면 어떤 식으로 활용할 수 있느냐?
Member 클래스에서 int를 파라미터로 받는 오버로딩 생성자를 찾아, Memeber 리스트를 생성해보겠습니다.
메인 클래스를 아래와 같이 수정하겠습니다.
ConstructorClazz.java package reflection; import java.lang.reflect.Constructor; public class ConstructorClazz { public static void main(String[] args) { // 생성자의 배열을 얻어옵니다. Constructor<Member>[] cstrArr = (Constructor[]) new Member().getClass().getConstructors(); Constructor<Member> intConstructor = null; for(Constructor<Member> cnstr : cstrArr) { // 생성자의 파라미터 개수를 얻어옵니다. int paramCount = cnstr.getParameterCount(); if(paramCount == 1) { intConstructor = cnstr; // 파라미터가 하나인 생성자를 찾습니다 break; } } // 생성자와 리스트의 사이즈 넘겨 인스턴스 리스트를 가지고 옵니다 List<Member> memberList = getInstanceList(intConstructor, 10); for(Member m : memberList) { System.out.println(m.toString()); } } static <T> List<T> getInstanceList(Constructor<T> cnstr, int len) { // len 만큼 사이즈의 list 를 생성 List<t> result = new ArrayList<t>(len); for(int i = 0; i < len ; i++) { try { // newInstance 메소드를 이용하여 인스턴스를 생성 T node = cnstr.newInstance(i); // 리스트에 넣음 result.add(node); } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { e.printStackTrace(); } } return result; } }
getInstanceList 메소드의 52번 라인에서 newInstance메소드로 인스턴스를 생성합니다.
이렇게 수정한 메인 클래스의 실행 결과는 아래와 같습니다.
2. 클래스의 멤버변수를 갖고 놀아봅시다
위의 Memeber 클래스에서 일반적이지 않은것을 보신 기억이 있나요?
보통 멤버변수를 private로 선언하고, getter와 setter를 선언하는것이 보통이겠죠.
private 변수에 getter와 setter가 없이 접근은 일반적으로 가능하지 않습니다.
Memeber.idx 는 보이지 않는 필드라고 에러가 뜨고
이클립스에서 친절하게 접근지정자를 바꾸거나 getter와 setter 를 만들라고 안내하고 있죠
하지만 Reflection을 이용하면
이 private 또한 개무시를 할 수 있습니다.
코드 보시죠
FieldClazz.java package reflection; import java.lang.reflect.Field; public class FieldClazz { public static void main(String[] args) throws IllegalArgumentException, IllegalAccessException { // member 인스턴스를 생성합니다. Member m = new Member(1); // 선언되어있는 필드를 가져옵니다. Field[] fields = m.getClass().getDeclaredFields(); for(Field f : fields) { // 필드를 접근할 수 있도록 강제 수정합니다 f.setAccessible(true); System.out.println(String.format("%s : %s", f.getName(), f.get(m))); // 필드명이 block 일때 값 변경 if("block".equals(f.getName())) f.set(m, true); } System.out.println(""); System.out.println("값 변경 후 : "); System.out.println(m.toString()); } }
해당 메인 클래스를 실행하면
따란~
Member 클래스에 private 으로 선언한 멤버변수의 변수명과 값을 조회하고 수정도 하였습니다.
여기서 22라인(setAccessible 메소드)을 주석처리 하면
해당 필드는 private 이여서 접근할 수 없다고 에러가 발생합니다
그리고, 17라인(getDeclaredFields 메소드) 와 비슷한 이름의 getFields 라는 메소드가 있는데요
private Field 까지 가지고 올 수 있는 메소드는 getDeclareFields 메소드이니까 유의하시기 바랍니다.
3. 어노테이션을 가져와봅시다
스프링을 해보신적이 있으신 분이라면 멤버변수에 @Autowired나 @Inject를 추가하고, bean을 주입 받은 기억이 있으실껍니다.
이번에는 멤버변수에 추가되어 있는 어노테이션을 어떻게 활용하는지 한번 해보시죠
먼저 커스텀 어노테이션을 하나 만들어 줍니다.
(어노테이션에 대한 자세한것은 추후에 글을 써보도록 하겠습니다)
Printable.java package reflection; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface Printable { boolean value(); }
Printable 이라는 어노테이션을 생성하고,
Memeber 클래스의 password 변수 위에 어노테이션을 추가하도록 하겠습니다.
Member.java .... @Printable(value=false) private String password; ....
그리고 위에서 작성했던 FieldClazz.java 파일을 수정해서 활용해보도록 하겠습니다.
17라인(getDeclaredFields 메소드) 위에 추가해주시면 됩니다.
FieldClazz.java .... // 필드에 선언되어 있는 Printable 어노테이션을 얻어옵니다 Printable[] p = f.getAnnotationsByType(Printable.class); // Printable 어노테이션이 null 이 아니고, 값이 false 이면 continue 합니다. if(p != null && p.length==1 && !p[0].value()) { System.out.println("이 필드는 출력할 수 없습니다"); continue; } ....
코드를 추가하고 클래스를 실행시키면
요렇게 password 필드는 출력하지 않게 됩니다
지금은 출력하지 않는 코드를 보여드렸지만
스프링에서 빈을 넣어주는 것처럼
위에서 필드 값을 바꿔주었던 코드 FieldClazz.java 27라인에 f.set(~~) 을 사용해서 값을 넣어주거나 바꿔줄 수 있겠죠
4. 메소드도 찾아봅시다
작성 중입니다....