Reflection과 Annotation으로 프록시 만들어보기

2023. 2. 28. 18:05카테고리 없음

1. 어노테이션을 만든다.

 

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface StringMethodInject {

     String  value() default  "method";

}

1) StringMethodInject이라는 어노테이션을 만들었다.

2) 런타임 시점에 메소드를 타깃으로 해당  어노테이션이 달려있다면 메소드에 값을 주입한다.

package hello.hellospring.annotation2;

import java.lang.annotation.*;

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface StringFieldInject {

     String  value() default  "method";

}

3) 런타임 시점에 필드를 타깃으로 해당  어노테이션이 달려있다면 필에 값을 주입한다.

 

2.프록시 생성을 위해  Reflection을 적용할 클래스에 인터페이스를 만든다.

package hello.hellospring.annotation2;

public interface Reflection {
    public  void setMethod(String method);
}

3. Reflection 적용할 클래스의 필드 및 메소드에 어노테이션 선언 

package hello.hellospring.annotation2;

public class ReflectionImpl  implements  Reflection{

    @StringFieldInject("inject field")
    private  String field;

    private  String method;

    @StringMethodInject("inject method")
    public  void setMethod(String method){

        this.method=method;
        System.out.println(method);
    }

    @Override
    public String toString() {
        return "Reflection{" +
                "field='" + field + '\'' +
                ", method='" + method + '\'' +
                '}';
    }
}

2개의 어노테이션을 생성후 각각 필드와 메소드에 선언해주었다. 어노테이션 인자에 아무값도 할당하지 않았으므로
default로 선언된 값이 들어갈 것이다.

 

4. 런타임시 Reflection으로  Annotation이 붙어 있다면 프록시를 생성해서 프록시를 통해   타겟 (ReflectionImpl) 메소드가 호출되기 전 후로  타겟의  코드의 수정 없이 부가기능을 추가한다.

 

1)invoke() 메서드는 런타임 시점에 생긴 동적 프록시의 메서드가 호출되었을때, 실행되는 메서드이고, 어떤 메서드가 실행되었는지 메서드 정보와 메서드에 전달된 인자까지 invoke()메서드의 인자로 들어오게 됩니다.

 

package hello.hellospring.annotation2;

import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

@Slf4j
public class TimelnvoocationHandler implements InvocationHandler {//

    private  final  Object target;

    public TimelnvoocationHandler(Object target) {

        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

      log.info("TimeProxy 실행");
      long startTime = System.currentTimeMillis();

      Object result = method.invoke(target,args);//원본 인스턴스 메소드 호출

      long endTime = System.currentTimeMillis();
      long resultTime = endTime- startTime;
      log.info("TimeProxy 종료 resultTime={}",resultTime);//부가기능 : 시간 측정 로직


        return result;
    }
}

5.reflection 인스턴스 클래스에 있는 필드 정보를 리플랙션을 통해서 가져오기 . 가져온 필드 정보를 배열로  가져온뒤 

루프를 돌리면 현재 "StringFieldInject"이름의 어노테이션이 달려있는 필드를 찾아 낸다.    if문을 통해 인자에 정보를 변경한다.(값주입) 똑같이 메서드도 이 방식으로 어노테이션이 달려있는 메소드를 찾는다.

 

 

package hello.hellospring.annotation2;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class App {
    public static void main(String[] args) throws IllegalAccessException, InvocationTargetException {

       Reflection reflection = new ReflectionImpl();

        //클래스의 Field 정보 얻기
        Field[] fields = reflection.getClass().getDeclaredFields();

        for (Field field :fields){

            //어노테이션에
            StringFieldInject stringFieldInject =field.getDeclaredAnnotation(StringFieldInject.class);

            if(stringFieldInject != null){
                field.setAccessible(true);
                //첫번째 인자엔 field의 정보가 있는 클래스의 인스턴스를 넣고
                //두번째 인자엔 어노테이션에 정의된 값을 넣는다

                field.set(reflection,stringFieldInject.value());




            }
        }
        Method[] methods = reflection.getClass().getDeclaredMethods();
        for (Method method : methods){
            StringMethodInject stringMethodInject = method.getDeclaredAnnotation(StringMethodInject.class);

            if(stringMethodInject != null){
                //method는 field와 달리 set이 없고 invoke메서드가 있다
                /*method.invoke(reflection,stringMethodInject.value());*/
                TimelnvoocationHandler handler =new TimelnvoocationHandler(reflection);

                Reflection proxy = (Reflection) Proxy.newProxyInstance(Reflection.class.getClassLoader(),new Class[]{Reflection.class},handler);

                proxy.setMethod(stringMethodInject.value());


            }

        }
        System.out.println(reflection);
    }
}

1) 런타임 시점에 프록시 클래스를 만들어 주기 때문에 대상 클래스 수만큼 프록시 클래스를 만들어야하는 첫번째 단점을 해결해 줍니다.

 

2)메서드에 해당 어노테이션이 달려있다면  reflection 객체를  파라미터로 넘겨, 그 해당 타깃으로 handler를 만든다.

그다음에 프록시를 생성한다.   프록시에서 원본 인스턴스 메소드를 호출한다.

 

 

참고

[Java] 동적 프록시(Dynamic Proxy) (tistory.com)

 

[Java] 동적 프록시(Dynamic Proxy)

이번 글에서는 동적 프록시(Dynamic Proxy)가 왜 필요하고 어떻게 사용되는지 알아보려 합니다. 동적 프록시(Dynamic Proxy)와 프록시(Proxy) 동적 프록시가 왜 필요한지 알기위해서는 그전에 프록시가 사

gong-story.tistory.com

[JAVA] Reflection과 Annotaion으로 필드 및 메소드에 값 주입하기. (tistory.com)