리플렉션

반응형
반응형

스프링을 공부하다보면..
빈을 주입하는 (객체를 만들어주는)... 것이 나온다.

@Repository
public class SchoolRepository {
}

@Service
public class SchoolService {

    @Autowired
    SchoolRepository schoolRepository;

}

그리고 이것을 테스트 해보자.

@SpringBootTest
class SchoolServiceTest {

    @Autowired
    SchoolService schoolService;

    @Test
    void test() {
        System.out.println(schoolService.schoolRepository);
    }
}

나는 분명히 new 키워드를 이용해서 만들지도 않았다.

그런데

정상적으로 객체값이 나온다.
스프링이 주입시켜준것은 알겠는데...
이것이 어찌 가능한 걸까?

거참 신기한 일이다.
DI를 시켜주긴 했지만...(나중에 DI정리 해야겠다.)

그래도 new는 만들어줘야 하는거 아닌가...

쓸데 없는 소리는 그만하고 
@Repository나 @Autowired의 코드를 보면 정답이 보지이지 않을까?

찾다가 이런걸 찾았다.

private GenericsFactory getFactory() {
   Class<?> c = getDeclaringClass();
   // create scope and factory
   return CoreReflectionFactory.make(c, ClassScope.make(c));
}
protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
       Field field = (Field)this.member;
       Object value;
        if (this.cached) {
          value = AutowiredAnnotationBeanPostProcessor.this.resolvedCachedArgument(beanName, this.cachedFieldValue);
        } else {
        DependencyDescriptor desc = new DependencyDescriptor(field, this.required);
        desc.setContainingClass(bean.getClass());
        Set<String> autowiredBeanNames = new LinkedHashSet(1);
 
 ...

AutowiredFieldElementClass

코드가 길어서 전문을 보여주기는 어렵지만... 아무튼 inject는 이런식으로 동작?한다.
찾아보니 Class<?>,Field라는 걸로 리플렉션한다고 한다.

이제 리플렉션에 대해 자세히 알아보자.

리플렉션은 '반사'라는 뜻을 가지고 있으며,
리플렉션을 이용하게 되면 '클래스','메소드','변수'에 대한 내용들을
접근 지시자의 제약 없이 가져올 수 있다.

그래서 리플렉션이라는 이름을 가지게 되었다.

이것이 가능한 이유는 jvm에 로딩되는 시점에
가져오기 때문에 가능한것이라고 한다.

Class<?> 도 마찬가지다.

사용해보자.

나는 클래스에서 사용할 수 있는 모든 맴버를 사용해봤다.(몇개는 안햇지만 말이다.)

public class Preparations {
    public String a = "a";
    protected String b = "b";
    String c = "c";
    private String d = "d";
    
    public static final String e = "e";
    protected static final String f = "f";
    static final String g = "g";
    private static final String h = "h";
    
    public void i() {
        System.out.println("i");
    }
    
    protected void j() {
        System.out.println("j");
    }
    
     void k() {
        System.out.println("k");
    }

    private void l() {
        System.out.println("l");
    }
    
    public String m() {
        return "m";
    }

    protected String n() {
        return "n";
    }

    String o() {
        return "o";
    }

    private String p() {
        return "p";
    }
    
    final public void q()  {
        System.out.println("q");
    }

    final protected void r()  {
        System.out.println("r");
    }

    final void s()  {
        System.out.println("s");
    }

    final private void t()  {
        System.out.println("t");
    }


    final static public void u()  {
        System.out.println("u");
    }

    final static protected void v()  {
        System.out.println("v");
    }

    final static void w()  {
        System.out.println("w");
    }

    final static private void x()  {
        System.out.println("x");
    }
    
}

리플렉션을 사용해 보자.

클래스는 리플렉션을 이용하는 3가지가 존재한다.
1. Class.forName으로 접근 throws ClassNotFoundException 필요!

// 접근 방법1
Class<?> aClass = Class.forName("org.example.Preparations");

이걸로는 필드값을 가져와보자.

Arrays.asList(aClass.getDeclaredFields()).forEach(t ->
		{
            t.setAccessible(true);
            System.out.printf("%s %s\n", t, t.getName());
        });

t.setAcessible은 모든 접근을 가능하게 만들어준다.
이것을 실행하면

사진을 보면 접근 지시자에 상관없이 값을 보여주는 것을 확인 할 수 있다.

2. 클래스.class 로 접근

// 접근 방법2
Class<Preparations> preparationsClass = Preparations.class;

이번에는 필드값이 아닌 메소드를 가져와보자.

Arrays.asList(preparationsClass.getDeclaredMethods())
    .forEach(t ->
  {
      System.out.printf("%s %s\n",t,t.getReturnType());
  });

메소드는 t.setAcessible을 하지 않아도 privat한것도 보여줄 수 있다.

리턴값을 보여주고 싶었는데 찾지 못했다.ㅜㅜ;
근데 왜 뒤죽박죽이지?

더 공부한 뒤 방법을 찾아봐야 겠다. 리플렉션이 동적으로 하는것이기 때문에 무조건 가능할 거라 생각하긴
하지만,
항상 내 생각과는 같을 수 없는 법! 

3. 인스턴스.getClass()로 접근

// 접근 방법3
Class<? extends Preparations> aClass1 = new Preparations().getClass();

생각해보니 생성자를 추가하지 않았다.
또한 상속을 이용해보지도 않았다.

이번에는 생성자를 뽑아내 보자.

public Preparations(String a, String b, String c) {
        this.a = a;
        this.b = b;
        this.c = c;
    }

public Preparations() {
}

생성자 추가

public class Survey extends Preparations{

}

상속 클래스 추가

Arrays.asList(aClass1.getConstructors()).forEach(t -> System.out.println(t.getParameterCount()));
Survey survey = new Survey();
Class<?> superclass = survey.getClass().getSuperclass();
Arrays.asList(superclass.getConstructors()).forEach(System.out::println);

처음에는 파라미터 갯수를 세주었고, 
그 다음에는 자식 클래스에서 상위클래스에 접근한 다음, 출력시켜주었다.

이제 우리는 어떤것이 들어있는지 확인 할 수 있게 되었다.

이번에는 필드,메소드값을 조작해보자.

public class Preparations {
    public static String a = "a";
    private static String b = "b";
    public String c = "c";
    private String d = "d";

    public Preparations(int a,int b) {
        a = 2;
        b = 4;
    }

    public Preparations() {
    }
    
    
    public void e() {
        System.out.println("e");
    }
    
    private int sum(int a,int b) {
        return a + b;
    }
}

이번에는 이렇게 조작하였고,
어떤 방식으로 리플렉션을 사용할지 생각해보자..

클래스.class 로 접근방식으로 사용해보자

Class<Preparations> classz = Preparations.class;

   1. 필드 값 가져오기

    - static

Field a = classz.getDeclaredField("a");
System.out.println(a.get(null));

static일때는 객체가 필요없기 때문에 null을 넣어주면 된다.

정상적으로 나왔다.!

  이제 이 값을 수정해보자.

a.set(null,"abc");
System.out.println(a.get(null));
a.set(null,"abc");
System.out.println(a.get(null));

   - non static

이번에는 private한 필드를 가져와서 수정해보자.

Preparations preparations = new Preparations();
Field d = classz.getDeclaredField("d");
System.out.println(d.get(preparations));

이번에는 static이 아니기 때문에 인스턴스가 필요하다 그래서 인스턴스를 가져왔다.
하지만 어찌된 영문인지 

값이 나오지 않는다.

왜냐하면 이 값은 private한 값이기 때문이다.
그러면 어떻게 해야 할까?

바로

d.setAccessible(true);

이걸 추가하면 된다.

실행이 될까 내침김에 수정도 해보자.

d.set(preparations,"hello");
System.out.println(d.get(preparations));

신기하다.

2. 생성자값 가져오기

     생성자를 가져오기 위해서는 인스턴스가 필요하다. 위에 처럼 인스턴스를 직접 만들어도 되지만...
    리플렉션 기능으로 인스턴스를 만들 수 있다.

Constructor<Preparations> constructor = classz.getConstructor(int.class,int.class);
Preparations instance = constructor.newInstance(2,4);
System.out.println(instance);

이런식으로 작성할 수 있는데,
주의해야할점은 실제로 인스턴스를 만드는것 과 똑같이 만들어야 한다.
그러니까

Preparations preparations = new Preparations(1,4);

이런식으로....

애초에 생성자는 리턴하는게 없으니 여기서 완료하자.

3. 메소드 값 가져오기

마지막으로 메소드값을 조작해보자.

  - non prameter

Method e = classz.getDeclaredMethod("e");
System.out.println(e.invoke(instance));

null뭐야...

정상적으로 나오는 것을 확인 할 수 있었다.

그렇다면 이것은 어떨까?

 - prameter

Method sum = classz.getDeclaredMethod("sum", int.class, int.class);
sum.setAccessible(true);
System.out.println(sum.invoke(instance,1,10));

놀랍게도 private임에도 불구하고 값이 더해지는 것을 확인 할 수 있었다.

이를 활용해서 나만의 DI를 만들어보는것도 좋을 것 같다.

반응형

'프로그래밍 언어 > 자바' 카테고리의 다른 글

인터페이스  (0) 2021.01.04
나만의 DI프로그램 만들기  (0) 2021.01.03
Mockito 사용해보기  (0) 2020.12.31
패키지  (0) 2020.12.30
Failed to execute goal org.apache.maven.plugins:maven-surefire-plugin:2.22.2:test  (0) 2020.12.29

댓글

Designed by JB FACTORY