소프트웨어 패턴은 목적에 따라 생성 패턴, 구조 패턴, 행위 패턴으로 나뉜다.

오늘은 생성 패턴의 싱글톤 패턴에 대해 공부해보도록 하겠다.

1. Singleton Pattern 사용 이유?


싱글톤 패턴(Singleton Pattern)

    - 프로그램내에서 하나의 인스턴스만 만들어서, 언제 어디서든 그 인스턴스에 접근할 수 있도록 하기 위한 패턴

    - 커넥션 풀이나(DBCP) 환경 설정 클래스 같은, 하나만 사용되어야 하는 객체를 만들때 유용하다.

    - 생성자를 private로 선언하여 상속이 불가능함을 지정하기도 한다. 

2. Singleton Pattern 예제


가장 기본적인 싱글톤 패턴의 구현부에서 주목할 부분은 private 제약이다. 이는 하나의 인스턴스를 유지하고 new로
생성하여 사용할 수 없게한다. 


Lazy initialization

public class Singletone1 {
	
	private static Singletone1 singletone;
	
	public static Singletone1 getInstance() {
		
		if ( null == singletone ) {
			singletone = new Singletone1();
			return singletone;
		}
		
		return singletone;
	}
}


해당 laze initialization은 필요할때 인스턴스를 생성시는 것이 핵심이다.

Singletone1 객체는 getInstance method 내, if문으로 null인 경우에만 생성하였다.

이는 최초 사용할때만 인스턴스화 시키기때문에 객체가 heap으로 올라갈때의 시간과 비용을

줄일 수 있다고 한다. 하지만 문제는 프로그램이 multi thread 환경일때, 위와 같은 방법은 잠재적인 문제를 가지고 있다.

그 이유는 getInstance() method를 호출하면 인스턴스가 두번 생성될 위험이 있기 때문이다.


그렇다면 위와 같은 잠재적인 위험에서 안전한 코드를 작성하려면 어떻게 해야될까?

첫번째로는 getInstance() method를 동기화(synchronized) 시키는 것이다. 



Synchronized(해결1)

public class Singletone2 {
	
	private static Singletone2 singletone;
	
	public synchronized static Singletone2 getInstance() {
		
		if ( null == singletone ) {
			singletone = new Singletone2();
			return singletone;
		}
		
		return singletone;
	}
}

이로써 여러개의 thread들이 동시에 접근해서 인스턴스를 생성하는 위험은 피했지만, 

여러 thread들이 getInstance() method를 호출하게 되면 높은 비용(cost)로 인해 프로그램 전반적으로

성능 저하가 발생한다.



LazyHolder(해결2)

public class Singletone3 {
	
	public Singletone3() {}
	
	private static class LazyHolder {
		static final Singletone3 SINGLETONE3 = new Singletone3();
	}
	
	public static Singletone3 getInstance() {
		return LazyHolder.SINGLETONE3;
	}
}

위 코드는 LazyHolder를 통해 싱글톤의 동시성 문제를 해결하는 방법이다.

이는 자바 버전에 상관없으며, 상기 synchronized 키워드도 사용하지 않는다

여기선 Singletone3 class는 클래스 로드 시점에서 초기화 되지만, static class로 정의된 LazyHolder는 초기화 되지않고

static method인 getInstance()가 호출될때에만 실행된다. 이는 JVM에서 클래스를 로딩하고 초기화할때 원자성이

보장되기 때문에 thread safety한 방법이다.




참고 사이트 : https://tomcat.apache.org/tomcat-5.5-doc/servletapi/javax/servlet/http/HttpServletRequest.html


클라이언트의 요청 정보를 확인하기 위해서  Interface HttpServletRequest 객체를 사용합니다.

보통 Spring MVC 구조에서는 컨트롤러나 인터셉터에서 parameter를 추가하여 사용하는 편인데 그 내용은 아래 코드에서

확인하도록 하겠습니다.


  예제코드


sampleContorller.java

@RequestMapping(value = "/getDomainInfo/example/test", method = RequestMethod.GET) public ModelAndView getDomainInfo(HttpServletRequest request) { ModelAndView modelAndView = new ModelAndView(); modelAndView.addObject("RequestURL", request.getRequestURL()); modelAndView.addObject("RequestURI", request.getRequestURI()); modelAndView.addObject("ContextPath", request.getContextPath()); modelAndView.addObject("ServletPath", request.getServletPath()); modelAndView.addObject("QueryString", request.getQueryString()); modelAndView.addObject("ServerName", request.getServerName()); modelAndView.addObject("ServerPort", request.getServerPort()); modelAndView.addObject("Method", request.getMethod()); modelAndView.addObject("Referer", request.getHeader("referer")); modelAndView.setViewName("test"); return modelAndView; }

test.jsp

<div> <h3>RequestURL</h3> <ul> <li> 요청정보 : ${RequestURL } </li> <li> 리턴정보 : ${RequestURL } </li> </ul> <h3>RequestURI</h3> <ul> <li> 요청정보 : ${RequestURI } </li> <li> 리턴정보 : ${RequestURI } </li> </ul> <h3>ContextPath</h3> <ul> <li> 요청정보 : ${ContextPath } </li> <li> 리턴정보 : ${ContextPath } </li> </ul> <h3>ServletPath</h3> <ul> <li> 요청정보 : ${ServletPath } </li> <li> 리턴정보 : ${ServletPath } </li> </ul> <h3>QueryString</h3> <ul> <li> 요청정보 : ${QueryString } </li> <li> 리턴정보 : ${QueryString } </li> </ul> <h3>ServerName</h3> <ul> <li> 요청정보 : ${ServerName } </li> <li> 리턴정보 : ${ServerName } </li> </ul> <h3>ServerPort</h3> <ul> <li> 요청정보 : ${ServerPort } </li> <li> 리턴정보 : ${ServerPort } </li> </ul> <h3>Method</h3> <ul> <li> 요청정보 : ${Method } </li> <li> 리턴정보 : ${Method } </li> </ul> <h3>Referer</h3> <ul> <li> 요청정보 : ${Referer } </li> <li> 리턴정보 : ${Referer } </li> </ul> </div>


  결과화면


http://localhost:8080/getDomainInfo/example/test?ver=2017&test_id=test 로 요청하여 jsp페이지에 출력된 결과화면입니다.


RequestURL :

 쿼리스트링을 제외한 Protocol(http)+ServerName(도메인)+Port(8080)+ContextPath+ServletPath를 반환합니다.

- 요청정보 : http://localhost:8080/getDomainInfo/example/test

- 리턴정보 : http://localhost:8080/getDomainInfo/example/test


RequestURI: 

 프로젝트의 Context Path + Servlet Path, 즉 전체 경로를 반환합니다.

- 요청정보 : /getDomainInfo/example/test

- 리턴정보 : /getDomainInfo/example/test


ContextPath :

 프로젝트의 Context path를 반환합니다.

- 요청정보 : /getDomainInfo

- 리턴정보 : /getDomainInfo


ServletPath :

 프로젝트의 Servlet path를 반환합니다.

- 요청정보 : /example/test

- 리턴정보 : /example/test


QueryString :

 쿼리 스트링(Query String)의 문자열을 반환합니다.

- 요청정보 : ver=2017&test_id=test

- 리턴정보 : ver=2017&test_id=test


ServerName :

 ServerName(도메인)을 반환합니다.

- 요청정보 : localhost

- 리턴정보 : localhost


ServerPort :

 ServerPort 정보를 반환합니다.

- 요청정보 : 8080

- 리턴정보 : 8080


Method: 

 get / post 정보를 반환합니다.

- 요청정보 : GET

- 리턴정보 : GET


Referer :

링크를 이동할 경우 이동하기 전에 링크를 포함하고 있던 페이지의 URL을 반환합니다.(링크를 클릭한 경우에만 출력)

- 요청정보 : (공백)

- 리턴정보 : (공백)