Curookie's Infagic Blog

쿠루키의 인포직 블로그


  • 홈

  • 태그10

  • 카테고리3

  • 아카이브4

  • 검색

[스프링 부트] 1. 개발 환경의 변화와 자바

작성일 2018-07-09 | Edited on 2018-07-18 | In Spring Boot |

이 내용은 "윤석진"님의 "스프링 부트로 배우는 자바 웹 개발" 책을 기반으로 참고하여 제 생각과 이해한 내용을 요약한 것임을 알려드립니다.

[1.1] 인프라와 스프링 프레임워크의 변화

최근 웹 개발의 트랜드는 조립식이다. 오픈 소스들을 조합해서 서비스를 출시하는 경우가 많은데, 스프링 부트는 웹 개발을 쉽게 할 수 있게 돕는 툴인프라이다. 이를 시작하기 전에 자바 기술의 변화에 대해 알 필요가 있다.

[1.1.1] 아키텍처의 변화

인터넷 시장의 트랜드 변화

인트라넷 -> B2C


이 결과 인프라 아키텍처가 변화게되었다. Spring이 뜨고 Spring Boot가 생기고

1. 메인프레임 2. 서버/클라이언트 3. 웹 4. 클라우드
인터페이스 터미널 웹 브라우저/GUI 웹 서버 인스턴트 또는 컨테이너
주 언어 코볼, 포트란 델파이, C++, 펄 PHP, JSP, ASP 파이썬
목적 B2B B2C B2C B2C
플랫폼 지원 수준 단일접속, 순차 배치 처리 클라이언트 설치를 통한, 접속 지원 웹 서버에 의한 접속 처리, 별도의 데이터베이스 서버 활용 빈도수가 높아짐 물리 설치 없이 인스턴스 형태로, 자유롭게 확장 가능

[1.1.2] 스프링 프레임워크의 변화

초기에는 오라클 IBM과 같은 업체들이 제공하는 솔루션을 주로 사용했다.
서버: 웹로직, 웹스피어
개발API: EJBEnterprise JavaBean 사용

스프링은 EJB의 복잡하고 테스트하기도 어려우며 무거운 요소를 해결하기 위해 만들어졌다.
톰캣서버가 버전업, 스프링 프레임워크도 2.5버전 이후로 안정화되면서 스프링이 기반이 되고, 결정타로 전자정부 프레임워크에서도 기반 기술로 스프링 프레임워크를 채택하면서 스프링 프레임워크와 톰캣 조합이 표준이 되었다.
서버: 톰캣
개발 프레임워크: 스프링 프레임워크

최근에는 스타트업이 대거 등장, 빠르게 서비스를 런칭= 클라우드 사용 빈도가 증가. B2C 서비스의 경우 서버를 물리적으로 증설하지 않고 PaaS(Platform as a Service)로 서버를 물리적으로 증설하지 않고 인스턴스를 추가로 사용함에 따라 스프링은 위기에 빠져들었다. 스프링의 JDK, Tomcat설치 복잡한 빡치는 XML설정하는 일련의 작업들을 간소화해야하는 방법이 필요해졌다. 결국 스타트업들은 답답한 스프링을 견딜 수 없었고, 루비온레일스(RubyOnRails)나 장고(Django)를 이용해 빠르게 개발하는 것을 선호하게 되었다.

스프링 측은 이런 문제를 해결하기 위해 스프링 부트Spring Boot를 제작한다.
스프링 부트는 설정 자동화를 이용해서 MVC 모듈의 DispatcherServlet설정 JDBC DataSource 설정 등 웹 개발을 하는 데 필요한 인프라성 코드를 제공해 줌으로써 복잡한 XML 설정을 하지 않아도 개이득 개발을 시작할 수 있다. 또한, 임베디드 톰켓을 이용해 톰캣설치가 필요없고 main 메서드로 실행할 수 있다. 그리고 클라우드 환경에서도 별도의 작업 없이 스프링 부트를 이용하면 시간을 많이 단축할 수 있다.
서버: 임베디드 톰켓
개발 프레임워크: 스프링 부트 프레임워크

[1.2] 웹 애플리케이션 컨테이너

일반적으로 HTML과 같이 정적 파일들을 전달해 주는 역할을 하는 서버를 웹 서버라고 하고,
PHP, JSP, ASP와 같은 언어들을 사용해서 동적인 페이지들을 생성 가능한 서버를 웹 애플리케이션 서버(Web Application Server), 자바 계열에서는 웹 애플리케이션 컨테이너(Web Application Container)라고 하며, 이는 웹 애플리케이션이 배포되는 공간을 뜻한다. Web Application Server/Container를 줄여서 WAS라고 부른다.
WAS가 어떻게 웹 애플리케이션을 인식하고 동작시키는지 알기 위해서는 클래스 로더를 알아야한다.

[1.2.1] 자바 개발을 위해 꼭 필요한 클래스 로더

자바의 가장 큰 특징 중 하나인 "Write once, run anywhere"한번 작성하면 플랫폼에 상관없이 쓸 수 있다.는 클래스 로더(Class Loader)가 있기에 가능하다.
자바 코드를 컴파일하면 JVM에서 실행가능한 상태가 된다. 클래스를 실행시키기 위해 클래스를 로딩하는 과정이 필요한데 그 과정을 수행해주는 녀석이 클래스 로더다.
클래스패스(Classpath)에 해당 모듈 또는 라이브러리를 추가하면 클래스파일 메타 정보 중 첫 번째 시작 필드를 이용해 클래스를 로딩한다.

[1.2.1.1] 클래스 로더의 특징

  1. 구조가 계층적이다.
  • 상위 클래스 로더에서 하위 클래스 로더를 갖는 방식이며, 최상위 클래스 로더는 부트스트랩 클래스 로더다.
  1. 클래스 로딩을 위임할 수 있다.
  2. 가시적인 규약이 있다.
  • 클래스를 로딩할 떄 가능한 범위가 있다는 말이다. 부모 클래스 로더는 자식 클래스 로더가 로딩한 클래스를 알 수 없다.
  1. 클래스 언로딩 불가능.
  • 클래스 로더로 로딩한 클래스들을 언로딩 할 수 없다. Garbage Collector가 동작하거나 WAS가 재시작할 때 초기화 된다.

[1.2.1.2] 클래스 로더의 유형

클래스 로더에는 네 가지 유형이 있고 순차적으로 로드한다.

  1. 부트스트랩 클래스 로더(Bootstrap Class Loader)
  • JVM 런타임 실행을 위해 기반이 되는 파일들을 로드한다. rt.tar 파일과 연관이 있다.
  1. 확장 클래스 로더(Extension Class Loader)
  • 자바의 최상위 객체인 Object를 포함한 자바 API를 로드한다. ext 폴더 하위에 있는 jar 파일들과 연관있다.
  1. 시스템 클래스 로더(System Class Loader)
  • 클래스패스에 포함된 클래스들을 로드한다.
  1. 사용자 정의 클래스 로더(User-defined Class Loader)
  • 개발자가 만든 클래스 로더이다. 가장 마지막에 로드한다.

[1.3] WAR 파일의 특성

배포시에 로컬 실행 프로그램은 jar로 패키징하고, 웹은 war(Web Application Resource)로 패키징한다.
war는 압축 파일에 자바 관련 규약이 포함된 것이다. 바로 WEB-INF 폴더다.
WAS는 war파일의 WEB-INF 폴더를 기준으로 클래스 파일들을 로드한다.
war로 패키징하면 클래스 파일들은 WEB-INF 하위 classes 폴더에 저장된다.
WEB-INF의 하위 libs 폴더에는 jar형식의 외부 라이브러리들이 있다.
jar 라이브러리들은 사용자 정의 클래스 로더인, 웹 애플리케이션 컨테이너 로더를 통해 클래스패스에 추가된다.
class 파일들은 웹 애플리케이션 클래스 로더를 통해 추가된다.
WAS는 웹 애플리케이션 자체 API를 제공하기 위해 컨테이너를 로드하는 클래스 로더와 사용자가 추가한 JSP나 WAR 파일들을 다루기 위한 ServletContext Loader를 사용한다.
컨테이너가 시작되고 콘텍스트가 초기화되면 서블릿 스펙의 권장 사항에 따라 WEB-INF/classes 파일을 먼저 검색해서 로딩하고, 그 후에 WEB-INF/libs에 있는 jar파일들을 로드한다.

[스프링 부트] 2. 서블릿

작성일 2018-07-09 | Edited on 2018-07-18 | In Spring Boot |

이 내용은 "윤석진"님의 "스프링 부트로 배우는 자바 웹 개발" 책을 기반으로 참고하여 제 생각과 이해한 내용을 요약한 것임을 알려드립니다.

[2.1] 서블릿 시작하기

서블릿은 Java EE(Enterprise Edition)에 포함 된 스펙 중 하나다.
자바에서 HTTP 요청과 응답을 처리하기 위한 내용들을 담고 있다.

[2.1.1] 서블릿 설정

그래들(Gradle)은 메이븐(Maven), 앤트(Ant)와 같은 빌드 도구다.
간단히 말해 자바에서 라이브러리를 편리하게 추가할 수 있는 도구라고 할 수 있다.
그래들은 build.gradle 파일을 생성해서 사용한다.
jar파일은 build.gradle 파일에 추가하면 사용할 수 있다.

[2.1.1.1] 그래들을 이용한 서블릿 설정

라이브러리 의존성을 추가할 때는 이클립스(Eclipse)나 인텔리제이(IntelliJ)와 같은 도구에서 자동완성을 이요해서 추가할 수 있는데, 사이트에서 검색을 통해 추가하는 방법도 있다.
메이븐 중앙 저장소 https://mvnrepository.com/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
buildscript{
repositories {
jcenter()
}
dependencies {
classpath 'org.akhikhl.gretty:gretty:+'
}
}
apply plugin: 'java'
apply plugin: 'war'

apply plugin: 'org.akhikhl.gretty'

apply plugin: 'eclipse'
apply plugin: 'idea'

sourceCompatibility = 1.8
targetCompatibility = 1.8

repositories {
jcenter()
}

compileJava.options.encoding = 'UTF-8'

dependencies {
compile 'org.slf4j:slf4j-api:1.7.7'
testCompile 'junit:junit:4.12'
providedCompile 'javax.servlet:javax.servlet-api:3.1.0'
}

//gretty는 jetty와 같은 내장 서블릿 컨테이너(내장 WAS)
gretty{
httpPort = 8080
contextPath = '/'
servletContainer = 'jetty9'
}

//webappDir은 JSP,HTML,CSS 파일등이 놓일 root폴더 경로
def webappDir = "$rootDir/src/main/webapp"

eclipse{
classpath{
downloadSources = true
defaultOutputDir = file("${buildDir}/classes/main")
}
}

//idea는 설정 안해줘도 된다. 그래서 intelliJ가 짱짱.

[2.2] 서블릿 내부 동작

[2.2.1] 서블릿의 생명주기

웹 애플리케이션 컨테이너에서 콘텍스트가 초기화되면 생명주기가 시작된다.
초기화(initialize), 서비스(Service), 소멸(destroy)의 3단계로 구성되어 있다.

  1. 초기화(initialize) - 로드한 서블릿의 인스턴스를 생성하고, 리소스를 로드하는 등 클래스 생성자의 초기화 작업과 동일한 역할을 수행한다.
  2. 서비스(Service) - 클라이언트의 요청에 따라서 호출할 메서드를 결정한다.
  3. 소멸(destroy) - 서블릿이 언로드된다. 서블릿의 메서드 호출결과가 정상적으로 표출되지 않는다.

[2.2.1.1] 서블릿 초기화와 init 메서드

init 메서드는 초기화를 담당하는 메서드다.
HttpServlet은 추상 클래스인데 서블릿을 만들 때는 이 클래스를 상속받아서 만든다.
URL매핑은 WebServlet 어노테이션(annotation)으로 작성한다.

gradlew는 gradle wrapper를 실행하는 명령어로, gradle을 설치하지 않은 사용자는 gradle대신 gradlew로 실행 할 수 있다.

[처음코드] [/ch02/src/main/java/info/thecodinglive/basic/initServlet.java]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package info.thecodinglive.basic;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;

//URL 매핑은 @WebServlet 어노테이션을 이용해서 작성
@WebServlet("/init")
public class InitServlet extends HttpServlet{

//init() 메서드는 한 번만 호출 됨
@Override
public void init() throws ServletException {
System.out.println("init call");
}
}

gradlew appStartWar로 실행한 다음, 브라우저에서 http://localhost:8080/init 를 입력한 뒤 콘솔창을 확인하면
init call

서블릿 3.0 이후 버전부터 XML 없이 URL을 매핑할 수 있다.
이전 버전과 비교해보자면

[이전버전]

1
2
3
4
5
6
7
8
<servlet>
<servlet-name> Init </servlet-name>
<servlet-class> InitServlet </servlet-class>
</servlet>
<servlet-mapping>
<servlet-name> Init </servlet-name>
<url-pattern>/init</url-pattern>
</servlet-mapping>

[이후버전]

1
2
3
4
@WebServlet(name="Init", urlPatterns={"/init"})
public class InitServlet extends HttpServlet{
--- 중략 ---
}

@WebServlet(name="Init", urlPatterns={"/init"})

init 메서드의 초기화 시에만 작동하는 성격을 이용해 초기화 시 파라미터를 전달 하고 싶은 경우 servletConfig를 사용한다.

[수정코드] [/ch02/src/main/java/info/thecodinglive/basic/InitServlet.java]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package info.thecodinglive.basic;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebInitParam;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet(
name = "initServlet", urlPatterns = {"/init"},
//@WebInitParam 이노테이션으로 파라미터 설정
initParams = {@WebInitParam(name = "siteName", value = "jpub")}
)
public class InitServlet extends HttpServlet{
private String myParam = "";

public void init(ServletConfig servletConfig) throws ServletException{
System.out.println("init call");
//이와 같이 servletConfig.getInitParameter를 이용해서 web.xml 또는 WebInitParam 어노테이션의 정보를 서블릿 초기화 시 전달한다.
this.myParam = servletConfig.getInitParameter("siteName");
System.out.println("입력받은 사이트 명은" + myParam + "입니다.");
}

//doGet은 아래에서 얘기하는걸로
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().println("hello");
}
}

[2.3] 서블릿 활용

[2.3.1] HTTP 요청과 응답

HTTP 요청에 대한 응답을 브라우저를 통해 확인하기
Get방식은 Select 할떄 사용한다.
Post방식은 입력/수정 할때 사용한다. Http Request Body에 파라미터 정보가 추가된다.

[2.3.1.1] GET 요청 처리

서블릿에서는 doGet 메서드를 이용해서 GET 메서드 방식의 요청을 응답받을 수 있다.
doGet은 HttpServletRequest, HttpServletResponse를 파라미터로 전달받도록 되어 있는데
HttpServletRequest는 요청에 대한 정보를 가지고 있고,
HttpServletResponse는 브라우저에서 정보를 표현하기 위해 사용한다.

[코드] [/ch02/src/main/java/info/thecodinglive/basic/HelloServlet.java]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package info.thecodinglive.basic;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

@WebServlet(name = "HelloServlet", urlPatterns = {"/helloget"})
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("doGet 메소드 호출");
resp.setCharacterEncoding("UTF-8");
PrintWriter writer = resp.getWriter();

//contentType 정의 이런식으로 response시 보여줄 내용을 코드로 작성할 수 있다.
resp.setContentType("text/html");
writer.println("<html>");
writer.println("<head><title>jpub java webservice</title></head>");
writer.println("<body> get 요청 예제입니다. </body>");
writer.println("</html>");
}
}

웹브라우저에서 http://localhost:8080/helloget 을 입력하면 doGet 메서드가 호출된다.
메서드 안에서는 response 객체에 printWriter의 인스턴스를 얻어서 HTML 내용을 출력한다.
HTTP 메서드 요청 방식이 예제와 같이 GET인 경우에는 브라우저를 통해서 직접 URL을 입력해서 확인하면 된다.

[2.3.1.2] POST 요청 처리

doPost는 post요청에 대해서만 처리할 수 있는 메서드다.
그래서 URL이 일치해도 에러 405가 발생한다.

[코드] [/ch02/src/main/java/info/thecodinglive/basic/HelloServlet2.java]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package info.thecodinglive.basic;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

//기본 doPost 골격
@WebServlet(name = "HelloServlet2", urlPatterns = {"/hellopost"})
public class HelloServlet2 extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("doPost 호출");
}
}

URL에 /hellopost 입력 시 오류는 해당 URL이 get 요청을 지원하지 않는다는 뜻이다. 서블릿뿐만 아니라 모든 HTTP 요청에 대해 서버가 허용하지 않는 경우 HTTP 에러코드 405를 응답받게 된다.

[2.3.1.3] HTML 폼 데이터 전송

POST 방식은 주로 폼(form)에서 데이터를 입력 후 전송할 때 사용하는데 회원가입, 로그인등의 기능을 구현할 때 많이 쓰인다.

폼의 두 가지 기억 해야하는 속성은 action="요청을 보낼 경로(urlPatterns)"과 method="post"

[전송용폼] [/ch02/src/main/webapp/login.html]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="style.css" type="text/css"/>
</head>
<body>
<div class="login-card">
<h1>Log-in</h1><br>
//method="post"속성을 입력해줘야하며 action이 post로 보내는 url과 같다.
<form method="post" action="postsend">
<input type="text" name="user" placeholder="Username">
<input type="password" name="pwd" placeholder="Password">
<input type="submit" class="login login-submit" value="login">
</form>
</div>
</body>
</html>

[폼으로부터 Post로 받는 코드] [/ch02/src/main/java/info/thecodinglive/basic/LoginServlet.java]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package info.thecodinglive.basic;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

@WebServlet(name = "LoginServlet", urlPatterns = {"/postsend"})
public class LoginServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("doPost 메소드 호출");
resp.setCharacterEncoding("UTF-8");
req.setCharacterEncoding("UTF-8");
PrintWriter writer = resp.getWriter();

resp.setContentType("text/html");

String user = req.getParameter("user");
String pwd = req.getParameter("pwd");
writer.println("<html>");
writer.println("<head><title>Login Servlet</title></head>");
writer.println("<body>");
writer.println("전달받은 이름은" + user + "이고" + "<br/>" + "비밀번호는" + pwd + "입니다.");
writer.println("</body>");
writer.println("</html>");
}
}

doPost 메서드 안에서 getParameter 메서드로 아이디와 패스워드를 각각 전달받은 후에 printWriter 객체를 생성하여 println 메서드로 출력한다.
폼에서 만든 input 필드의 name 속성값과 getParameter의 메서드의 파라미터는 같아야 한다.
폼태그의 action 속성값과 서블릿의 urlPatterns 값은 같아야한다.

폼에서 입력한 아이디와 비밀번호 값이 LoginServlet의 doPost 메서드로 출력되는 결과를 볼 수 있다.

[2.3.2] 멀티파트

멀티파트(multipart)는 바이너리 데이터 전송을 위해 사용한다. (= 사진, 동영상, 프로그램, 알집 같은 거)
서블릿 3.0 이후부터는 서블릿 스펙에 multipart가 추가되어서 별도의 라이브러리 없이 구현이 가능해졌다.

[업로드 하는 폼] [/ch02/src/main/webapp/upload.html]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>업로드</h1><br>
<form method="post" action="upload" enctype="multipart/form-data">
File:
<input type="file" name="file" id="file">
업로드할 서버 경로:
<input type="text" value="c:/upload" name="destination"/>
<br/>
<input type="submit" value="upload">
</form>
</body>
</html>

파일을 전송할 때는 폼 속성에 enctype="multipart/form-data"를 입력해야한다.
그리고 폼 태그 하위에 input 타입의 속성을 'file'로 설정한다.

이 예제에서는 c드라이브에 upload 디렉터리를 만든다. 디렉터리를 반드시 먼저 만들 필요는 없고, upload 서블릿이 동작할 때 upload 디렉터리도 함께 생성된다.

멀티파트 데이터를 처리를 위해 MultiPartConfig 어노테이션을 사용한다.

MultiPartConfig 어노테이션 속성

Annotation 명 설명
@fileSizeThreshold fileUploa 시에 메모리에 저장되는 임시 파일 크기를 정의한다.
[자료형: int]
@location 파일 업로드 시에 임시 저장 디렉터리를 지정한다.
[자료형: String]
@maxFileSize 업로드할 파일의 최대 크기를 지정한다.
[자료형: long]
@maxRequestSize request시에 최대 크기를 지정한다.
[자료형: long]

[폼으로부터 Post로 업로드하는 코드] [/ch02/src/main/java/info/thecodinglive/upload/UploadServlet.java]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
package info.thecodinglive.upload;

import javax.servlet.ServletException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;
import java.io.*;

@WebServlet(urlPatterns = "/upload", name = "uploadServlet")
@MultipartConfig(
fileSizeThreshold = 1024 * 1024 * 2, // 2mb
maxFileSize = 1024 * 1024 * 10, // 10mb
maxRequestSize = 1024 * 1024 * 50, //50mb
location = "c:/upload" //파일저장위치
)
public class UploadServlet extends HttpServlet {

@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html");
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
//경로
final String path = request.getParameter("destination");
//파일
final Part filePart = request.getPart("file");
//파일이름
final String fileName = getFileName(filePart);
final PrintWriter writer = response.getWriter();

try (OutputStream out = new FileOutputStream(new File(path + File.separator + fileName)); InputStream filecontent = filePart.getInputStream()) {
int read = 0;
final byte[] bytes = new byte[1024];

while ((read = filecontent.read(bytes)) != -1) {
out.write(bytes, 0, read);
}

writer.print("new File: " + fileName + path + "에 생성되었습니다.");

} catch (FileNotFoundException fne) {
System.out.println(fne.getMessage());
}
}


private String getFileName(final Part part) {
final String partHeader = part.getHeader("content-disposition");
System.out.println("Part Header = {0}" + partHeader);
for (String content: part.getHeader("content-disposition").split(";")) {
if (content.trim().startsWith("filename")) {
return content.substring(
content.indexOf('=') + 1).trim().replace("\"", "");
}
}
return null;
}
}

doPost 메서드 블록 안에 request.getPart 메서드로 참조한다.
request.getPart 메서드로 Part 객체 생성 후 getInputStream 메서드로 파일의 내용을 저장한다.
헤더에 있는 파일 정보 Part 객체에서 getHeader 메서드로 얻을 수 있다.

upload 버튼을 클릭하면 multipart 요청이 uploadServlet의 doPost 메서드로 전달되고 uploadServlet의 PrintWriter로 결과를 브라우저에 출력한다.

자바에서는 파일 쓰기 시에 먼저 임시 디렉터리에 파일을 저장한다.
기본 임시 디렉터리는 자바의 시스템 프로퍼티로 java.io.tmpdir로 저장되어 있고,
실제 저장 위치는 System.getProperty("java.io.tmpdir");로 확인할 수 있다.

Get -> 데이터를 쿼리스트링 형식으로 전송
Post Application/x-www-form-urlencoded: -> 데이터를 스트림 형태로 인코딩하여 전달할 때 사용되는 전송 방식
Multipart/form-data: -> 파일 업로드 시 사용되는 전송 방식
파일 전송용 폼을 만들 때는 enctype="multipart/form-data" 추가

[2.4] 서블릿 관련 객체

[2.4.1] 필터

C언어의 전처리기와 비슷한 기능, 웹 클라이언트의 요청에 대해서 필요한 사전 작업이 있을 경우에 필터(filter)를 사용한다.

[2.4.1.1] 웹 필터

필터는 필터 인터페이스를 상속받아 만들 수 있다. 필터는 서블릿의 생명주기처럼 init와 destroy 메서드를 가지고 있고, 필터 기능 사용을 위한 doFilter 메서드가 있다.
인코딩 필터를 만들어 사용할 때 ex(UTF-8 필터) 쓰기도 한다.

[필터(전처리) 기능을 사용하는 코드] [/ch02/src/main/java/info/thecodinglive/filter/FilterEx.java]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package info.thecodinglive.filter;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
import java.io.PrintWriter;

@WebFilter("*.jsp")
public class FilterEx implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {

}

@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
res.setContentType("text/html");
res.setCharacterEncoding("UTF-8");
PrintWriter out = res.getWriter();
out.println("필터 동작 전");
chain.doFilter(req, res);
out.println("필터 동작 후");
}

@Override
public void destroy() {

}
}

필터의 URL 매핑을 위해서 WebFilter 이노테이션을 사용할 수 있다.
필터의 실제 작업은 doFilter 메서드 안에서 이루어지는데, 이 예제에서는 필터가 jsp보다 먼저 동작하는지 확인하기 위해서 doFilter 이전과 이후를 표시하도록 했다.

jsp파일을 실행하면 앞 뒤로 전처리로 생성된 글을 볼 수 있다.
ex)
필터 동작 전 filter test 필터 동작 후

필터는 여러개 등록해서 사용할 수 있다. 하나의 요청에 대해서 다양한 변경이 필요하다면 여러 개의 필터를 매핑해서 처리할 수 있다. 이렇게 여러 개의 필터를 등록해서 처리하는 것을 필터 체인(filter chain)이라고 한다.

[2.4.2] 쿠키

[2.4.2.1] 쿠키의 구성

쿠키(Cookie)는 사용자가 사이트를 방문했을 때, 사용자의 컴퓨터에 저장되는 정보를 말한다. 쿠키의 구성 요소는 다음과 같다.

  • 이름: 각각의 쿠키의 값을 식별하기 위한 키
  • 값: 특정 이름으로 쿠키에 지정된 값
  • 유효 시간: 쿠키의 유지 시간
  • 도메인: 쿠키를 전송할 도메인
  • 경로: 쿠키를 전송할 요청 경로

쿠키는 HTTP 헤더 정보에 포함되어 전달된다. HTTP 프로토콜은 비연결지향으로 상태 정보를 저장하지 않는다. 자연스럽게 상태 정보를 저장할 공간이 필요하게 되며 이때 사용할 수 있는 메커니즘 중 하나가 쿠키다. 쿠키는 사용자의 PC에 저장되므로 로그인하지 않은 사용자에 대해서 다르게 적용할 필요가 있을 경우에 유용하다.

[2.4.2.2] 쿠키 생성

쿠키를 생성할 때는 간단하게 생성자를 이용해서 cookie 객체를 다음과 같이 생성할 수 있다.

Cookie jcookie = new Cookie(name, value);

쿠키는 javax.servlet.http 패키지에 포함되어 있는데, map을 사용할 때처럼 key, value형태로 사용하고 도메인과 최대 유효 기간 등을 설정할 수 있다. 서블릿에서 생성하는 방법은 다음과 같다.

[쿠키 생성하는 서블릿 코드] [/ch02/src/main/java/info/thecodinglive/cookie/CookieCreateServlet.java]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package info.thecodinglive.cookie;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

@WebServlet(urlPatterns = "/newcookie")
public class CookieCreateServlet extends HttpServlet {
@Override
public void doGet(HttpServletRequest req,
HttpServletResponse resp) throws ServletException,
IOException {
resp.setCharacterEncoding("UTF-8");
resp.setContentType("text/html");
PrintWriter out = resp.getWriter();
out.println("<html><head><title> 쿠키 예제</title></head><body>");
out.println("<br/>");

Cookie jcookie = new Cookie("jpub", "books");
//만료시간을 설정하는 setMaxAge(second) 메서드 초단위다.
jcookie.setMaxAge(3600);
//여기서는 response에 저장했다, application이나 다른방식으로 저장가능.
resp.addCookie(jcookie);
out.println("<a href='/readcookie'>readcookie</a></body></html>");
}
}

이 코드는 쿠키를 생성한 후에 만료 시간(1시간)을 설정하고, response객체에 쿠키를 저장한다.

setDomain 메서드를 이용해서 사용 가능한 도메인을 지정할 수도 있다.
ex)
cookie.setDomain("*.jpub.com")

[쿠키 읽어오는 서블릿 코드] [/ch02/src/main/java/info/thecodinglive/cookie/CookieReadServlet.java]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package info.thecodinglive.cookie;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet(urlPatterns = "/readcookie")
public class CookieReadServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setCharacterEncoding("UTF-8");
resp.setContentType("text/html");
PrintWriter out = resp.getWriter();
out.println("<html><head><title>쿠키 읽기</title></head><body>");
Cookie[] cookies = req.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
if (cookie.getName().equals("jpub")) {
out.println("cookie::" + cookie.getValue());
}
}
}
//이 다음 코드 쿠키 수정 서블릿을 위한 링크
out.println("<a href='/modicookie'>쿠키수정</a></body></html>");
}
}

이 코드는 request객체에서 getCookies 메서드로 저장된 쿠키들을 꺼낸 뒤에 쿠키 생성 시에 'jpub'을 키로 저장된 쿠키값을 출력한다.

브라우저에서 http://localhost:8080/newcookie 를 입력하면 쿠키가 create되면서 read서블릿으로 넘어갈수 있는 링크가 생긴다.
readcookie 링크를 클릭 시 -> cookie::books 텍스트를 출력한다.

[2.4.2.3] 쿠키값 수정 및 삭제

쿠키값을 변경하려면 같은 이름으로 쿠키를 생성해서 새로운 값을 지정하면 된다.

Cookie modifiedCookie = new Cookie("name", "새로운 값")

CookieModifyServlet을 만들어서 기존에 jpub이란 이름으로 만든 쿠키값을 수정할 수 있다.

[쿠키 수정하는 서블릿 코드] [/ch02/src/main/java/info/thecodinglive/cookie/CookieModifyServlet.java]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package info.thecodinglive.cookie;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

@WebServlet(urlPatterns = "/modicookie")
public class CookieModifyServlet extends HttpServlet{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setCharacterEncoding("UTF-8");
resp.setContentType("text/html");
PrintWriter out = resp.getWriter();
out.println("<html><head><title> cookie 수정 </title></head>");
out.println("<body>");
Cookie[] cookies = req.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
if (cookie.getName().equals("jpub")) {
Cookie modifiedCookie = new Cookie("jpub", "read");
resp.addCookie(modifiedCookie);
}
}
}
out.println("<a href='/readcookie'>readcookie</a></body></html>");
}
}

request 객체에서 쿠키 이름이 기존에 만든 jpub과 같은지 비교한 뒤에 jpub과 같으면 동일한 이름으로 쿠키를 생성한다.
쿠키 수정을 쉽게 하기 위해서 기존에 만든 CookieReadServlet에서 다음과 같이 modicookie로 이동할 수 있는 링크 태그를 추가했다.

out.println("<a href='/modicookie'>쿠키수정</a></body></html>");

쿠키의 결과값이 books read로 변경된 것을 알 수 있다.
쿠키 자체를 삭제하는 API는 존재하지 않는다.
그렇지만 쿠키의 유효 시간을 '0'으로 설정함으로써 쿠키값을 무효화 할 수 있다.

[쿠키 무효화(삭제)하는 서블릿 코드] [/ch02/src/main/java/info/thecodinglive/cookie/CookieDeleteServlet.java]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package info.thecodinglive.cookie;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

@WebServlet(urlPatterns = "/delcookie")
public class CookieDeleteServlet extends HttpServlet{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setCharacterEncoding("UTF-8");
resp.setContentType("text/html");
PrintWriter out = resp.getWriter();
out.println("<html><head><title> cookie 삭제 </title></head>");
out.println("<body>");
Cookie[] cookies = req.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
if (cookie.getName().equals("jpub")) {
Cookie deletedCookie = new Cookie("jpub", "");
deletedCookie.setMaxAge(0);
resp.addCookie(deletedCookie);
}
}
}
out.println("<a href='/readcookie'>readcookie</a></body></html>");
}
}

jpub 쿠키의 유효 시간을 setMaxAge 메서드를 사용해서 '0'으로 설정했다.
http://localhost:8080/delcookie 를 들어가 readcookie 링크를 누르면 쿠키가 소멸되어서 값이 표시되지 않음을 확인할 수 있다.

쿠키값 한글 입력 시에는 URLEncoder를 이용해서 문자열을 감싸줘야한다.
ex) Cookie newCookie = new Cookie("kor", URLEncoder.encode("데이터", "UTF-8"));

[2.4.3] 세션

[2.4.3.1] 세션의 구성

세션(session)은 서버와 클라이언트의 유효한 커넥션을 식별하는 정보.
서버는 클라이언트가 요청을 보내면 요청을 식별할 수 있는 ID를 부여하는데, 이 ID가 세션 ID다.
세션ID는 JSESSIONID란 이름으로 쿠키로 저장되고, 클라이언트가 재접속할 때 해당 쿠키를 이용해 세션 ID값을 서버에 전달한다.
서블릿에서는 세션이 javax.servlet.http 패키지에 HttpSession 인터페이스로 정의되어 있다.

[2.4.3.2] 세션 생성

현재 생성된 세션 정보는 request 객체에서 꺼내서 사용할 수 있다.
request.getSession();

[기본 세션 서블릿 코드] [/ch02/src/main/java/info/thecodinglive/session/DefaultSessionServlet.java]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package info.thecodinglive.session;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.io.PrintWriter;

@WebServlet("/session")
public class DefaultSessionServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html");
resp.setCharacterEncoding("UTF-8");
PrintWriter out = resp.getWriter();
out.println("<html><head><title>세션</title></head><body>");
HttpSession session = req.getSession();
out.println("sessionId::" + session.getId() + "<br/>");
out.println("session created::" + session.getCreationTime() + "<br/>");
out.println("session lastAccessTime" + session.getLastAccessedTime() + "<br/>");
out.println("</body></html>");
}
}

getSession() 메서드를 이용해서 session 객체를 생성한 뒤 세션 정보를 출력하도록 한다.
.getId() : 세션의 고유 아이디를 얻을 수 있는 메서드
.getCreationTime() : 세션이 생성된 시간을 얻을 수 있는 메서드
.getLastAccessedTime() : 웹 브라우저가 가장 마지막에 세션에 접근한 시간을 얻을 수 있는 메서드다.

브라우저에서 http://localhost:8080/session 을 입력하면 세션의 기본 정보를 볼 수 있다.

[2.4.3.1] 세션에 값 저장 및 삭제

세션에 값을 저장하는 방식은 쿠키와 동일하게 이름, 값 형태로 저장할 수 있다.
setAttribute() 메서드를 사용한다.

Session.setAttribute('이름', 값)

[세션에 값을 저장하는 서블릿 코드] [/ch02/src/main/java/info/thecodinglive/session/CreateSessionValueServlet.java]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package info.thecodinglive.session;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.io.PrintWriter;

@WebServlet(urlPatterns = "/createse")
public class CreateSessionValueServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html");
resp.setCharacterEncoding("UTF-8");
PrintWriter out = resp.getWriter();
out.println("<html><head><title>세션</title></head><body>");

HttpSession session = req.getSession();
session.setAttribute("jpub", "book");
out.println("세션이 생성되었습니다.");
out.println("<a href='/readse'>세션 읽기</a></body></html>");
}
}

현재 세션에 정보를 저장하기 위해서 request 객체에서 getSession 메서드를 이용해서 세션을 얻은 후 setAttribute 메서드를 이용해 jpub이란 이름으로 book을 값으로 입력했다.

[세션에 값을 읽어오는 서블릿 코드] [/ch02/src/main/java/info/thecodinglive/session/ReadSessionValueServlet.java]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package info.thecodinglive.session;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.io.PrintWriter;

@WebServlet(urlPatterns = "/readse")
public class ReadSessionValueServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html");
resp.setCharacterEncoding("UTF-8");
PrintWriter out = resp.getWriter();
out.println("<html><head><title>세션</title></head><body>");

HttpSession session = req.getSession();
String sessionValue = (String) session.getAttribute("jpub");
out.println("생성된 세션 값:" + sessionValue);
out.println("</body></html>");
}
}

입력할 때 처럼 session을 얻은 후 getAttribute()로 세션에 입력한 값을 추출한다.
이 떄, getAttribute()의 반환값은 Object이므로 형변환이 필요하다.

[2.5] 디자인 패턴 활용

[2.5.1] Java EE 패턴

Java EE 패턴은 자바 기반의 엔터프라이즈 웹 애플리케이션 개발을 위한 패턴이다.
ValueObject, DataAccessObject 등은 여기서 나온 용어들이다.
Java EE 패턴은 자바 웹 개발 시에 겪는 문제를 해결하는 실마리를 제공한다.
Java EE 는 SUN사가 EJB를 출시하면서 N-Tier 각 레이어에 대한 클래스들의 역할에 관해서 로드맵을 만들고 백서(White Paper)로 배포해서 널리 알려지게 된 패턴이다. N-Tier 아키텍처에서 각 레이어에 적합한 패턴으로 구분되어 있다.
결국 디자인 패턴을 사용해서 스프링의 형태로 가게 된다.

[2.5.1.1] Java EE 패턴 목록

[Java EE 패턴]

패턴 이름 개요
Intercepting Filter 요청에 대한 전처리 및 후처리
요구 사항에 대해서 전처리와 후처리에 대한 솔루션을 제공하고 이를 통해 유동적인 아키텍처를 가능하게 한다.
Front Controller 요청에 대한 처리를 관리하는 중앙 컨트롤러
프리젠테이션 레이어에 일어나는 일들의 창구로 facade 패턴의 역할과 MVC 패턴에서 controller의 역할을 함으로써 보안, 뷰 관리, 탐색들을 관리한다.
View Helper 뷰의 표현을 위해 비즈니스 로직을 가지고 있는 개념상의 Helper
비즈니스 로직과 프레젠테이션 로직의 결합도를 낮추기 위해 사용한다.
Composite View 레고 블럭 같은 작은 뷰들을 조합해서 만드는 전체의 뷰
복잡한 뷰를 만들기 위해서 기본적인 뷰 레이어를 융통성 있게 하고, 개인화 영역과 커스터마이징을 보다 수월하게 한다.
Service to Worker Front Controller와 View Helper Pattern을 이용해 dispatcher 컴포넌트를 구성
대규모 애플리케이션에서 이용되는 기법으로 뷰에 대한 처리 이전에 동작한다.
Dispatcher View Service to Worker와 동일하며 차이점은 뷰에 대한 처리 중에 수행되어야 하고, 작은 시스템에서 더 안정적이다.

앞에서의 필터는 Intercepting Filter Pattern에 대한 구현체다.
Front Controller Pattern은 대부분의 웹 프레임워크에서 개념을 차용하고 있다.

[2.5.2] 프론트 컨트롤러 패턴

컨트롤러가 공동 요청을 먼저 수행하고 뷰를 호출하는 패턴이다.

Client의 요청에 의해서 컨트롤러가 응답하고, 결과에 따라 서블릿이나 JSP로 만든 뷰를 보여준다.
서버 측에서 메서드를 사용하여 화면을 전환하는 방법에는 두 가지가 있다.

  • Response 객체의 sendRedirect 메서드
  • RequestDispatcher 객체의 forward 메서드

[2.5.2.1] sendRedirect

sendRedirect 는 속성을 저장할 수 없고, 다른 로직을 추가할 수 없다.
HttpServletResponse에 속한 메서드다.

response.sendRedirect(경로);

[2.5.2.2] forward

forward 메서드는 서버 내부에서만 흐름이 이동하므로 속성을 저장할 수 있고, 브라우저에게 바로 전달하지 않고 원하는 작업을 처리한 후에 응답을 전환할 수 있으므로 컨트롤러를 만들 때 많이 사용하는 메서드다.
RequestDispatcher객체를 생성해야 forward 메서드를 사용할 수 있다.

1
2
RequestDispatcher rd = request.getRequestDispatcher(경로);
rd.forward(ServletRequest request, ServletResponse response);

RequestDispatcher 객체의 경로는 절대경로로 지정하고 상대경로를 사용할 수 없다.
forward 메서드 사용 시에는 ServletRequest와 ServletResponse 객체를 파라미터로 전달하므로 sendRedirect와는 다르게 ServletContext와 Session에 속성을 저장하고, 포워딩 한 곳에서 사용할 수 있다.

1
2
3
4
5
6
7
if(url == "list") {
RequestDispatcher rd = req.getRequestDispatcher(url);
rd.forward(request, response);
} else if(url == "write") {
RequestDispatcher rd = req.getRequestDispatcher(url);
rd.forward(request, response);
}

컨트롤러에서 화면을 보여 주는 구문은 위와 같이 if 문으로 분기처리하게 되는데, 이렇게 컨트롤러에서 직접적으로 forward 메서드를 사용하게 될 경우에는 URL이 변경되거나 뷰가 변경될 때마다 컨트롤러를 변경하게 되어서 추후에 유지보수가 어려워진다. 이럴 때는 커맨드 패턴을 이용해서 컨트롤러 클래스의 복잡도를 낮출 수 있다.

[2.5.2.3] 커맨드 패턴

커맨드 패턴은 명령(로직)을 객체 안에 캡슐화해서 저장함으로써 컨트롤러와 같은 클래스를 수정하지 않고 재사용할 수 있게 하는 패턴이다.

Invoker의 역할은 컨트롤러가 담당한다. 이전에 있던 forward 메서드 관련 코드를 커맨드로 옮길 수 있다.

[forward 메서드 관련 코드를 Command Pattern화 한 코드] [/ch02/src/main/java/info/thecodinglive/pattern/Command.java]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
package info.thecodinglive.pattern;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public abstract class Command {
private HttpServletRequest req;
private HttpServletResponse res;
private ServletContext servletContext;

abstract public void execute();

public void forward(String url){
try{
RequestDispatcher rd = req.getRequestDispatcher(url);
rd.forward(getReq(), getRes());
}catch (IOException ioe){
servletContext.log("forward Error",ioe);
}catch (ServletException servletEx){
servletContext.log("servlet Error", servletEx);
}
}

public HttpServletRequest getReq() {
return req;
}

public void setReq(HttpServletRequest req) {
this.req = req;
}

public HttpServletResponse getRes() {
return res;
}

public void setRes(HttpServletResponse res) {
this.res = res;
}

public ServletContext getServletContext() {
return servletContext;
}

public void setServletContext(ServletContext servletContext) {
this.servletContext = servletContext;
}
}

커맨드 클래스는 서블릿 클래스가 아니므로 HttpServletRequest와 HttpServletResponse를 변수로 선언하고 setter 메서드를 통해서 컨트롤러에서 인스턴스를 얻을 수 있도록 한다.
요청을 응답받고 전달받은 데이터를 Command 객체에 제공할 서블릿 클래스를 만들어야 한다.

[command 객체에 제공할 서블릿 클래스 코드] [/ch02/src/main/java/info/thecodinglive/pattern/FrontController.java]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
package info.thecodinglive.pattern;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebInitParam;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

@WebServlet(urlPatterns = "/controller", initParams = {@WebInitParam(name = "mapping", value = "/WEB-INF/command.properties")})
public class FrontController extends HttpServlet {
private Properties cmdMapping;

@Override
public void init(ServletConfig config) throws ServletException {
super.init(config);
InputStream is = null;
try {
String location = config.getInitParameter("mapping");
is = getServletContext().getResourceAsStream(location);
cmdMapping = new Properties();
cmdMapping.load(is);
} catch (IOException e) {
getServletContext().log("I/O Error", e);
} finally {
try {
is.close();
} catch (IOException iog) {

}
}
}

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String cmd = req.getParameter("cmd");
String cmdClass = (String) cmdMapping.get(cmd);
Command command = null;

try {
command = (Command) Class.forName(cmdClass).newInstance();
} catch (ClassNotFoundException | IllegalAccessException | InstantiationException ex) {
getServletContext().log("class not found", ex);
}
command.setReq(req);
command.setRes(resp);
command.setServletContext(getServletContext());
command.execute();
}
}

doGet 메서드에서 command 객체로 사용될 클래스의 이름을 입력받은 후 리플랙션을 이용해서 인스턴스를 생성하고 execute 메서드를 호출한다. execute 메서드는 입력받은 파라미터와 뷰 클래스들을 매칭해서 응답을 전달하는 역할을 한다. 뷰 클래스들의 패키지 위치 정보는 command.properties 파일로 관리한다.

[command.properties 파일] [/ch02/src/main/webapp/WEB-INF/command.properties]

1
2
3
home=info.thecodinglive.pattern.HomeView
list=info.thecodinglive.pattern.ListView
write=info.thecodinglive.pattern.WriteView

command.properties 파일은 입력된 파라미터가 view 클래스들과 연결되도록 하는 역할을 한다.
아래는 home.jsp 페이지를 연결하는 HomeView 클래스를 만든 것이다.

[HomeView 클래스] [/ch02/src/main/java/info/thecodinglive/pattern/HomeView.java]

1
2
3
4
5
6
7
8
package info.thecodinglive.pattern;

public class HomeView extends Command {
@Override
public void execute() {
forward("/home.jsp");
}
}

브라우저에 localhost:8080/controller?cmd=home를 입력하고 결과를 확인해보면 HomeView 클래스에서 Command를 상속받아서 home.jsp로 리퀘스트를 포워딩하는 것을 볼 수 있다.

클래스의 흐름을 정리해보면
FrontController -> command
^
|
home.jsp <- HomeView
모든 요청은 FrontController 클래스가 받고, HomeView는 Command클래스를 상속받았고 요청 파라미터가 Home인 경우 home.jsp를 호출한다.

이와 같이 프론트 컨트롤러 패턴은 뷰 페이지 요청을 한 곳에서 관리할 수 있게 해준다. HomeView외에 다른 뷰를 만들어 jsp와 연결 시킬 수 있다.

[실용영어] 필수 구어체 5개 wanna gonna gotta kinda outta

작성일 2018-06-15 | Edited on 2018-06-18 | In 영어 |

오늘은 실제로 많이 쓰이지만회화, 영화, 드라마에 꼭 나온다 시험영어만 가르치는 우리나라에선 잘 언급되지 않는 구어체 5가지 표현을 알려드리려고 합니다.

Wanna 워너 (= want to)

개요

want원하다 동사 뒤에는 to가 붙기 마련입니다. 생각해보세요, 원한다는 걸 말할 때 무조건 뭘 원하는지to 말하기 마련이잖아요.
그리고 want to~을 원하다를 미국식으로 읽으면 t발음이 잘 안나면서 워너- 라고 읽을 수 있습니다. 모든 언어는 짧고 간단할 수록 좋습니다. 결국 이런식으로 쓰게 되는 데 한 단어로 표현 할 수 있으면 좋은거죠. 그래서 wanna가 탄생합니다.
게다가 우린 이 wanna에 익숙합니다. 워너원(Wanna One) 덕분이죠
중요한 건, 동사 같이 보이지만 동사+전치사 이기 때문에 주어+wanna+명사or 동사 이런 구조로 사용해야한다는 점이에요. ex) I wanna meet your friends 처음 사용한다면 wanna = want로 착각해서 wanna to ~ 이런 식의 실수를 하지 마세요.

의미

동사+전치사 ~을 원하다.

이렇게 써보세요

Let's Say ) I wanna get some rest. 난 좀 쉬고싶어

Let's Say ) Do you wanna go to Starbucks? 스타벅스 갈래?

Let's Say ) What do you wanna eat? 넌 뭐 먹고 싶은데?


Gonna 가너 (= going to)

개요

be going to~하려고 해라는 표현은 정말 많이 쓰는 표현인데요, 많이 쓸 수록 짧으면 좋겠죠?
going(gon) + to(na) gonna 가너-가 만들어집니다.
중요한 건, going이 동명사 라서 앞에 의미없는 be-동사를 붙여줘야합니다. I'm gonna read a book. 이런 식으로요 wanna처럼 to 뒤에 명사or 동사를 넣으면 됩니다.
하지만, 언어를 쓰는 사람이 언어의 법입니다. gonna를 be going to로 쓰는 원어민도 종종 있다보니까 잘 못써도 이해 해줄겁니다.
다음 구어체 gotta와 헤깔리지 마세요

의미

동명사+전치사 ~하려고

이렇게 써보세요

Let's Say ) I'm gonna study now. 난 이제 공부하려고

Let's Say ) What're we gonna do? 이제 어쩌게?/ 뭐하게?

Let's Say ) We're not gonna take it. 우린 참지 않을거야


Gotta 가러(가터) (= have got to)

개요

get은 원어민들이 좋아하는 동사죠, 무엇인가를 얻으며 변화되는 이미지를 갖고있는데요, p.p의 모습을 띄고 있는 have gotp.p의 뜻이 아님는 have와 같은 뜻으로 사용됩니다. 즉, have got to는 have to입니다.
회화나 채팅할 때는 should, must, have to 같은 표현을 사용하지 말고, gotta를 사용하는 연습을 해보세요.
추가로, gotta는 맨 앞에 의문문으로 사용해 have you got a ~? 라는 의미로 가끔 쓰기도 한답니다.

의미

동사+전치사 ~해야 해 (=should, have to)

이렇게 써보세요

Let's Say ) I gotta go home today. 난 오늘 집에 가야해

Let's Say ) Gotta pencil? 연필 한개 있니?

Let's Say ) You gotta be stronger. 넌 더 강해져야 돼


Kinda 카인더 (= kind of)

개요

kind of를 빨리 발음해보세요, 그럼 왜 kinda(카인더)가 됐는지 알겠죠?
이 표현 역시 매우 많이 사용하는데, kind of라는 그 뜻 그대로 일종의 라는 전치사로 사용하거나,
젊은 사람들 사이에서 우리가 자주 쓰는 그냥 좀~ 이런 투의 전치사로도 사용합니다.
서로 다른 뜻이니 상황에 맞게 잘 사용해보세요~

의미

전치사 좀(조금), 일종의(종류의)

이렇게 써보세요

Let's Say ) What kinda music do you like? 어떤 류의 음악 좋아하니?

Let's Say ) I'm just kinda tired. 나 그냥 좀 힘들어

Let's Say ) It's kinda cool. 그거 좀 멋진데


Outta 아우러(아우타) (= out of)

개요

이것도 역시 out과 of를 빨리 발음하면 outta(미국-아우러/영국-아우타)발음이 됩니다.
out of는 의미가 엄청 다양합니다. 그래서 많이 사용하겠죠?

1. out of [~] ~에서 벗어난
ex) It's out of answer. 그건 답에서 벗어 났다.

2. out of [~] ~때문에
ex) I took the job out of necessity because we had no money left. 우리는 남은 돈이 없었기에 난 직업을 필요성 때문에 갖었다.

3. [숫자A] out of [숫자B] B중에서 A
ex) five out of seven people 7명 중에서 5명

4. out of [~] ~로 부터
ex) Didn't you know that butter was made out of milk? 너 버터가 우유로부터 만들어진다는 거 몰랐니?

의미

전치사 ~에서 벗어난, ~때문에, B중에서 A, ~로 부터

이렇게 써보세요

Let's Say ) We'd better get outta here. 우리 여길 나가는게 좋겠어

Let's Say ) I was scared outta my mind. 난 내가 미칠까봐 두려웠어

Let's Say ) What is God want outta my life. 하나님이 내 삶에서 원하는게 무엇인가?


그 외 구어체

그 외에도.. 수 많은 구어체들이 있지만 위에 5개정도만 기억하세요, 아래는 영덕영어덕후이 되고싶은 분들만보세요.

ain't (=am not, isn't, aren't, don't)

hafta (=have to)

donno (=don't know)

gimme (=give me)

lemme (=let me)

musta (=must have)

lotta (=a lot of)

Hexo와 GitHub Pages로 블로그 오픈

작성일 2018-05-28 | Edited on 2018-06-15 | In Hexo |

Hexo헥소와 GitHub Pages깃허브 페이지로 블로그를 만들었습니다.
개인적으로 깃허브 페이지깃 허브 웹사이트는 리눅스의 마인드자유 소프트웨어, 오픈개발가 담겨 있다는 점이 가장 맘에 듭니다. 무료 호스팅, 도메인 제공 넘조아


프롤로그

깃 페이지Git Page로 블로그를 만든다는건 개발자들에게 특히 좋지만, 엄청난 응용커스터 마이징이 가능한 블로그이기에 제약받지 않는 자유로운 나만의 홈페이지를 만들고싶은 모든분들에게 적극 추천합니다!

게다가 Hexo라는 도구를 이용한다면 이런 강점을 갖을 수 있습니다. 최신기술웹 관련, 최적화엄청 가볍다 ㅎㅎ, SEO검색 잘 되게 하는 기술, 마크다운키보드 만으로 꾸밀 수 있음


블로그 소개


제 블로그에는 주로 개발자들을 위한 컨텐츠와 개인적인 포스팅을 할 예정입니다.


블로그 신념?

제 블로그 신념은 정보를 전달할 대상에게 이해하기 쉬운 글을 작성할 것입니다.

  • 장황하고 횡설수설하는 글, 사전 같은 글보다는 필요한 정보를 쉽고 빠르게 이해할 수 있게 사족은 최대한 적게, 예시나 사진을 사용해서 포스팅할 계획입니다. 초등학생도 이해할 수 있을지도..?
  • 제가 작성하는 포스트는 이해를 돕기 위해 포스팅 타겟에 따라 말투, 게시하는 스타일이 달라질 것입니다.

개인적인, 정리하는 공간으로서 사용할 것입니다.

  • 개인 포토폴리오와 공부한 내용을 정리하는 목적의 포스팅도 올릴 계획입니다.

Hexo + GitHub Pages의 특징

99% 자유로운 블로그 제작 가능!

  • 한 마디로 표현해서 모든 것이 수정 가능하다 심지어 포스팅 날짜도
  • 필요한 기능이 있다면 플러그인을 추가해 제작이 가능하다.

키보드 만으로 포스팅이 가능하다.

  • 깃허브 블로그 답게 포스팅도 코딩같이 할 수 있다.
  • MarkDown문법을 사용해 텍스트를 꾸미는 것 외에도, 복잡한 수식 또한 텍스트로 표현할 수 있다.

    이렇게 $(ax^2 + bx + c = 0)$, $(a \ne 0)$ $$x = {-b \pm \sqrt{b^2-4ac} \over 2a}$$

테마가 Jekyll보다 이쁘고, 테마를 개발하는 유저도 많다.

  • 블로그 프레임워크로 유명한 3가지가 있다. Hexo - JS(nodeJS), Hugo - Go, Jekyll - Ruby
  • 각자 사용 언어가 다르고 장, 단점이 있지만 포스트 원본 파일을 따로 관리할 수 있고Jekyll이라면 깃 허브에서 private아닌 이상(=매달 돈을 내지 않는 이상) 그대로 공개됨 테마가 확실히 좋다. 그리고 더 가벼움. 맨 처음엔 나도 Jekyll라는 툴을 사용 했지만, 내가 고른 테마가 너무 문제가 많아 Hexo로 갈아탔다. 웹 마스터가 아니기에 테마가 중요하고 이 때문에 스트레스 받고 싶지 않았다.




Hexo와 GitHub Pages로 블로그 만드는 포스팅을 함 해드려겠다. :)

JJ WON

JJ WON

MR Developer, Infagic Smith

4 포스트
3 카테고리
10 태그
Curookie
© 2018 JJ WON
Powered by Hexo v3.7.1
|
Theme — NexT.Gemini v6.3.0
0%