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