Custom annotation parameterMap 만들기

떡ol·2022년 8월 21일
0

😊 API를 사용 하여 json형식으로 가져올때나, List형식의 toString 타입으로 자료를 불러오거나 Data를 요청하고 가져옴에 있어서 우리는 리턴받는 Parameter(파라미터)의 형식을 고정시켜야 할 때가 있다.
그럴때 Intercepter나 Filter, AOP등 다양한 방법을 이용하여 파라미터를 전처리할 수 있다.
여기서는 @Target과 @Retention 및 ArgumentResolver를 이용하여 Map형식으로 파라미터를 관리하는 방법을 알아보겠다.

Custom Annotation 만들기

@Target 과 @Retention

  • @Target : 어노테이션을 작성할 곳 입니다. default 값은 모든 대상입니다. 예를 들어@Target(ElementType.FIELD)로 지정해주면, 필드에만 어노테이션을 달 수 있습니다. 만약 필드 말고 다른부분에 어노테이션을 사용한다면 컴파일 때 에러가 나게 됩니다.

ElementType.TYPE (class, interface, enum)
ElementType.FIELD (instance variable)
ElementType.METHOD
ElementType.PARAMETER
ElementType.CONSTRUCTOR
ElementType.LOCAL_VARIABLE
ElementType.ANNOTATION_TYPE (on another annotation)
ElementType.PACKAGE (remember package-info.java)

  • @Retention : 어노테이션의 지속 시간을 정합니다.

RetentionPolicy.SOURCE : 컴파일 후에 정보들이 사라집니다. 이 어노테이션은 컴파일이 완료된 후에는 의미가 없으므로, 바이트 코드에 기록되지 않습니다. 예시로는 @Override와 @SuppressWarnings 어노테이션이 있습니다.
RetentionPolicy.CLASS : default 값 입니다. 컴파일 타임 때만 .class 파일에 존재하며, 런타임 때는 없어집니다. 바이트 코드 레벨에서 어떤 작업을 해야할 때 유용합니다. Reflection 사용이 불가능 합니다.
RetentionPlicy.RUNTIME : 이 어노테이션은 런타임시에도 .class 파일에 존재 합니다. 커스텀 어노테이션을 만들 때 주로 사용합니다. Reflection 사용 가능이 가능합니다.

😊 자, 우리는 여기서 ElementType.PARAMETER를 만들어야하며, 지속적으로 웹서버가 켜져있는 동안 계속 사용되야 하므로 RetentionPlicy.RUNTIME 속성을 이용하여 만들것이다.

package com.resolver;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;


@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface AnnoParamMap {
	//value 라는 값의 defalut는 ""이다. 여기서는 필요 없으므로 더 알아서 공부해보시길,
	//String value() default ""; 
}

😊 다음으로는 HandlerMethodArgumentResolver를 상속 받아서 작성한다.

package com.resolver;

import java.util.Iterator;

import org.springframework.core.MethodParameter;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;

public class ParamMapArgumentResolver implements HandlerMethodArgumentResolver{

	@Override
	public boolean supportsParameter(MethodParameter parameter) {
		/*
		 * System.out.println("paramMapResolver checkPoint :: ."+parameter.
		 * hasParameterAnnotation(AnnoParamMap.class) +
		 * ParamMap.class.isAssignableFrom(parameter.getParameterType()));
		 */
		return parameter.hasParameterAnnotation(AnnoParamMap.class) 
				&& ParamMap.class.isAssignableFrom(parameter.getParameterType());
	}

	@Override
	public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
		ParamMap paramMap = new ParamMap();
		for (Iterator<String> iterator = webRequest.getParameterNames(); iterator.hasNext();) {
			String key = iterator.next();
			paramMap.put(key, webRequest.getParameter(key));
		}
		System.out.println(paramMap.toString());
		return paramMap;
		
	}
	
}

😊 webPage에서 파라미터를 submit하면 이 ParamMapArgumentResolver.class를 거쳐 파라미터를 처리하게 된다. (주석설명)

/*
  파라미터를 넘겨 받으면 supportsParameter를 먼저 실행하고 true이면,
  다음 단계(resolveArgument Method부분)으로 이동한다.
  */
public boolean supportsParameter(MethodParameter parameter) {
		/*hasParameterAnnotation : 영어로 해석해봐도 알겠지만,
        	Annotation type이 AnnoParamMap 맞는지 체크하고 boolean값으로 반환한다.
            isAssignableFrom은 해당 class랑 맞는지 체크한고 boolean값으로 반환한다.
        */
		return parameter.hasParameterAnnotation(AnnoParamMap.class) 
				&& ParamMap.class.isAssignableFrom(parameter.getParameterType());
}

😊 여기서 추가적으로 궁금할거 같아서 찾아봤다.

instanceof 와 Class.isAssignableFrom 의 차이점

  • instanceof는 특정 Object가 어떤 클래스/인터페이스를 상속/구현했는지를 체크하며
  • Class.isAssignableFrom()은 특정 Class가 어떤 클래스/인터페이스를 상속/구현했는지 체크합니다.
// instanceof
MacPro obj = new MacPro();
if (obj instanceof Computer) {
  ...
}
// Class.isAssignableFrom()
if (Computer.class.isAssignableFrom(MacPro.class)) {
  ...
}

😊 다음으로 supportsParameter에 통과하면 resolveArgument가 실행이 된다. (주석설명)

public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        //새로운 dto를 생성하여 관리한다.
		ParamMap paramMap = new ParamMap();
        //webRequest에서 파라미터를 가져와 나만의 객체(ParamMap)에 바인딩한다.
		for (Iterator<String> iterator = webRequest.getParameterNames(); iterator.hasNext();) {
			String key = iterator.next();
			paramMap.put(key, webRequest.getParameter(key));
		}
		System.out.println(paramMap.toString());
		return paramMap;
		
	}

😊 아래는 ParamMap에 대한 내용이다.

public class ParamMap {

	private Map<String, Object> map;
	
	public ParamMap() {
		map = new HashMap<String, Object>();
	}
	
	public void put(String key, Object value) {
		map.put(key, value);
	}
	
	public Object get(String key) {
		return map.get(key);
	}
	
	public String toString() {
		StringBuilder sb = new StringBuilder();
		sb.append("Parameter :: ");
		for(Iterator<String> iterator = map.keySet().iterator();iterator.hasNext();) {
			String key = iterator.next();
			sb.append(key);
			sb.append(" : ");
			sb.append((String) map.get(key));
			if(iterator.hasNext()) {
				sb.append(", ");
			}
		}
		return sb.toString();
	}

✔ 여기서 궁금점? 왜 객체를 만들어서 담지? 그냥 java.util.Map타입으로 하면되는거 아닌가? (난 이거땜에 고생함...)

ParamMap paramMap = new ParamMap(); //??
Map paramMap = new HashMap(); // 왜이거 안씀?

참고로 Map 객체나 Map을 상속받은 객체는 스프링에서 이미 선언한 ArgumentResolve가 처리하기 때문에 전달할 수 없다.
그래서 Map 객체를 전달하려면 Map 객체를 필드로 가지고 있는 별도의 객체를 선언한 후에 사용해야 된다.

😊 servlet-context에도 빠짐없이 등록해주자

	<mvc:annotation-driven>
		<mvc:argument-resolvers>
			<bean class="com.resolver.ParamMapArgumentResolver"/>
		</mvc:argument-resolvers>
	</mvc:annotation-driven>

java config 방식은 상단에 작성한resolver파일에 @Component로 등록해주고, WebMvcConfigurationSupport을 상속 받는 클래스에서 argumentResolvers.add해주면 된다.

@Component
public class ParamMapArgumentResolver implements HandlerMethodArgumentResolver{
//이하생략
@MySpringBootApplication
public class PracticeApplication extends WebMvcConfigurationSupport {

	private final ArgumentResolveConfig argumentResolve;

	public PracticeApplication(ArgumentResolveConfig argumentResolve) {
		this.argumentResolve = argumentResolve;
	}

	@Override
	protected void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
		super.addArgumentResolvers(argumentResolvers);
		argumentResolvers.add(argumentResolve);
	}

	public static void main(String[] args) {
		SpringApplication.run(PracticeApplication.class, args);
	}

😊 마지막으로 컨트롤러와 페이지에 각각 설정해서 찍어보면 잘나온다.

	//Controller
	@RequestMapping(value = "/paramMap")
	public String paramMap(@AnnoParamMap ParamMap paramMap, HttpServletRequest request
    						, HttpServletResponse respose) {
		System.out.println(paramMap.toString());
		return "paramMap";
	}
    
    //jsp
    <%@ page language="java" contentType="text/html; charset=EUC-KR"
    pageEncoding="EUC-KR"%>
    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="EUC-KR">
    <title>Insert title here</title>
    </head>
    <body>
    paramMap
    <form action="/paramMap" method="post">
        <input type="text" name="userId" value=""/>
        <input type="password" name="password" value=""/>
        <input type="text" name="age" value=""/>
        <input type="submit"/>
    </form>
    </body>
    </html>

-끗-

(참고) Spring Annotation의 원리와 Custom Annotation 만들어보기
(참고) 아규먼트 리졸버(Argument Resolver)
(참고) @Retention 어노테이션 까보기

profile
하이

0개의 댓글