이 프로그램은 자바의 Reflection 개념들을 연습하기 위해 만든 예제로 리플렉션을 이용하여 Spring 프로젝트에서 MVC 패턴을 사용할 때 컨트롤러 등에 있는 ModelAndView (+ Model) 객체의 정보를 프론트엔드 측에서 볼 수 있도록 가공해서 보여주는 프로그램입니다. 자바스크립트의 console.log() 등을 사용해 내용을 볼 수 있습니다.

사용 제약
MVC 패턴을 사용하는 스프링 프로젝트가 필요합니다. 모델 객체가 필요하며 현재 버전은 ModelAndView와 Model을 지원합니다. 표시되는 자료형 중 다중 자료형의 경우 현재는 일반 배열, List, Map, Set만 지원합니다.

사용 방법
코드를 복사해서 클래스 파일로 만든 다음 import 하면 됩니다.

특징
  • JSP 파일에서 사용할 수 있는 변수명을 정리해서 볼 수 있습니다.
  • 타입, 변수명, 값을 한 번에 볼 수 있습니다.
  • 어떤 DTO에서 상위 클래스(superclass)가 존재하는 경우 그 상위 클래스에서 선언된 멤버 변수도 모두 찾아서 표시한다.
  • 일반 배열도 안에 있는 값들을 보여준다.
  • Map 객체의 경우 key 마다 줄바꿈을 지원해서 보여줍니다.
private String outputString;

public JspMvcHelper(ModelAndView mav){
    outputString = help(mav.getModel(), false, false);
}

public JspMvcHelper(ModelAndView mav, boolean isThisForJSConsole){
    outputString = help(mav.getModel(), isThisForJSConsole, false);
}

public JspMvcHelper(ModelAndView mav, boolean isThisForJSConsole, boolean isWholeStringWrap)
{
    outputString = help(mav.getModel(), isThisForJSConsole, isWholeStringWrap);
}

public JspMvcHelper(Model m){
    outputString = help(m.asMap(), false, false);
}

public JspMvcHelper(Model m, boolean isThisForJSConsole){
    outputString = help(m.asMap(), isThisForJSConsole, false);
}

public JspMvcHelper(Model m, boolean isThisForJSConsole, boolean isWholeStringWrap)
{
    outputString = help(m. asMap(), isThisForJSConsole, isWholeStringWrap);
}

생성자 목록으로, 모든 생성자는 toString()을 통해 String 문자열을 반환합니다.mav란에는 ModelAndView 또는 Model의 인스턴스를 넣습니다. isThisForConsole은 자바스크립트 콘솔에서 사용하고자 할 경우 사용하는 옵션이며  true를 선택해야 콘솔에서 정상적으로 볼 수 있습니다. false를 선택할 경우 일반 텍스트로 출력하며 자바스크립트에 일반 따옴표(쌍따옴표나 홑따옴표)로 삽입시 에러를 유발합니다. isWholeStringWrap은 출력 결과 전체를 쌍따옴표(“)로 묶을 지 여부를 선택하는 것이며 기본 값은 false이다.

 

컨트롤러에서의 사용예
mav.addObject("instruction",  new JspMvcHelper(mav, true));

뷰(프론트엔드) 작업자는 다음과 같이 정보를 사용하면 됩니다.

<script>
    console.log("${ instruction }");
    // EL을 둘러쌓은 기호는 반드시 쌍따옴표("") 이어야 한다.
    // isWholeStringWrap이 true일 경우 다음과 같이 사용
    console.log(${ instruction });
</script>

또는 <pre>${ instruction }</pre> 과 같이 사용할 수 있다. 이 때는 isThisForConsolefalse인 경우가 좋습니다.

출력결과는 다음과 같습니다.

제네릭 타입은 실제 제네릭 타입을 찾아 표시하는게 아니라 리스트 등에서 모든 원소의 타입이 동일할 것이라는 가정 하에 첫 번째 원소의 객체를 읽어 그 객체의 타입에 관한 정보를 읽어들이는 약간은 편법적인 방식입니다. 만약 제너릭 타입이 Object 또는 Superclass인 경우는 정확한 하위 클래스를 판별할 수 없습니다. 아래에서 set1 에서 제너릭 타입은 Object으로 지정하였지만, 첫 번째 원소가 Integer인 관계로 Integer인 것처럼 표시되었다.

********** 제너릭 타입 상세(β) **********
list1: [egovframework.map.web.dto.LayerInfo] 
 - String name
 - String thumbnailURL
 - String style 

map1:
 key: [java.lang.String] 
 value: [egovframework.map.web.dto.TopicArticleDaughter] 
 - String daughterName
 - int seq
 - String title
 - String link 

set1: [java.lang.Integer]

소스 보기 [2018-10-29 업데이트]


import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.springframework.ui.Model;
import org.springframework.web.servlet.ModelAndView;
public class JspMvcHelper {
private String outputString;
public JspMvcHelper(ModelAndView mav){
outputString = help(mav.getModel(), false, false);
}
public JspMvcHelper(ModelAndView mav, boolean isThisForJSConsole){
outputString = help(mav.getModel(), isThisForJSConsole, false);
}
public JspMvcHelper(ModelAndView mav, boolean isThisForJSConsole, boolean isWholeStringWrap)
{
outputString = help(mav.getModel(), isThisForJSConsole, isWholeStringWrap);
}
public JspMvcHelper(Model m){
outputString = help(m.asMap(), false, false);
}
public JspMvcHelper(Model m, boolean isThisForJSConsole){
outputString = help(m.asMap(), isThisForJSConsole, false);
}
public JspMvcHelper(Model m, boolean isThisForJSConsole, boolean isWholeStringWrap)
{
outputString = help(m. asMap(), isThisForJSConsole, isWholeStringWrap);
}
public String help(Map<String, ?> mm, boolean isThisForJSConsole, boolean isWholeStringWrap)
{
// ModelMap mm = mav.getModelMap();
StringBuilder sb = new StringBuilder();
sb.append("********** 변수 목록 **********" + "\n");
sb.append(mm.keySet() + "\n\n");
sb.append("********** 변수 상세 ********** \n《[타입] 변수명 → 값(toString)》" + "\n\n");
for(String key : mm.keySet()){
Class<? extends Object> c = mm.get(key).getClass();
sb.append("[" + c.getName() + "] " + key + " → " + mm.get(key) + "\n");
}
sb.append("\n");
sb.append("********** 타입 상세 **********" + "\n");
for(String key: mm.keySet()){
Class<? extends Object> c = mm.get(key).getClass();
List<Field> fs = recursiveGetField(c);
sb.append("[" + c.getName() + "] " + key + "\n");
try{
String superName = c.getSuperclass().getSimpleName();
if(c.getPackage().getName().equals("java.lang")){
sb.append("\n");
continue;
} else if (superName.equals("AbstractList") || superName.equals("List")){
sb.append(" – ");
List<?> tempList = (List<?>) mm.get(key);
sb.append(tempList + "\n\n");
continue;
} else if (superName.equals("AbstractMap") || superName.equals("Map")){
Map<?, ?> tempMap = (Map<?, ?>) mm.get(key);
for(Object oKey : tempMap.keySet()){
sb.append( " – " + oKey + "=" + tempMap.get(oKey) + "\n");
}
sb.append("\n");
continue;
} else if (superName.equals("AbstractSet") || superName.equals("Set")){
sb.append(" – ");
Set<?> tempList = (Set<?>) mm.get(key);
sb.append(tempList + "\n\n");
continue;
}
} catch (Exception e){
System.err.println(c.getName() + ": "+ e);
if (c.isArray()){
sb.append(" – ");
if (c.isPrimitive()) {
int length = Array.getLength(mm.get(key));
Object[] tempArr = new Object[length];
for (int i = 0; i < length; i++) {
tempArr[i] = Array.get(mm.get(key), i);
}
sb.append(Arrays.toString(tempArr) + "\n");
}
else {
Object[] tempArr = (Object[]) mm.get(key);
sb.append(Arrays.toString(tempArr) + "\n");
}
sb.append("\n");
continue;
}
}
for(Field f : fs){
sb.append(" – " + f.getType().getSimpleName() + " " + f.getName() + "\n");
}
sb.append("\n");
}
sb.append("********** 제너릭 타입 상세(β) **********" + "\n");
for(String key: mm.keySet()){
Class<? extends Object> c = mm.get(key).getClass();
// List<Field> fs = recursiveGetField(c);
try{
String superName = c.getSuperclass().getSimpleName();
if (superName.equals("AbstractList") || superName.equals("List")){
List<?> tempList = (List<?>) mm.get(key);
if(tempList != null){
Class<? extends Object> innerClass = tempList.get(0).getClass();
List<Field> innerFs = recursiveGetField(innerClass);
sb.append(key + ": [" + innerClass.getName() + "] " + "\n");
if(!innerClass.getPackage().getName().equals("java.lang")){
for(Field f : innerFs){
sb.append(" – " + f.getType().getSimpleName() + " " + f.getName() + "\n");
}
}
}
sb.append("\n");
continue;
} else if (superName.equals("AbstractMap") || superName.equals("Map")){
Map<?, ?> tempMap = (Map<?, ?>) mm.get(key);
Object firstKey = tempMap.keySet().iterator().next();
Object firstValue = tempMap.get(firstKey);
if( firstKey != null && firstValue != null){
Class<? extends Object> innerKeyClass = firstKey.getClass();
Class<? extends Object> innerValueClass = firstValue.getClass();
List<Field> innerKeyFs = recursiveGetField(innerKeyClass);
List<Field> innerValueFs = recursiveGetField(innerValueClass);
sb.append(key + ":\n");
sb.append(" key: [" + innerKeyClass.getName() + "] " + "\n");
if(!innerKeyClass.getPackage().getName().equals("java.lang")){
for(Field f : innerKeyFs){
sb.append(" – " + f.getType().getSimpleName() + " " + f.getName() + "\n");
}
}
sb.append(" value: [" + innerValueClass.getName() + "] " + "\n");
if(!innerValueClass.getPackage().getName().equals("java.lang")){
for(Field f : innerValueFs){
sb.append(" – " + f.getType().getSimpleName() + " " + f.getName() + "\n");
}
}
}
sb.append("\n");
continue;
} else if (superName.equals("AbstractSet") || superName.equals("Set")){
Set<?> tempSet = (Set<?>) mm.get(key);
Object first = tempSet.iterator().next();
if( first != null){
Class<? extends Object> innerClass = first.getClass();
List<Field> innerFs = recursiveGetField(innerClass);
sb.append(key + ": [" + innerClass.getName() + "] " + "\n");
if(!innerClass.getPackage().getName().equals("java.lang")){
for(Field f : innerFs){
sb.append(" – " + f.getType().getSimpleName() + " " + f.getName() + "\n");
}
}
}
sb.append("\n");
continue;
}
} catch (Exception e){
System.err.println(c.getName() + ": "+ e);
if (c.isArray()){
sb.append(" – ");
if (c.isPrimitive()) {
int length = Array.getLength(mm.get(key));
Object[] tempArr = new Object[length];
for (int i = 0; i < length; i++) {
tempArr[i] = Array.get(mm.get(key), i);
}
sb.append(Arrays.toString(tempArr) + "\n");
}
else {
Object[] tempArr = (Object[]) mm.get(key);
sb.append(Arrays.toString(tempArr) + "\n");
}
sb.append("\n");
continue;
}
}
}
if(isThisForJSConsole) {
if(isWholeStringWrap){
return "\"" + sb.toString().replace("\n", "\\n\" + \n\"") + "\"";
} else {
return sb.toString().replace("\n", "\\n\" + \n\"");
}
} else {
return sb.toString();
}
}
public List<Field> recursiveGetField(Class<?> c){
List<Field> a = new ArrayList<>();
Field[] fs = c.getDeclaredFields();
a.addAll(Arrays.asList(fs));
Class<? extends Object> superClass = c.getSuperclass();
if(superClass != null){
a.addAll( recursiveGetField(superClass) );
}
return a;
}
@Override
public String toString() {
return outputString;
}
}

문의 | 코멘트 또는 yoonbumtae@gmail.com




0개의 댓글

답글 남기기

Avatar placeholder

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다