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

토비의 스프링 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
반응형

벌써 2023년이 되었다.
지난 2022년을 되돌아 작년의 목표가 얼마나 지켜졌는지 확인해보고 2023년에 대한 목표를 잡아보고자 한다.


2022년 회고

[방통대 졸업]

물론 2022년 이전 학기부터 학점이 쌓여 온 것이지만 어쨌든 3. 후반대 학점으로 졸업 했다.
졸업은 했지만, 시험을 위한 공부를 한 탓에 CS 지식이 많이 모자라다는 것을 느끼고 2023년에도 CS 공부를 부지런히 해야겠다.

[SQLD 취득]

생각보다 노력을 덜 하고 sqld를 땄다. 6주 정도 퇴근하고 3시간, 출퇴근 길에 요약 내용 복습 정도로 공부하고 붙었으니 운이 좋았던 것 같다.
사실 자격증이 있다고 실무를 잘 하는 건 아니지만, 자격증을 딴 덕분에 쿼리를 짜거나 DB 구조를 변경할 때 한 번 더 생각하게 되었다.
물론 아직은 경험치가 모자라니 더 정진해야겠다.

[JPA 학습]

인프런에서 인강을 듣긴 했지만, 완강하지 못했다. 아무래도 지금 일 하는 곳, 지금 까지 일 했던 곳은 MyBatis를 사용하다보니 JPA 공부에 절실하지 않았던 것 같다. 테크기업 이직을 생각하고 JPA를 꼭 미리 학습 하도록 해야겠다.

[알고리즘 문제풀이]

1학기 끝나고 7월부터 인강과 함께 코테공부를 했었다. 과거형이다.
10월부터 극심한 야근의 늪에 빠지면서 지금까지 게을리 해버렸다. 올해는 꼭 프로그래머스 3레벨까지는 풀어야 겠다.

[경력기술서 지속적인 업데이트]

블로그엔 올리지 않았지만 분기별로 업데이트를 잘 했다고 생각한다. 23년에도 잘 정리해서 이직 준비 해야겠다.

[다른 개발자들과의 교류]

따로 커뮤니티를 하는 것은 아니지만 동네 스터디 모임에 들어갔고, 이전 직장 동료들이나 친구들과 꾸준히 연락하면서 소식을 듣고 있다. 나랑 다른 분야의 선후배 개발자들이나 같은 분야의 다른 회사사람들을 통해 업계 소식을 조금씩 듣고 있다. 이렇게 조금이나마 간접 경험을 하고 있고, 다른 사람의 시각이나 생각을 들으면서 내 식견도 늘리려 하고 있다.


2023년 목표

목표를 너무 많이 세우지 않고 작은 목표를 집중적으로 수행하려 한다.

[Java, Spring 더 깊게 공부하기]

Spring 소스코드도 뜯어보고, 인강이나 토비의 스프링 책읽기등을 하면서 레벨업을 해야겠다고 느꼈다. 회사 과제를 수행하면서, 종종 기초지식이 부족하다는게 느껴질 때가 있었다. 회사에서 사용하는 스펙이 심각한 레거시라 생긴 문제인지, 아니면 정말 내가 뭔가를 모르고 있는 것인지는 공부 해봐야 아는 것이다.

[인강 및 프로그래밍 책 읽기]

게을리한 JPA, 알고리즘 코테 인강을 올해는 끝내고 그 이상으로 발전 해야겠다.

[이직]

회사 사정까진 모르겠고, 지금 회사에 머물 이유가 배울 수 있는 선임들 이였는데, 그 사람들이 지금 다 나갔다.
회사가 학교는 아니지만 개인의 성장에 많은 영향을 미친다고 생각하며, 그 성장에는 같이 일하는 동료들이 중요하다고 생각한다.
떠나신 그 세분을 내가 따라 갈 수 있을지는 모르겠고, 우선 적으로 회사 매출이 어떻든 개발팀의 조직이 8인 이상인 곳으로 이직하고싶다고 생각 했다.
이직의 결심은 22년에 "다른 선/후배/동료 개발자들과 생각을 교류하고/보고/배움으로써 개인이 성장할 수 있다"고 느낀게 가장 큰 것 같다.

[여행]

적어도 분기에 한번은 여행을 다녀와야겠다고 생각했다.
일 하면서 쌓인 생각이나 걱정들도 잠시 내려두고, 여행에서 마주하는 낯선 경험들이 나를 찾도록 해주는 것 같다.

[운동, 건강]

건강하지 않으면 공부도 할 수 없는 것 같다. 살로인해(?) 집중에 방해되는 것도 있고.
일단 1년 동안 10키로 빼는 게 목표로 지금 68키로다.

 
728x90
반응형

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

[면접 회고] W사  (0) 2023.02.27
[면접 회고] C사  (0) 2023.02.27
[코테 회고] 2022 10 01  (0) 2022.10.01
[채용 과제 회고] 2022 09 22 P사  (0) 2022.09.22
패션 쇼핑몰 1년 회고  (0) 2022.09.14

 

계열사 채용 사이트에서 직접 지원, 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
반응형

화면을 수정했는데, 클라이언트의 브라우져에 캐시가 남아서 이전 화면이 나오는 경우가 있다.
직접 브라우져 설정 들어가서 캐시를 지워달라고 요청 할 수도 있지만, 그게 한두번이 아니게 되니 코드에서 캐시를 지울수 없는지 찾아보았다.
이럴수가...
크게 어렵지도 않지만, html한정으로 이게 꼭 필요한 화면이 아니면 아래 설정은 가급적 안 하는게 좋을 것 같다.

HTML 메타태그 이용하기

    <!-- 1990년 이후 이 페이지의 캐시를 만료시킴. -->
    <meta http-equiv="Expires" content="Mon, 06 Jan 1990 00:00:01 GMT" />

    <!-- 캐시를 바로 만료시킴. -->
    <meta http-equiv="Expires" content="-1" />

    <!-- 페이지 로드시마다 페이지를 캐싱하지 않음. (HTTP 1.0) -->
    <meta http-equiv="Pragma" content="no-cache" />

    <!-- 페이지 로드시마다 페이지를 캐싱하지 않음. (HTTP 1.1) -->
    <meta http-equiv="Cache-Control" content="no-cache" />

JSP, Java 사용시

자바 코드 지만 html과 같음.

    response.setHeader( "Pragma", "no-cache" );
    response.setDateHeader( "Expires", -1 );
    response.setHeader( "Cache-Control", "no-cache" );

JS, CSS 파일 캐시 사용 방지

js나 css등 정적파일도 종종 변경을 해도 캐시문제로 적용이 되지 않는 경우가 많다.
그럴 경우 파일의 url 뒤에 ?timestamp 형태를 넣어준다.

  <link rel="stylesheet" href="/css/common.css?20210512" />
  <script type="text/javascript" src="/js/common.js?20210512"></script>

728x90
반응형

'WEB > JSP, JSTL' 카테고리의 다른 글

[JSP, JSTL] fn function 정리 - 2  (0) 2020.12.11
[JSP, JSTL] fn function 정리  (0) 2020.11.30
[JSP, JSTL] forEach 잘 사용하기  (0) 2020.11.30

성능을 고려한 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

SiteMesh와 Tiles

이직 하고 프로젝트를 인계 받다 보니 SiteMesh라는 것을 사용하고 있었다.
도대체 이게 뭐신고 하니 apache tiles 와 같은 화면 구성을 위한 템플릿 프레임워크였다.

현재는 소멸 된 오픈 소스 프로젝트로(이마짚...) 2015년 이후로는 업데이트가 없다는 설명에 tiles와 무슨 차이가 있는건지 찾아보았다.

혹시 라도 SiteMesh 사용법을 찾는 사람이라면 여기서 뒤로가기를 누르시라...

 


 

Google Trends

SiteMesh가 과연 얼마나 자주 사용 되는 것인가가 궁금해서 Google Trends에 비교 검색 해 보았다.

지난 5년간 압도적으로 Tiles 검색이 더 많았다...

지역별 비교 분석을 보면 한국과 중국에서 많이 검색을 했단다...

"공공 SI 에서 많은 메뉴들을 간단한 설정으로 표출 하고 싶어서" 가 아닐까 싶다.

 


 

SiteMesh와 Tiles의 차이점

 

SiteMesh와 Tiles의 가장 큰 차이점은 패턴이다.
Tiles의 경우에는 Composite View 패턴을, SiteMesh의 경우에는 Decorator 패턴을 사용하고 있다.

아래 비교 내용는 해당 링크에서 가져왔다. http://tiles.apache.org/framework/tutorial/pattern.html

Composite View vs. Decorator

Tiles is a composite view framework: it allows to reuse page pieces across the application. But another approach to achieve the same result is using the Decorator pattern. For example, Sitemesh is based on the Decorator pattern.

Instead of creating a template and organizing the pieces together, the Decorator pattern (in this case) takes a simple HTML page, transforms it adding the missing pieces (in our example, adding header, footer and menu) and finally renders it.

Here you can find a comparison table between the two patterns.

Aspect Composite View Decorator
Reusability The different parts of the page (template and pieces) can be reused across the whole application. Each decorator can be reused, but the decoration itself can be applied to one page at a time.
Ease of configuration Each page must be defined explicitly. The decorator can be applied even to the entire application.
Runtime configuration The pages can be configured and organized at runtime Since one page is decorated at a time, this feature is not present.
Performances Low overhead for composition. The page to be decorated has to be parsed.



재사용성

Composite View Pattern의 Tiles의 경우에는 페이지의 모든 부분을 모두 재사용 가능하다.
반면, Decorator Pattern을 사용하는 SiteMesh는 각각의 데코레이터를 재사용 할수는 있지만, 데코레이션 자체는 한 페이지씩 적용할 수 있다고 한다.
데코레이터가 무엇인지 모르겠지만, 모든 부분을 재사용할 수 있는 Composite View Pattern이 Decorator Pattern 보다 나아보인다.

설정

각 페이지를 일일히 다 정의 해야하는 Tiles 보다는 SiteMesh가 더 쉽다고 한다.
근데 Tiles도 일일히까지는 설정 안해도 됬던 것으로 기억하는데,,

실행중 수정 여부

Composite View Pattern은 런타임중 각 페이지의 설정을 변경할 수 있고, Decorator Pattern 은 최초 한번에 적용 되기 때문에 런타임준 수정할 수 있는 기능은 제공하지 않는다.

성능

Composite View Pattern에 비해 매번 페이지를 파싱해야 하는 Decorator Pattern은 성능면에서는 당연히 떨어질 것이라 생각한다.

 


 

결론

이렇게 보니, Tiles가 trend인 이유도, 공공 SI에서 SiteMesh를 사용하는 이유도 이해가 간다.
간단한 프로그램에 복잡하지 않지만 페이지의 양이 많은 공공 지자체 SI라면 SiteMesh를 사용하는 합리적인 이유인것 같긴 하다만, 그래도 Tiles가 더 편한 것 같다는 생각은 변하지 않는다.
SiteMesh가 2015년 이후로 업데이트가 없었고, Tiles는 2016 이후로 retired 했다고 한다. (?)
그리고 비슷한 대안으로 Thymeleaf, Freemarker, mustach.java등이 있다고 한다. 그리고 Thymeleaf가 Tiles 만큼 사용율도 높다...

기술 트렌드... 참 사람 어쩌라는 건지 모르겠다.

728x90
반응형

+ Recent posts