본문 바로가기

Spring/Servlet

뉴렉처 서블릿3 - MVC Model | EL | 태그라이브러리

out.write(""); 출력 부분을 jsp페이지로 넘길 예정

jsp는 add.jsp 파일명 그대로 URL매핑되어 실제 만들어진 서블릿 코드는 "\work\Catalina\~~~~\add_jsp.java"가 됨

 

doGet에서 직접 out.write할 필요 없이 calculator.jsp파일을 만듦

${3+4} → 7로 출력됨

서버 재가동할 필요없이 새로고침만 해도 변동 반영

 

서버 OPEN 혹은 더블클릭 시 sever 설정창 열림

WebContent : 개발 디렉토리

Server path : 임시 배포 디렉토리

[컴파일된 배포 파일]

workspace/.metadata/.plugins/org.eclipse.wst.server.core/temp0/work/Catalina/localhost/Root/org/apache/jsp/calculator_jsp.class

 


멤버함수, 멤버 변수 정의

<%! %>

 

일반적인 코드블럭

<% %>

 

출력 코드블럭

<%= %>

 

Page 지시자[지시블럭]

<%@ page ~~ %>

- html태그에 앞서 일반적인 코드블럭에 response.setCharacterEncoding("utf-8"); 같은 설정을 한다 하더라도 예외발생

   코드지시자는 어떤 출력보다 앞서 설정이 이루어지기 때문에 지시자를 통해 설정함


내장객체

- 내가 직접 한 코드에는 없지만 jsp가 만들어낸 서블릿에 내가 모르는 변수가 존재할 수 있음 ex. "page"

- Jasper가 만들어낸 서블릿 안에 미리 선언된 변수 

- pageContext, session, application, config, out, page, request, response > 내장객체라고 함

- ServletContext 전역적 사용 | PageContext 내부에서만 사용

 

Request 입력도구

- getParameterNames(), getParameter(name), getParameterValues(name), getCookies(), getMethod(), getSession()

  getRemoteAddr(), getProtocol(), setCharacterEncoding(), getHeaderNames(), getHeaders(name), getQueryString()

 

Response 출력도구

- setContentType(type), setHeader(name, value),setDateHeader(name, date), sendError(status, msg), sendRedirect(url), addCookie(cookie), endcodeURL(url), setStatus(sc)

 

Out

- getBufferSize(), getRemaining(), clearBuffer(), flush(), close(), println(content). print(content)

- out객체는 직접 잘 쓰지 않음. jsp에서는 response.getParameter("name");할 필요 없이 바로 작성

 

Session

- getId(), getCreationTime(), getLastAccessedTime(),getMaxInactiveInterval(), setMaxInactiveInterval(t)

-getAttribute(attr), setAttribute(name, attr), invalidate(), removerAttribute(name)

 

Application

- setAttribute(name, value), getAttribute(name), getRealPath(path), getResource(path), getServerInfo(), getSession(), getRemoteAddr(), getProtocol(), setCharacterEncoding()

 


MVC model1

컨트롤러와 뷰가 물리적으로 분리되지 않은 방식

 

Model : 출력 데이터

Controller : 입력과 제어를 담당 [ 자바코드 ]

View : 출력 담당 [ HTML코드 ]

- 출력코드와 입력코드로 양분화해서 코드를 단순화

- 입력코드는 상위에 <% %>

- 출력코드는 최대한 model변수만을 출력하는 간단한 코드 ex. <%=result%> 입니다

 

MVC model2

컨트롤러와 뷰가 물리적으로 분리된 방식

- Controller & Dispatcher

   컨트롤러에서 뷰를 연결하기 위한 방식 포워딩 > Dispatcher

- JSP : view단

cf. jsp또한 엄밀히 따지면 서블릿

 

- Dispatcher를 집중화하기 전, 페이지마다 Controller와 Dispatcher를 만들었지만

- 집중화 후 다수, 별도의 Controller(일반 클래스 형태)를 만들고 실질적 서블릿은 Distpatcher 하나만 만듦

- 사용자 요청이 들어오면 Dispatcher에서 적절한 Controller찾아서 처리

- Controller는 진행한 로직에 해당하는 뷰를 호출할 수 있도록 다시 Dispatcher에게 관련된 내용 전달(url-mapping)

- Dispatcher는 View 호출

 

- 서블릿 코드로 만들기 : src 폴더에 java파일 만들고 HttpServlet 상속 & @WebServlet("/요청이름")

 

- redirect : 현재 작업내용과 무관한 새로운 요청

- forward : 현재 작업내용 그대로 이어감

 

        상태 저장소 
        1. pageContext : 페이지 내에서 혼자서 사용
        2. request : forward 관계에서 사용
        3. session : 현재 session에서 공유 
        4. page : 모든 페이지에서 공유
        5. cookie : 클라이언트에 저장

 

실행은 무조건 컨트롤러에서 할 수 있음. 뷰단은 껍데기일뿐

 

package com.newlecture.web;

@WebServlet("/spag")
public class Spag extends HttpServlet{
	@Override
    protectd void doGet(HttpServletRequest request, HttpServletResponse response){
    	int num = 0;
        String num_ = request.getParameter("n");
        if(num_!= null %% !num_.equals(""))
        	num = Integer.parseInt(num_);
            
        String result;

        if(num%2!=0)
            result = "홀수";
        else
            result = "짝수";
		
        request.setAttribute("result", result);
        
        //redirect : 별개의 새로운 작업일 때
        //forward : 작업을 이어갈 때
        RequestDispatcher dispatcher 
            = request.getRequestDispatcher("spag.jsp"); //URL상 같은 디렉터리라 경로지정안함
                                                    //jsp로 되어있지만 서블릿임
            dispatcher.forward(request, response); //처리한 내용을 공유할 수 있음
     }
}


-------------------------------------------------
spag.jsp

<%=request.getAttribute("result")%>

EL(Expression Language)

저장 객체에서 값을 추출해서 출력하는 표현식

Controller View
request.setAttribute("cnt",30);
List list = new ArrayList(){"1", "test" ....};
request.setAttribute("list", list);

String[] names = {"newlec", "dragon"};
request.setAttribute("names", names);

Map n = new HashMap("title", "제목");
request.setAttribute("n", n);

Map<String, Object> notice = new HashMap();
notice.put("id", 1);

notice.put("title", "EL 좋아요");

request.setAttribute("notice", notice);
기존의방식
request.getAttribute("cnt");
((List) request.getAttribute("list")).get(0)
((Map) request.getAttribute("n")).get("title")
EL
${cnt}

${list[0]}                                   //Array, ArrayList 모두 배열처럼 꺼냄
${names[1]}
${n.title}                                //HashMap : name.key으로 바로 꺼냄
${notice.title}


EL의 데이터 저장소

내장객체 설명
pageScope page 영역의 생명주기에서 사용되는 저장소
requestScope request 영역의 생명주기에서 사용되는 저장소, 두 개의 서블릿이 공유
sessionScope session 영역의 생명주기에서 사용되는 저장소
applicationScope application 영역의 생명주기에서 사용되는 저장소
param 파라미터 값을 저장하고 있는 저장소
paramValues 파라미터 값을 배열로 저장하고 있는 저장소
header Header 정보를 저장하고 있는 저장소
headerValues Header 정보를 저장하고 있는 저장소
cookie 쿠키 정보를 저장하고 있는 저장소
initParam 컨텍스트의 초기화 파라미터를 저장하고 있는 저장소
pageContext 페이지 범위의 컨텍스트 저장소 [페이지 내에서 사용]

- EL에선 request뿐만 아니라 pageContext (페이지객체) 또한 이용가능

<%
pageContext.setAttribute("aa", "hello");
%>

<body>
 ${aa} //hello 출력됨
</body>

- 일반적으로 한정사를 붙이지 않고 EL을 사용한다면 아래 저장 객체에서 값을 추출하는 순서에 따라 탐색

- 한정사를 붙인다면 특정 객체에서만 값을 찾아오겠다는 뜻, 단 sessionScope가 session객체를 뜻하는 것은 아니고 범위를 한정

- EL에서는 함수 호출하는 방식으로는 불가하고 속성처럼 가져올 수 있음

- 예시) pageContext의 경우 getter를 사용해 메소드 사용 가능 (단 표현 방식이 달라짐 - 예시참고)

   그러나 getter가 아닌 속성처럼 객체를 얻거나 메소드를 호출 get빼고 소문자, 괄호 없이 이어서 씀

 

저장 객체에서 값을 추출하는 순서 한정사 사용
page → request → session → application
page에서 찾으면 그 뒤는 찾지 않음
같은 이름의 속성이 있어도 page에서 찾으면 그 값을 출력
묵시적으로 ${cnt}로 검색하면 순서에 따라 검색

Scope 키워드를 사용하여 검색한다면
해당 특정 객체에서만 값을 꺼내옴
${sessionScope.cnt}
${requestScope.cnt}
${param.cnt}
${header.host}
${header["host"]}     //변수명 규칙에 맞지 않을 때는 대괄호 이용
                                             /, 사이띄기(스페이스)가 들어갈 때

${param.n}                  //?n=3
${header.accept}      //accept : 읽어들일 수 있는 문서의 종류
                                       클라이언트 환경을 확인해 맞는 정보 전송할 때

<%= pageContext.getRequest().getMethod() %>
${pageContext.request.method} 
                                                             //request 객체를 가져와서 메소드 호출



EL연산자

[] .  
()  
not ! empty
${not empty param.n}
삼항연산자로 활용 가능
${not empty param.n ? '값이 비어있습니다.' : param.n}
empty: 논리연산자
${empty param.n} > null이어도 참, 빈문자열이어도 참
즉 ${param.n == null || param.n == ''}과 같음

* / div % mod  
+ -  
< > <= >= lt gt le ge               

cf. less than, greater than, greater or equal, less or equal
왜 <>가 아닌 lt gt를 쓸까?
html 태그에 꺾음쇠가 빈번하게 사용되기 때문에 에러 방지
기본적인 html에는 간편하게 기호 사용.
쓸수없는 환경에서 문자 사용
== != eq ne  
&& and  
|| or  
? :  

html파일 jsp파일로 변환하기

1. 복사 붙여넣기 후 File-Properties[ Alt+Enter ] : 인코딩 방식 변경 (utf-8)

2. 여전히 서블릿 코드는 달라진 게 없기 때문에 페이지 설정 상단에 추가

   <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>

 

서블릿코드는 재배포, 컴파일 다시 해야하기 때문에 서버를 재가동해야했지만

jsp는 파일 내용이 바뀌어도 서버를 재시작할 필요 없음. 서블릿코드를 알아서 재스퍼가 새로 만들어줌. 새로고침만 하면 됨

 


실습과제 주의사항

 

1.

ojdbc.jar을 기존에는 bulid Path했지만,

실제로 웹개발할 때는 라이브러리를 같이 배포할 수 있도록 WEB-INF lib에 포함시켜야 한다.

 

특별한 자료 파일이 아니라 자바 실행환경에 해당하는 라이브러리, 톰캣 라이브러리는 굳이 넣지 않아도 된다.

(실행환경을 이미 갖추고 있기 때문에)

 

 → ojdbc6.jar lib파일에 복사붙여넣기

 

2.

CreateStatement PrepareStatement
Statement st = con.createStatement();
ResultSet rs = set.executeQuery(sql);
String sql = "select * from Notice where id =?";

PreparedStatement st = con.prepareStatement(sql); 
st.setInt(1, id);

ResultSet rs = set.executeQuery();
미리 쿼리문을 준비하기 때문에 sql을 매개변수로 가지지 않음

 

3. MVC1으로 바꾸기

 

Control : 입력처리, 자바코드 상단으로 모두 빼서 rs.getString("~~")를 변수(Model)에 담음

View : 문서출력

 

4.MVC2로 바꾸기

Controller는 Servlet으로, View는 JSP는 물리적으로 분리코드의 복잡도는 높아지지만 개별적 유지관리 가능, 협업 가능실행 성능에서도 도움 됨

- Controller는 미리 컴파일되어 로드 후 실행하면 가벼움 | View는 그때그때 컴파일 이루어짐

  Controller에서 model을 View단에 전달하기 위해 앞서 다루었던 객체들을 사용

 

pageContext | request | session | applicationrequest는 입력도구이면서 저장소로 사용

 

  1. Servlet 패키지, 클래스 만들기 → 패키지명 controller 클래스명 NoticeDetailController
  2. HttpServlet 상속, @WebServlet("/notice/detail")
  3. service를 추가할 것인지 get요청에 특화된 메소드 doGet을 추가할 것인지 결정해야하는데,
     가능한 특화된 메소드를 사용하는 것이 좋다. 
  4. doGet안에 자바코드 복붙 후 import, 예외처리
  5. 흐름 제어 redirect(완전히 보냄) OR  forward(작업 유지)
    → request.getRequestDispatcher("/notice/detail.jsp").forward(request, response);
  6. forward 전에(try안에) 데이터 저장 request.setAttribute("이름", 변수);
  7. 서블릿을 매핑했기 때문에 list.jsp 목록의 href 링크 걸어줄 때 <a href="detail?id=~~">로 수정해야 함
    기존에는 detail.jsp에서 제어+출력을 모두 담당했지만, 이제는 서블릿에서 제어를 전담하기 때문에 해당 서블릿으로 먼저 연결
  8. dto 만들기 (아래 참조) - controller에서 해당 객체 만들어 속성으로 담기
    Notice notice = new Notice(id, title, writerId, regdate, hit, files, content); //순서 주의
    request.setAttribute("n", notice);
  9. list.jsp에서 자바코드 따로 빼서 서블릿으로 만들기

    package com.newlecture.web.controller
    @WebServlet("/notice/list")
    public class NoticeListController {
        List<Notice> list = new ArrayList<>(); //목록은 Notice객체 여러개
        자바코드 복붙
        list.add(notice);

        //view단에 전달하기
        request.setAttribute("list", list);
        request.getRequestDispatcher("/notice/list.jsp").forward(request, response);
    }
    list.jsp

    출력 시 EL사용
    반복문 사용 : EL은 반복을 할 수 없기 때문에 반복을 위해 태그라이브러리 사용
    ${list[0].writerId}
    ${list[0].title}
    //57강에서는 jstl 배우지 않아 forEach문으로 넣었다 빼는 방법 설명함

- 사실 스파게티 코드, mvc1, mvc2 모두 사용 가능하지만 mvc2가 유지보수차원에서 권장됨

- view는 사용자가 직접 요청할 수 없게 해야함 →WEB-INF에 view폴더 생성 후 전부(admin, notice, member, student) 넣기

   이에 따른 경로 변경 필수 (getRqeustDispatcher("/WEB-INF/view/notice/list.jsp").forward(request, response));

 

 


Model 데이터를 위한 구조화

- 엔티티(개체) : 개념적인 데이터 집합- 묶어서 계층을 만들었다는 의미에서 구조화된 데이터라고도 한다

request.setAttribute("title", rs.getString("title"));
request.setAttribute("writerId", rs.getString("writerId));
request.setAttribute("regdate", rs.getDate("regdate"));
request.setAttribute("content", rs.getString("content"));
request.setAttribute("hit", rs.getInt("hit"));

request.addAttribute("notice", notice); 

묶어서 개체로, 직관적으로 표현 가능
② com.newlecture.web.entity
public class Notice{
  private int id;
  private String title;
  private String writer;
  private Date regdate;
  private String content;
  private int hit;
 
  getter, setter, toString(테스트 출력위해 추가)
  생성자 (오버로드된 생성자 필수, 기본생성자는 선택)

}

View단
getter 메소드가 존재하면 표현언어로 바로 꺼낼 수 있다

${n.id}
${n.title}
${n.writer}
${n.regdate}
${n.content}
${n.hit}


forEach문을 쓰기 전 자바 코드&EL태그를 이용하여 목록 불러오기


JSTL 태그라이브러리 (JSP Standard Tag Library)

- 태그라이브러리를 실제로 만들 수 있다
  1.Tag Library Descriptor : WEB-INF/{파일명}.tld 파일 만듦
   - 문서에서 <for>태그는 forTag 클래스가 처리하겠다
  2. Tag Handler : TagSupport 클래스를 상속받아 메소드 선언
  과거에는 이런식으로 직접 태그라이브러리를 만들어 사용했지만 우리는 이미 만들어진 라이브러리 사용
  3. 같은 이름의 태그가 여러개 있을 수 있기 때문에 domain name으로 uri 식별자를 만들고 있다
  prefix = "c" 는 곧 uri="고유도메인주소"를 의미한다는 뜻
  Jasper에게 서버에서 처리할 taglib임을 알 수 있게 한다

 

수정전 수정후
list.jsp파일 반복문 발췌

<%
List<Notice> list = (List<Notice>) request.getAttribute("list");
for(Notice n : list) {
    pageContext.setAttribute("n", n);
}
%>
<%@ taglib prefix="c" uri=Ctrl+Space
                                   → "http://java.sun.com/jsp/jstl/core"%>

//list에서 하나씩 꺼내서 n에 담아 반복 후 setAttribute까지 자동
<c:forEach var="n" items="${list}">
<tr>
   <td> %{n.id}</td>
          중략
</c:forEach>

//저장소에서 값을 꺼내오는 건 EL
//태그는 꺼낸 것을 var="n"키워드로 담아 setAttribute대신함
항상 Controller에서 실행

태그라이브러리는 다섯개의 범주를 가진다. 그러나 여기선 세 가지 (Core, Format, Functions)만 알아본다.

SQL XML 사용하지 않는 게 바람직함. 뷰단에서 이것저것 처리

1. Core 제어의 행위 담당

Jasper가 HTML인지 어떤 태그인지 모르기 때문에 이를 설명하기 위해 접두사 c를 사용

<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" &>

<c:fotTokens> <c:set>
토큰을 만들어주면서 반복
<c:forTokens
  var="filesName" items="${n.files}" delims=",">
                                                                        //delims: 구분자
임시변수 설정
<c:set var="" value="${}">
연산은 el태그로 표현
삼항연산자 활용하여
null값일때 기본값 주기

<c:foreach> <c:if>
items ="${list}" 컬렉션의 내용을 꺼낸다
var = "n" pageContext에 "n"으로 담는다
begin="1" end="3" 인덱스1부터 3까지   [인덱스는 0부터시작]
varStatus="st" 상태값 알기위한 사용자 지정 변수명
#{st.} 관련 속성 아래 더보기
test="${ }" 속성
el태그 안에 연산해서 조건문 설정
else if 태그는 따로 없고 if로 작성


<c:choose> <c:when>
   
<c:otherwise> <c:catch>
   
<c:remove> <c:url>
   

비권장 : param, import, redirect, out

<forEach  var="n" items="${list}" varStatus="st">

더보기

${st.current} 현재 반복되는 아이템 ex.notic객체 | 숫자

${st.index}  현재 반복되는 반복 인덱스(0부터 시작) | ${st.count} 현재 반복 횟수

${st.first} 첫번째 아이템이라면 true | ${st.last} 마지막 아이템이라면 true

${st.begin} begin 속성에 설정한 값 | ${st.end} end 속성에 설정한 값

${st.step} 반복되는 인덱스의 증가치

<c:forEach var="n" items="${list}" varStatus ="st">
<tr>
<td>${st.index} / ${n.id}</td> <!-- detail.jsp에서 그냥 detail로 요청 -->

${st.index+1} //1부터 나오고 싶다면 더해주기

${st.current} 현재 반복되는 아이템 ex.notic객체 | 숫자

${st.index}  현재 반복되는 반복 인덱스(0부터 시작) | ${st.count} 현재 반복 횟수

${st.first} 첫번째 아이템이라면 true | ${st.last} 마지막 아이템이라면 true

${st.begin} begin 속성에 설정한 값 | ${st.end} end 속성에 설정한 값

${st.step} 반복되는 인덱스의 증가치

st.index를 글번호와 함께 출력할때

<c:forEach var="n" items="${list}" varStatus ="st">
<tr>
<td>${st.index} / ${n.id}</td> <!-- detail.jsp에서 그냥 detail로 요청 -->

${st.index+1} //1부터 나오고 싶다면 더해주기

 


Pager 번호 생성하기

 

현재 페이지가 해당하는 페이지 목록의 첫번째 페이지 번호(startNum)를 알아내야 한다.

먼저, 현재 페이지(page)에서 startNum까지의 간격을 알아낸다. (page-1)%5 (cf. 5 = 블록 단위)

startNum = page - (page-1)%5

사용 : set, if, forEach


forTokens로 첨부파일 목록 출력하기

 


2. Format 날짜, 화폐 포맷팅

Function EL을 이용해 데이터 추출해 사용할 때 문자열 조작

<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>

숫자 날짜 형식 로케일지정
formatNumber 숫자를 양식에 맞춰 출력 setLocale 국제화 태그들이 사용할 로케일 지정
formatDate 날짜 정보를 담은 객체 포맷팅하여 출력 requestEncoding 요청 파라미터의 인코딩 지정
parseDate 문자열을 날짜로 파싱 메시지처리
parseNumber 문자열을 수치로 파싱 bundle 태그 몸체에서 사용할 리소스 번들 지정
setTimeZone 시간대별로 시간 처리 message(param) 메시지 출력
timeZone 시간대별로 시간 처리 setBundle 특정 리소스 번들 사용할 수 있도록 로딩

 

날짜 및 시간 표현

"yyyy-MM-dd hh:mm:ss"

<fmt:formatDate pattern="yyyy-MM-dd hh:mm:ss" value="${n.regdate}"/>

월은 왜 대문자일까? minute과의 구분 위해 | yy-mm-h:m:s 등 패턴 다양하게 표현 가능

 

숫자표현

자릿수가 커질 때 가독성을 위해 숫자 끊어 볼 수 있도록 포맷팅

<fmt:formatNumber value="${n.hit}"/> value에 원하는 숫자값 넣는 순간 자동 포맷팅 (세자리 끊기)

  • type = "percent/number/currency($)"            //원화 표현 : <fmt:formatNumber value="${amount}" type="number/> 원
  • maxIntegerDigits (정수 몇째자리까지 표현할지)
  • maxFractionDigits (소수점은 셋째자리까지가 디폴트)
  • pattern 그외 표현        //넷째자리마다 끊기, pattern="#,####" type="number" 앞의 #의 개수는 중요하지 않음
                                              //"#,###원"

3. Functions로 EL에서 함수 이용하기

head에 페이지 지시자 추가

<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>

contains, containsIngnoreCase, endsWith, indexOf, replace, split, startWith, substring 등

 

사용방법 : ${fn:함수명(변수명)}         //el태그 내부에서 사용한다

 

원하는 함수가 없을 경우 jstl function으로 커스텀할 수 있지만,

뷰단에서 그렇게 까지 데이터를 변환할 일 없음.