토비의 스프링 3.1 VOL.1

스프링 사용자로서 항상 읽어야봐야 한다라는 소리에 두권을 샀지만, 엄청 어렵다는 소문에 읽을 엄두를 못 내던 책이다. 한 권에 800 페이지가 넘는 굉장한 분량에 두번의 정독 시도를 헛탕쳤었다. [유튜브] "개발바닥" 채널에서 저자 이일민님의 인터뷰와, 그 분이 이제는 스프링 부트 인강을 내시면서 이 책을 다시 시도하였고. 이번에는 1독을 끝냈다.
이제 4년차에 들어서면서 읽어보니 '어느 정도 개발을 했고, 좋은 구조와 효율을 고민해본 사람이 봐야 깊은 뜻을 더 잘 이해할 수 있겠구나' 라고 느꼈다.

이 책은 객체지향적 설계, 리팩토링, 테스트주도개발의 중요성에 대해 꾸준히 강조하고 있다. 단순하게 스프링 프레임워크의 기능 / 개념에 대한 설명도 깊게 나와있지만, 이 책의 요점은 스프링이 왜 이렇게 발전했고, 어떤 패러다임을 녹여서 만든건지 엿볼 수 있도록 도와주는 책이다. 따라서 개발 철학에 더 중점을 두고 읽어야 하는 책이라고 생각 된다.

마틴 파울러와 켄트 백이 강조하던 내용이 녹아 있으며, 아래와 같은 조언을 책 전반에 걸쳐 꾸준히 강조하고 있다.

  • '고정된 작업 흐름을 갖고 있으면서 여기저기서 자주 반복되는 코드가 있다면, 중복되는 코드를 분리할 방법을 생각해보는 습관을 기르자.'
  • '비슷한 기능이 새로 필요할 때마다 앞에서 만든 코드를 복사해서 사용할 것인가? 물론 아니어야 한다. 한두 번까지는 어떻게 넘어간다고 해도, 세번 이상 반복된다면 본격적으로 코드를 개선할 시점이라고 생각해야 한다.'

인상 깊었던 문장

인상 깊었던 문장들은 다음과 같다. 다 읽어보면 떠오르는 것이 있다. 그렇다, 대부분의 레거시 프로젝트의 소스들은 아래에서 하지 말라는 대로 개발되어 있다.

  계층형 아키텍쳐
  관심, 책임, 성격, 변하는 이유와 방식이 서로 다른 것들을 분리함으로써 분리된 각 요소의 응집도는 높여주고 서로 결합도를 낮춰줬을 때의 장점과 유익이 무엇인지 살펴봤다. 성격이 다른 모듈이 강하게 결합되어 한데 모여 있으면 한 가지 이유로 변경이 일어날 때 그와 상관이 없는 요소도 함께 영향을 받게 된다. 따라서 불필요한 부분까지 변경이 일어나고 그로 인해 작업은 더뎌지고 오류가 발생할 가능성이 높아진다. 어느 부분을 수정해야할지를 파악하기도 쉽지 않다.
  따라서 인터페이스와 같은 유연한 경계를 만들어 두고 분리하거나 모아주는 작업이 필요하다.
또, 흔히 저지르는 실수 중의 하나는 프레젠테이션 계층의 오브젝트를 그대로 서비스 계층으로 전달하는 것이다.
서블릿의 HttpServletRequest나 HttpServletResponse, HttpSession 같은 타입을 서비스 계층 인터페이스 메소드의 파라미터 타입으로 사용하면 안 된다.
계층의 경계를 넘어갈 때는 반드시 특정 계층에 종속되니 않는 오브젝트 형태로 변환해줘야 한다.
스프링을 사용하면 이런 데이터 중심의 코드를 만들 수 있을 뿐만 아니라, 실제로 매우 흔하게 발견된다.
데이터와 업무 트랜잭션 중심의 개발에 익숙한 사람들이 많고 이런 아키텍쳐를 의도적으로 선호하는 개발자도 많기 때문이다.
개발자들끼리 서로 간선없이 자신에게 할당된 기능을 독립적으로 만드는 데도 편하다.
최소한의 공통 모듈 정도만 제공되는 것을 사용하고, 그 외의 기능은 단위 업무 또는 웹 화면 단위로 만들어 진다.

하지만 이런 개발 방식은 변화에 매우 취약하다.
객체지향의 장점이 별로 활용되지 못하는데다 각 계층의 코드가 긴밀하게 연결되어 있기 때문이다.
중복을 제거하기도 쉽지 않다.
업무 트랜잭션에 따라 필드 하나가 달라도 거의 비슷한 DAO 메소드를 새로 만들기도 한다.
또한 로직을 DB와 SQL에 많이 담으면 담을수록 점점 확장성이 떨어진다.
DB는 확장에 한계가 있을 뿐 아니라 확장한다 하더라도 매우 큰 비용이 든다.
잘 작성된 복잡한 SQL 하나가 수백 라인의 자바 코드가 필요한 비지니스 로직을 한번에 처리할 수도 있다.
하지만 과연 바람직한 것일까?
이런 복잡한 sql을 누구나 쉽게 이해하고 필요에 따라 유연하게 변경할 수 있을까?
또, 복잡한 sql을 처리하기 위해 제한된 자원인 DB에 큰 부담을 주는 게 과연 바람직한 일인지 생각해볼 필요가 있다.
데이터 중심 아키텍쳐의 특징은 계층 사이의 결합도가 높은 편이고 응집도는 떨어진다는 접이다.
화면을 중심으로 하는 업무 트랜잭션 단위로 코드가 모이기 때문에 처음엔 개발하기 편하지만 중복이 많아지기 쉽고 장기적으로 코드를 관리하고 발전시키기 힘들다는 단점이 있다.
스프링은 그 개발철학과 목표를 분명히 이해하고 사용해야 한다.
자바의 근본인 OOP 원리에 충실하게 개발할 수 있으며,
환경이나 규약에 의존적이지 않은 POJO를 이용한 애플리케이션 개발은
엔터프라이즈 시스템 개발의 복잡함이 주는 많은 문제를 해결할 수 있다.
POJO 방식의 개발을 돕기 위해 스프링은 IoC/DI, AOP, PSA와 같은 기능 기술을 프레임워크와 컨테이너라는 방식을 통해 제공한다.
728x90
반응형

1. Debug Mode 활용

디버그 모드로 애플리케이션을 구동시키면 아래 스크린샷과 같이 변수, 객체 등의 값을 알 수 있다. log를 찍거나 system print 등을 사용하지 앖아도 실시간으로 값을 알 수 있고 브레이크 포인트를 사용해서 트래킹 하는데 수월하니 앞으로는 IDE의 디버그 모드를 적극 활용하자. intellijeclipse 모두 지원한다.

IntelliJ의 디버그 모드


2. Comment 제거

아래 기준을 참고해서 과감하게 기존 코멘트를 제거한다.

  • 위 디버그 모드 활용과 더불어 단순 값 확인용 코멘트는 제거한다.
  • 의미없는 ‘//////////////’ 와 같은 구분선 종류의 코멘트는 제거한다.
  • Github을 통해 트래킹, 복원이 쉽기 때문에 불필요한 주석은 과감하게 제거한다.

3. 선언, 초기화, 디폴트에 신경쓰자

코드 품질은 디테일에서 나온다. 그리고 디테일은 기본기 없이 챙기기 힘들다. 아래 스크린샷을 보자.

  • 선언부 이후에 로직 중에 항상 해당 변수에 값을 할당한다면 굳이 선언하면서 초기화 할 필요가 없다.
  • 의미없는 값으로 대충 초기화 하는 습관은 멀리하자. 초기화 하는 값은 일반적으로 디폴트 값이어야 하고 이 디폴트 값이 뭔지 정확히 알고서 초기화 해야한다.

4. 가독성을 높이자

// Before
// 결제상점아이디에 따른 분기
if (TRD_NO.indexOf(oldIdPart) >= 0) {
    sMallID = oldMid;
}

// After
if (TRD_NO.contains(oldIdPart)) {
    sMallID = oldMid;
}

위 코드는 파라미터로 넘긴 string의 존재 여부를 검사한 뒤 그에 따른 처리를 하기위한 것으로 보인다. 그래서 자바의 String에 있는indexOf() 메소드를 사용해 >= 0 조건으로 판별한다. 이 코드는 기능상 아무 이상 없이 작성자의 의도대로 잘 작동한다.

앞서 이야기한 것처럼 이런 코드에서 보이는 디테일을 잡으면 가독성과 품질이 상승한다.

indexOf 와 contains

자바에는 각 객체를 위한 유틸 메소드들이 많다. 그 중 contains라는 메소드로 indexOf를 대체할 수 있다. contains의 내용은 아래 스크린샷처럼 indexOf를 래핑한 메소드다. 따라서 성능이나 기능에선 차이가 없다고 봐도 된다.

JDK 1.5에 생긴 contains
indexOf

그럼 왜 indexOf를 contains로 대체하려고 하는거고 언제 써야 할까?

indexOf와 contains는 용도가 다르다.

indexOf는 스트링이 시작되는 index를 int값으로 반환하고 contains는 스트링의 포함 여부를 판단해 boolean으로 반환한다. 따라서 특정 비즈니스 로직 혹은 알고리즘을 구현하기 위한 경우가 아니면 일반적인 경우에 contains의 목적으로 더 많이 쓰게 된다.

Readability

물론 indexOf를 보고 >= 0 조건을 보면 뭘 하려는지 알 수 있다. 하지만 contains라는 단어가 그 모든걸 포함하기 때문에 훨씬 더 직관적이다. contains쓰면 indexOf라는 메소드는 잘 썼는지, 뒤에 >=0 인지 > 0 인지 > -1 인지 잘못쓰진 않았는지 이런 생각 할 필요도 없다. 가독성을 정의할 때 그저 문자가 잘 읽히는 정도를 넘어서 (특히 메소드는) 이름에 맞는 목적과 기능을 신뢰할 수 있어서 불필요한 생각도 줄여주는 수준까지 갈 수 있도록 신경써야 한다.


5. Naming - 변수명은 중요하다

현재 레거시 코드의 문제점

  • 
프론트 HTML tag'name을 그대로 백엔드 로직에 사용함
  • 마크업과 프론트 사정에 따른 hChk, pChk 와 같은 변수명 백엔드에서 실제 용도를 구분하기 어려움
  • 변수명과 주석이 일치하지 않거나 주석의 뜻을 담아내기에 부족한 변수명이 많음

실제 주문취소에서 볼 수 있는 케이스는 아래와 같다.

    // bad case
    String[] hChk = request.getParameterValues("hChk");//선택여부

    // good case
    String[] cancelSelectYn = request.getParameterValues("hChk"); // 취소선택여부

위 변수가 가리키고 있는 건 주문 취소 시 상품 별 체크박스 값이다.(아래 스크린샷) 따라서 hChk 라는 이름을 그대로 백엔드 로직에도 차용한다면 코드 전체를 트래킹 해야 하는 수고가 더해진다. 그래서 이름을 취소선택여부, 취소신청여부 등으로 하고 변수명도 이에 맞춰 수정하는 것이 좋다.

주문 취소 상품 선택화면

하지만 실제로 레거시 시스템은 볼륨도 크고 복잡한 상호관계를 이미 가지고 있는 상태기 때문에 변수명만 바꾸기에 위험하다. 래거시 프로젝트 소스코드도 마찬가지이며 따라서 변수명을 바꾸려 할 땐 아래 항목을 점검해서 진행한다.

  • JSP와 JAVA에서 동일하게 사용하고 있는 변수가 response나 query에서 반드시 동일하도록 짜여 있는지
  • 주석과 변수명이 다르다면 둘 중 어느 것이 맞는지, 둘 다 틀린지
  • 변수명을 바꿨을 때 영향 범위가 백엔드 비즈니스 로직에만 해당되는지

6. 조회와 요청, 트랜젝션

  1. 사용자의 화면에 보이는 내용은 화면을 그리는 시점에 유효한 정보이다(화면을 실시간으로 갱신하지 않는한)
  2. 현재 프로세스는 <step 2> 와 같고 주문 취소 요청 시 JSP에 뿌려진 주문/결제 값으로 환불요청을 시작한다
  3. 때문에 주문 조회 시점과 환불 요청 시점 차이가 있을 때 주문 상태 차이가 발생할 수 있고 중복 발생 가능성 또한 있는 구조다.
  4. 개선 프로세스는 <step 3> 와 같이 사용자 요청을 받고 현재 주문과 환불 진행상태 등을 DB에서 조회하는 ② 부터 완료된 주문/결제/환불 정보를 DB에 업데이트하는 ⑥ 까지를 하나의 트랜잭션으로 묶는다.

대부분의 레거시 프로젝트 코드에는 화면에서 가져온 값들로 무언가 처리하는 로직이 많은데 전반적인 수정이 필요하다.


7. 3-tier Architecture

아래 쿼리를 먼저 보자.

// 이런 패턴
, X.BANK_CD                <!--은행코드-->                        
, CASE WHEN X.BANK_CD IS NOT NULL
  THEN DECODE(X.BANK_CD, '02', '산업', '03', '기업', '05', '외환', '06', '국민', '07', '수협', '11', '농협', '20', '우리', '23', 'SC제일', '27', '한국씨티', '31', '대구'
                  , '32', '부산', '34', '광주', '35', '제주', '37', '전북', '39', '경남', '45', '새마을금고', '48', '신협', '71', '우체국', '81', '하나', '88'
                  , '신한(계좌이체)', '26', '신한(가상계좌)','S0', '동양증권', 'S1', '미래에셋', 'S2', '신한금융투자', 'S3', '삼성증권', 'S6', '한국투자증권' , 'SG', '한화증권')
  ELSE '-'
  END    BANK_NM            <!--은행명-->

// 비슷한 패턴
, X.MEMO                <!--메모(가상)-->
, NVL(X.MEMO, 'X') SHW_MEMO                <!--화면용메모(가상)-->

// 비슷한 패턴
, X.RECP_PSN_TELNO            <!--수령인전화번호-->
, CASE WHEN  LENGTH(REPLACE(NVL(X.RECP_PSN_TELNO, 'X'), '-', '')) <![CDATA[ < ]]> 9
  THEN 'X'
  ELSE X.RECP_PSN_TELNO
  END   SHW_RECP_PSN_TELNO            <!--수령인전화번호-->

안좋은 케이스로 보이는 건..

  • 쿼리 안에서 데이터 가공을 한다는 것이다. 게다가 화면에 보여줄 목적인 것들이 꽤 있다.
  • 코드 관리나 관련 메소드가 애플리케이션에 있는게 아니라 쿼리에 들어가 있다.

대부분의 레거시 프로젝트들은 JSP, Spring, Oracle 기반이지만 잘 구분된 계층 구조를 이루고 있지 않다. 해당 계층에서 해야 할 일들이 다른 계층으로 번져간다면 결국 문제가 생길때마다 화면~데이터 모든 영역의 코드를 살펴봐야만 어느 부분이 문제인지 찾아낼 수 있다. 그래서 앞으로 신규 개발하는 화면, 기능은 이러한 강한 결합을 피하고 우리 시스템과 기술이 지향하는 3티어 계층에 맞춰 프로그래밍을 하도록 한다. 추후 가이던스를 마련하고 공유하겠지만 먼저 간단하게 요약하면 아래와 같다.

  • 화면 코드에(JSP)에 비즈니스 로직 넣지 말자
  • 쿼리에 비즈니스 로직, 하드코딩, 화면 표시용 글자 넣지 말자(특수한 경우 제외)
  • 컨트롤러에 비즈니스 로직 넣지 말자
  • 실제 필요한 것들만 파라미터로 전달하고 용도에 맞는 모델을 만들자

 


출처 : 같이 일 했던 선배가 컨플루언스에 올린 글

 
728x90
반응형

'뭐라할까' 카테고리의 다른 글

캐시 설계 전략  (0) 2023.08.21
성능과 가독성을 높이는 분기처리 방법  (0) 2020.12.11

세션 유져 카운터 클래스

import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class SessionUserCounter implements HttpSessionListener {
  private static Logger logger = LoggerFactory.getLogger(SessionUserCounter.class);
  
  //  총 접속자 수
  public static int count;
  
  public static int getCount() {
    return count;
  }

  @Override
  public void sessionCreated(HttpSessionEvent event) {
    //  세션이 생성될 때 세션객체를 꺼내옴.
    HttpSession session = event.getSession();
    count ++;
    logger.error("\n\tSESSION CREATED : {}, TOTAL ACCESSER : {}", session.getId(), count);
  }

  @Override
  public void sessionDestroyed(HttpSessionEvent event) {
    // 세션이 소멸될 때
    count--;
    if( count < 0 ) count = 0;
    
    HttpSession session = event.getSession();
    logger.error("\n\tSESSION DESTROYED : {}, TOTAL ACCESSER : {}", session.getId(), count);
  }
  
}

 

web.xml 설정

  <listener>
    <listener-class>{패키지 경로}.SessionUserCounter</listener-class>
  </listener>

 

Java 소스에서 클래스를 불러와서 사용해도 되고, JSP에서 class를 import 해서 사용해도 된다.

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" trimDirectiveWhitespaces="true"
	import ="{패키지 경로}.SessionUserCounter"
%>
현재 접속자 수 : <%=SessionUserCounter.getCount()%>

 

 

스프링 허접이라 Lazy-Init SingleTon Pattern을 사용해야 하는지, 아니면 Listener에 등록된 객체이기 때문에 필요 없는지는 잘 모르겠다.

728x90
반응형

egovframework의 EgovHttpSessionBindingListener 클래스와 EgovMultiLoginPreventor 클래스로 중복 로그인 방지를 구현하던 중,
java.lang.illegalargumentexception setattribute non-serializable attribute ***에러가 발생했다.

session에 저장하기 위해선 해당 클래스 Serializable 클래스를 상속해 줘야 하는데, VO는 이미 상속이 되어 있었다.

VO 말고도 EgovHttpSessionBindingListener, EgovMultiLoginPreventor역시 Serializable 클래스를 상속해줘야 한다.

egov도 버젼 코드에 실수가 존재하나보다.

 

EgovHttpSessionBindingListener.java

package egovframework.com.utl.slm;

import java.io.Serializable;
import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionBindingListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @Class Name : EgovHttpSessionBindingListener.java
 * @Description : 중복 로그인 방지를 위해 사용자의 로그인 아이디와 세션을 제어하는 구현 클래스
 * @Modification Information
 *
 *               수정일 수정자 수정내용 ------- ------- ------------------- 2014.09.30 표준프레임워크 최초생성
 * @author YJ Kwon
 * @since 2014.09.30
 * @version 3.5
 */
public class EgovHttpSessionBindingListener implements HttpSessionBindingListener, Serializable {

  private static final long serialVersionUID = -6535453381287200501L;
  private static Logger logger = LoggerFactory.getLogger(EgovMultiLoginPreventor.class);

  /**
   * 사용자의 로그인 세션에 EgovHttpSessionBindingListener가 바인딩될 때 자동 호출되는 메소드로, 사용자 세션이 이미 존재하는지를 검사하여 하나의
   * 어플리케이션 내에서 하나의 세션만 유지되도록 한다
   */
  public void valueBound(HttpSessionBindingEvent event) {
    logger.error("\n\t!! VALUE BOUND !!");
    if (EgovMultiLoginPreventor.findByLoginId(event.getName())) {
      EgovMultiLoginPreventor.invalidateByLoginId(event.getName());
    }

    EgovMultiLoginPreventor.loginUsers.put(event.getName(), event.getSession());
  }


  /**
   * 
   * 로그아웃 혹은 세션타임아웃 설정에 따라 사용자 세션으로부터 EgovHttpSessionBindingListener가 제거될 때 자동 호출되는 메소드로, 사용자의 로그인
   * 아이디에 해당하는 세션을 ConcurrentHashMap에서 모두 제거한다
   */
  @Override
  public void valueUnbound(HttpSessionBindingEvent event) {
    logger.error("\n\t!! VALUE UNBOUND !!");
    EgovMultiLoginPreventor.loginUsers.remove(event.getName(), event.getSession());
  }

}

 

 

EgovMultiLoginPreventor.java

package egovframework.com.utl.slm;

import java.io.Serializable;
import java.util.Enumeration;
import java.util.concurrent.ConcurrentHashMap;

import javax.servlet.http.HttpSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @Class Name : EgovMultiLoginPreventor.java
 * @Description : 중복 로그인 방지를 위해 사용자의 로그인 아이디와 세션 아이디를 관리하는 구현 클래스
 * @Modification Information
 *
 *               수정일 수정자 수정내용 ------- ------- ------------------- 2014.09.30 표준프레임워크 최초생성
 * @author YJ Kwon
 * @since 2014.09.30
 * @version 3.5
 */
public class EgovMultiLoginPreventor implements Serializable {

  private static final long serialVersionUID = 4012190701164146206L;

  private static Logger logger = LoggerFactory.getLogger(EgovMultiLoginPreventor.class);

  public static ConcurrentHashMap<String, HttpSession> loginUsers =
      new ConcurrentHashMap<String, HttpSession>();

  /**
   * 사용자의 로그인 아이디로 생성된 세션이 있는지를 확인한다
   */
  public static boolean findByLoginId(String loginId) {
    logger.error("\n\t>> loginUsers id : {} <<", loginId);
    return loginUsers.containsKey(loginId);
  }


  /**
   * 사용자의 로그인 아이디로 이미 존재하는 세션을 무효화한다
   */
  public static void invalidateByLoginId(String loginId) {
    Enumeration<String> e = loginUsers.keys();

    while (e.hasMoreElements()) {
      String key = (String) e.nextElement();

      logger.error("\n\t>> session key : {} <<", e.toString());
      if (key.equals(loginId)) {
        logger.error("\n\t:: SESSION INVALIDATE {} <<", loginId);
        loginUsers.get(key).invalidate();
      }
    }
  }

}
728x90
반응형

pom.xml

pom.xml에 다음과 같은 Dependency를 추가 해 준다.
버전에 따라 지원이 되지 않을 수 있다.

<!-- JSON을 위한 Dependency -->
<dependency>
  <groupId>net.sf.json-lib</groupId>
  <artifactId>json-lib</artifactId>
  <version>2.4</version>
  <classifier>jdk15</classifier>
</dependency>
<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-databind</artifactId>
  <version>2.9.5</version>
</dependency>
<dependency>
  <groupId>org.codehaus.jackson</groupId>
  <artifactId>jackson-mapper-asl</artifactId>
  <version>1.9.13</version>
</dependency>
<dependency>
  <groupId>org.codehaus.jackson</groupId>
  <artifactId>jackson-mapper-asl</artifactId>
  <version>1.9.13</version>
</dependency>
<!-- JSON을 위한 Dependency -->

servlet.xml

servlet.xml 혹은 dispatcher-servlet.xml

<mvc:annotation-driven>
  <mvc:message-converters>
  
  <!-- 
    이 부분은 Controller에서 일반적인 HTML을 리턴하기 위한 설정이다.
    JSON을 리턴하지 않을 경우는 Default 값으로 지정 되어 있기 때문에 설정 할 필요 없지만,
    JSON 리턴과 HTML 리턴을 모두 하려면은 명시적으로 설정 해 줘야 한다.
   -->
    <bean class="org.springframework.http.converter.StringHttpMessageConverter">
      <property name="supportedMediaTypes">
        <list>
          <value>text/html; charset=UTF-8</value>
        </list>
      </property>
    </bean>
    
    <!--
      Controller에서 JSON 리턴시 객체를 변환 해주기 위해서 MessageConverter가 필요하다. 
    -->
    <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
      <property name="supportedMediaTypes">
        <list>
          <value>application/json; charset=UTF-8</value>
        </list>
      </property>
    </bean>
    
  </mvc:message-converters>
</mvc:annotation-driven>

Controller

Controller는
필수적으로 @ResponceBody
권장 사항으로 produces = "application/json; charset=UTF-8" 를 명시 해 준다.
produces 속성은 Response의 Content-Type을 제어한다.

@RequestMapping(
    value = "/reqWaterDepth.do",
    method = RequestMethod.POST,
    produces = "application/json; charset=UTF-8")
@ResponseBody
public Map<String, List<WaterDepthDTO>> reqWaterDepth (
    HttpServletRequest req,
    HttpServletResponse res,
    @RequestParam Map<String, String> area
      ) throws Exception {
  logger.info("\n\tREQ Water Depth . do \n" + area.get("area") + "\n");
  return service.getWaterDepthInRange(area.get("area"));
}

Ajax Request

Request 부분에서는 딱히 설정할 것이 없다.

function getData(area) {
  return new Promise(function(resolve, reject) {
    console.group('drawSurvArea getData');
    area = {
      'area' : area
    };
    //  console.log( area );
    $.ajax({
      type : "POST",
      url : "/reqWaterDepth.do",
      data : area,
      dataType : 'json',
      beforeSend : function(xhr, opts) {
        console.log("before send");
        // when validation is false
        if (false) {
          xhr.abort();
        }
      },
      success : function(res) {
        console.log("SUCCESS");
        console.log(res);
        if (Object.keys(res).length === 0
            && JSON.stringify(res) === JSON.stringify({})) {
          alert('해당 영역에 수심데이터가 없습니다.');
          return;
        } else {
          resolve(res);
        }
      },
      error : function(err) {
        console.log("ERR");
        console.error(err.statusText);
        alert('데이터를 처리 하던중 에러가 발생했습니다.\n' + err.statusText + ' : ' + err.status);
        reject(err);
      }
    });
    console.groupEnd('drawSurvArea getData');
  })
}
getData(area).then(function(data) {
  console.log('THEN')
  console.log(data);
});
728x90
반응형

+ Recent posts