Spring의 Controller 방식을 흉내만 내기에 방식이 다를 수 있습니다.
필자는 Spring을 사용하다가 Controller를 직접 만들어보면 어떨까? 라는 생각을 했었다.
지금 그 생각을 펼쳐보려 한다.
개발환경
- IntelliJ
- JDK 1.8
1. Annotation 생성
Page 요청 : @Controller
JSON 반환 : @RestController
Get Mapping : @GetMapping
Post Mapping : @PostMapping
Controller종류는 Class위에 선언할 것이므로 Target을 Type으로 설정한다.
Mapping종류는 Method위에 선언할 것이므로 Target을 METHOD로 설정한다.
2. Filter 생성 및 등록
어떠한 방식으로 초기화와 핸들러 매핑을 할 수 있을까 고민하다가 간단하게 만들것이기에 Filter를 선택하였다.
init에서 초기화, doFilter에서 핸들러 매핑을 진행한다.
Spring의 DispatcherServlet의 축소판이라고 생각하면 편할 것같다.
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.PrintWriter;
public class ControllerFilter implements Filter {
HandlerMapping handlerMapping = new HandlerMapping();
@Override
public void init(FilterConfig filterConfig) {
handlerMapping.init();
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
}
@Override
public void destroy() {
}
}
3. 초기화 로직 작성
초기화를 도와줄 HandlerMapping이라는 Class를 만듭니다.
MainApplication이 존재하는 패키지 이하의 파일들을 읽으면서 Controller와 RestController 클래스에 등록되어있는 @GetMapping @PostMapping 정보를 등록한다.
2021-01-15 :: 아직 만드는중이기에 변경사항이 생길 수 있습니다. 3탄,4탄 가면서 소스가 조금 달라졌다면 다시 봐주시길 부탁드리겠습니다.
public class HandlerMapping {
Map<String, MethodInvoker> controller = new HashMap<>();
Map<String, MethodInvoker> restController = new HashMap<>();
public void init(){
subDirList(MainApplication.class.getResource(".").getPath());
}
private void subDirList(String source) {
File dir = new File(source);
File[] fileList = dir.listFiles();
if (fileList != null) {
try {
for (File file : fileList) {
if (file.isDirectory()) {
subDirList(file.getCanonicalPath());
} else if (file.isFile()) {
String path = file.getPath();
if (path.endsWith(".class")) {
int classes = path.lastIndexOf("classes");
String substring = path.substring(classes + 8);
String className = substring.split(".class")[0].replace("\\", ".");
Class<?> aClass = Class.forName(className);
if (aClass.isAnnotationPresent(Controller.class)) {
Method[] methods = aClass.getMethods();
Arrays.stream(methods)
.forEach(m -> addPageHandler(aClass, m));
} else if (aClass.isAnnotationPresent(RestController.class)) {
Method[] methods = aClass.getMethods();
Arrays.stream(methods)
.forEach(m -> addRestHandler(aClass, m));
}
}
}
}
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
private void addPageHandler(Class<?> aClass, Method m) {
if (m.isAnnotationPresent(PostMapping.class)) {
addPostHandler(aClass, m, MethodType.PAGE);
} else if (m.isAnnotationPresent(GetMapping.class)) {
addGetMapping(aClass, m, MethodType.PAGE);
}
}
private void addRestHandler(Class<?> aClass, Method m) {
if (m.isAnnotationPresent(PostMapping.class)) {
addPostHandler(aClass, m, MethodType.POST);
} else if (m.isAnnotationPresent(GetMapping.class)) {
addGetMapping(aClass, m, MethodType.GET);
}
}
private void addPostHandler(Class<?> aClass, Method m, MethodType methodType){
PostMapping declaredAnnotation = m.getDeclaredAnnotation(PostMapping.class);
String value = declaredAnnotation.value();
MethodInvoker methodInvoker = new MethodInvoker(value, methodType, aClass, m);
if(MethodType.PAGE.equals(methodType)){
controller.put(value, methodInvoker);
}else{
restController.put(value, methodInvoker);
}
}
private void addGetMapping(Class<?> aClass, Method m, MethodType methodType){
GetMapping declaredAnnotation = m.getDeclaredAnnotation(GetMapping.class);
String value = declaredAnnotation.value();
MethodInvoker methodInvoker = new MethodInvoker(value, methodType, aClass, m);
if(MethodType.PAGE.equals(methodType)){
controller.put(value, methodInvoker);
}else{
restController.put(value, methodInvoker);
}
}
}
Method의 정보를 가지고 있는 MethodInvoker Class를 생성한다.
원래대로라면 invoke로 실행할 때 bean에 등록되어있는 Class를 가지고 오겠지만 우리의 목적은 Controller를 만드는 것이지 BeanFactory를 만드는 것이 아니기때문에 newInstance로 생성하였다.
컨트롤러를 다 만들고나면 추후 BeanFactory를 생성하여 아래부분을 수정해야한다.
public class MethodInvoker {
private String path;
private MethodType methodType;
private Class<?> aClass;
private Method method;
public MethodInvoker(String path, MethodType methodType, Class<?> aClass, Method method) {
this.path = path;
this.methodType = methodType;
this.aClass = aClass;
this.method = method;
}
public Object invoke() throws InvocationTargetException, IllegalAccessException, InstantiationException {
try{
return method.invoke(aClass.newInstance());
} catch (IllegalAccessException | InvocationTargetException | InstantiationException e) {
e.printStackTrace();
throw e;
}
}
}
public enum MethodType {
PAGE,
POST,
GET
}
4. Controller 작성
네이밍이 거슬려도 조금만 참아주세요 😂
잘 등록이 되는지 테스트할 컨트롤러 2개를 작성합니다.
@Controller
public class TestController {
public TestController() {
}
@GetMapping("/index")
public String controllerGetMapping(){
return "/index.jsp";
}
@GetMapping("/test")
public String controllerPostMapping(){
return "/test.jsp";
}
}
@RestController
public class RestTestController {
@PostMapping("/rest/posttest")
public String restPostMapping(){
return "post";
}
@GetMapping("/rest/gettest")
public String restGetMapping(){
return "get";
}
}
5. 등록이 잘 되는지 확인
잘 등록 되는것을 확인할 수 있다.
'개발 > JAVA' 카테고리의 다른 글
[JAVA] Spring Controller를 직접 만들어보자 (4) - 파라미터 바인딩 (0) | 2021.01.28 |
---|---|
[JAVA] Spring Controller를 직접 만들어보자 (3) - 핸들러 매핑 (0) | 2021.01.16 |
[JAVA] Spring Controller를 직접 만들어보자 (1) - 프로젝트 생성 (0) | 2021.01.12 |
[JAVA] Parameter name arg0이 나올 때 (0) | 2021.01.10 |
[Mybatis] 동적쿼리를 어떻게 만들까? (0) | 2020.12.31 |
댓글