[    웹개발(JAVA,JSP,Spring) 관련 예상 면접 질문    ]




웹 개발 면접시 인성 질문, 자기소개, 프로젝트 내용 질문을 제외한 JAVA와 관련된 기술 예상 면접 질문을 정리해 보았습니다.



1) 자바의 특징에 대해 말해보시오.

1) OOP(객체 지향 언어) 

: 부품에 해당하는 객체들을 먼저 만들고, 이것들을 하나씩 조립해 전체 프로그램을 완성하는 개발 기법

2) "가비지 컬렉션"에 의한 메모리 자동 관리

3) "멀티 쓰레드"를 지원한다.

4) JVM 위에서 동작하기 때문에 특정 OS에 종속적이지 않고 이식성이 좋으며, 보안성이 좋다.

5) 다양한 Open 라이브러리들이 존재한다.



2) 자바를 만든 사람에 대해 아시나요?

: "제임스 고슬링"



3) 변수란?

: "하나의 값을 저장할 수 있는 메모리 공간"



4) 객체와 클래스의 차이점에 대해 설명해 보시오.

- 클래스(Class) : 현실 세계의 객체의 속성과 동작을 추려내 필드와 메서드로 정의한 것으로 "아직 메모리가 할당되지 않은 상태"

vs

- 객체(Object) : 이 Class라는 설계도를 기반으로 실제 메모리가 잡힌 것을 의미하며 이런 객체를 조합해 전체 프로그램을 완성해

   나가는 방식을 OOP(객체지향 프로그래밍)이라고 한다.



5) 객체 지향 PG이란? 또 그 특징은?

: 현실세계의 객체를 필드와 메서드로 정의한 Class를 기반으로 실제 메모리가 잡혀 만들어진 부품과 같은 객체들을 조합해

  전체 프로그램을 완성해 나가는 개발 기법으로

특징)

- 캡슐화, 은닉화 : 외부 객체에서 구현방식은 알 수 없도록 숨기고 별도로 접근할 수 있는 getter/setter 메서드를 통해 접근하도록 하는 방식

- 상속 : 부모 Class를 자식이 접근할 수 있도록 물려 받는 방식

- 다형성 : 부모 클래스 타입으로 해당 부모를 상속받는 여러 자식 class를 대입할 수 있는 성질

등을 들 수 있다.



6) 다형성이란?

: 서로 다른 클래스로부터 만들어진 객체지만 같은 부모의 Class 타입으로 이들을 관리할 수 있는(=대입될 수 있는) 성질



7) 자바의 메모리 영역(간단하게 설명)

1. 메서드 영역 : static 변수, 전역변수, 코드에서 사용되는 Class 정보 등이 올라간다.

    , 코드에서 사용되는 class들을 로더로 읽어 클래스별로 런타임 필드데이터, 메서드 데이터 등을 분류해 저장한다.

2. 스택(Stack) : 지역변수, 함수(메서드) 등이 할당되는 LIFO(Last In First Out) 방식의 메모리

3. 힙(Heap) : new 연산자를 통한 동작할당된 객체들이 저장되며, 가비지 컬렉션에 의해 메모리가 관리되어 진다.



8) 추상메서드? 추상 클래스?

- 추상메서드 : 메서드의 정의부만 있고 구현부는 있지 않은 메서드

- 추상 클래스 : 추상메서드를 적어도 하나 이상 가지고 있는 클래스로 자식클래스에서 오버라이딩(재정의)가 필요한 추상메서드를 가지고 있기

        때문에 객체화 할 수 없다.



9) 인터페이스(Interface)란? 또 왜 사용하나?

: 인터페이스는 모든 메서드가 구현부가 없는 추상메서드로 이루어진 클래스로, abstract 키워드를 붙이지 않아도 자동으로 모든 메서드는 추상메서드로

  정의가 된다. 또한 변수도 자동으로 final static 키워드가 붙게 된다.


왜 인터페이스를 사용하는가? 

: 팀작업시 개발코드 부분과 객체가 서로 통신하는 접점 역할을 지원하게 되는데, 이는 개발코드에선 객체의 내부 구조를 모르더라도 인터페이스의

  메서드 명만 알고 있으면 되기 때문이다. 이를 통해 얻을 수 있는 장점은 해당 메서드를 통해 나오는 결과물을 알고 있기 때문에 다른 팀의

  작업을 기다리고 있지 않아도 되며, 또한 해당 객체가 수정될 경우 개발 코드 부분은 수정을 하지 않아도 된다.

  또한, 부가적으로 객체를 파일에 쓰기 위해 Serializable 인터페이스를 구현하거나, Collections.sort()를 하기 위해서 Comparable 인터페이스를 

  상속하는 것, Cloneable 을 구현하는 것처럼 특정 작업을 하겠다라는 "Mark"역할을 해주기도 한다.



10) 프로세스(Process) 와 쓰레드(Thread)의 차이점에 대해 아는가?

- 프로세스 : OS가 메모리 등의 자원을 할당해준 실행중인 프로그램을 가리킨다. 이때, 각각의 프로세스는 서로 메모리 공간을 독자적으로 갖기 때문에

    서로 메모리 공간을 공유하지 못한다. 따라서 공유하기 위해서는 IPC(InterProcess Communication)과 같은 방식이 필요하다.

- 쓰레드 : 쓰레드는 프로세스 내에서 프로세스의 자원을 가지고 실제로 일하는 "일꾼"과 같으며 각 쓰레드는 독자적인 Stack 메모리를 갖고 그 외의

  자원(메모리)는 프로세스 내에서 공유하게 된다.



11) 컬렉션프레임워크(CollectionFramework)에 대해 아는만큼 말해 보시오.


- Collection 인터페이스 

- List 인터페이스 : 배열과 유사하되, 추가할때마다 자동으로 Boundary를 늘려주는 구조로, 중복된 데이터를 허용하며, 순서가 존재한다.

ex) - ArrayList : 배열로 구현됬으며, 인접해 있기 때문에 데이터 조회에 매우 빠르다 하지만, 빈번한 삽입, 삭제시 새로 배열을 만들고 데이터를

옮겨야 하기 때문에 LinkedList에 비하여 속도가 느리다.

    - LinkedList : 링크 구조로 되어 있기 때문에 조회는 ArrayList에 비해 느리지만, 삽입 삭제시 링크를 끊고 새로 추가되는 데이터에 링크만

연결하면 되기 때문에 삽입, 삭제에 유리하다.

    - Vector : 구현 방식은 ArrayList와 유사하지만 Vector를 개선한 것이 ArrayList이다. 또한 Vector의 경우에는 ArrayList와 달리

Synchronized(동기화)가 걸려 있어 여러 쓰레드에서 동시에 접근할 수 없다.

- Set 인터페이스 : 집합처럼 중복된 데이터를 허용하지 않으며, 순서가 없다. 또한, 객체 내부의 중복된 데이터를 배제하고 싶은 경우

  Object 클래스의 equals 메서드와 hashCode 메서드의 재정의가 반드시 필요하다.

ex) - HashSet

    - TreeSet : 순서가 있는 HashSet으로 이진 트리 구조로 만들어 졌다. 순서에 맞게 정렬되어 저장되기 위해서 Comparable을 구현해야한다.

    


- Map 인터페이스 : key와 value 쌍으로 데이터를 저장하며, key는 중복될 수 없고, value는 중복 저장이 가능하다.

ex) - HashMap

     - TreeMap

     - Properties : key value 쌍으로 저장되지만 value의 타입이 String만 가능하다.

     - Hashtable : HashMap과 구조는 같으며, 단지 Synchronized(동기화) 되어져 있다는 점이 다른점이다.


12) 쿠키(Cookie)와 세션(Session)의 공통점과 차이점은?

- 공통점 : 둘 다 사용자의 데이터를 저장한다.

- 차이점

- 쿠키:  쿠키는 Client 컴퓨터에 저장했다 서버 요청시 네트워크를 타고 서버로 전달되기 때문에 보안에 취약하다.

- 세션 : 세션은 서버에 저장되고 브라우저 단위로 관리된다. 캐시에 비해 보안성이 좋다.


13) Request 전송 방식에는 어떤 것들이 있는지 아시나요?

- Get 방식 : URL의 쿼리문자열에 데이터를 같이 전달하는 방식으로 데이터 길이에 제한이 있고, 보안에 취약하다.

- POST 방식 : 헤더에 데이터를 넣어 보내기 때문에 보안에 조금 더 유리하고 데이터 길이에 제한이 없다. 하지만, Get에 비해 다소 느리다.

- DELETE 방식 : RESTFUL에서 삭제 기능을 할 때 주로 사용된다.

- PUT/PUSH 방식 : RESTFUL에서 수정 작업을 할 때 주로 사용된다.



14) RESTFUL이란?

: 해당 URL만 보더라도 바로 어떤 작업을 하는지를 알 수 있도록 하나의 데이터는 하나의 URL을 갖도록 작업하는 방식



15) Spring에서 DI란 무엇인지 아시나요?

: DI는 Dependency Injection(의존성 주입)의 약자로, 객체들 간의 의존성을 줄이기 위해 사용되는 Spring의 IOC 컨테이너의 구체적인 구현 방식입니다.

  DI는 기존처럼 개발코드 부분에서 객체를 생성하는 것이 아니라, 팩토리 패턴처럼 객체의 생성과, 데이터를 주입만 담당하는 Factory에 해당 하는 

 별도의 공간에서 객체를 생성하고 데이터간의 의존성을 주입해 개발코드에서는 이를 가져다 씀으로서 의존성을 줄이는 방식입니다. 이때, 

 Factory 패턴의 Factory Class의 역할을 스프링의 환경설정 파일이 담당합니다.



16) Spring의 AOP란?

: AOP는 Aspect Oriented Programming 관점 지향 프로그래밍의 약자로, 기존의 OOP(객체 지향 프로그래밍)에서 기능별로 class를 분리했음에도 불구하

 고, 여전히 로그, 트랜잭션, 자원해제, 성능테스트 메서드 처럼 공통적으로 반복되는 중복코드가 여전히 발생하는 단점을 해결하고자 나온 방식으로

 이러한 공통 코드를 "횡단 관심사"라 표현하며 개발코드에서는 비지니스 로직에 집중하고 실행시에 비지니스 로직 앞, 뒤 등 원하는 지점에

 해당 공통 관심사를 수행할 수 있게 함으로서 중복 코드를 줄일 수 있는 방식입니다.



17) Filter와 Interceptor 방식의 차이?



18) 디자인 패턴 아는 것?

1) 싱글톤(SingleTone Pattern) : 대표적으로 Calendar 객체나 dataSource 객체처럼 객체가 하나만 생성되어야 하는 경우

 전체 코드에서 하나의 객체만 존재할 수 있도록 이미 생성된 객체가 있으면 그 객체를 사용하도록 하는 방식입니다.

2) 팩토리 패턴(Factory pattern) : 객체간 의존성을 줄이기 위해 객체의 생성과 데이터 주입만 담당하는 Factory Class를 정의하고 개발 코드 부분에서는

   생성된 객체를 가져다 사용함으로서 의존성을 줄이는 방식입니다.

3) 옵저버 패턴(Observer Pattern) : 기후 정보처럼 RSS 수신시 하나의 객체가 변하면 다른 객체에 객체가 변했다는 사항을 알려주어야 할 경우에 주로

    사용됩니다.



19) MVC 패턴이란?

- Model : data 처리와 접근을 담당

- View : Client에 보여지는 화면을 담당

- Controller : Model과 View를 제어

하는 3가지 부분으로 나눔으로서, 데이터와 화면간의 의존관계를 벗어날 수 있게하는 개발 기법입니다.



20) 프로젝트 개발 순서??

대강...

1) 요구사항 분석 

기획 및 스토리 보드 작성

2) WBS(Work Breakdown Structure) 작성 : 작업 분해도로 프로젝트 범위와 최종산출물을 세부요소로 분할한 계층적 구조도

3) 논리 ERD 작성

4) 물리 ERD 작성

5) 개발

6) Testing

7) 유지보수



21) 오버로딩과 오버라이딩의 차이?

- 오버로딩 : 메서드 명은 동일하지만, 매개 변수 타입과 개수를 다르게 해 선언하는 방식

- 오버라이딩 : 상속한 자식에서 부모의 메서드를 재정의하는 방식


22) Servlet vs JSP

- Servlet : 자바 언어로 웹 개발을 위해 만들어진 것으로, Container가 이해할 수 있게 구성된 순수 자바코드로만 이루어진 것

- JSP : html 기반에 JAVA 코드를 블록화하여 삽입한 것으로 Servlet을 좀 더 쉽게 접근할 수 있도록 만들어 진 것


23) Wrapper Class의 사용이유를 아나요?

: 기본 data 타입은 객체가 아니어서 Object로 받는 다형성을 지원할 수가 없다. 하지만, 메서드에서 실재로 기본데이터 타입을 다형성으로

 넘겨주어야 하는 경우가 빈번히 발생하는데 이때, 기본 데이터 타입을 객체로 변환시켜 전달하기 위해 사용되며

 최근에는 AUTO Boxing, AUTO UnBoxing이 지원된다.



24) DataBase에서 Index란?

: Table에 대한 동작 속도를 높여주는 자료구조로서 빠른 검색을 가능하게 해준다.



25) private, protected, public, default 제어자에 대해 설명해 보시오

- private : 같은 class 내부에서"만" 접근이 가능하다.

- public : 어디서든 자유롭게 접근이 가능하다.

- protected : 같은 class 내부 + 상속받은 자식에서는 부모 class에 접근이 가능하다.

- default : 아무 것도 선언하지 않은 경우로 같은 패키지 내부에서만 접근이 가능하다.



26) SI가 무엇을 하는 건지 알고 오셨나요?

: System Integration의 약자로 시스템 통합 사업으로 고객의 기존 전산시스템을 통합하거나 새로운 시스템을 구축하는 작업입니다.



27) SW 개발시 가장 비중을 크게 두어야 할 부분은 어디라고 생각하나요?

: Testing 부분입니다. 



28) 자바의 제네릭이란??

: 클래스 내부에서 사용할 데이터 타입을 인스턴스(객체) 생성시에 결정짓는 방식


-----------------------------------------------------------------------------------------------------------------------------------------

-> 여기까지는 제가 예상하는 면접 질문을 올리고 이 아래부터는 실제로 제가 기술면접 당시 받았던 질문들입니다.



[ 기술 면접 질문 ]

 

 

1. CVSSVN에 대해서 아는대로 설명해 보시오.

 

2. 64bit CPU32bit CPUOS적 관점에서의 차이를 설명해 보시오.

 

3. 프로세스와 쓰레드의 차이점에 대해서 설명해 보시오.( 메모리 구조 포함 )

 

4. ‘데드락이란 무엇이고 이를 해결하기 위한 방법을 설명해 보시오.

 

5. 변수 명명법이 중요한 이유에 대해서 설명하고 예를 들어 보시오.

 

6. 자바의 JVM의 역할에 대해서 설명해 보시오.

 

7. 자바의 특징에 대해서 말해 보시오.

 

8. Linux에서 톰캣 환경설정을 잡는 것에 대해 설명해 보시오.

 

9. WAS와 웹서버의 차이점은?

 

10. JqueryAjax에 대해 아는가?

 

11. 비동기와 동기 방식의 차이점에 대해서 말해보시오.(네트워크 동기,비동기 아님)

 

12. 개발시에 중요하다 생각하는 요소를 3가지 기술해 보시오.

 

13. 스프링의 MVC에 대해서 설명하시오.

 

14. AOP란 무엇이고 왜 사용하는지

 

15. ‘에자일방법론에 대해서 아는가?

 

16. 스프링 환경설정 혼자 잡을 수 있는가? 대강 어떻게 해야하는지 설명해 보시오.

 

17. 웹서버 내부 구동 방식에 대해 설명할 수 있는가?

 

18. 스프링 DI?

 

19. UML 그려본 적 있는가?

 

20. Node jsAngular JS를 사용해 본 적이 있는가?

 

21. 캐시와 세션의 공통점과 차이점에 대해 말해보시오.

 

22. 디자인 패턴 아는 것들만 간략히 설명해 보시오.

 

23. DataBase에서 index관련 질문이었는대 잘 모르겠어서..기억이..

 

24. 크롬이나 파이어폭스에서 개발도구를 사용해 디버깅을 해보았는가?

 

25. JDBC는 무엇인가?

 

26. 스프링을 사용하지 않고 MVCJSP에서 만들어 보았는가?

 

27. DB 옵티마이저에 대해 아는가?

[    스프링과 안드로이드 연동4 : JSON으로 가져오기    ]



이번에는 안드로이드에서 스프링 프로젝트로 요청시 JSON으로 서버에서 제공하는 데이터를 가져오는 방식을 다루어 보겠습니다.


1. 먼저, 스프링 프로젝트에서 pom.xml에 라이브러리를 추가합니다.


1) jackson-databind 추가

: @ResponseBody로 반환시 필요

<dependency>

<groupId>com.fasterxml.jackson.core</groupId>

<artifactId>jackson-databind</artifactId>

<version>2.8.7</version>

   </dependency>


2) jason-simple 추가

: JSONObject와 같은 json 객체 생성시 필요

<dependency>

<groupId>com.googlecode.json-simple</groupId>

<artifactId>json-simple</artifactId>

<version>1.1.1</version>

  </dependency>


2. 다음으로 스프링 컨트롤러 부분을 작성합니다. 스프링 컨트롤러에서 다음의 코드를 입력합니다.



http://localhost:8080/json.do 로 요청시 필요한 데이터를 JSONObject에 저장시켜 반환하는 컨트롤러 부분입니다.

반환시 한글 인코딩 문제 때문에 produces 부분에서 JSON 객체 생성시 utf-8로 인코딩하겠다는 설정을 하고,

JSONObject는 {변수명:값} 을 담을 수 있음으로

JSONArray로 배열 객체를 생성해 여기에 JSONObject 객체를 담아서

sendData라는 변수명으로 이 배열을 저장시켜 JSON으로 반환하게 됩니다.




2. 안드로이드에서 작업


안드로이드에서도 JSON 객체를 받아오기 위해서 2개의 라이브러리를 추가해주어야 합니다.

1. httpclient-4.2.2.jar

2. httpcore-4.2.2.jar

두개의 라이브러리를 mvnrepository 에서 검색해서 files 부분에 보면 다운로드를 할 수 있는데 다운로드 합니다.

그리고 안드로이드 프로젝트에서 프로젝트 보기가 Android로 되어 있는데 이 부분을 눌러 Project로 변경합니다.

그러면, App/libs 폴더가 보일겁니다.

여기 Android일 땐 보이지 않지만

Project로 변경시 app/libs 폴더가 보입니다. 그러면 이 폴더에 다운받은 2개의 라이브러리를 집어 넣습니다.


다음으로, android 형식으로 보기를 다시 누르고, Gradle Scripts 의 build.gradle 부분에 들어가서


dependency 부분에 


다음의 2줄을 추가해줍니다.

compile files('libs/httpclient-4.2.2.jar')
compile files('libs/httpcore-4.2.2.jar')


그리고 Sync Now 선택해주면 적용이 된 것입니다.



3. 이제 안드로이드에서 코드를 작성합니다.


역시 네트워크 작업임으로 이전에서 했던 3가지 사항을 준수합니다.


1) 인터넷 사용 권한 설정(manifest에)

<!-- 인터넷 접속 권한 추가 -->
<uses-permission android:name="android.permission.INTERNET" />


2) 네트워크 작업임으로 백그라운드 스레드에서 작업할 것


3) 백그라운드 스레드에선 메인 뷰에 접근할 수 없음으로 핸들러를 통해 뷰에 접근할 것!






**** 마지막으로.........

위의 경우에서는 안드로이드에서 스프링 컨트롤러로 post로 요청을 보낼 때 데이터를 같이 보내지 않았다. 만약 데이터를 같이 넘겨줘야 하는 경우에는

// NameValuePair : 변수명과 값을 함께 저장하는 객체로 제공되는 객체이다.
ArrayList<NameValuePair> postData = new ArrayList<>();
// post 방식으로 전달할 값들을 postData 객체에 집어 넣는다.
postData.add(new BasicNameValuePair("id","아이디"));
postData.add(new BasicNameValuePair("pw","패스워드"));
// url encoding이 필요한 값들(한글, 특수문자) : 한글은 인코딩안해주면 깨짐으로 인코딩을 한다.
UrlEncodedFormEntity request = new UrlEncodedFormEntity(postData,"utf-8");
HttpPost httpPost = new HttpPost(url);
// post 방식으로 전달할 데이터 설정
httpPost.setEntity(request);
// post 방식으로 전송, 응답결과는 response로 넘어옴
HttpResponse response = http.execute(httpPost);
// response text를 스트링으로 변환
String body = EntityUtils.toString(response.getEntity());
// 스트링을 json으로 변환한다.
JSONObject obj = new JSONObject(body);

// 스프링 컨트롤러에서 리턴해줄 때 저장했던 값을 꺼냄
String message = obj.getString("message");

과 같이 작성하면 된다.



[  스프링과 안드로이드 연동3 : 서버에서 XML로 반환해 안드로이드로 가져오기 ]


먼저, 이 작업을 하기 위해서 Spring 프로젝트에서 pom.xml에 3가지 라이브러리를 추가해주어야 한다.

[필요 라이브러리]


1.org.jdom을 mvnrepoisitory에서 검색해 dependency를 pom.xml에 추가한다.

-> 자바 객체를 가지고 xml을 생성해주는 라이브러리


2.json-simple 도 추가

-> json 객체를 생성


3.jackson-databind 추가

-> 스프링 컨트롤러에서 @ResponseBody로 json으로 전송하기 위해 사용



3개의 라이브러리를 pom.xml에 추가했으면 다음으로 스프링 컨트롤러에서


XML로 보낼 데이터를 구성해 전달하는 코드를 작성해 주어야 한다.

(mvnrepository에서 검색해서 추가하자.)


그 다음 아래는 서버쪽 컨트롤러 코드에 대한 부분이다.


bookService.bookList()를 통해서 db에서 BookDTO 객체를 갖는 List를 반환받아서 이 List 정보를 XML 형태로 만들어서

return하는 내용의 코드이다. XML을 구성하는 부분은 주석으로 남겨 놓았다.


List<BookDTO> 를 가지고 구성할 XML 의 구성은 아래와 같다.


<books>
<book>
<book_code>1</book_code>
<book_name>자바</book_name>
<press>출판사</press>
</book>
</books>




여기까지 했으면 스프링 프로젝트에서 필요한 부분은 끝이났다.

이제 안드로이드에서 스프링에서 던진 데이터를 저장하기 위한 BookDTO 클래스를 만들자




그리고 마지막으로 안드로이드에 관련된 작업이다.

역시 네트워크 작업임으로 앞에서 한 예제에서 주의해야하는 3가지 사항을 지켜주어야 한다.

1. 네트워크 작업임으로 백그라운드 스레드로 동작시켜야한다.

2. 네트워크 접근이 필요함으로 인터넷 권한을 주어야한다.

3. 백그라운드 스레드에서 메인 뷰에 접근할 수 없음으로 핸들러를 통해 처리해야한다.


위 사항을 기반으로해서 XMLPullparser를 이용해 데이터를 끄집어 내 핸들러를 통해 안드로이드에 출력해주면 된다.


[    스프링과 안드로이드 연동2 : 서버에서 안드로이드로 이미지 가져오기    ]





이전 포스팅과 마찬가지로 네트워크를 통한 작업임으로 주의해야할 사항이 2가지 있다.



1. 안드로이드는 네트워크 작업시 "백그라운드 스레드"로 동작을 시켜야한다.(대용량 데이터를 네트워크로 가져오는 동안 다른 작업은 할 수 없게 될 수 있음으로...)

2. 백그라운드 스레드에서는 메인 뷰 화면을 제어할 수 없다. 따라서 가져온 이미지를 백그라운드 스레드에서 적용할 수가 

없다. 따라서, 핸들러에게 대신 요청을 해주어야 한다.

3. 인터넷을 사용해야 함으로 manifest에 인터넷 사용 권한을 추가해 주어야 한다.



라는 사항을 유의하고 작업하면 그 외의 부분은 이전과 거의 유사하다.

나머지는 코드의 주석을 통해 설명하겠습니다.



[    스프링(Spring)과 안드로이드(Android)를 연동해 서버 Html 소스 가져오기    ]


이번에는 스프링과 안드로이드를 연동해서 하는 예제 중 첫번째인 요청한 서버의 Html 소스코드를 가져오는 예제를 작성하겠습니다.


    1. 첫번째로 네트워크를 통해 가져와야함으로 "인터넷 권한을 설정해야 합니다."


manifest파일에 <!-- 인터넷 접속 권한 추가 -->

<uses-permission android:name="android.permission.INTERNET" /> 을 추가해줍니다.




2. 다음으로, 안드로이드 네트워크 작업시에 주의해야할 사항이 2가지 있습니다.



1) 안드로이드에서는 네트워크를 통해 작업을 할 때는 "반드시" "백그라운드 스레드"를 통해서 작업이 이루어져야합니다.


: 왜냐하면, 네트워크로 이미지 등을 받아올 때 시간이 다소 걸릴 수 있는데 메인스레드에서 작업할 시 다른 작업에도

지장을 주기 때문에 백그라운드에서 동작시키지 않을 경우 동작하지 않도록 제한이 걸려져 있습니다.


따라서, 쓰레드를 별도로 구현해 run() 메서드 내부에서 작업을 해야합니다.



2) 다음으로 알아둬야 할 사항은 "백그라운드 스레드"에서는 메인 스레드의 "뷰"를 제어할 수 없다는 것입니다.


예를들어, 백그라운드 스레드의 run()메서드 내에서 URL에 연결을 해 데이터를 가져오고 그 데이터를


이 run() 메서드 내부에서 textView같은 곳에 setText()해서 붙인다면 오류가 발생합니다. 이처럼 안드로이드는 백그라운드 스레드에서


메인스레드의 뷰에 접근할 수 없도록 제한이 걸려있습니다.


따라서 이러한 제한때문에 필요한 것이 "Handler"입니다.



"Handler란?"


: 핸들러는 안드로이드 네트워크 작업시 백그라운드에서 수행해야하는데 백그라운드 스레드에선 메인 뷰를 제어할 수 없기 때문에


백그라운드와 메인 스레드 사이의 통신(중계)를 담당하게 됩니다.


즉, 백그라운드 스레드는 핸들러에게 "네가 대신 화면 제어를 해줘"라고 sendMessage() 메서드를 통해 요청을 하게 됩니다.


그러면, 핸들러는 이 메시지를 받아 처리할 작업을 대신해주게 되는 것입니다.





3. 이제 이러한 이론적 지식을 가지고 아래의 구현 예제를 보겠습니다.



다음은 스프링 웹 서버로 요청해 해당 뷰의 html 소스를 가져오는 과정을 담은 예제입니다.


세부 부분에 대한 설명은 주석으로 남겨놓았습니다.



[    AJAX를 동기화, 비동기화 설정해서 처리하기    ]



: JAVASCRIPT AJAX를 사용할 때, 일반적인 경우 비동기로 통신을 하게 된다. 즉, 순서대로 처리되는 것이 아니라

 처리 순서를 보장할 수가 없다. 따라서 꼭 순서를 지켜서 처리해야하는 경우는 동기화 방식으로 처리하도록 설정해주어야 한다.

 그럼 어떻게 해야할까?


 우선, ajax 내에서 async : false, 로 지정할 경우 동기화 방식으로 보내고 true로 하거나 생략할 경우 비동기방식으로 통신이 이루어지  게 된다. 


[    HandleBars 적용하기    ]

: HandleBars는 Javascript 라이브러리 중 하나로, 보통 AJAX로 가져온 데이터를 JQuery에서 문자열로 조합한다음에 append해주는

  불편함을 줄여주기 위해 주로 사용됩니다.

  특징)

1. 태그를 이용해서 구성을 잡아준다.(템플릿을 만든다.)
2. 템플릿 사이사이에 데이터가 들어갈 곳에 {{ 변수 }} 로 넣어놓는다.
3. 템플릿과 데이터를 연결시 {{}} 부분에 데이터가 들어가게 된다.

이때, {{#변수}} {{/변수}} 부분에는 배열과 같은 타입의 데이터의 길이가 들어와 그 길이만큼 반복작업을 수행하게 된다.

제일먼저 사용하기 위해, HandleBars js 라이브러리를 다운로드하고, 

<script src="" 를 이용해서 임포트 해주고 사용해야 한다. 이후 설명은 아래 소스코드에서 이어가겠습니다.



타일즈와 유사하게 header, footer, body 등의 형식을 지원하는 sitemesh 설정 방법

 

[    SiteMesh 설정하기    ]

 

1. pom.xml에 dependency 추가

 

2. web.xml에 한글처리 filter 위에 filter 추가

이때, 모든 경로가 siteMesh의 영향을 일단 받을 수 있도록 /*로 설정한다.

 

3. /WEB-INF 폴더 밑에 sitemesh.xml 파일을 만들고 다음 코드를 붙여넣는다.

sitemesh 라이브러리를 추가했다면, WEB-INF 폴더 밑에 sitemesh.xml 파일을 자동으로 인식한다.

이 곳에서 sitemesh 설정파일의 위치를 등록해준다.

여기서는 /WEB-INF/decorators.xml 로 sitemesh의 설정파일을 사용하겠다는 것을 알 수 있다.

 

4. 위에서 설정파일로 사용할 xml 파일을 WEB-INF 폴더 하위에 decorators.xml로 만들고 다음 코드를 붙여넣는다.

/WEB-INF/decorators.xml

 

5. 데코레이터 기본 폴더에 decorator로 등록한 파일을 생성하자

/WEB-INF/views/layout/header-footer-layout.jsp 를 만들고 다음 코드를 붙여넣자.

 

6. 그 외의 설정

여기서는 4번에서 header-footer-layout.jsp 하나만을 등록하고 *(모든 요청)시 작동하도록 하였는데,

그 외에 다른 적용이 필요한 경우 이 부분에 다른 layout 설정 파일을 등록해서 사용할 수 있다.

 

그 외에도, layout 파일 내에도 별도의 header.jsp 등을 만든 것을

<c:import 태그를 이용해서 등록해서 사용할 수 있다.

 

안녕하세요. 지금까지는 게시글 목록 페이지에서 전체 게시글 1000개가 있으면 한 페이지 내에 1000개가 다 출력되고 있는데요.

이제 게시글 목록 페이지에서 [이전][1][2][3]....[10][다음] 과 같은 페이지 처리 부분을 넣어 3 을 클릭하면 3페이지 게시글 목록을

보여주고, 또 옆에 select 박스로 한 페이지당 출력할 게시글 수를 선택할 수 있게 만들어 사용자가 현재 페이지당 출력할 게시글

수도 선택할 수 있게끔 만들어 보도록 하겠습니다.


[    1. 페이징 처리 구현    ]

페이징 처리를 할 때 가장 필요한게 무엇일까요? 저는 페이지를 처리하기 위한 VO 클래스가 필요하다 생각합니다. 그래서 가장 먼저 할 작업은


1) Criteria VO class와 PageMaker VO 클래스 두개를 만들겠습니다.

- Criteria 클래스 : 

: - page : 현재 페이지를 나타내는 필드 변수

 - perPageNum : 페이지당 표시할 게시글의 수

  - getPageStart() : limit #{pageStart}, #{perPageNum} 부분에서 pageStart를 위한 부분

두 필드 변수를 담도록 할 것이고 이때 좀 중요한 부분이 있는데 Mybatis Mapper.xml 부분에서 

게시글 목록을 검색할 때, 페이지에 맞는 게시글을 뽑아와야 합니다. 이때

select * from boardTable order by bno desc limit #{pagStart}, #{perPageNum} 

이런식으로 검색을 해야합니다. 해석해보자면 boardTable에서 bno(게시글)을 내림차순으로 정렬해줍니다. 최신글이 가장 

위로 올라와야하기 때문이죠. 그 다음 limit 부분을 보면 limit 시작 출력 행, 몇개씩 으로 ~부터 n개를 출력하겠다는 부분입니다.

이때, Mapper.xml에서 #{ } 이 안에 들어 있는 변수명은 getter를 호출해서 대입받는 것을 아실겁니다. 따라서

Criteria 클래스에선 getPageStart() 메서드가 추가로 반드시 필요합니다.!!!

이때, getPageStart()를 보면 return (page-1)*perPageNum을 볼 수 있을 것인데 

1페이지일 때 -> 0 ~ 9 게시글

2페이지 ->      10 ~ 19 

3페이지 ->      20 ~ 29

등을 적어놓고 생각해보면 왜 저런 식이 나왔는지 알 수 있습니다.



- PageMaker 클래스

- Criteria cri : 위에서 만든 page와 perPageNum을 가지고 있는 객체

- int totalCount : 전체 게시글 수로 Mapper.xml을 통해 DB에서 전체 게시글 수를 가져와야한다.(가장 중요)
why? 전체 게시글 수가 있어야 이를 기반으로 모든 다른 "페이지"들에 대한 계산이 가능하기 때문.
- int startPage : 현재 페이지 기준에서 시작 페이지 : 현재 페이지가 12쪽이면 11쪽이 startPage일 것이고
현재 페이지가 27페이지면 21페이지가 startPage 이겠죠?
- int endPage : 현재 페이지를 기준으로 한 마지막 페이지

[11][12][13].......[20] : 현제 페이지가 13일 때 startPage는 11, endPage는 20

- boolean prev : [이전]으로 이동하는 버튼이 생길 조건이 되는지를 나타내는 boolean 변수입니다.
- boolean next : [다음]으로 이동하는 버튼이 생길 조건이 되는지를 나타내는 boolean 변수입니다.

--> 특히, 이 PageMaker 클래스는 view 단에서 페이징 처리를 하기 위해 필히 사용되어야 하는 아주 중요한 클래스입니다.
한번 잘 작성해 놓으면 가져다 쓰기 좋겠죠?


이때, PageMaker 클래스에서 주의해야할 점은

다른 변수들을 계산되려면 totalCount(전체 게시글 수)가 먼저 세팅되어야 하는 점입니다. 또한, page와 perPageNum을 이용하려면 Criteria도 먼저 세팅이 되어야 합니다.

따라서, PageMaker 객체를 사용할 때에는 setCriteria()와 setTotalCount()를 먼저 호출해서 값을 지정할 수 있도록 해주어야 한다는 점과, setTotalCount()에서 다른 변수들에 대한 계산을 해야한다는 점을 기억해야만 합니다.

아래 코드를 추가합시다.


각 항목들에 대한 계산은 처음 한번 이해한 뒤엔 가져다만 씁시다.. 너무 수학적이야 ㅠㅠ
그냥 상식(?)적으로... endPage는 (무조건올림)(현재 페이지 / 페이지당 보여줄 게시글 수) * 페이지당 보여줄 게시글 수 
startPage 는 마지막 페이지에서 페이지당 보여질 게시글 수를 빼면 되고

중간에 tempEndPage 부분의 경우 endPage가 현재페이지 기준의 마지막 페이지이기 때문에 전체 게시글에 대한 마지막 페이
지를 알고 있어야 boolean next에 대한 처리를 할 수 있겠죠?




2) /resources/mappers/boardMapper.xml 을 작성해봅시다.(Mybatis DB 처리)

페이징 처리를 하기 위한 DB처리는 어떤 것들이 필요할까요? 
1) 게시글 목록을 가지고 올 때, 페이지 마다 출력해야하는 BoardVO를 담은 List를 반환하는 부분(listCriteria)
2) 전체 게시글이 몇개인지를 가져오는 부분(getTotalCount)
이 두개가 필요할 겁니다.

아래 코드를 작성합시다.


위에서 다 설명했던대로 limit 부분이 보일 것입니다. 최신글을 뽑아내기 위해 order by bno desc와 
페이지에 맞는 게시글을 뽑기 위해

limit 시작 행, 몇개 를 이용해서 BoardVO를 담고 있는 List를 반환하도록 했습니다.

혹시나해서 쓰는 것이지만, 이때 <select 안에서 resultType="BoardVO" 이 부분은 본래 풀 페키지 경로를 포함한 클래스
명으로 써야하지만, 저처럼 클래스 명으로만 쓰고 싶은 경우에는

Mybatis 설정시 <typeAlias>에 default 패키지를 지정해야 한다는 점.... 알고 계시겠죠??
혹시 모르신다면... Mybatis를 한번 더 살펴보시거나, 그냥 resultType="패키지.BoardVO" 로 하시면 됩니다.


3) DAO 작업과 Service 작업

이제 익숙해졌으리라 생각합니다. Mapper.xml에 추가했음으로 당연히 DAO와 Service 객체에도 추가해야겠죠.

- BoardDAO 인터페이스
- BoardDAOImpl 클래스
- BoardService 인터페이스
- BoardServiceImpl 클래스
를 작성합시다.

먼저 BoardDAO

여러분, 정말 혹시나!해서... 페이징 처리를 위한 메서드 2개(listCriteria()와 TotalCount()) 만 추가하시는 것 알죠?
나머지 부분들은 게시글 추가,삭제, 수정 등에 대한 메서드에요 여러분 것과 제가 많이 다를 수 있으니
그 부분은 제껄 그대로 추가하시면.... 오류생깁니다...


BoardService


BoardServiceImpl

다 마찬가지에요 페이징 처리 부분만 복사하세요.



4) BoardController에서 listPage() 부분 작업을 해봅시다.

위에서 얘기한대로 컨트롤러의 게시글 목록을 보여주는 listPage() 메서드에서는 다음과 같은 작업을 해주어야 합니다.

@RequestMapping(value = "/listPage", method = RequestMethod.GET) public String listPage(@ModelAttribute("cri") Criteria cri, Model model) throws Exception {                 // 커맨드 객체로 Criteria를 매개변수로 넣어줘, 넘어오는 page와 perPageNum정보를 받습니다.

// 해당 cri 를 이용해서 service->dao->mapper.xml 순으로 접근하면서 DB처리를 해 cri 전달된

// 현재 페이지 정보를 기준으로 BoardVO 객체를 담은 ArrayList가 반환될 것입니다.

List<BoardVO> dto = service.listCriteria(cri);

// 이제 view jsp 페이지에서 페이징 처리를 위해 사용할 PageMaker 객체를 생성하고 PageMaker pageMaker = new PageMaker();

// 여기 부분을 "꼭" 해 주어야 한댔죠? Criteria를 set해주고 setTotalCount() 를 해주어야

// 페이징처리에 필요한 것들이 내부적으로 계산될 수 있도록 작성했다고 했습니다. pageMaker.setCri(cri); Integer totalNum = service.totalCount(); pageMaker.setTotalCount(totalNum);

// /views/board/listPage.jsp 에서 페이징 처리를 하기 위해 PageMaker 객체를 저장해 놓아야 할 것이고

// 당연히 화면에 게시글을 뿌려주기 위해서 꺼내온 dto도 저장을 해 주어야 할 것입니다.(model 객체에) model.addAttribute("pageMaker", pageMaker); model.addAttribute("list", dto); return "board/listPage"; }



5) 이제 /views/board/listPage.jsp 페이지에 대한 작업과 그에 필요한 사전 작업을 해봅시다.


listPage.jsp에서 이제 페이징 처리를 할 것인데, 페이지에 따라 a태그를 이용해서 href로 페이지를 띄우는 요청을 걸어놓겠죠?

그런데, 이러한 작업이 계속 반복될 것입니다. 그러면 이걸 하드코딩하는 것 보단 메서드로 만들어 놓으면 어떨가요?

ex) <a href="/board/listPage?page=3?perPageNum=10>[3]</a> 3쪽을 누르면 이런식으로 호출되도록 링크를 걸겁니다.

그러면 page변수가 넘어가서 Controller의 listPage(Criteria cri) 해 놓은 부분에서 cri의 setPage()와 setPerPageNum()을 호출해 데이터를

넣겠죠? 이러한 부분이 계속 반복된다는 것입니다. 근대 그나마 여기서는 칠만해요... 솔직히

근대 동적 검색을 지원하는 기능까지 붙이잖아요? 변수가 늘어나고? 엄청 짜증나기 시작할거에요... 그래서 미리 만들어 놓는 것이 좋습니다.


이러한걸 Spring은 지원하는대요 그게 

"UriComponent"라는 겁니다.

<a href="/board/listPage?page=3?perPageNum=10>에서 ? 이 부분부터 "쿼리 문자열"이라고 하잖아요?

그 부분을 생성하는 메서드를 만들건대 page라는 정보와 perPageNum 정보가 필요하니까 두 변수를 가지고 있는 Criteria나 PageMaker에서

해주는게 좋겠죠? 여기서는 PageMaker 에서 해봅시다.

PageMaker class에서 makeQuery(int page) 라는 메서드를 만들어서 작성해봅시다.




6) /views/board/listPage.jsp 게시글 목록을 보여주는 페이지에서 작업합니다.


7) listPage.jsp 페이지에 현재 페이지에 따른 게시글을 표시하도록 합시다.

6번에 작성한 코드 바로 윗 부분에 해주어야 [이전][1][2][3]...[10][다음] 윗 부분에 들어갈 게시글 목록부분입니다.

여기서 관심있게 볼 점은 게시글 목록의 경우 제목을 클릭하면, 해당 상세 읽기 페이지로 분기해야합니다.

따라서, href="/board/read${pageMaker.makeQuery(pageMaker.cri.page)}&bno=${boardVO.bno} 부분을 통해

page, perPageNum, bno(클릭했을 때 읽을 게시글 번호) 정보를 함께 넘겨 주어야 합니다.

그외에 boardVO 객체에 있는 게시글 등록 일 같은 정보를 출력하기 위해 jstl의 <fmt:formatDate 태그를 이용한 것을 알 수 

있습니다.

그리고 마지막 부분에 글을 등록할 수 있는 글등록 버튼을 하나 넣었습니다.



8) listPage.jsp에서 글 등록 버튼을 클릭했을 때 글등록 페이지로 이동하도록 JQuery를 이용해보겠습니다.


<script>

// 글 작성처리에서 글 작성 성공시 redirect 직전에 RedirectAttribute 객체의 addFlashAttribute로 저장한 msg 변수

var result = '${msg}'; // 글작성 등록에 성공했을 때 msg 지정해 논 부분


if (result == 'SUCCESS') {

alert("처리가 완료되었습니다.");

}

// 이 부분이 8번에서 핵심 부분입니다.

$(document).ready(function() {

// 글 등록 버튼을 클릭하면...

$("#register").click(function() {

self.location = "/board/register"; // 글 작성 폼 페이지로 이동한다.

});

});

</script>



9) BoardController의 read() 부분을 수정해봅시다.


글 등록처리야 /board/register 를 호출하면서 글 등록 폼으로 잘 이동할 것이고 문제는 글 제목을 클릭했을 때 

읽는 페이지 부분입니다. 우리가 /board/read?page=3&perPageNum=10 이런식으로 전달했기 때문에

이 read 부분에서도 page와 perPageNum을 Criteria를 이용해서 받아야만 합니다.


그리고 "진짜 진짜" 중요한 점은, 이 page정보와 perPageNum 정보를 숨김값(hidden) 으로 수정페이지나 삭제 페이지에 전달

해야만 합니다. 왜그럴까요?

왜냐하면 여러분이 3페이지의 게시글을 보고 있다가 글으로고 제목을 클릭했습니다. 그런데 글을 읽고 목록으로 다시 돌아오거나

삭제 페이지 혹은 수정페이지로 이동을 했다가 목록으로 돌아왔을 때 첫페이지로 돌아와있다면 어떨까요? 불편하겠죠?(그렇다해줘요...)


80페이지까지 넘기며 보고 있었는데 게시글 한번 클릭해 보고 왔더니 1페이지... 최악이잖아요? 다시 넘기기도 귀찮고 그래서

현재 보고 있었던 page 정보는 무조건 계속 다른 곳으로 전달하면서 유지시켜주어야합니다. 그럴려면 어떻게 할까요?

<form ~~>

<input type="hidden" name="page" value="${pageMaker.cri.page}" />

</form>

이런식으로 숨김값으로해서 전달해주는 것이 필요합니다.


그래서 아래 코드에서는 

우선, 1) read메서드에서 page와 perPageNum을 전달받을 수 있도록 커맨드 객체를 추가시켜주고, readPage쪽 jsp 페이지에서

페이지에 대한 정보를 사용할 수 있도록 model 객체에 add해주어야 합니다.

  2) read.jsp 에서 hidden값으로 값이 넘어갈 수 있도록 작업도 들어가야겠내요.


BoardController의 read()



/views/board/read.jsp


 + [ read.jsp 페이지에서 게시글 정보가 표시되는 부분입니다. ]


<div class="box-body">

<div class="form-group">

<label for="exampleInputEmail1">Title</label> <input type="text"

name='title' class="form-control" value="${boardVO.title}"

readonly="readonly">

</div>

<div class="form-group">

<label for="exampleInputPassword1">Content</label>

<textarea class="form-control" name="content" rows="3"

readonly="readonly">${boardVO.content}</textarea>

</div>

<div class="form-group">

<label for="exampleInputEmail1">Writer</label> <input type="text"

name="writer" class="form-control" value="${boardVO.writer}"

readonly="readonly">

</div>

</div>

<!-- /.box-body -->


<div class="box-footer">

<button type="submit" class="btn btn-warning">수정</button>

<button type="submit" class="btn btn-danger">삭제</button>

<button type="submit" class="btn btn-primary">목록으로</button>

</div>


이 부분을 바로 아래 추가하고


read.jsp 페이지에서 목록가기,수정 등 버튼 클릭에 따른 JQuery 처리


히든 값이 있는 form의 속성들을 JQuery로 변경해가며 수정, 삭제, 목록으로 가기 등에 맞춰 변경해 submit해주고 있습니다.




10) BoardController의 remove() 부분


글을 읽다가 해당 글을 삭제하기를 눌렀을 때, 위의 코드까지 진행하면 page와 perPageNum이 전달될 것입니다.
컨트롤러의 remove() 부분에서는 글을 삭제하고 삭제처리가 완료되면 목록보기로 redirect 해주어야 합니다.

이때, 중요한 점은 redirect시에는 model 객체가 전달되지 않는다는 점 알고 계시죠?

따라서, model을 사용하는 것이 아니고, RedirectAttribute 객체를 사용해야합니다. 다시 한번 말하지만 Model 객체는

redirect시에는 전달되지 않습니다.!





11) 마지막, 수정 페이지에 대한 작업


읽기, 삭제 페이지까지의 작업을 다 했음으로 마지막으로 수정에 관한 작업만 남았습니다.

어차피 위에서 한 작업과 마찬가지로 코드만 추가하도록 하겠습니다. 수정페이지도 read 페이지처럼

hidden으로 데이터를 전송하는 부분이 있겠죠?


- BoardController의 modify 메서드 부분



- /views/board/modify.jsp 페이지 작업


<form role="form" method="post" action="/board/modify"> <!-- action이 없음으로 이 페이지 요청될 때의 url과 동일한 url로 요청하되 post방식으로 됨. -->


<div class="box-body">


<div class="form-group">

<label for="exampleInputEmail1">BNO</label> <input type="text"

name='bno' class="form-control" value="${boardVO.bno}"

readonly="readonly"> <!-- 게시글 번호는 수정하지 못하도록 readonly 로 설정 -->

</div>


<div class="form-group">

<label for="exampleInputEmail1">Title</label> <input type="text"

name='title' class="form-control" value="${boardVO.title}"> <!-- 읽어온 게시글 값들을 미리 세팅해 놔야함 -->

</div>

<div class="form-group">

<label for="exampleInputPassword1">Content</label>

<textarea class="form-control" name="content" rows="3">${boardVO.content}</textarea>

</div>

<div class="form-group">

<label for="exampleInputEmail1">Writer</label> 

<input type="text" name="writer" class="form-control" value="${boardVO.writer}">

</div>

<!-- 숨김값으로 보내줘야 할 값들 -->

<input type="hidden" name="page" value="${cri.page }" />

<input type="hidden" name="perPageNum" value="${cri.perPageNum }" />

</div>

<!-- /.box-body -->

</form>



<div class="box-footer">

<button type="submit" class="btn btn-primary">수정</button>

<button type="submit" class="btn btn-warning">취소</button>

</div>


<script>

$(document).ready(function() {


var formObj = $("form[role='form']");


console.log(formObj);

// 취소 클릭시 다시 리스트 목록페이지로 돌아가도록 함

$(".btn-warning").on("click", function() {

// self.location : 현재 페이지를 다른 페이지로 전환할 떄 사용하는 JQuery 메서드

self.location = "/board/listPage?page=${cri.page}&perPageNum=${cri.perPageNum}";

});

// 수정 버튼 클릭시 폼 제출되도록 함

$(".btn-primary").on("click", function() {

formObj.submit();

});


});

</script>






쿠키와 세션을 이용한 자동 로그인 방식에 대해서 정리해 보겠습니다.


[    1. 쿠키와 세션이란?    ]


: 쿠키와 세션은 매우 유사하면서도 다른 특징을 지니고 있는데요.

- 공통점 : 사용자의 정보(데이터)를 저장할 때 이용된다.

- 차이점 : 

- 쿠키 : 1) 사용자의 로컬에 저장되었다가 브라우저가 요청시 왔다갔다하게 됨(보안에 취약)

     2) 세션과 달리 여러 서버로 전송이 가능함

     3) 세션이 브라우저 단위로 생성되어 브라우저 종료시 사라지는데 반해, 쿠키는 유효시간 설정을 할 수 있음. ex) 7일

- 세션 : 1) 서버에 데이터를 저장하여 쿠키에 비해 보안에 안전함

     2) 브라우저 단위로 생성됨 => 익스플로러를 켜고 크롬을 켜고 하면 각각 2개의 세션이 생성되는 것


[    2. why 쿠키와 세션을 이용한 로그인 처리를 하게 될까?    ]


: 세션은 위에서 설명한대로 기본 단위가 "웹 브라우저"입니다. 따라서, 웹 브라우저 종료시 소멸하게 되죠...

  그에 반해 쿠키는 사용자 PC에 저장되기 때문에 서버 요청시 전달되는 동안 네트워크 상에서 보안상 취약할 수는 있지만 유효시간을

  길게 설정할 수 있어 브라우저가 종료되는 것과 별개로 7일 30일 등 기간을 길게 설정할 수 있습니다.

  하지만, 

  그렇다고 쿠키에 로그인할 사용자의 정보를 담고 있는다면 정말 정말 너무 너무 보안상 취약할 것을 알 수 있겠죠?

  따라서, 자동 로그인을 구현할 때에는 "< 세션과 쿠키를 동시에 사용하는 것 >"이 바람직하다고 생각합니다.


[    3. 세션과 쿠키를 이용한 자동 로그인 구현에 대한 개요    ]


: 사용자가 로그인 폼에서 로그인을 할 당시, 자동로그인을 설정하겠다는 CheckBox를 클릭할 경우 사용자의 정보를 저장시키고 유효

기간을 설정한다는 것 까지는 알겠는데 그럼 도대체 어떤 사용자의 정보를 저장시켜 놓아야할까요?


먼저, 사용자가 로그인에 성공한 경우! -> 세션에 사용자 객체(UserVO)를 저장시켰었는데 앞에서 이 객체를 쿠키에 저장시킨다면, 굉장히 보안상 취약합니다. 비밀번호, 아이디 그 외 정보까지 UserVO에 들어 있었죠...

따라서, 로그인에 성공했을 때 사용자 DB 테이블에 sessionId와 유효시간 속성에 값을 지정하는 겁니다. 그리고 쿠키에는 세션Id를

넣어 놓는거죠... 그리고 "인터셉터"에서 해당 쿠키값이 존재하면 사용자 DB 테이블 내에서 유효시간 > now() 즉, 유효시간이 아직 

남아 있으면서 해당 세션 Id를 가지고 있는 사용자 정보를 검색해 해당 사용자 객체를 반환하는 겁니다.


당연히, 쿠키가 유효시간이 다되면 해당 자동완성 기능은 동작하지 않게 되고 다시 쿠키를 사용하겠다는 선택을 했을 때 동작하게 되겠죠

그럼, 다음으로 코드상에서 직접 한번 알아 봅시다.


[    4. 자동 로그인 실재로 구현해보기    ]


이번 장의 예제는 앞 게시글을 다 수행했다는 가정하에서 진행됩니다.


1) 먼저, UserController에서 로그인에 성공했으면서 사용자가 쿠키 사용 여부를 체크한 경우 -> 쿠키를 생성하고 세팅합시다.

코드를 살펴보면, service 객체의 login메서드를 통해 UserVO 객체를 반환하고 null이 아닌 경우 로그인에 성공했었죠?

이렇게 로그인에 성공되었으면서, 로그인 폼에서 checkBox를 선택한 경우(쿠키 사용하겠다는 체크박스) submit을 했을 때

UserVO 클래스 내의 useCookie 변수에 true/false로 값이 저장되어 들어 왔을 테니까

로그인에 성공했으면서 + 쿠키사용을 체크한 경우에 세션을 추가하도록 하는 부분이 앞에 코드에서 추가된 겁니다.

이때, 사용자 PC에서 쿠키를 보내는 경로가 "/" 로 설정함으로써 contextPath 이하의 모든 요청에 대해서 쿠키를 전송할 수 있

도록 설정한다는 것이고, 유효시간은 (초)단위 임으로 60 * 60 * 24 * 7로 세팅해주면, 로그인 후 해당 쿠키는 7일동안 유지될 수

있게 됩니다.(브라우저의 종료와 관계없이)

이때, 가장 중요하게 볼 부분이 쿠키에 UserVO 객체를 저장하는 것이 아니고!!!!(사실 쿠키는 문자열만 저장되기 때문에 가능하지도 않습니다.)

현재 브라우저의 세션 id를 저장해 놓는 겁니다.


그럼... 쿠키에 의해 자동로그인 기간은 제어가 될 것이고... 사용자는 해당 세션 id에 대한 정보를 가지고 있어야 겠죠??

따라서, 다음으로는 DB의 userTable에 세션Id와 유효시간 정보를 담을 수 있는 컬럼을 추가하도록 합시다.


2) DB userTable에 세션Id와 유효시간을 설정할 수 있는 컬럼을 만들기


3) userMapper.xml에 작업을 합시다.

1. 로그인 성공시 sessionId와 유효시간을 저장하는 부분 작성

2. 사용자가 이전에 로그인에 성공했었는지 확인하는 부분


4) userDAO 인터페이스와 userDAOImpl 클래스를 수정합시다.


5) UserService 인터페이스와 UserServiceImpl 클래스 수정하기


6) UserController에서 로그인 성공하고 쿠키사용 체크한 경우에 사용자 테이블에 세션id와 유효시간 처리해주기

아까 쿠키를 생성해서 세션id 저장한 부분 바로 아래에다가 사용자 테이블에도 세션 id와 유효시간을 저장해 놓아야함!


이후, AuthenticationInterceptor의 preHandle() 부분에서 

세션에 UserVO 객체가 null이 아닌 경우는 로그인 되어 있는 부분이니까 그대로 처리되도록 놔두고, 세션의 UserVO 객체가

null이지만, 쿠키가 null이 아닌 경우 쿠키에서 sessionId를 꺼내와서 사용자 객체를 반환받도록 작업할 것이다.


7) AuthenticationInterceptor에서 자동 로그인의 핵심 부분을 처리하자.

AuthenticationInterceptor에서도 DB에 접근해서 처리를 해야함으로 UserService를 필드변수에 선언해주고 자동 주입을 위해

@Inject해주었다.

또한 preHandle() 메서드에서 로그인 세션이 없으면서 WebUtils를 이용해 쿠키를 가져온 뒤 

로그인 세션이 없지만, loginCookie가 존재하는 경우 웹브라우저를 새로 켜고 로그인을 하지는 않았지만 이전에 로그인 하면서

쿠키 체크를 해논 유효기간이 남아 있는 경우임으로 service.checkUserWithSessionKey() 메서드를 통해 DB에서 유효기간이 남아있고

해당 세션id를 가지고 있는 사용자 정보를 받아온다.

그리고 마지막으로 해당 사용자 정보로 세션의 login을 세팅해주면 자동로그인에 필요한 모든 작업이 완료되었다.


8) 로그아웃 처리(UserController에서...)

로그아웃 처리를 깜빡하고 마지막이랬다.. 하하하....


9) /views/board/listPge.jsp 에서 로그아웃 버튼 하나 넣기

<a href="/logout">[로그아웃]</a> 하나 추가하자 로그아웃 버튼!


10) 결과 확인하기


1) 로그인 하면서 로그인 상태를 기억하시겠습니까를 클릭하고 로그인 하는 모습

2) 로그인 한 직후 모습


3) 로그인 했음으로 글 작성 바로 잘 된다.


4) 로그아웃 버튼을 눌렀다. -> 눌렀지만, 내가 로그아웃 처리후 redirect를 listPage로 했기 때문에 다시 현재 화면이 뜸


5) 하지만 분명한건 로그아웃하고 다시 글등록 누르면 로그인 폼으로 이동된다는거~


여기까지해서 캐시 + 세션 + 인터셉터를 응용한 자동 로그인 구현을 마치겠습니다.


ps) 코드로 배우는 스프링 웹 프로젝트 책을 공부하고 정리한 내용임을 다시 한번 언급드립니다.

pf) 개인적으로 공부한 내용을 정리했습니다. 여러 블로그도 참고하였지만 주로 참고한 것은 "코드로 배우는 스프링 웹 프로젝트"(남가람북스) 책을 공부한 후 참고해 작성하였음을 미리 말씀드립니다.

[    1. 인터셉터란?    ]


특정 URI로 요청시 Controller로 가는 요청을 가로채는 역할을 한다.


[    2. Interceptor와 JSP Filter의 차이??    ]


- 공통점 : 둘 다 Controller로 들어가는 요청을 가로채 특정 작업을 하기 위한 용도로 사용된다.

- 차이점 : 케어할 수 있는 영역(범위)가 다르다. Filter는 같은 웹 어플리케이션 내에서만 접근이 가능하며,

  Interceptor의 경우 스프링에서 관리되기 때문에 스프링내의 모든 객체에 접근이 가능하다.

-> JSP Filter의 경우 주로 한글처리에 이용되고

-> Interceptor의 경우 "로그인 처리"에 이용이 된다.

- why 로그인 처리에 이용?? )

: 만약 인터셉터를 이용하지 않고, 로그인 처리를 한다면, 게시물을 작성("/board/register"), 게시물 수정("/board/modify"),

 게시물 삭제("/board/delete") 등 모든 요청마다 Controller에서 session을 통해 로그인 정보가 남아 있는지를 확인하는 코드

 를 중복해서 입력해야 할 것이다. 

 하지만!, 인터셉터를 이용하면, A, B, C 작업(A,B,C 경로로 요청)을 할 경우에는 ~~Interceptor를 먼저 수행해 session에서 

 로그인 정보가 있는지 확인해 주는 역할을 한다면, 중복 코드가 확 줄어들 수 있을 것이다. 이러한 장점 때문에 사용!


[    3. 인터셉터를 지원하는 인터페이스와 클래스, 메서드    ]


: Spring에서 인터셉터를 지원하기 위해서 

- HandlerInterceptor 인터페이스

- HandlerInterceptorAdapter 추상 클래스를 지원한다. => 요녀석은 위의 인터페이스를 사용하기 쉽게 구현해 놓은 추상클래스.


=> 이때, HandlerInterceptorAdaptor는 3가지 메서드를 제공한다. 이 3가지 메서드를 오버라이딩해서 우리가 이용할 수 있다.

1) public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)

: Controller로 요청이 들어가기 전!!에 수행된다.

 : request, response, handler 등의 매개변수를 이용가능한데 우리가 아는 HttpServletRequest, HttpServletResponse,

  이고, 나머지 하나는 이 preHandle() 메서드를 수행하고 수행될 컨트롤러 메서드에 대한 정보를 담고 있는 handle

  이다.

2) postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,

ModelAndView modelAndView)

: 컨트롤러의 메서드의 처리가 끝나 return 되고 화면을 띄워주는 처리가 되기 직전에 이 메서드가 수행된다.

: ModelAndView 객체에 컨트롤러에서 전달해 온 Model 객체가 전달됨으로 컨트롤러에서 작업 후 

: postHandle() 에서 작업할 것이 있다면 ModelAndView를 이용하면 된다.

3) afterCompletion()

: 컨트롤러가 수행되고 화면처리까지 끝난 뒤 호출된다.



[    4. 인터셉터를 이용한 로그인 구현해 보기    ]



1) 로그인 처리를 위한 DB table을 만들자

: 여기서는 mysql을 이용해서 아래와 같이 테이블을 생성하였다.

: 이와 같이 id, pw, name, point 를 갖는 테이블을 만들고, 4개 정도 회원 정보를 insert 해 놓았다.


2) 로그인에 사용될 UserVO 클래스를 정의하자.

: DAO(Data Access Object)와 Controller의 ModelAttribute 부분에 사용하기 위해 UserVO를 id와 pwd, name, point 정보 등을

 넣어 아래와 같이 만들어 보자.


3) 로그인처리를 할 MyBatis userMapper.xml 을 작성하자.

     : 살펴보면, namespace명은 고유한 값을 지정하고 select 문에 호출시 전달받은 userId와 userPw를 통해

 DB에서 select한 뒤 해당 튜플 정보를 resultType = UserVO 로 반환한다.

 이때, #{userId} 부분은 전달 받은 객체에서 getter메서드를 통해 호출됨으로 UserVO의 getUserId() 를 호출해 자동 대입이 될

 것임을 알 수 있다.


4) UserDAO 인터페이스와 이를 구현한 UserDAOImpl 클래스를 작성하자.

먼저) UserDAO

UserDAOImpl 

UserDAOImpl은 UserDAO인터페이스를 구현하였고 DAO(Data Access Object)로 DB에 직접 접근하는 처리를 담당하는 

클래스이다. 이 클래스에서는 스프링 환경설정에서 만들어 놓은 mybatis 접근 객체 : 즉, SqlSessionTemplate 객체를 

이용해야함으로 sqlSession.selectOne("MyBatis Mapper의 namespace명.지정한id",넘겨줄 데이터 객체); 를 이용해서 

MyBatis를 통해 작성한 쿼리를 실행해 UserVO 객체를 얻어오는 작업을 해준다. 이때, @Inject나 @Autowired를 통해서

자동 주입을 한다. @Inject와 @Autowired는 ByType(타입이 같은 것을 자동 대입)하는 성질이며, 환경설정 파일에서 우리가

sqlSessionTemplate 빈을 생성해 놓았음으로 자동 대입이 가능하다.(이후, 이런 설명은 생략한다.)

마찬가지로 서비스 객체에서 DAO를 자동주입할 것임으로 UserDAOImpl 클래스 위에 @Repository 어노테이션을 붙였다.

이는, 이 클래스는 DAO 클래스에요! 라는 의미 + 자동으로 new해서 이 객체를 생성해 주세요라는 의미를 가지고 있다.

이후, Service 객체에서도 @Inject를 이용해 자동 주입하기 위해 생성해 놓는다. 단, @Component, @Repoisitory, 

@Service(서비스 자동생성) 등의 어노테이션을 인식하기 위해서는 환경설정 파일에서 <context:component-scan 부분을

잘 지정해야한다. 여기서 지정한 패키지 내에서 해당 어노테이션이 탐색되기 때문이다.


여하튼...

하나의 UserVO 객체가 반환되야 함으로 selectOne() 메서드를 사용한 것이고, 여러 객체를 담고 있는 List로 반환을 받을

경우 selectList() 메서드를 호출한다. 이런 내용은 기본 적인 내용이니까... 이해가 안되면 MyBatis 연동 부분을 살펴보길 바란다.



5) UserService 인터페이스와 UserServiceImpl 클래스를 정의한다.

별거 없다... 단순한 서비스 클래스이다...

 dao와 마찬가지로 @Service 어노테이션을 사용해 이 클래스는 서비스 클래스로 사용될 녀석이에요 !! + 자동 객체 생성을 해준다.

 컨트롤러에서 사용되어야 하니까... 역시 이 서비스 클래스가 있는 패키지 경로도 <context:component-scan 이 잘 지정되어 있어야하

 는 것은 당연하다...


자... 여기까지가 기본 세팅이다... 수고했다... 이제 본격적으로 로그인과 관련된 기능을 만들어 보자.

---------------------------------------------------------------------------------------------------------------------------------

6) UserController에서 로그인 처리를 작성해 보자.

: 먼저 로그인 요청시 로그인 폼 화면을 띄워주는 부분을 작성해야 한다.

  loginForm() 부분이 바로 그 부분! RequestMapping된 메서드의 return한 부분은 환경설정 파일의 ViewResolver에 맵핑

  됨으로 loginForm.jsp 파일을 view아래에 적당한 위치에 만든다음에 return을 잘해서 http://localhost:8080/login으로 요청시

  해당 웹페이지가 뜨도록 설정한다. 주소에 url 요청시 GET 방식임으로 GET으로 설정한다.


 그 다음은, 로그인을 처리하는 부분이다. 로그인을 처리할 때 해야할 작업은 2가지이다.

1) 새로 로그인 요청이 온다면, 기존에 세션에 저장되어 있던 이전 로그인 사용자의 정보를 제거해 주어야 한다.

2) 새로 로그인 하는 사용자의 정보가 일치한다면, 해당 사용자 정보를 DB에서 가져와 세션에 저장해 놓아야 한다.

 

service.login()을 수행->dao.login()->UserMapper.xml 순으로 수행되며 id와 pw가 일치하면 사용자 정보를 담은 UserVO를 반환

할 것이고, 틀렸다면 null이 반환될 것이다. 

null 이 아닐 경우, 세션을 통해 사용자 객체를 저장해 놓는다.


이때, 마지막으로 작업해 주어야 하는 부분이 어디로 이동하느냐에 대한 정보이다.

로그인에 실패할 경우 return "redirect:/login"; 을 통해 다시 로그인 폼으로 이동시켜 주어야 할 것이고,

로그인에 성공해 session에 사용자 객체를 저장한 이후에는 return "redirect:/board/listPage" 와 같이 최초 페이지로 이동시켜주어

야 할 것이다.


로그아웃 부분은 그냥 로그아웃 url 요청이 있으면 해당 브라우저의 세션을 통째로 날려버리면 된다.

물론, session.removeAttribute('변수명") 해서 하나를 날려도 되지만, 만약 날려야하는 정보가 여러개라면 일일이 하기 힘들 수 있

기 때문에 invalidate() 를 많이 사용한다.


===> 여기까지 작업을 완료했다면 로그인, 로그아웃 기능은 다 만든 것이다. 하지만, 가장 중요하게 처리해 주어야 할 부분이

남아 있다.!!


** 게시물을 등록, 수정, 삭제 등을 할 때는 반드시 로그인 된 사용자만 할 수 있도록 "인터셉터"를 적용하는 것 말이다.!! ***


7) AuthenticationInterceptor 를 만들어 게시물 등록, 수정, 삭제 요청 전에 로그인 여부를 확인하는 인터셉터 클래스를 작성하자.

먼저 Interceptor를 구현하기 위해서는 가장 쉽게 사용할 수 있는 것이 HandlerInterceptor 인터페이스를 구현해 놓은 HandlerInterceptorAdapter이다.


이를 extends해서 preHandle()과 postHandle() 을 오버라이딩한다. shift + alt + s + v 를 하면... 알죠...?

보통, 세션에 로그인 사용자 정보를 컨트롤러에서 저장하지 않고 여기서 저장할 것이었으면 postHandle() 부분에서 수행해 주어야 겠

지만, 우리는 간편하게.....(그냥 귀찮았어...ㅠㅠ) 컨트롤러에서 다 해버렸음으로 인터셉터 클래스에서는 preHandle() 메서드만 필요하다.


그럼, preHandle() 에서는 request에서 세션 객체를 가지고 오고 세션에서 login변수에 사용자 정보 객체가 담겨 있나 확인을 한다.

없으면?? response.sendRedirect("uri") 를 통해 uri경로로 날려버리면 된다. 사용자 정보가 세션에 없다는건 지금 로그인이 되어 있지

않다는 것을 의미함으로 response.sendRedirect("/login") 을 통해서 다시 로그인하는 폼으로 이동시켜버리면 된다.

단, 이때 이 메서드를 수행 후 return false를 한 이유는, 이 preHandle() 메서드의 return 이 의미하는 바는 true일 경우 preHandle() 

메서드를 수행한 뒤에 본래 요청한 Controller 를 수행한다는 의미이고 false를 주면 수행하지 않는다는 의미를 담고 있다.

따라서, 로그인 안된 상태에서 요청시 해당 컨트롤러로 요청이 가지 않도록 false를 해주자.


반대로 사용자 정보가 세션에 담겨 있는 경우에는 단순히 return true를 해줘서 본래 사용자가 요청했던 Controller의 RequestMapping부분이 수행될 수 있도록 해주면 된다.


8) 가장 중요한... servlet-context.xml에 인터셉터 설정 정보를 등록해야한다..


: 일단, 인터셉터 자체는 웹 관련 설정임으로 root-context.xml이 아닌 servlet-context.xml에 작성하자. 물론, 요소마다 설정파일을

  다 나누어서 설정하셨다면... 본인에 맞는 환경설정 파일에서 작업을 해주세요...


이때, 인터셉터 빈 객체를 생성해 놓고, 인터셉터 정보를 등록할 때 해당 객체와 어떤 url이 요청시에 인터셉터가 작동할지를

설정해 놓아야 한다.


여기서는 필요한 uri를 하나하나 지정했는대 이렇게 하지 않고, 전체 경로 /** 로 잡아 놓고

<exclue-mapping path="예외url" /> 을 지정해서 모든 페이지에 인터셉터를 적용하지만 예외는 ~다 라고 지정할 수도 있다.



9) 죄송합니다... 기존거에서 빠트린 부분이 있어 이부분 추가합니다. -> "자기 글만 수정, 삭제가 가능하도록"


여러분... 제가 이 부분을 빠트렸었내요 ㅠㅠ... 지금은 로그인만하면, 모든 게시글들을 다 삭제하고 수정할 수 있죠...

그러면 큰일나자나요... ㅠㅠ

자기가 작성한 글만 수정, 삭제를 할 수 있도록 구성해야합니다. 어떻게하면 좋을까요... 여러 방법이 있겠지만 전 2가지

정도 생각이 나내요... 인터셉터의 postHandler() 메서드를 통해서 요청 uri를 분석한다음 수정일 경우 postHandler()에서 전달

받은 ModelAndView로 사용자 정보를 전달받아서 하는 방법이 있겠고... 근대 이건 좀 귀찮고...


사실... 훨씬 간단한 방법이 있어요... 뷰 페이지에서 처리하면 말이죠...


머냐....


일단, 수정버튼과 삭제 버튼은 해당 글을 클릭했을 때만 뜬답니다. 그렇죠? 그러면 글을 읽는 /views/board/read.jsp 페이지에서

<c:if test="${login.userId eq boardVO.writer}">


</c:if>

인 경우에만 수정하고 삭제 버튼이 보이도록하면 어떨까요? 세션에 login 변수로 userVO 객체를 담고 있으니까

현재 로그인한 사용자 userId와 현재 게시글의 boardVO 에서 writer를 비교해서 eq(같은경우)만 수정과 삭제 버튼이 보이고

목록으로가는 버튼은 항상 보이도록 하는거죠.

그러면 별다른 처리없이 안보이면 사용자는 수정과 삭제버튼은 클릭조차 할 수 없기에 모든 처리가 끝나게 되죠..

아래처럼 /views/board/read.jsp 페이지를 수정합시다.

아직 댓글에 대한 처리를 안했지만, 댓글도 마찬가지로 이와 같은 방식으로 해주면 되겠죠? 


10) 결과를 확인해 보자!!!

메인페이지)



로그인이 되어 있지 않은 상태에서 글등록을 누르면, 로그인 폼 페이지로 이동된다.


로그인에 성공하면, 게시글 첫 페이지로 이동된다.



이제 로그인이 된 상태임으로 "글등록"을 누르면 정상적으로 글 등록 폼으로 이동된다.


그리고 마지막으로, 로그인은 한 상태지만 다른 사람의 글을 클릭했을 때 수정과 삭제가 보이지 않고 목록으로 버튼만 보이는 화면입니다.



이상입니다.


열심히 끝까지 읽어주셔서 감사합니다.



+ Recent posts