아무나개발하자

JAVA Reflection 정리2 본문

JAVA

JAVA Reflection 정리2

개발천재나천재 2022. 12. 27. 10:51

지날글에 내용을 간단히 요약하겠다.

RequestMapping Class

// Retention을 통해 RequestMapping이 런타임시점에서 동작하도록 설정 (compile시점에서 동작하는것도 가능)
@Retention(RetentionPolicy.RUNTIME)
// Target을 통해 이 어노테이션을 어디다 설정할지를 결정 (TYPE : 클래스, METHOD : 메서드, FIELD : 필드)
@Target({ElementType.METHOD})
public @interface RequestMapping {
	String value;
}

- 어노테이션을 직접 설정하였고

 

UserController -2

//@Controller
public class UserController {
	
	@RequestMapping("/join")
	public void join() {
		System.out.println("join 함수 요청됨");
	}
	
	@RequestMapping("/login")
	public void login() {
		System.out.println("login 함수 요청됨");
	}
	
	@RequestMapping("/user")
	public void user() {
		System.out.println("user 함수 요청됨");
	}
}

- 설정한 어노테이션을 userController에서 사용할 메서드에 지정해주었다.

 

 

Dispatcher Class -2

public class Dispatcher implements Filter {

	@Override
	public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
			throws IOException, ServletException {

		HttpServletRequest request = (HttpServletRequest) req;
		HttpServletResponse response = (HttpServletResponse) resp;

		System.out.println("컨텍스트패스 : " + request.getContextPath()); // 프로젝트 시작주소
		System.out.println("식별자주소 : " + request.getRequestURI()); // 끝주소
		System.out.println("전체주소 : " + request.getRequestURL()); // 전체주소

		String endPoint = request.getRequestURI().replaceAll(request.getContextPath(), "");
		System.out.println("엔드포인트 : " + endPoint);

		UserController userController = new UserController();
		Method[] methods = userController.getClass().getDeclaredMethods();

		for (Method method : methods) { // 리플렉션한 메서드 개수만큼 순회함

			Annotation annotation = method.getDeclaredAnnotation(RequestMapping.class);
			RequestMapping requestMapping = (RequestMapping) annotation;

			// requestMapping의 value와 endPoint를 비교하는 코드
			if (requestMapping.value().equals(endPoint)) {
				try {
					method.invoke(userController);
				} catch (Exception e) {
					e.printStackTrace();
				}
				break;
			}

		}

	}
}

- 리플렉션을 직접 만들어봤는데, userController에서 "requestMapping" 어노테이션을 지정한 메서드를 찾아와서 url의 endPoint와 requestMapping의 value와 일치하는 메소드를 실행하는 리플렉션이다. 정말 신기하지 않은가?? 런타임 시점에서 동적으로 요청 url을 처리하는 메소드를 선택해서 실행하였다.

- 하지만 요청 메소드에 파라미터가 있을경우 파라미터를 넘겨주는 경우는 생략하였다. 이러한 경우도 실제로 한번 해보겠다.

 

 

Version 2 시작

 

 

User Class

public class User {
	private int id;
	private String username;
	private String password;
	private String email;
	
	@Override
	public String toString() {
		return "User [id=" + id + ", username=" + username + ", password=" + password + ", email=" + email + "]";
	}
	
	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}
	public String getUsername() {
		return username;
	}
	public void setUsername(String username) {
		this.username = username;
	}
	public String getPassword() {
		return password;
	}
	public void setPassword(String password) {
		this.password = password;
	}
	public String getEmail() {
		return email;
	}
	public void setEmail(String email) {
		this.email = email;
	}
	
	
}

- User Class를 다음과 같이 구성하였다. 

 

JoinDto Class

public class JoinDto {
	private String username;
	private String password;
	private String email;
	
	@Override
	public String toString() {
		return "JoinDto [username=" + username + ", password=" + password + ", email=" + email + "]";
	}
	
	public String getUsername() {
		return username;
	}
	public void setUsername(String username) {
		this.username = username;
	}
	public String getPassword() {
		return password;
	}
	public void setPassword(String password) {
		this.password = password;
	}
	public String getEmail() {
		return email;
	}
	public void setEmail(String email) {
		this.email = email;
	}
}

- join은 ID, PW, Email을 입력해야 회원가입을 할 수 있다고 가정을 해서, 클라이언트로부터 3가지의 값을 받는 dto를 생성하였다.

 

 

LoginDto Class

public class LoginDto {
	private String username;
	private String password;
	
	@Override
	public String toString() {
		return "LoginDto [username=" + username + ", password=" + password + "]";
	}
	
	public String getUsername() {
		return username;
	}
	public void setUsername(String username) {
		this.username = username;
	}
	public String getPassword() {
		return password;
	}
	public void setPassword(String password) {
		this.password = password;
	}	
}

- login은 ID, PW를 통해 로그인을 할  수 있다고 가정해서, 클라이언트로부터 2가지의 값을 받는 dto를 생성하였다.

 

 

UserController Class

//@Controller
public class UserController {
	
	@RequestMapping(uri = "/user/join")
	public void join(JoinDto dto) {
		System.out.println("join 함수 요청됨");
		System.out.println(dto);
	}
	
	@RequestMapping(uri = "/user/login")
	public void login(LoginDto dto) {
		System.out.println("login 함수 요청됨");
		System.out.println(dto);
	}
	
	@RequestMapping(uri = "/user")
	public void user() {
		System.out.println("user 함수 요청됨");
	}
}

- 각 메서드 마다 dto 파라미터를 추가 하였다. 물론 여러개의 파라미터를 받는 경우도 고려를 해야되지만 학습상 1개의 파라미터만 받는다고 가정하고 실습을 진행해 보겠다.

 

Dispatcher Class 

public class Dispatcher implements Filter {

    @Override
    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
            throws IOException, ServletException {

        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) resp;

        System.out.println("컨텍스트패스 : " + request.getContextPath()); // 프로젝트 시작주소
        System.out.println("식별자주소 : " + request.getRequestURI()); // 끝주소
        System.out.println("전체주소 : " + request.getRequestURL()); // 전체주소

        String endPoint = request.getRequestURI().replaceAll(request.getContextPath(), "");
        System.out.println("엔드포인트 : " + endPoint);

        UserController userController = new UserController();
        Method[] methods = userController.getClass().getDeclaredMethods();

        for (Method method : methods) { // UserController의 메서드 개수 만큼 돈다.

            // 메서드 중 RequestMapping어노테이션을 설정한 메서드를 찾기 위해 해줌
            Annotation annotation = method.getDeclaredAnnotation(RequestMapping.class);
            RequestMapping requestMapping = (RequestMapping) annotation;

            // "requestMapping"에서 설정한 uri와 endPoint와 일치하는지 여부를 확인한다.
            if (requestMapping.uri().equals(endPoint)) {
                try {
                    Parameter[] params = method.getParameters();
                    if (params.length != 0) {
                        // param[0]을 뽑는 이유는 -> 우리는 파라미터를 한개만 넣는다고 가정함
                        // dtoInstance는 타입이 LoginDto, JoinDto일경우 두개가 있다.
                        Object dtoInstance = params[0].getType().newInstance();
                        setData(dtoInstance, request); // 인스턴스에 파라미터 값 추가하기 (레퍼런스를 넘겨서 리턴 안받아도 됨)
                        method.invoke(userController, dtoInstance);
                    } else {
                        //파라미터가 없을 경우 -> 그냥 해당 함수 실행하면됨
                        method.invoke(userController);
                    }

                    break; // 더 이상 메서드를 리플렉션 할 필요 없어서 빠져나감.
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }

        }

    }

    private String keyToMethodKey(String key) {
        String firstkey = key.substring(0, 1);
        String remainKey = key.substring(1);
        return "set" + firstkey.toUpperCase() + remainKey;
    }

    private <T> void setData(T instance, HttpServletRequest request) {

        // login -> params : username, password , join -> params : username, password, email      
        // 열거형으로 선언한 이유는 params에 값이 몇개가 들어올지 모르기 때문이다.
        Enumeration<String> params = request.getParameterNames();

        // hasMoreElements 열거형 타입 (linkedList 형태)
        while (params.hasMoreElements()) {
            //key : username, password 등
            String key = (String) params.nextElement();
            String methodKey = keyToMethodKey(key); // -> 예: setUsername, setPassword 등등

            Method[] methods = instance.getClass().getMethods();
            for (Method m : methods) {
                if (m.getName().equals(methodKey)) {
                    try {
                        m.invoke(instance, request.getParameter(key));
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

}

- 오우야 코드가 너무 길어서 깜짝 놀랐을 것이다. 하지만 쫄지마라 하나하나 보면 별거 아니다. 차근차근 설명해주겠다.

- 우선 앞서서 작성한 코드와 함께 같이 설명하겠다. userController 객체를 하나 만들고 해당 객체에 선언되어있는 method를 가져온다. 우리같은경우는 3개를 가져올것이다. (join, login, user 메서드)

- methods를 for문을 돌면서 해당 메서드의 requestMapping 어노테이션이 선언되어있는지를 확인한다. 확인 후 어노테이션의 uri가  endPoint("/user/login", "/user/join", "/user")와 일치하는게 있는지를 확인하다.

- 일치한다면, 해당 메서드의 파라미터를 뽑아낸다. (우리같은 경우는 파리미터가 1개만 있다고 가정하고 진행하였다.) 해당 파리미터 타입의 객체를 하나 새로 만든다. 그것이 바로 "dtoIntance" 이다.

- setData라는 메서드에 dtoInstace와 request를 넘겨주는데, 넘겨서 하는일은 복잡하지만 간단하다. 우선 열거형 타입으로 요청 파라미터의 이름을 뽑는다. 예를 들어 요청이 login이 왔다고 하면 params에 "username", "password"가 담기고 join이 왔다고하면 params에 "username", "password", "email"이 담길 것이다. 그리고 params를 for문을 돌면서 클라이언트로 부터 받은 값을 dtoInstace에 적용시켜주기 위해서 keyToMethodKey를 만들었다.

- keyToMethodKey는 예를 들어 파라미터가 username, password가 왔다고 하면 setUsername, setPassword메서드를 이용해 해당 값을 넣어주기 위해 만든 메소드이다.

- keyToMethodKey를 통해 클라이언트로 부터 받은 파라미터의 값이 넣어주기 위해 메소드 이름과 똑같은 methodKey를 만들었다. 따 라서 dtoInstace에서 생성한 메서드를 돌면서 set이 들어가있는 메서드를 찾아서 값을 넣어준다.

- 그리고 마지막으로 method.invoke(userController, dtoInstance);를 통해 endPoint에 해당하는 메소드를 실행시킬 수 있는것이다. dtoInstance를 통해 파라미터를 담은 객체도 넘겨준다.

 

 

 

- 지금과 같은 과정을 통해 url에 해당하는 특정 메소드를 런타임 시점에서 동적으로 실행 시킬 수 도 있고, 파라미터도 넘겨주는것도 가능하다. 하지만 실제로 이러한 과정까지 직접 구현하려면 개발자는 너무나도 수고스러울 것이다. 사실은 spring을 사용하면 spring에서 대신 해주고 있는것이다. 정말 편하지 않은가 그래서 우리는 자연스럽게 이러한 제어의 흐름을 spring framework에 넘기고 있는것이다. 제어권을 스프링에게 넘기면서 얻어가는 많은 장점은 나중에 한번 더 정리해서 올리겠다!!

알고 넘어가면 좋을것 같아서 정리한다~@@

 

 

출처 : https://www.youtube.com/watch?v=ni6qfiHcuUo&list=PL93mKxaRDidFGJu8IWsAAe0O7y6Yw9f5x&index=5

'JAVA' 카테고리의 다른 글

JAVA Reflection  (2) 2022.12.22