S3 mount 방법에는 S3QL, S3FS, S3Backer 등 여러가지가 있습니다.
그중에서 S3FS를 이용하여 처리를 진행하였음.
latest S3FS 설치
sudo apt-get install build-essential git libfuse-dev libcurl4-openssl-dev libxml2-dev mime-support automake libtool
sudo apt-get install pkg-config libssl-dev
git clone https://github.com/s3fs-fuse/s3fs-fuse
cd s3fs-fuse/
./autogen.sh
./configure --prefix=/usr --with-openssl
make
sudo make install
마운트 처리
- passwd-s3fs 파일 생성
touch /etc/passwd-s3fs && chmod 640 /etc/passwd-s3fs && echo 'AKIAJDDVU52VB4IXNV4Q:hz9DUHicEeEbOXgdRyleHiO+/Ah/9asUIUAXvjJf' > /etc/passwd-s3fs
- 해당 디렉토리 마운트 처리
mkdir /mnt/s3
sudo s3fs daesang-storage -o use_rrs=1,allow_other,uid=1000,gid=111,default_acl=public-read /mnt/s3
fusermount -u /mnt/s3
2015년 12월 20일 일요일
2015년 11월 30일 월요일
HttpLogginFilter 관련 - HttpRequest logging 처리
위 그림과 같이 filter의 경우 request와 reponse에 대해서 처리를 해주지만 interceptor처럼 prehandle/posthandle/afterCompletion 와 같이 실행되는 시점을 나눠서 처리할 수가 없다.
그래서 나는 doFilter() 메쏘드가 request와 response 시점에 두번 실행되는 것으로 생각했다.
하지만 debug모드를 이용하여 테스트 한 결과 chain.doFilter(,); 메쏘드의 코드 라인 기준으로 먼저 선언된 코드는 request에 실행되고 chain.doFilter(,); 메쏘드 이후 작성된 코드는 response처리 시점에 실행되는 것으로 확인 되었다.
filter 자체가 쓰레드 기반으로 실행이 되고 dofilter기점으로 해당 쓰레드는 sleep상태를 유지하다가 servelt이 실행되고 결과를 리턴하여 dispatcherServlet이 filter를 깨워주는 형태로 WAS가 처리해주는 것으로 예상된다.
- 주의점 exception 발생시 Interceptor과 filter 를 통하지 않으므로 참고
해당 필터를 통해서 HttpRequest에서 getInputStream을 하게 되면 해당 Request에서 inputStream의 데이타 없어지므로 주의 해야 한다.
HttpServletRequest의 InputStream은 한 번 읽으면 다시 읽을 수 없습니다. 톰캣 개발자님들께서 친히 막아두셨기 때문이죠! 만약 Interceptor나 Filter에서 InputStream을 읽게되면, 이후 Spring이 Converter를 이용해 Json 데이터를 바인딩 처리할 때 아래와 같은 에러를 만날 수 있습니다. 불쌍한 Spring이 이미 읽어버린 InputStream을 다시 읽으려고 시도하다가 슬픈 에러를 뱉어내는거죠.
java.lang.IllegalStateException: getReader() has already been called for this request
org.springframework.http.converter.HttpMessageNotReadableException: Could not read JSON: Stream closed; nested exception is java
기본적으로 HttpServletRequestWrapper을 상속 받아서 처리를 하나 Multipart처리를 위하여 AbstractMultipartHttpServletRequest 를 상속 받고 StandardMultipartHttpServletRequest 클래스의 Multipart 처리 부분의 코드를 그대로 copy해서 구현한다.
HttpRequestWrapper.java
/**
* HttpRequestWrapper
* Multipart 처리를 위한 AbstractMultipartHttpServletRequest 상속으로 처리 변경
* @author mike
* @date 2017. 8. 8.
* @version
*/
public class HttpRequestWrapper extends AbstractMultipartHttpServletRequest {
private byte[] bodyData;
public HttpRequestWrapper(HttpServletRequest request) throws IOException {
this(request, false);
InputStream is = super.getInputStream();
bodyData = IOUtils.toByteArray(is);
}
public HttpRequestWrapper(HttpServletRequest request, boolean lazyParsing) throws MultipartException {
super(request);
if (!lazyParsing) {
parseRequest(request);
}
}
//InputStream에 대해서 지속적으로 사용할 수 있도록 수정
@Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream bis = new ByteArrayInputStream(bodyData);
return new ServletInputImpl(bis);
}
//Multipart 처리 부분
private static final String CONTENT_DISPOSITION = "content-disposition";
private static final String FILENAME_KEY = "filename=";
private Set<String> multipartParameterNames;
private void parseRequest(HttpServletRequest request) {
try {
Collection<Part> parts = request.getParts();
this.multipartParameterNames = new LinkedHashSet<String>(parts.size());
MultiValueMap<String, MultipartFile> files = new LinkedMultiValueMap<String, MultipartFile>(parts.size());
for (Part part : parts) {
String filename = extractFilename(part.getHeader(CONTENT_DISPOSITION));
if (filename != null) {
files.add(part.getName(), new StandardMultipartFile(part, filename));
}
else {
this.multipartParameterNames.add(part.getName());
}
}
setMultipartFiles(files);
}
catch (Exception ex) {
throw new MultipartException("Could not parse multipart servlet request", ex);
}
}
private String extractFilename(String contentDisposition) {
if (contentDisposition == null) {
return null;
}
// TODO: can only handle the typical case at the moment
int startIndex = contentDisposition.indexOf(FILENAME_KEY);
if (startIndex == -1) {
return null;
}
String filename = contentDisposition.substring(startIndex + FILENAME_KEY.length());
if (filename.startsWith("\"")) {
int endIndex = filename.indexOf("\"", 1);
if (endIndex != -1) {
return filename.substring(1, endIndex);
}
}
else {
int endIndex = filename.indexOf(";");
if (endIndex != -1) {
return filename.substring(0, endIndex);
}
}
return filename;
}
@Override
protected void initializeMultipart() {
parseRequest(getRequest());
}
@Override
public Enumeration<String> getParameterNames() {
if (this.multipartParameterNames == null) {
initializeMultipart();
}
if (this.multipartParameterNames.isEmpty()) {
return super.getParameterNames();
}
// Servlet 3.0 getParameterNames() not guaranteed to include multipart form items
// (e.g. on WebLogic 12) -> need to merge them here to be on the safe side
Set<String> paramNames = new LinkedHashSet<String>();
Enumeration<String> paramEnum = super.getParameterNames();
while (paramEnum.hasMoreElements()) {
paramNames.add(paramEnum.nextElement());
}
paramNames.addAll(this.multipartParameterNames);
return Collections.enumeration(paramNames);
}
@Override
public Map<String, String[]> getParameterMap() {
if (this.multipartParameterNames == null) {
initializeMultipart();
}
if (this.multipartParameterNames.isEmpty()) {
return super.getParameterMap();
}
// Servlet 3.0 getParameterMap() not guaranteed to include multipart form items
// (e.g. on WebLogic 12) -> need to merge them here to be on the safe side
Map<String, String[]> paramMap = new LinkedHashMap<String, String[]>();
paramMap.putAll(super.getParameterMap());
for (String paramName : this.multipartParameterNames) {
if (!paramMap.containsKey(paramName)) {
paramMap.put(paramName, getParameterValues(paramName));
}
}
return paramMap;
}
@Override
public String getMultipartContentType(String paramOrFileName) {
try {
Part part = getPart(paramOrFileName);
return (part != null ? part.getContentType() : null);
}
catch (Exception ex) {
throw new MultipartException("Could not access multipart servlet request", ex);
}
}
@Override
public HttpHeaders getMultipartHeaders(String paramOrFileName) {
try {
Part part = getPart(paramOrFileName);
if (part != null) {
HttpHeaders headers = new HttpHeaders();
for (String headerName : part.getHeaderNames()) {
headers.put(headerName, new ArrayList<String>(part.getHeaders(headerName)));
}
return headers;
}
else {
return null;
}
}
catch (Exception ex) {
throw new MultipartException("Could not access multipart servlet request", ex);
}
}
/**
* Spring MultipartFile adapter, wrapping a Servlet 3.0 Part object.
*/
private static class StandardMultipartFile implements MultipartFile {
private final Part part;
private final String filename;
public StandardMultipartFile(Part part, String filename) {
this.part = part;
this.filename = filename;
}
@Override
public String getName() {
return this.part.getName();
}
@Override
public String getOriginalFilename() {
return this.filename;
}
@Override
public String getContentType() {
return this.part.getContentType();
}
@Override
public boolean isEmpty() {
return (this.part.getSize() == 0);
}
@Override
public long getSize() {
return this.part.getSize();
}
@Override
public byte[] getBytes() throws IOException {
return FileCopyUtils.copyToByteArray(this.part.getInputStream());
}
@Override
public InputStream getInputStream() throws IOException {
return this.part.getInputStream();
}
@Override
public void transferTo(File dest) throws IOException, IllegalStateException {
this.part.write(dest.getPath());
}
}
}
class ServletInputImpl extends ServletInputStream {
private InputStream is;
public ServletInputImpl(InputStream bis) {
is = bis;
}
@Override
public int read() throws IOException {
return is.read();
}
@Override
public int read(byte[] b) throws IOException {
return is.read(b);
}
@Override
public boolean isFinished() {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean isReady() {
// TODO Auto-generated method stub
return false;
}
@Override
public void setReadListener(ReadListener arg0) {
// TODO Auto-generated method stub
}
2015년 11월 26일 목요일
우분투 부팅시 자동 실행
1. /etc/init.d/ 경로 실행할 Script 파일 생성
2. chmod +x 스크립트 명령으로 실행가능토록 명령어 실행
3. sudo update-rc.d 스크립트 defaults 명령을 실행 - 부팅시 실행 되도록 설정
2. chmod +x 스크립트 명령으로 실행가능토록 명령어 실행
3. sudo update-rc.d 스크립트 defaults 명령을 실행 - 부팅시 실행 되도록 설정
2015년 10월 21일 수요일
vsftpd 설치 - 우분투
//설치
sudo apt-get install vsftpd
기본 포트: 21
//설정
설정 파일 /etc/vsftpd.conf
아래 내용 추가
pasv_enable=YES
pasv_promiscuous=YES
#pasv_address=172,51,16,104,195,177, 127.0.0.1, localhost
pasv_min_port=50000
pasv_max_port=50100
pasv_addr_resolve=YES
passive 모드를 이용하기 위해서는 pasv_min_port - pasv_max_port 해당 포트 범위를 방화벽 오픈을 해주어야 한다.
//ftp 실행
sudo apt-get install vsftpd
기본 포트: 21
//설정
설정 파일 /etc/vsftpd.conf
아래 내용 추가
pasv_enable=YES
pasv_promiscuous=YES
#pasv_address=172,51,16,104,195,177, 127.0.0.1, localhost
pasv_min_port=50000
pasv_max_port=50100
pasv_addr_resolve=YES
passive 모드를 이용하기 위해서는 pasv_min_port - pasv_max_port 해당 포트 범위를 방화벽 오픈을 해주어야 한다.
//ftp 실행
sudo service vsftpd start
2015년 9월 10일 목요일
개발서버 환경 설정
1. tomcat설정 및 설정
- 설치
: sudo apt-get install tomcat7
: sudo apt-get install tomcat7-admin tomcat7-examples tomcat7-docs <-- jenkins 자동배포를 위하여 해당 패키지도 설치
2. jenkins설치 및 설정
3. nexus설치 및 설치
4. WAS 서버별 service 설정
5. 프로젝트 설정
- conf설정
: server.xml
: tomcat-users.xml
아래의 내용 추가
<role rolename="manager-script"/>
<role rolename="manager-gui"/>
<role rolename="admin"/>
<user name="admin" password="tomcat!@#$" roles="admin,manager-gui,manager-script"/>
6.기타 설정
- Hostname 설정
: sudo vi /etc/hostname 원하는 호스트명으로 변경
: /etc/hosts 해당 hostname등록
: sudo hostname -F /etc/hostname 해당 호스트명 반영
- 설치
: sudo apt-get install tomcat7
: sudo apt-get install tomcat7-admin tomcat7-examples tomcat7-docs <-- jenkins 자동배포를 위하여 해당 패키지도 설치
2. jenkins설치 및 설정
3. nexus설치 및 설치
4. WAS 서버별 service 설정
5. 프로젝트 설정
- conf설정
: server.xml
: tomcat-users.xml
아래의 내용 추가
<role rolename="manager-script"/>
<role rolename="manager-gui"/>
<role rolename="admin"/>
<user name="admin" password="tomcat!@#$" roles="admin,manager-gui,manager-script"/>
- Hostname 설정
: sudo vi /etc/hostname 원하는 호스트명으로 변경
: /etc/hosts 해당 hostname등록
: sudo hostname -F /etc/hostname 해당 호스트명 반영
nexus, Jenkins 설치 및 설정 - 우분투
nexus 설치
- tar파일 다운로드 및 압축 풀기
sudo wget http://www.sonatype.org/downloads/nexus-latest-bundle.tar.gz
sudo tar -xvzf nexus-latest-bundle.tar.gz
- bin 디렉토리 nexus 실행파일 /etc/init.d로 이동 및 수정
sudo cp /srv/service/nexus/bin/nexus /etc/init.d/
sudo vi /etc/init.d/nexus
RUN_AS_USER=root <-- 추가 루트로 실행할 수 있도록 처리
- port 변경
: port설정 nexus설치 디렉토리 conf의 nexus.properties의 port값 변경
- security 설정
: http://54.92.56.233:6001/nexus/ 접속 (default ID: admin/admin123)
: 패스워드 수정 및 user 생성
Authentication 설정
jenkins 설치
1. /etc/apt/sources.list 파일에 deb http://pkg.jenkins-ci.org/debian binary/ 추가
2. apt-get update
3. apt-get install jenkins
4. port 설정 - vi /etc/default/jenkins port변경 8088
5. jenkins 실행 - sudo service jenkins start
6. maven 사용의 경우 settings.xml 파일 작성 및 프로젝트 pom.xml 설정
jenkins 설정
2. 플러그인 및 환경 설정
- tar파일 다운로드 및 압축 풀기
sudo wget http://www.sonatype.org/downloads/nexus-latest-bundle.tar.gz
sudo tar -xvzf nexus-latest-bundle.tar.gz
- bin 디렉토리 nexus 실행파일 /etc/init.d로 이동 및 수정
sudo cp /srv/service/nexus/bin/nexus /etc/init.d/
sudo vi /etc/init.d/nexus
RUN_AS_USER=root <-- 추가 루트로 실행할 수 있도록 처리
- port 변경
: port설정 nexus설치 디렉토리 conf의 nexus.properties의 port값 변경
- security 설정
: http://54.92.56.233:6001/nexus/ 접속 (default ID: admin/admin123)
: 패스워드 수정 및 user 생성
Authentication 설정
jenkins 설치
1. /etc/apt/sources.list 파일에 deb http://pkg.jenkins-ci.org/debian binary/ 추가
2. apt-get update
3. apt-get install jenkins
4. port 설정 - vi /etc/default/jenkins port변경 8088
5. jenkins 실행 - sudo service jenkins start
6. maven 사용의 경우 settings.xml 파일 작성 및 프로젝트 pom.xml 설정
jenkins 설정
1. 계정 관련 설정
- 인터넷 브라우져를 통해서 변경한 port로 http://192....:9000/jenkins로 접속
- 인터넷 브라우져를 통해서 변경한 port로 http://192....:9000/jenkins로 접속
- 왼쪽 메뉴에서 Jenkins 관리 -> Configure Global Security선택
- enable security에 체크
- 사용자의 가입을 허용
- Matrix-based security를 선택 한후, User/group to add 에 admin을 입력하고 Add 버튼 클릭.
- 그러면 matrix에 admin이 추가되는데, 맨 오른쪽에 버튼을 클릭 하면 모든 권한이 체크 된다.
- 적용 후 오른쪽 위에 있는 가입버튼을 통해서 기존에 추가한 admin계정에 대해서 동일한 이름으로 가입을 진행 한다.
2. 플러그인 및 환경 설정
1. Jenkins관리 -> 플러그인 관리 -> 설치가능
필요한 항목의 플러그인을 설치해준다.
GIt Plugin, deploy container 설치를 하고 재시작을 진행한다.
2. 다시 Jenkins관리 -> 시스템 설정으로 들어간다.
JDK 설정을 해준다.
git 설정을 해준다. package로 설치하면 git이란 명령어로 어디서든 실행이 가능하므로 명령어에 git이라고 입력하고 저장해준다.
그외 필요한 부분에 대해서 설정을 처리하고 진행한다.
프로젝트 설정
소스코드 관리 선택 및 설정
bitbucket 이용 Credentials은 패스워드로 처리
build하기 전의 설정 처리
excute shell을 이용하여 기존 디렉토리 삭제 및 액션 추가
"Publish Over SSH"플러그인을 설치하여 ssh접속을 통하여 명령어를 실행
이로써 프로젝트 설정 완료
jenkins설정을 진행하기 전에 프로젝트에 대한 자동 배포를 위해서
tomcat-docs 와 tomcat-admin 패키지를 설정하고
tomcat-users.xml에 해당 내용을 추가 하도록 한다.
<role rolename="manager-script"/>
<role rolename="manager-gui"/>
<role rolename="admin"/>
<user name="admin" password="tomcat!@#$" roles="admin,manager-gui,manager-
프로젝트 설정
소스코드 관리 선택 및 설정
bitbucket 이용 Credentials은 패스워드로 처리
build하기 전의 설정 처리
excute shell을 이용하여 기존 디렉토리 삭제 및 액션 추가
"Publish Over SSH"플러그인을 설치하여 ssh접속을 통하여 명령어를 실행
빌드후 deploy처리 설정 추가
이로써 프로젝트 설정 완료
jenkins설정을 진행하기 전에 프로젝트에 대한 자동 배포를 위해서
tomcat-docs 와 tomcat-admin 패키지를 설정하고
tomcat-users.xml에 해당 내용을 추가 하도록 한다.
<role rolename="manager-script"/>
<role rolename="manager-gui"/>
<role rolename="admin"/>
<user name="admin" password="tomcat!@#$" roles="admin,manager-gui,manager-
manage모쥴을 사용하기 위한 설정
- conf/Catalina 디렉토리 설정 및 tomcat-users.xml 설정
- work/catalina.policy 및 Catalina 디렉토리 설정
5. settings.xml 및 pom 작성
settings.xml 설정
================================================
<settings>
<servers>
<server>
<id>nexus</id>
<username>nexus</username>
<password>nexus</password>
</server>
</servers>
</settings>
pom.xml
=================================================
<distributionManagement>
<!-- use the following if you're not using a snapshot version. -->
<repository>
<id>nexus</id>
<name>RepositoryProxy</name>
<url>${deploy.nexus.uri}/content/repositories/releases</url>
</repository>
<snapshotRepository>
<id>nexus</id>
<name>RepositoryProxy</name>
<url>${deploy.nexus.uri}/content/repositories/snapshots</url>
</snapshotRepository>
</distributionManagement>
================================================
- conf/Catalina 디렉토리 설정 및 tomcat-users.xml 설정
- work/catalina.policy 및 Catalina 디렉토리 설정
5. settings.xml 및 pom 작성
settings.xml 설정
================================================
<settings>
<servers>
<server>
<id>nexus</id>
<username>nexus</username>
<password>nexus</password>
</server>
</servers>
</settings>
pom.xml
=================================================
<distributionManagement>
<!-- use the following if you're not using a snapshot version. -->
<repository>
<id>nexus</id>
<name>RepositoryProxy</name>
<url>${deploy.nexus.uri}/content/repositories/releases</url>
</repository>
<snapshotRepository>
<id>nexus</id>
<name>RepositoryProxy</name>
<url>${deploy.nexus.uri}/content/repositories/snapshots</url>
</snapshotRepository>
</distributionManagement>
================================================
2015년 8월 19일 수요일
ERWIN logic 컬럼명을 comment로 sql문 생성하기
physical mode에서 Model > Domain Dictionary 선택
1) Domain : _default_ 선택
2) Comment tab > Comment Inherited by Col : %AttName 입력
3) OK 버튼 클릭
1) Domain : _default_ 선택
2) Comment tab > Comment Inherited by Col : %AttName 입력
3) OK 버튼 클릭
Pre & Post 등록
2)Database -> Pre & Post Script -> Model-Level 을 선택3)아래의 스크립트를 추가
%ForEachTable() {COMMENT ON TABLE %TableName IS '%EntityName';%ForEachColumn() {COMMENT ON COLUMN %TableName.%ColName IS '%AttName';}}sql문 생성시 Schema 옵션에서 Post-Script 를 선택하고 Generate 혹은 Preview 를 선택하면 Comment 스크립트가 생성된다.
2015년 8월 17일 월요일
servlet에 대한 고찰(쓰레드)
서블릿... 모두가 서블릿은 쓰레드로 처리 된다고 알고 있다.
서블릿의 생명 주기는 아래와 같이 쓰레드와 동일하게 처리 된다라고 알고 있다.
요청의 들어오면,
- 서블릿 인스턴스가 존재하지 않을 경우,
1. 서블릿의 인스턴스를 생성한다.
2. init() 메서드를 통해 초기화 한다.
- 서블릿 인스턴스가 존재할 경우,
- 각 요청에 대해 서블릿 인스턴스의 스레드를 생성해서 service() 메서드를 호출한다.
- 서블릿 인스턴스를 삭제하게 될 경우, destroy() 메서드를 실행한다.
'서블릿 인스턴스의 스레드'를 생성해서 service() 메서드를 호출한다...
아무리 라이브러리를 까서 서블릿 API 를 아무리 뒤져봐도 Servlet 클래스가 Thread 를 상속받거나 Runnable 을 구현하는 게 보이지 않는다.
애초부터 서블릿은 쓰레드로 구현된 것이 아닌 것이였다.
서블릿 인스턴스의 스레드를 생성하는 게 아니었다.
요청이 들어오면 WAS(Web Application Server)에서 스레드를 생성하여
서블릿 인스턴스의 service() 메서드를 실행한다.
즉, 생성되는 스레드는 WAS 의 쓰레드였던 거다.
WAS의 쓰레드 풀을 이용하여 쓰레드를 생성하고 해당 쓰레드에 서블릿을 할당하여 싱글톤의 servlet 클래스의 service메쏘드를 실행하여 서블릿이 쓰레드로 실행되도록 한 것이였다.
2015년 8월 4일 화요일
Mysql Foreign Key 무시하고 데이타 입력
mysql> SET foreign_key_checks = 0;
- foreign key를 무시하고 입력하도록 처리
mysql> drop table TABLENAME
- 데이타 입력 or 테이블 삭제
mysql> SET foreign_key_checks = 1;
- 다시 원상 복구
- foreign key를 무시하고 입력하도록 처리
mysql> drop table TABLENAME
- 데이타 입력 or 테이블 삭제
mysql> SET foreign_key_checks = 1;
- 다시 원상 복구
2015년 7월 21일 화요일
디자인패턴1. Strategy Pattern
디자인 패턴에 대해서 좀 더 몸에 익히기 위해서 분석을 하고 예제를 통해서 몸에 익히도록 해본다.
- Strategy Pattern
- Strategy Pattern
- defines a family of algorithms,
- encapsulates each algorithm, and
- makes the algorithms interchangeable within that family.
위키피아의 원문 내용이다.
- 상속 알로리즘을 정의
- 각 알고리즘을 캘슐화 해라 (다른 부분에 대해서 캡슐화 하여 분리 시킴)
(1) 변하는 부분 캡슐화
- 내부적으로 변경이 가능하도록 알고리즘을 구성해라
Spring의 DI개념이 해당 패턴을 이용하여 만들어 구성한 것이라고 볼 수 있다.
(1) 변하는 부분 캡슐화
변경이 필요한 부분에 대해서 interface를 통해서 행위에 대해서 정의를 하고 구현체에 역활을 넘긴다.
아래의 이미지를 참고하면
excute라는 공통된 행위에 대해서 interface로 정의를 하고 각각 다른 전략진행하는 A, B를 나눈다.
해당 strategy 를 이용하여 다른 객체에서 excute를 실행하면 실행하는 객체는 실제 excute의 각기 다른 행위에 대해서는 인지하지 않고 excute라는 행위의 결과에 대해서 처리를 할수 있도록 한다.
(2) 인터페이스에 위임
Customer에서 가격 측정을 위한 데이타는 BillingStrategy라는 Interface를 위임하고
실제 특정 상황에 따라 각각의 맞는 가격전략을 위임하도록 한다.
Customer에서 가격 측정을 위한 데이타는 BillingStrategy라는 Interface를 위임하고
실제 특정 상황에 따라 각각의 맞는 가격전략을 위임하도록 한다.
아래는 wiki예제를 클래스 다이어 그램으로 표현하였다.
Customer는 BillingStrategy를 가지고 있으면 printBill을 할때 BillingStrategy의 getActPrice를 통하여 실제 가격을 가져올때 여러가지 가격 전략에 따라 가격이 다르게 측정된다.
이때 BillingStrategy interface를 구현하여 여러가지 가격 전략을 생성하고 Customer는 상황에 맞는 전략을 가져와 가격을 측정하도록 한다.
이와 같이 특정 행위에 대해서 interface를 통하여 정의를 하고 구현체를 통해서 구현을 함으로써 각 가격을 가져오는 것에 대해서 캡슐화를 진행하여 Customer는 해당 행위에 대해서 전혀 모르게 하고 행위만 진행할 수 있도록 설계를 해야 하는 것이 중요하다.
wiki 예제
package com.mike.designPattern.strategy;
import java.util.ArrayList;
import java.util.List;
public class Strategy {
public static void main(String[] args) {
Customer a = new Customer(new NormalStrategy());
// Normal billing
a.add(1.0, 1);
// Start Happy Hour
a.setStrategy(new HappyHourStrategy());
a.add(1.0, 2);
// New Customer
Customer b = new Customer(new HappyHourStrategy());
b.add(0.8, 1);
// The Customer pays
a.printBill();
// End Happy Hour
b.setStrategy(new NormalStrategy());
b.add(1.3, 2);
b.add(2.5, 1);
b.printBill();
}
}
class Customer {
private List<Double> drinks;
private BillingStrategy strategy;
public Customer(BillingStrategy strategy) {
this.drinks = new ArrayList<Double>();
this.strategy = strategy;
}
public void add(double price, int quantity) {
drinks.add(strategy.getActPrice(price * quantity));
}
// Payment of bill
public void printBill() {
double sum = 0;
for (Double i : drinks) {
sum += i;
}
System.out.println("Total due: " + sum);
drinks.clear();
}
// Set Strategy
public void setStrategy(BillingStrategy strategy) {
this.strategy = strategy;
}
}
interface BillingStrategy {
public double getActPrice(double rawPrice);
}
// Normal billing strategy (unchanged price)
class NormalStrategy implements BillingStrategy {
@Override
public double getActPrice(double rawPrice) {
return rawPrice;
}
}
// Strategy for Happy hour (50% discount)
class HappyHourStrategy implements BillingStrategy {
@Override
public double getActPrice(double rawPrice) {
return rawPrice*0.5;
}
}
selectize 와 autocomplete 기능
셀렉트 박스에 몇가지의 value를 선택하여 dynamic하게 설정할 때 구현이 가능한지 몰라서 다르게 구현한 적이 많았다.
하지만 selectize라는 오픈 소스를 접한다면 생각을 달라질 것이다.
그림과 같이 여러개의 값을 선택할 수 있고 해당 input박스 영역에서 입력 가능한 input데이타를 출력 할수 있으면 해당 문자열을 입력하는 경우 autocomplete기능을 제공한다.
- jquery의 autocomplete 기능을 사용하여 구현한 것으로 보여짐
- 여러가지 기능이 추가된 UI
그 외 여러가지 추가적인 기능이 가능하니 아래의 해당 홈페이지의 api명세를 참조하기 바란다.
http://brianreavis.github.io/selectize.js/
해당 플러그인이 아닌 몸소구현이 하고 싶다 jquery auto complete기능을 이용하여 구현이 가능할 것으로 보여진다.
위의 그림 과 같이 jquery의 autocomplete와 select multiple values를 이용하면 selectize와 유사한 기능을 구현할 수 있다.
자세한 내용은 jquery ui의 홈페이지를 참조하기 바란다.
https://jqueryui.com/autocomplete
하지만 selectize라는 오픈 소스를 접한다면 생각을 달라질 것이다.
그림과 같이 여러개의 값을 선택할 수 있고 해당 input박스 영역에서 입력 가능한 input데이타를 출력 할수 있으면 해당 문자열을 입력하는 경우 autocomplete기능을 제공한다.
- jquery의 autocomplete 기능을 사용하여 구현한 것으로 보여짐
- 여러가지 기능이 추가된 UI
그 외 여러가지 추가적인 기능이 가능하니 아래의 해당 홈페이지의 api명세를 참조하기 바란다.
http://brianreavis.github.io/selectize.js/
해당 플러그인이 아닌 몸소구현이 하고 싶다 jquery auto complete기능을 이용하여 구현이 가능할 것으로 보여진다.
위의 그림 과 같이 jquery의 autocomplete와 select multiple values를 이용하면 selectize와 유사한 기능을 구현할 수 있다.
자세한 내용은 jquery ui의 홈페이지를 참조하기 바란다.
https://jqueryui.com/autocomplete
2015년 6월 16일 화요일
Design Pattern 정리
1. 디자인 패턴이 필요한 이유
- 특정 클래스에서 객체 생성
객체를 생성할 때 클래스 이름을 명시하면 어떤 특정 인터페이스가 아닌 어떤 특정 구현에 종속됩니다. 이런 종속은 앞으로의 변화를 수용하지 못합니다. 이를 방지하려면 객체를 직접 생성해서는 안됩니다.
(디자인 패턴 : 추상 팩토리, 팩토리 메서드, 원형)
- 특정 연산에 대한 의존성
특정한 연산을 사용하면, 요청을 만족하는 한 가지 방법에만 매이게 됩니다. 요청의 처리 방법을 직접 코딩하는 방법을 피하면, 컴파일 시점과 런타임 모두를 만좃하면서 요청 처리 방법을 쉽게 변경할 수 있습니다.
(디자인 패턴 : 책임 연쇄, 명령)
- 하드웨어와 소프트웨어 플랫폼에 대한 의존성
기존에 존재하는 시스템 인터페이스와 응용프로그래램 프로그래밍 인터페이스는 소프트웨어 및 하드웨어 플랫폼마다 모두 다릅니다. 특정 플랫폼에 종속된 소프트웨어는 다른 플랫폼에 이식하기도 어렵고요. 또한 본래의 플랫폼에서도 버전의 변경을 따라가기 어려울 수 도 있습니다. 이런 플랫폼 종속성을 제거하는 것은 시스템 설계에 있어 매우 중요합니다,.
(디자인 패턴 : 추상 팩토리, 가교)
- 객체의 표현이나 구현에 대한 의존성
사용자가 객체의 표현 방법, 저장 방법, 구현 방법, 존재의 위치에 대한 모든 방법을 알고 있다면 객체를 변경할 때 사용자도 함"께 변경해야 합니다. 이런 정보를 사용자에게 감춤으로써 변화의 파급을 막을 수 있습니다.
(디자인 패턴 : 추상 팩토리, 가교, 메멘토, 프록시)
- 알고리즘 의존성
알고리즘 자체를 확장할 수도, 최적화할 수도, 다른 것으로 대체할 수도 있는데, 알고리즘에 종속된 객체라면 알고리즘이 변할 때마다 객체도 변경해야 합니다. 그러므로 변경이 가능한 알고리즘은 분리해 내는 것이 바람직합니다.
(디자인 패턴 : 빌더, 반복자, 전략, 템플리 메서드, 방문자)
- 높은 결합도
높은 결합도를 갖는 클래스들은 독립적으로 재사용하기 어렵습니다. 높은 결합도를 갖데 되면 하나의 커다란 시스템이 되어 버립니다. 이렇게 되면 클래스 하나르 수정하기 위해서 전체를 이해해야 하고 다른 많은 클래스도 변경해야 합니다. 또한 시스템은 배우기도 힘들고, 이식은 커녕 유지보수하기 조차도 어려운 공룡이 되어 버립니다.
약한 결합도는 클래스 자체의 재사용을 가능하게 하고 시스템의 이해와 수정, 확장이 용이해서 이식성을 증대시킵니다. 추상 클래스 수준에서 결합도를 정희한다거나 계층화시키는 방법으로 디자인 패턴은 낮은 결합도의 시스템을 만들도록 합니다.
(디자인 패턴 : 추상 팩토리, 가교, 책임 연쇄, 명령, 퍼사드, 중재자, 감시자)
- 서브 클래싱을 통한 기능 확장
객체 합성과 위임은 행동 조합을 위한 상속보다 훨씬 유연한 방법입니다. 기존 객체들을 새로운 방식으로 조합함으로써 새로운 서브클래스를 정의하지 않고도 응용프로그램에 새로운 기능성을 추가할 수 있습니다. 한편, 객체 합성을 많이 사용한 시스템은 이해하기가 어려워집니다. 많은 디자인 패턴에서는 그냥 서브클래스를 정의하고 다른 인스턴스와 새로 정의한 클래스의 인스턴스를 합성해서 기능을 재정의하는 방법을 도입합니다.
(디자인 패턴 : 가교, 책임 연쇄, 장식자, 감시자 , 전력)
- 클래스 변경이 편하지 못한 점
가끔 클래스를 변경하는 작업이 그렇게 단순하지 않을 때가 많습니다. 소스 코드가 필요한데 없다고 가정해 봅시다. 또한 어떤 변경을 하면 기존 서브클래스의 다수를 수정해야 한다고 가정합시다. 디자인 패턴은 이런 환경에서 클래스를 수정하는 방법을 제시합니다.
(디자인 패턴 : 적응자, 장식자, 방문자)
2. 디자인 패턴을 고르는 방법
- 패턴이 어떻게 문제를 해결하는지 파악합시다.
- 패턴의 의도 부분을 봅시다.
- 패턴들 간의 관련성을 파악합시다.
- 비슷한 목적의 패턴들을 모사어 공부합시다.
- 재설꼐의 원인을 파악합시다.
- 설계에서 가변성을 가져야 하는 부분이 무엇인지 파악합시다.
4. 디자인 패턴을 통해 다양화 할 수 있는 설계 측면
목적
|
디자인 패턴
|
이 패턴을 통해 다양화 할 수 있는 부분
|
생성
|
추상 팩토리 (Abstract Factory)
|
제품 객체군
|
빌더(Builder)
|
복합 객체 생성 방법
| |
팩초리 메서드(Factory Method)
|
인스턴스화 될 객체의 서브클래스
| |
원형(Prototype)
|
인스턴스화 될 객체의 클래스
| |
단일체(Singleton)
|
클래스의 인스턴스가 하나일 때
| |
구조
|
적응자(Adapter)
|
객체에 대한 인터페이스
|
가교(Bridge)
|
객체 구현
| |
복합체(Composite)
|
객체의 합성과 구조
| |
장식자(Decorator)
|
서브클래싱 없이 객체의 책임성
| |
퍼사드(Facade)
|
서브시스템에 대한 인터페이스
| |
플라이급(Flyweight)
|
객체의 저장 비용
| |
프록시(Proxy)
|
객체 접근 비용
| |
행동
|
책임 연쇄(Chain of Responsibility)
|
요청을 처리하는 객체
|
명령(Command)
|
요청을 처리 시점과 처리 방법
| |
해석자(Interpreter)
|
언어의 문법과 해석 방법
| |
반복자(Iterator)
|
집합 객체 요소들의 접근 방법 및 순회 방법
| |
중재자(Mediator)
|
어떤 객체들이 어떻게 상호작용하는지
| |
메멘토(Memento)
|
어제 어떤 정보를 객체의 외부에 저장하는지
| |
감시자(Observer)
|
다른 객체의 종속적인 객체 수
종속적인 객체들의 상태 변경 방법
| |
상태(State)
|
객체의 상태
| |
전략(Strategy)
|
알고리즘
| |
템플릿 메소드(Template Method)
|
알고리즘 단계
| |
방문자(Visitor)
|
클래스의 변경 없이 객체에 적용할 수 있는 연산
|
5. 패턴 대략적 설명
스트래티지 패턴(Strategy Pattern)
- 알고리즘군을 정의하고 각각을 캐슐화하여 교환해서 사용할 수 있도록 만든다.
- 알고리즘을 사용하는 클라이언트와는 독립적으로 알고리즘을 변경할 수 있다.
- 구성을 사용한다.
- 일반적으로 서브클래스를 만드는 방법을 대신하여 유연성을 극대화하기 위한 용도로 쓰인다.
- 예: QuarkBehavior & FlyBehavior
옵저버 패턴(Observer Pattern)
- 한 객체의 상태가 바뀌면 그 객체에 의존하는 다른 객체들한테 연락이 가고
자동으로 내용이 갱신되는 방식으로 일대다(one-to-many) 의존성을 정의한다.
- 주제(Subject) & 옵저버(Observer)
- Observable & Observer:
Observable 에 register, remove, notify 가 있고,
Observer 에 update 가 있다. (notify 에서 update 를 호출)
- 예: 신문 구독 서비스, 기상관측 시스템
데코레이터 패턴(Decorator Pattern)
- 객체에 추가적인 요건을 동적으로 첨가한다.
- 데코레이터는 서브클래스를 만드는 것을 통해서 기능을 유연하게 확장할 수 있는 방법을 제공한다.
- 예: 스타버즈 커피
팩토리 패턴(Factory Pattern)
- 팩토리 메서드 패턴 :
객체를 생성하기 위한 인터페이스를 정의하는데, 어떤 클래스의 인스턴스를 만들지는 서브클래스에서 결정하게 만든다.
클래스의 인스턴스를 만드는 일을 서브클래스에 맡긴다.
- 제품을 생산하는 부분과 사용하는 부분을 분리시킬 수 있다.
- 추상 팩토리 패턴 :
인터페이스를 이용하여 서로 연관된, 또는 의존하는 객체를 구상 클래스를 지정하지 않고 생성한다.
구상 클래스는 서브 클래스에 의해 만들어진다.
싱글턴 패턴(Singleton Pattern)
- 해당 클래스의 인스턴스가 하나만 만들어지고,
어디서든지 그 인스턴스에 접근할 수 있도록(전역 접근) 하기 위한 패턴
커맨드 패턴(Command Pattern)
- 요구 사항을 객체로 캡슐화 할 수 잇으며, 매개변수를 써서 여러 가지 다른 요구 사항을 집어넣을 수 있다.
또한 요청 내역을 큐에 저장하거나 로그로 기록할 수도 잇으며, 작업취소 기능도 지원 가능하다.
- 예: 리모콘
- 서블릿의 doGet(), doPost() 또는 스트럿츠의 Action() 메서드도 커맨드 패턴이지 않을까?
어댑터 패턴(Adapter Pattern)
- 한 클래스의 인터페이스를 클라이언트에서 사용하고자 하는 다른 인터페이스로 변환한다.
어댑터를 이용하면 인터페이스 호환성 문제 때문에 같이 쓸 수 없는 클래스들을 연결해서 쓸 수 있다.
퍼사드 패턴(Facade Pattern)
- 어떤 서브시스템의 일련의 인터페이스에 대한 통합된 인터페이스를 제공한다.
퍼사드에서 고수준 인터페이스를 정의하기 때문에 서브시스템을 더 쉽게 사용할 수 있다.
- 서브시스템의 호출을 퍼사드에서 처리해준다. (기본 명령 호출 정도랄까...)
- 일련의 클래스들에 대한 인터페이스를 단순화 시킨다.
- 각 패턴별 차이점:
데코레이터 패턴 : 인터페이스는 바꾸지 않고 책임(기능)만 추가
어댑터 패턴 : 한 인터페이스를 다른 인터페이스로 변환
퍼사드 패턴 : 인터페이스를 간단하게 바꿈
템플릿 메서드 패턴(Template Method Pattern)
- 메서드에서 알고리즘의 골격을 정의한다.
알고리즘의 여러 단계 중 일부는 서브클래스에서 구현할 수 있다.
템플릿 메서드를 이용하면 알고리즘의 구조는 그대로 유지하면서 서브클래스에서 특정 단계를 재정의할 수 있다.
- 스트래티지 패턴과 다른 점:
템플릿 메서드 패턴은 알고리즘의 개요를 정의한다. 실제 작업 중 일부는 서브클래스에서 처리.
스트래티지 패턴은 객체 구성을 통해서 알고리즘을 캡슐화 및 구현
- 예) Arrays.sort(배열); --- compareTo() 를 구현하도록 되어 있다.
Applet , init(), start(), stop(), destory()
그렇다면 서블릿에도 템플릿 메서드가 쓰이는 거구나. init() - service() - destory()
이터레이터 패턴(Iterator Pattern)
- 컬렉션 구현 방법을 노출시키지 않으면서도
그 잡합체 안에 들어있는 모든 항목에 접근할 수 있게 해주는 방법을 제공한다.
- 컬렉션의 구현을 드러내지 않으면서 컬렉셔네 있는 모든 객체들에 대해 반복작업할 수 있다.
컴포지트 패턴(Composite Pattern)
- 객체들을 트리 구조로 구성하여 부분과 전체를 나타내는 계층구조로 만들 수 있다.
이 패턴을 이용하면 클라이언트에서 개별 객체와 다른 객체들로 구성된
복합 객체(composite)를 똑같은 방법으로 다룰 수 있다.
- 클라이언트에서 객체 컬렉션과 개별 객체를 똑같은 식으로 처리할 수 있다.
- 예) 트리 구조의 패턴, 디렉토리 구조
- 예) XMLObject 객체가 컴포지트 패턴을 구현한 게 아닐까
스테이트 패턴(State Pattern)
- 객체의 내부 상태가 바뀜에 따라서 객체의 행동을 바꿀 수 있다.
마치 객체의 클래스가 바뀌는 것과 같은 결과를 얻을 수 있다.
- 상태 전환의 흐름을 결정하는 코드를 어느 쪽에 집어넣는지 잘 고려해야 한다.
(상태 객체인지, Context 객체인지)
- 각 상태를 클래스로 캡슐화함으로써 나중에 변경시켜야 하는 내용을 국지화시킬 수 있다.
- 스트래티지 패턴:
어떤 클래스의 인스턴스를 만들고 그 인스턴스에게 어떤 행동을 구현하는 전략 객체를 건내준다.
스테이트 패턴:
컨텍스트 객체를 생성할 때 초기 상태를 지정해주는 경우 이후로는 컨텍스트 객체가 알아서 상태를 변경.
프록시 패턴(Proxy Pattern)
- 어떤 객체에 대한 접근을 제어하기 위한 용도로 대리인이나 대변인에 해당하는 객체를 제공하는 패턴
- 다른 객체를 대변한느 객체를 만들어서 주 객체에 대한 접근을 제어할 수 있다.
- 원격프록시(remote proxy): 원격 객체에 대한 접근 제어
클라이언트와 원격 객체 사이에서 데이터 전달을 관리
가상프록시(virtual proxy): 생성하기 힘든(인스턴스를 만드는 데 많은 비용이 드는) 자원에 대한 접근 제어
보호프록시(protection proxy): 접근 권한이 필요한 자원에 대한 접근 제어
호출하는 쪽의 권한에 따라서 객체에 있는 메소드에 대한 접근 제어
방화벽 프록시: 일련의 네트워크 자원에 대한 접근 제어
스마트 레퍼런스 프록시: 주 객체가 참조될 때마나 추가 행동을 제공. 객체에 대한 레퍼런스 개수를 세는 등
캐싱 프록시: 비용이 많이 드는 작업의 결과를 임시로 저장
웹 서버 프록시 또는 컨텐츠 관리 및 퍼블리싱 시스템 등에서 사용
동기화 프록시: 여러 스레드에서 주 객체에 접근하는 경우 안전하게 작업을 처리할 목적(분산 환경 등에서 사용)
복잡도 숨김 프록시: 복잡한 클래스들의 집합에 대한 접근을 제어하고 복잡도를 숨겨줌
퍼사드 프록시라고도 함.
프록시에서는 접근을 제어하지만 퍼사드 패턴에서는 대체 인터페이스만 제공
지연 복사 프록시: 클라이언트에서 필요로 할 때까지 객체가 복사되는 것을 지연시킴으로써 객체의 복사 제어
- 아래 객체들은 모두 클라이언트와 객체 사이에 끼여들어서 요청을 전달한다.
데코레이터 패턴: 클래스에 새로운 행동을 추가하기 위한 용도
어댑터 패턴: 다른 객체의 인터페이스를 바꿔주기 위한 용도
프록시 패턴: 어떤 클래스에 대한 접근을 제어하기 위한 용도
- java.reflect.Proxy 에 기능이 내장되어 있다.
디자인 패턴 정의
- 패턴이란 특정 컨텍스트 내에서 주어진 문제에 대한 해결책이다.
- 어떤 컨텍스트 내에서 일련의 제약조건에 의해 영향을 받을 수 있는 문제에 봉착했다면,
그 제약조건 내에서 목적을 달성하기 위한 해결책을 찾아낼 수 있는 디자인을 적용하면 된다.
주의점 및 추가 사항
- 디자인 패턴의 과다한 사용은 불필요하게 복잡한 코드를 초래할 수 있다.
항상 가장 간단한 해결책으로 목적을 달성할 수 있도록 하고, 반드시 필요할 때만 디자인 패턴을 적용하자.
- 코딩할 때 어떤 패턴을 사용하고 있는지 주석으로 적어주자.
클래스와 메서드 이름을 만들 때도 사용 중인 패턴이 분명하게 드러날 수 있도록 해보자.
다른 개발자들이 그 코드를 볼 때 무엇을 어떻게 구현했는지 훨씬 빠르게 이해할 수 있다.
참조:
- http://ohgyun.com/279
- http://bleedmin.tistory.com/entry/Chapter1-%EB%94%94%EC%9E%90%EC%9D%B8%ED%8C%A8%ED%84%B4%EC%9D%B4%EB%9E%80
피드 구독하기:
글 (Atom)