본문 바로가기
개발/JAVA

[JAVA] Spring Controller를 직접 만들어보자 (2) - 핸들러 등록

by 상용최 2021. 1. 15.
반응형

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. 등록이 잘 되는지 확인

잘 등록 되는것을 확인할 수 있다.

반응형

댓글