on
Java 리플렉션(reflection)으로 만들어보는 dispatcher-servlet...
Java 리플렉션(reflection)으로 만들어보는 dispatcher-servlet...
728x90
반응형
스프링에서 엄청나게 편하게 사용중인 RequestMapping어노테이션의 작동원리를
서블릿과 필터로 만들어보는 dispatcher-servlet을 대신할 필터하나
컨트롤러 부분의 MainController
커스텀 어노테이션 부분의 MyRequestMapping
각각 페이지를 연결시킬 index.jsp join.jsp login.jsp
최초에는 Web 프로젝트를 생성해준다.spring이 아니라 구 방식의 프로젝트
Dynamic Web Project 생성모습
그리고 reflect란 이름으로 프로젝트 생성 및 톰캣 9.0사용 및 경로지정.
프로젝트 구성모습
Web.xml에 DisPatcher 서블릿을 대신하여 url를 맵핑하여 연결해줄때 사용할 필터를 등록한다..!
web.xml에 필터 생성 및 등록.
dispatcher com.test.reflect.filter.Dispatcher dispatcher /*
그리고 각각의 클래스나 어노테이션을 생성해주었다.
MyRequestMapping 어노테이션 (연결할 url주소를 받기위해 String value를 하나 추가되어있음)
package com.test.reflect.anno; 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; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface MyRequestMapping { String value(); }
MainController (뷰리졸버가 없다는 가정하에 /WEB-INF와 .jsp를 그대로 매칭시켰다.)
아래는 /login /index /join 각각 3개의 연결에 대한 페이지처리를 수행한다.
package com.test.reflect.controller; import com.test.reflect.anno.MyRequestMapping; public class MainController { @MyRequestMapping(value = "/login") public String login() { System.out.println("login() 호출"); return "/WEB-INF/login.jsp"; } @MyRequestMapping("/index") public String index() { System.out.println("index() 호출"); return "/WEB-INF/index.jsp"; } @MyRequestMapping("/join") public String join() { System.out.println("join() 호출"); return "/WEB-INF/join.jsp"; } }
Dispatcher 필터!!
요청받은 url에 따라서 endPoint를 가져와서 어노테이션에 동일한 value값이 존재하는경우 메서드를 수행하면서 매칭되는 파일에다가 연결시켜준다!
ex)http://localhost:8080/reflect/login를 호출한경우
endPoint = /login
MainController에 MyRquestMapping 어노테이션이 존재하는지 확인한 뒤 존재한다면 value값을 확인하고
/login과 동일할 경우 메서드를 수행하여 결과값을 받는다(결과값 = /WEB-INF/login.jsp)
그리고 foward방식으로 필터에서 연결시켜준다!
package com.test.reflect.filter; import java.io.IOException; import java.lang.annotation.Annotation; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.RequestDispatcher; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.test.reflect.anno.MyRequestMapping; import com.test.reflect.controller.MainController; public class Dispatcher implements Filter{ @Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; //URL주소 매핑을 위해 파싱하기(replace를 수행하면 기존 contextpath인 /reflect 를 제외한걸 가져옴. String endPoint = request.getRequestURI().replaceAll(request.getContextPath(), ""); System.out.println(endPoint);//실제 호출한 주소 ex) /login MainController mainController = new MainController(); Method[] methods = mainController.getClass().getDeclaredMethods();//해당 클래스에 존재하는 모든 메서드를 가져온다 for(Method method : methods) { //어노테이션이 한개인경우 /* Annotation annotation =method.getDeclaredAnnotation(MyRequestMapping.class); */ //어노테이션이 여러개인경우 Annotation[] annotations =method.getDeclaredAnnotations(); Annotation annotation=null; for(Annotation anno : annotations) { if(anno.annotationType().equals(MyRequestMapping.class)) { annotation = anno; } break; } MyRequestMapping myRequestMapping = (MyRequestMapping) annotation; String value = myRequestMapping.value(); //어노테이션에 선언된 value값(URL주소 매핑을 위한 값을 받는부분) if(value.equals(endPoint)) { try { String path = (String) method.invoke(mainController); RequestDispatcher dis = request.getRequestDispatcher(path); dis.forward(request, response); } catch (Exception e) { e.printStackTrace(); } break; } } } }
각각의 페이지를 요청하면 해당페이지가 나타난다.
로그인 URL요청시 Join요청시 Index 요청시
##그리고 !! Controller에 DTO나 VO가 존재하여 거기에 해당되는 값을 받을때 자동으로 주입받는 형태가 스프링에서는 지원이 되는데..!##
테스트용 LoginDTO JoinDTO 생성
package com.test.reflect.dto; 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; } }
package com.test.reflect.dto; 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; } }
그리고 MainController를 각각의 DTO를 사용하도록 변경!
package com.test.reflect.controller; import com.test.reflect.anno.MyRequestMapping; import com.test.reflect.dto.JoinDTO; import com.test.reflect.dto.LoginDTO; public class MainController { @MyRequestMapping(value = "/login") public String login(LoginDTO logindto) { System.out.println("login() 호출"); System.out.println(logindto); return "/WEB-INF/login.jsp"; } @MyRequestMapping("/index") public String index() { System.out.println("index() 호출"); return "/WEB-INF/index.jsp"; } @MyRequestMapping("/join") public String join(JoinDTO joindto) { System.out.println("join() 호출"); System.out.println(joindto); return "/WEB-INF/join.jsp"; } }
Dispatcher필터를 수정 파라미터를 기준으로 setter를 가져올 수 있도록 하는 메서드, 그에 해당하는 이름으로 변경시켜주는 메서드등이 추가 되었다!
package com.test.reflect.filter; import java.io.IOException; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.lang.reflect.Parameter; import java.util.Enumeration; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.RequestDispatcher; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.test.reflect.anno.MyRequestMapping; import com.test.reflect.controller.MainController; public class Dispatcher implements Filter{ @Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; //URL주소 매핑을 위해 파싱하기(replace를 수행하면 기존 contextpath인 /reflect 를 제외한걸 가져옴. String endPoint = request.getRequestURI().replaceAll(request.getContextPath(), ""); System.out.println(endPoint);//실제 호출한 주소 ex) /login MainController mainController = new MainController(); Method[] methods = mainController.getClass().getDeclaredMethods();//해당 클래스에 존재하는 모든 메서드를 가져온다 for(Method method : methods) { //어노테이션이 한개인경우 /* Annotation annotation =method.getDeclaredAnnotation(MyRequestMapping.class); */ //어노테이션이 여러개인경우 Annotation[] annotations =method.getDeclaredAnnotations(); Annotation annotation=null; for(Annotation anno : annotations) { if(anno.annotationType().equals(MyRequestMapping.class)) { annotation = anno; } break; } MyRequestMapping myRequestMapping = (MyRequestMapping) annotation; String value = myRequestMapping.value(); //어노테이션에 선언된 value값(URL주소 매핑을 위한 값을 받는부분) if(value.equals(endPoint)) { try { Parameter[] params = method.getParameters(); String path = null; if(params.length!=0) {//파라미터 있을때 Object dtoInstance=null; for (Parameter param : params) { dtoInstance = param.getType().newInstance();//해당 클래스로 만들어준다 setData(dtoInstance, request); } path = (String) method.invoke(mainController,dtoInstance); }else {//없을때 path = (String) method.invoke(mainController); } RequestDispatcher dis = request.getRequestDispatcher(path); dis.forward(request, response); } catch (Exception e) { e.printStackTrace(); } break; } } } private void setData(T instance, HttpServletRequest request) { //모든 파라미터의 이름들을 가져온다. ex) username,password Enumeration keys = request.getParameterNames(); //key--> set으로 변형 ex) username --> setUsername while(keys.hasMoreElements()) { String key = keys.nextElement(); String methodKey = keyToMethodKey(key); Method[] methods = instance.getClass().getDeclaredMethods(); for (Method method : methods) { //만약 username의 파라미터가 존재한다면 setUsername메서드를 찾아서 해당값을 넘겨주고 메서드를 실행시킨다! if(method.getName().equals(methodKey)) { try { method.invoke(instance, request.getParameter(key)); } catch (Exception e) { e.printStackTrace(); } } }//methods }//while } /** * 파라미터 값을 받으면 setter의 기본형태로 만들어서 string을 return한다 * @param key * @return String methodKey */ private String keyToMethodKey(String key) { String firstKey = "set"; String upperKey = key.substring(0,1).toUpperCase(); String remainKey = key.substring(1); return firstKey+upperKey+remainKey; } }
##일반적인 요청시 파라미터가 없는경우##
login호출시 DTO에 셋팅된 값 모습
##파라미터가 존재하고 postman을통해 요청했을때##
Postman 요청화면 DTO에 리플렉션을 이용하여 Setter를 찾아서 넣어준화면
DTO에 존재하는 setter를 찾아서 제대로 작동한 모습이다!!
자바 리플렉션을 통하여 스프링에서 어노테이션등을 붙여서 자동으로 페이지 연결 시켜주거나, 그 안에있는 DTO나 VO등에 set을 해주는 것 등을 어떠한 방식으로 하는지 알아보았다!
728x90
반응형
from http://lollaziest.tistory.com/170 by ccl(A) rewrite - 2021-10-15 22:01:28