Head First Design Patterns는 객체지향 프로그래밍과 디자인 패턴에 대한 입문서다. 이 책 또한 다른 전공 서적과 다르게 쉽게 읽히는 책이다. 그림과 예제 코드를 통해 설명되며, 이러한 방식으로 개념을 쉽게 이해할 수 있었다.  
  
"변하지 않는 사실은 계속 변화한다"라는 문장을 책에서 강조하는데, 현업에서 개발하다 보면 기획내용이나 사용자의 요구사항은 계속 변한다. 어제까지는 분명 A를 얘기했는데, 오늘 저녁에 갑자기 B나 C 또는 H로 요구사항이 바뀔 수 있다. 열심히 다 만들었더라도 요구사항이 추가되거나 변하면 새로운 것을 다시 만들어야 하는 상황이 흔한 것이다. 사실 요구사항의 변화는 SW의 본질이라고 할 수 있다. 그렇기에 변경에 용이한 아키텍쳐를 설계하고 개발하는 것이 개발자의 중요 역량이라고 생각한다. 그게 안 되면 IT로 밥 벌어먹으면 안 된다는 생각. 어쨌거나, 변경에 용이한 코드를 작성하기 위해서는 여러 가지 디자인 패턴이 있고, 예제를 패턴을 적용해 개선해 나가는 예시로 설명해준다.

책에서는 디자인 패턴의 개념과 각 패턴이 어떤 상황에서 사용되는지에 대해 설명한다. 디자인 패턴은 각각 객체 생성, 객체 구조, 인터페이스, 행위 등의 다양한 측면에서 소프트웨어 디자인을 개선하기 위한 것이다. 또한, 이 책은 디자인 패턴을 적용하는 과정에서 발생하는 문제와 해결 방법에 대해 다루며, 이를 통해 디자인 패턴을 적용하는 방법을 익힐 수 있도록 돕는다. 디자인 패턴은 사실 무궁무진하게 많다, java, c, javascript 언어에 따라서도 다른 패턴이 나온다. 이 책에서는 java 계열 실무에서 자주 사용하는 디자인 패턴을 집중해서 다뤄주고 있다. 책은 650여 페이지로 굉장히 두꺼운데, 그림이 대다수를 차지하고 있고, 독자의 가독성을 고려하여 쓴 책이라 쉽게 읽힌다. (글을 잘 쓰는 사람이 개발도 잘한다는 말을 반증하는 것 같다.)

나는 객체 지향을 조금 더 잘 활용해보고 싶은 개발자로 추상화, 캡슐화, 다형성, 상속을 어떻게 하면 더 잘 적용할 수 있을지를 이 책을 통해 심도있게 고민해보았다. 흔히들 코드 재사용을 막기 위해 상속을 사용하는데, 생각지 못한 상황에 반복된 코드를 짤 수밖에 없었던 적이 많다. 그런 상황에 대한 해법들도 이 책을 통해 얻었다.
Head First Design Patterns는 객체지향 프로그래밍과 디자인 패턴에 대한 이해를 쉽게 접근할 수 있도록 돕는다. 이 책으로 습득한 디자인 패턴을 사용하면 더 나은 소프트웨어 디자인을 구현할 수 있을 것으로 기대되고, 이는 개발자로서의 역량 향상에 큰 도움이 될 것 같다.

728x90
반응형

이론 적인 것은 잘 정리된 글이 많기 때문에 생략.
java로 int 형 stack과 Object형 stack을 구현한 코드.


int 형을 저장하는 스택

public class IntStack {
    // 스택용 배열
    private int[] stk;
    // 스택 용량
    private int capacity;
    // 스택 포인터
    private int ptr;

    // 실행 시 예외 : 스택이 비어있는 경우
    public class EmptyIntStackException extends RuntimeException {
        public EmptyIntStackException(){}
    }

    // 실행 시 예외 : 스택이 가득 찬 경우
    public class OverflowIntStackException extends RuntimeException {
        public OverflowIntStackException(){}
    }

    // 생성자
    public IntStack(int maxLen) {
        ptr = 0;
        capacity = maxLen;
        try {
            // 스택 본채용 배열 생성
            stk = new int[capacity];
        } catch (OutOfMemoryError e) {
            // 생성할 수 없음
            capacity = 0;
        }
    }

    // 스택에 X를 푸시
    public int push(int x) throws OverflowIntStackException {
        if (ptr >= capacity)
            // 스택이 가득 찬 경우
            throw new OverflowIntStackException();

        return stk[ptr++] = x;
    }

    // 스택에서 데이터를 팝 (꼭대기에 있는 데이터를 꺼냄)
    public int pop() throws EmptyIntStackException {
        if (ptr <= 0)
            // 스택이 비어 있음
            throw new EmptyIntStackException();

        return stk[--ptr];
    }

    // 스택에서 데이트를 피크 (꼭대기에 있는 데이터 조회)
    public int peek() throws EmptyIntStackException {
        if (ptr <= 0)
            // 스택이 비어 있음
            throw new EmptyIntStackException();

        return stk[ptr - 1];
    }

    // 스택을 비움
    public void clear() {
        // 모든 작업이 ptr 값으로 이루어 지므로 배열의 요소를 변경할 필요가 없음.
        ptr = 0;
    }

    // 스택에서 X 찾아 인덱스 반환, 없다면 -1 반환
    public int indexOf(int x) {
        // 뒤(꼭대기)에서 부터 선형 탐색
        for (int i = ptr - 1; i >= 0; i--) {
            if (stk[i] == x)
                // 검색 성공
                return i;
        }
        // 검색 실패
        return -1;
    }

    // 스택의 용량 반환
    public int getCapacity() {
        return this.capacity;
    }

    // 스택에 쌓여있는 데이터 개수 반환
    public int size() {
        return ptr;
    }

    // 스택이 비어있는지 확인
    public boolean isEmpty() {
        return ptr <= 0;
    }

    // 스택이 가득 찼는지 확인
    public boolean isFull() {
        return ptr >= capacity;
    }

    public void dump() {
        if (ptr <= 0)
            System.out.println("EMPTY STACK");
        else {
            for (int i = 0; i < ptr; i++)
                System.out.print(stk[i] + " ");

            System.out.println("");
        }
    }
}

Object 형을 저장하는 스택

// 임의의 객체형을 쌓을 수 있는 테네릭 스택 클래서 Stack<E> 작성
public class GenericStack<E> {
    // 스택용 배열
    private E[] stk;
    // 스택 용량
    private int capacity;
    // 스택 포인터
    private int ptr;

    // 실행 시 예외 : 스택이 비어있는 경우
    public static class EmptyGStackException extends RuntimeException {
        public EmptyGStackException(){}
    }

    // 실행 시 예외 : 스택이 가득 찬 경우
    public static class OverflowGStackException extends RuntimeException {
        public OverflowGStackException(){}
    }

    // 생성자
    public GenericStack(int maxLen) {
        ptr = 0;
        capacity = maxLen;
        try {
            // 스택 본채용 배열 생성
            stk = (E[])new Object[capacity];
        } catch (OutOfMemoryError e) {
            // 생성할 수 없음
            capacity = 0;
        }
    }

    // 스택에 X를 푸시
    public E push(E x) throws OverflowGStackException {
        if (ptr >= capacity)
            // 스택이 가득 찬 경우
            throw new OverflowGStackException();

        return stk[ptr++] = x;
    }

    // 스택에서 데이터를 팝 (꼭대기에 있는 데이터를 꺼냄)
    public E pop() throws EmptyGStackException {
        if (ptr <= 0)
            // 스택이 비어 있음
            throw new EmptyGStackException();

        return stk[--ptr];
    }

    // 스택에서 데이트를 피크 (꼭대기에 있는 데이터 조회)
    public E peek() throws EmptyGStackException {
        if (ptr <= 0)
            // 스택이 비어 있음
            throw new EmptyGStackException();

        return stk[ptr - 1];
    }

    // 스택을 비움
    public void clear() {
        // 모든 작업이 ptr 값으로 이루어 지므로 배열의 요소를 변경할 필요가 없음.
        ptr = 0;
    }

    // 스택에서 X 찾아 인덱스 반환, 없다면 -1 반환
    public int indexOf(Object x) {
        // 뒤(꼭대기)에서 부터 선형 탐색
        for (int i = ptr - 1; i >= 0; i--) {
            if (stk[i].equals(x))
                // 검색 성공
                return i;
        }
        // 검색 실패
        return -1;
    }

    // 스택의 용량 반환
    public int getCapacity() {
        return this.capacity;
    }

    // 스택에 쌓여있는 데이터 개수 반환
    public int size() {
        return ptr;
    }

    // 스택이 비어있는지 확인
    public boolean isEmpty() {
        return ptr <= 0;
    }

    // 스택이 가득 찼는지 확인
    public boolean isFull() {
        return ptr >= capacity;
    }

    public void dump() {
        if (ptr <= 0)
            System.out.println("EMPTY STACK");
        else {
            for (int i = 0; i < ptr; i++)
                System.out.print(stk[i] + " ");

            System.out.println("");
        }
    }
}
728x90
반응형

'Computer Science > DataStructure' 카테고리의 다른 글

[DS] Array 배열  (0) 2022.07.17

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
반응형

 

계열사 채용 사이트에서 직접 지원, 3주 만에 메일로 과제 받았음. 과제 내용은 공개된 곳에 게시하지 말라고 해서 서술하지는 못하지만, java, 네트워크에 대한 이해도가 높고 기초지식이 많이 요구되는 내용이었음. 5일 기한이 주어졌고, 재직 중인 회사 작업 일정 때문에 월요일 연차 사용하고 금요일 저녁부터 월요일까지 3일간 문제 해결하는 데 시간 썼음.

공고가 1년 차부터이고, 전체 구현 완료 여부가 합격/불합격의 기준은 아니라는 조건 때문에 쉽게 생각했는데, 생각보다 쉽지 않았음. '내가 java에 대해서 아직 잘 모르는구나'라고 느끼게 되었음. 확실히 코테보다 기술 과제가 더 어려운 듯?
제출 직전에 소스 최대한 정리 했는데, 불필요한 import랑 미사용 메소드라니 섬세하지 못했다는 생각이 들었음. 근데, 나는 System.out.println 이런 건 습관적으로 쓰지 않는데, (System.out.println 치는 것보다 logger.debug 치는 게 더 경제적이기 때문에) 혼용해서 사용했다는 의견은 좀...그렇지만 이 정도로 디테일하게 리뷰를 해줬다는 점에서 대체 이 회사는 어떤 회사인지 궁금하고, 다른 큰 테크기업이 많지만, 이 회사에서 일해보고 싶다 생각함. 아, 제일 중요한 건 기초부터 다시 공부해야 할 것 같은데, 공부할 게 너무 많아서 어디서부터 손대야 할지, 그리고 회사에서 야근이 많은데 과연 투자 투자할 수 있는적인 시간이 얼마나 확보가 될지 너무 막막하다.

 
728x90
반응형

'커리어 디버깅' 카테고리의 다른 글

[면접 회고] C사  (0) 2023.02.27
2022년 회고 및 2023년 목표 설정  (0) 2023.01.03
[코테 회고] 2022 10 01  (0) 2022.10.01
패션 쇼핑몰 1년 회고  (0) 2022.09.14
2022 07 13 채찍  (0) 2022.07.13

문자열이 정해진 날짜 포맷에 맞는지 확인해야하는 로직이 종종 필요하다

문자열이 날짜 형식인지 유효성 검사하기

    /**
     * 입력받은 문자열이 날짜형식으로 변환이 가능한지 확인한다.
     *
     * @param str2cmp    날짜형식 변환 가능여부 확인 대상
     * @param dateFormat 비교할 날짜 형식
     * @return
     */
    public static boolean isDateFormat(String str2cmp, String dateFormat) {
        try {
            //  검증할 날짜 포맷 설정
            SimpleDateFormat dateFormatParser = new SimpleDateFormat(dateFormat);
            //  parse()에 잘못된 값이 들어오면 Exception을 리턴하도록 setLenient(false) 설정
            dateFormatParser.setLenient(false);
            // 대상 인자 검증
            dateFormatParser.parse(str2cmp);
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    public static boolean isDateFormat(String str2cmp) {
        return isDateFormat(str2cmp, "yyyyMMdd");
    }

setLenient( false );로 설정하면, 잘못된 형식의 데이터가 들어올 경우 Exception을 리턴하도록 설정함.

728x90
반응형

'Language > Java' 카테고리의 다른 글

[Java] 성능을 고려한 try-catch 문  (0) 2020.12.16
[Java] Map 반복문  (0) 2020.12.08
[JAVA] 구 버젼 설치  (0) 2020.11.02
[Java] ArrayList Sort  (0) 2020.05.22

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
반응형

성능을 고려한 try-catch 문

try-catch 문을 사용하면 예외 처리는 쉽지만, 에러객체.printStackTrace() 로 출력한 stack 이 시스템의 성능을 저하시킬 수 있다.
따라서 에러객체에서 다음과 같이 필요한 정보만 뽑아서 출력하는게 시스템 성능에 좋다.
어떻게 보면 긴 에러 출력보다는 아래와 같이 필요한 부분만 뽑아 보는게 디버깅에 효율 적 인것 같기도 하고...

        try {
            // 예외가 발생할 수 있는 로직
        } catch (Exception e) {
            StackTraceElement[] ste = e.getStackTrace();
            String className  = ste[0].getClassName();
            String methodName = ste[0].getMethodName();
            int lineNumber = ste[0].getLineNumber();
            String fileName = ste[0].getFileName();
            logger.error(">>> ERROR ON {} <<<", fileName);
            logger.error("{} >> {}() : {}", className, methodName, lineNumber);
            logger.error("MSG : {}", e.getMessage());
            logger.error("CUZ : {}", e.getCause());
        }
728x90
반응형

'Language > Java' 카테고리의 다른 글

[Java] 문자열이 날짜 형식인지 확인하기  (0) 2021.10.18
[Java] Map 반복문  (0) 2020.12.08
[JAVA] 구 버젼 설치  (0) 2020.11.02
[Java] ArrayList Sort  (0) 2020.05.22

Java 에서 Map 안의 요소 반복문으로 꺼내기.

package dev.jeaha.practice;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

public class MapIterationPractice {

  public static void main(String[] args) {
    Map<String, String> map = new HashMap<>();

    map.put("k00", "v00");
    map.put("k01", "v01");
    map.put("k02", "v02");
    map.put("k03", "v03");
    map.put("k04", "v04");
    map.put("k05", "v05");
    map.put("k06", "v06");
    map.put("k07", "v07");
    map.put("k08", "v08");
    map.put("k09", "v09");
    map.put("k10", "v10");

    System.out.println(">>>> SOLUTION 01-1 <<<<");
    /**
     * Generic 미사용.
     * Java 버젼이 낮을 경우 사용할 수 있는 방법.
     */
    Iterator entries = map.entrySet().iterator();
    while( entries.hasNext() ) {
      Map.Entry entry = (Map.Entry) entries.next();
      String key = (String) entry.getKey();
      String val = (String) entry.getValue();
      System.out.println( String.format("key : %s, value : %s", key, val ) );
    }


    System.out.println("\n\n>>>> SOLUTION 01-2 <<<<");
    /**
     * Generic, Entry 사용
     */
    Iterator<Map.Entry<String, String>> entriex = map.entrySet().iterator();
    while( entriex.hasNext() ) {
      Map.Entry<String, String> entry = entriex.next();
      System.out.println( String.format("key : %s, value : %s", entry.getKey(), entry.getValue() ) );
    }

    System.out.println("\n\n>>>> SOLUTION 01-3 <<<<");
    /**
     * Generic 사용, Entry 미사용
     */
    Iterator<String> keys = map.keySet().iterator();
    while( keys.hasNext() ) {
      String key = keys.next();
      System.out.println( String.format("key : %s, value : %s", key, map.get(key)) );
    }

    System.out.println("\n\n>>>> SOLUTION 02 <<<<");
    /**
     * Entry에 for-each loop 사용.
     * 가장 일반적이고 많은 경우에 사용됨.
     * 반복문 안에서 key 값과 value 값이 전부 필요할 때 사용.
     * Java 1.5 이상부터 사용 가능. NullPointerException 발생 가능.
     */
    for( Map.Entry<String, String> entry : map.entrySet() ) {
      System.out.println( String.format("key : %s, value : %s", entry.getKey(), entry.getValue() ) );
    }

    System.out.println("\n\n>>>> SOLUTION 03 <<<<");
    /**
     * entry 대신에 key 값이나 value 값만 필요할 때 사용.
     * entrySet을 사용할 때보다 약 10% 빠르다고 함.
     */
    for( String key : map.keySet() ) {
      System.out.println( String.format("key : %s", key ) );
    }
    for( String value : map.values() ) {
      System.out.println( String.format("value : %s", value ) );
    }

    System.out.println("\n\n>>>> SOLUTION 04 <<<<");
    /**
     * 코드상으로는 깔끔해 보이지만 비효율 적이고 성능면에서 좋지 않은 방법.
     * key 값을 이용해서 value 를 찾는 과정에서 시간 소모가 많음.
     * SOLUTION 02에 비해 20~200% 성능 저하 유발.
     */
    for( String key : map.keySet() ) {
      System.out.println( String.format("key : %s, value : %s", key, map.get(key) ) );
    }

    System.out.println("\n\n>>>> SOLUTION 05 <<<<");
    /**
     * stream을 사용한 forEach.
     */
    map.entrySet()
    .stream()
    .forEach(System.out::println);

    System.out.println("\n\n>>>> SOLUTION 06 <<<<");
    /**
     * forEach 및 Lambda를 사용한 반복.
     * forEach() 내에서 람다 표현식 사용.
     */
    map.forEach( (key, val) -> {
      System.out.println( String.format("key : %s, value : %s", key, val ) );
    });
  }
}
728x90
반응형

'Language > Java' 카테고리의 다른 글

[Java] 문자열이 날짜 형식인지 확인하기  (0) 2021.10.18
[Java] 성능을 고려한 try-catch 문  (0) 2020.12.16
[JAVA] 구 버젼 설치  (0) 2020.11.02
[Java] ArrayList Sort  (0) 2020.05.22

종종 특수한 이유로 Java 구 버젼을 설치해야 할 일이 있음.

오라클 공식 사이트에서는 1.7이하 버젼은 더 이상 다운로드를 지원하지 않지만.
잘 찾아보면 각 버젼별로 다운 받을 수 있음.

한참 헤매다 따로 링크를 저장해 두기로 하고 포스트를 작성함.

JDK 1.5
JDK 1.6
JDK 1.7
JDK 1.8

728x90
반응형

'Language > Java' 카테고리의 다른 글

[Java] 문자열이 날짜 형식인지 확인하기  (0) 2021.10.18
[Java] 성능을 고려한 try-catch 문  (0) 2020.12.16
[Java] Map 반복문  (0) 2020.12.08
[Java] ArrayList Sort  (0) 2020.05.22

JDK 설치하기

  1. JDK 선택
    • Oracle JDK와 OpenJDK 그리고 버젼.
      Oracle이 JDK를 년 단위 구독형 유료 라이센스로 전환하게 되면서 OracleJDK와 OpenJDK에서 선택 하게 되었음.
      알아서 선택 하겠지지만,
      기업에서 라이센스가 있다면 Oracle JDK를 설치하고,
      그게 아니라면 Open JDK를 설치하면 됨.
  2. JDK 다운로드

  3.  
  4. 설치
    일단 두 버젼 다 설치해 보도록 하겠음.
    • OracleJDK는 설치파일을 실행하면 됨.
    • OpenJDK는 압축을 푼 뒤 위치 시키고 싶은 경로에 폴더를 넣어주면 됨.
      나는 OracleJDK의 설치경로 C:\Program Files\Java\와 같은 경로에 위치 시키겠음.
  5.  
  6. 환경 변수 설정창 오픈
    제어판 -> 시스템 및 보안 -> 시스템으로 들어간 후,
    고급 시스템 설정 -> 환경변수 선택.
  7. JAVA_HOME 변수 설정
    변수 이름 : JAVA_HOME
    변수 값 : C:\Program Files\Java\jdk1.8.0_241
    값은 jdk의 bin 폴더 앞까지의 경로를 넣어줌.
  8. PATH 변수 편집
    • 새로만들기 선택 후, %JAVA_HOME%\bin를 입력해줌.
  9. JAVA 실행 및 버전 확인.
    cmd 또는 Powershell을 실행한 뒤 자바 버전을 확인해야 함.
    두 개의 명령어에 반응이 있어야 정상적인 설치가 됐고, 코딩 할 준비가 됨.
  10. java -version javac -version

 

728x90
반응형

+ Recent posts