본문 바로가기
Java

[Java] MVC 프레임워크에서 @RequestMapping 애노테이션 설정을 활용한 매핑하는 클래스 뜯어보기

by bkuk 2023. 4. 4.

 

 

[Java] @Controller 애노테이션 설정 클래스 스캔하는 ControllerScanner 클래스 뜯어보기

ControllerScanner 생성자 ControllerScanner 클래스가 생성될 때, basePackage를 입력받는다. 입력받은 basePackage를 이용해 reflections 객체를 생성한다. private Reflections reflections; public ControllerScanner( Object...basePack

starting-coding.tistory.com

앞에서 @Controller 설정이 되어 있는 클래스를 찾았다.

 

@RequestMapping 애노테이션이 설정된 메서드

 

아래를 보자. 

찾은 컨트롤러 클래스의 @RequestMapping 애노테이션 설정을 기반으로 매핑하는 것이다. 그렇다면 Map의 키(Key)로 사용되는 값이 요청 URL과 더불어 HTTP 메서드 조합으로 구성되어야 한다.

@Controller
public class MyController {
	 private static final Logger log = LoggerFactory.getLogger(MyController.class);
	 
    @RequestMapping("/user/findUserId")
    public ModelAndView findUserId(HttpServletRequest request, HttpServletResponse response) {
    	log.debug("findUserId");
    	return null;
    }
    
    @RequestMapping(value ="/save", method = RequestMethod.POST)
    public ModelAndView save(HttpServletRequest request, HttpServletResponse response) {
    	log.debug("save");
    	return null;
    }
}

 

 

HandlerKey 클래스는 URL과  HTTP 메서드 조합으로 구성

HashMap의 키(Key)로 활용하기 위해서 hashCode(), equals() 메서드를 반드시 구현한다.

public class HandlerKey {
    private String url;
    private RequestMethod requestMethod;

    public HandlerKey(String url, RequestMethod requestMethod) {
        this.url = url;
        this.requestMethod = requestMethod;
    }
    public boolean equals(Object obj) {
    [생략..]
    }
    public int hashCode() {
    [생략..]
    }

 

 

 

HandlerExecution 클래스는 인스턴스 정보와 Method 정보로 구성

HashMap의 값(value)은 @RequestMapping 애노테이션이 설정되어 있는 메서드 정보를 가져야 한다. 값에 저장되는 메서드 정보는 자바 리플렉션으로 해당 메서드를 실행할 수 있는 정보를 가져야 한다.

public class HandlerExecution {
	private static final Logger log = LoggerFactory.getLogger(HandlerExecution.class);
	
	private Object declaredObject;
	private Method method;
	
	public HandlerExecution( Object declaredObject, Method method ) {
		this.declaredObject = declaredObject;
		this.method = method;
	}
	
    public ModelAndView handle(HttpServletRequest request, HttpServletResponse response) throws Exception {
        
    	return (ModelAndView) method.invoke(declaredObject, request, response );
    }
}

 

HandlerKey와 HandlerExecution을 연결(Mapping)을 연결하는 AnnotationHandlerMapping 클래스

@RequestMapping 애노테이션이 적용된 메서드와 컨트롤러를 매핑하여 handlerExecutions 맵에 저장하고 이후 클라이언트의 요청 URL과 HTTP 메서드 타입에 따라 적절한 핸들러를 반환한다.

 

  • AnnotataionHandlerMapping(Object...basePackage) : AnnotataionHandlerMapping 객체를 생성한다. 생성자에 전달된 basePackage 배열의 패키지 경로를 이용하여 컨트롤러 클래스를 스캔한다.
public class AnnotataionHandlerMapping {
	private Object[] basePackage;
    
	public AnnotataionHandlerMapping(Object...basePackage ) {
		this.basePackage = basePackage;
	}

 

  • initialize() : 컨트롤러 클래스에서 @RequestMapping 애노테이션이 적용된 메서드를 찾아서 handlerExecutions 맵에 등록한다.
public void initialize() {
    ControllerScanner controllerScanner = new ControllerScanner(basePackage);

    Map<Class<?>, Object> controllers = controllerScanner.getControllers();

    Set<Method> methods = getRequestMappingMethods( controllers.keySet() );

    for( Method method : methods ) {
        RequestMapping rm = method.getAnnotation(RequestMapping.class);
        log.debug("register handlerExecution : url is {}, method is {} ", rm.value(), method );
        handlerExecutions.put(createHandlerKey(rm), new HandlerExecution(controllers.get(method.getDeclaringClass()), method));
    }
}

 

  • getRequestMappingMethods(Set<Class<?>> controllers) : @RequestMapping 애노테이션이 적용된 메서드를 찾는다. controllers 매개변수로 전달된 컨트롤러 클래스들의 모든 메서드를 검색하여 @RequestMapping 애노테이션이 적용된 메서드를 찾는다.
public Set<Method> getRequestMappingMethods( Set<Class<?>> controllers ) {
    Set<Method> requestMappingMethods = new HashSet<>();
    for( Class<?> clazz : controllers ) {
        requestMappingMethods.addAll( ReflectionUtils.getAllMethods(clazz, ReflectionUtils.withAnnotation(RequestMapping.class)) );
    }
    return requestMappingMethods; 
}

 

  • createHandlerKey(RequestMapping rm) : RequestMapping 애노테이션 정보를 이용하여 HandlerKey 객체를 생성한다. HandlerKey 객체는 요청 URL과 HTTP 메서드 타입을 가지고 있는 객체이다.
private HandlerKey createHandlerKey( RequestMapping rm ) {
    return new HandlerKey(rm.value(), rm.method());
}

 

클라이언트 요청에 해당하는 HandlerExecution 반환

아래 get() 메서드는 HashMap 클래스의 메서드를 그대로 사용하여 맵에서 주어진 키와 일치하는 값(HandlerExecution)을 반환한다. 이때 키 값은 HandlerKey 객체의 hashCode()와 equals() 메서드의 결과로 결정된다.

따라서 HandlerKey 클래스에서 hashCode()와 equals() 메서드가 적절하게 구현되어야 get() 메서드가 올바르게 작동합니다.

public HandlerExecution getHandler( HttpServletRequest request ) {
    String requestUri = request.getRequestURI();
    RequestMethod rm = RequestMethod.valueOf(request.getMethod().toUpperCase());

    log.debug( "RequestURI : {}, RequestMethod : {} ", requestUri, rm);
    return handlerExecutions.get(new HandlerKey(requestUri, rm));
};

 

 

 

댓글