JSP/Servlet - EL, JSTL!

Scope (저장공간)

EL을 알아보기 전에 Scope에 대해 먼저 알아야 한다.

WEB은 클라이언트가 요청을 하고 서버가 응답으로 이루어진다.

서버가 요청을 MVC패턴으로 처리하다 보면 서버의 각 서블릿 객체에서 데이터를 전달하고 View역할을 하는 jsp파일에서 데이터를 출력한다.

즉 MVC패턴으로 처리하면 서버의 여러 서블릿 객체에서 클라이언트의 요청과 응답할 데이터를 이리저리 이동시킨 다는 뜻이다.

이 데이터를 이동시키는 특별한 저장공간이 있는데 이를 Scope라 한다.
아래 예제를 통해 알아보자.

list.jsp에선 ${param.delete}형식으로 delete값을 가져오고
delete.jsp에선 ${delete}형식으로 delete값을 가져온다.

/* list.jsp */
var delete_st = "${param.delete}";
if(delete_st == "success")
{
  alert("Delete " + delete_st);
}
else if (delete_st == "fail") {
  alert("Delete " + delete_st);
}
...
/* delete.jsp */
var delete_st = "${delete}"
if(delete_st == "success")
{
  alert("Delete " + delete_st);
}
else if (delete_st == "fail") {
  alert("Delete " + delete_st);
}

${param.delete}을 jsp로 변환하면 <%= request.getParamter("delete") %>

${delete}을 jsp로 변환하면 <%= request.getAttribute("delete") %> 이다.

왜 이런 차이가 있는지 알려면 JSP 영역을 이해해야 한다.

JSP영역

사실 데이터를 넘기는 방법이 여러가지 있으며 이 방법에 따라 사용하는 공간이 달라지는데 공간이 4가지가 된다.

  1. pageScope페이지Scope에 접근

  2. request Scope리퀘스트Scope에 접근

  3. sessionScope세션Scope에 접근

  4. applicationScope어플리케이션Scope에 접근

추가로 url에 붙여 파라미터 형식으로 보내는 방법이 있다.(이녀석은 저장 공간이라고 표현하기에는 부족하다….)

이 4가지 공간은 각각의 영역을 갖고 있는데 다음과 같다.

영역 설명 사용객체
page영역 하나의 JSP페이지와 관련된 영역 pageContext 객체
request영역 하나의 요청과 관련된 영역 request 객체
session영역 하나의 웹 브라우저와 관련된 영역 response 객체
application영역 하나의 웹 애플리케이션과 관련된 영역 application 객체

각 영역은 각각의 기본객체들로 접근할 수 있다.

page영역이 제일 작고 application영역이 가장 크다.

EL을 사용하면 이 각 영역을 간편하게 접근하여 값을 가져올 수 있다.

<%
	String name = "admin";
	pageContext.setAttribute("pc_name", name);
	request.setAttribute("age", 20);
	session.setAttribute("addr", "강남구");
	application.setAttribute("tel", "010-1111-2222");
%>
이름: <%= name %> / ${ pc_name } <br>
나이: ${ age } <br>
주소: ${ addr } <br>
번호: ${ tel } <br>

출력값

웹 애플리케이션 영역 이름: admin / admin 
나이: 20 
주소: 강남구 
번호: 010-1111-2222 

스크립코드안 String name = "admin"은 어느 영역에도 속해있지 않다.

EL태그는 가장 작은 범위 영역에서 큰 범위 영역으로 찾아나가기 때문에 name으로 값을 찾을 수 있다.

다른영역끼리 속성 name이 같을경우도 있는데 그럴경우 앞에 scope명을 붙이면 된다.
주소: ${ sessionScope.addr }

이제 위의 ${param.delete}${delete}를 보면 이해가 갈듯 하다.

EL에서 ${delete}라는 변수를 pagerequestsessionapplication 영역순으로 EL변수를 찾는다.

반면 ${param.delete}url뒤에 붙어오는 파라미터를 찾아오기 때문에 위의 4개 영역과는 관련이 없다.

반대로 위의 4개 영역 역시 url뒤에 파라미터와 관련없다.

request.getAttribute()request.getParameter() 메서드 기능이 다른것과 같다.

또한 request.setAttribute로 객체를 저장하여 전송한 경우 ${dto.name}이런식으로 사용했는데 만약 dto클래스에 getName 메서드가 정의되어 있지 않다면 오류난다.

이는 EL의 문법중 하나로 ${dto.name}은 사실 ${dto.getName()}을 호출하는 것과 같다.(물론 get메서드 형식은 통용되는 것을 사용해야 오류 안남)

EL(Expression Language)

jsp에서 자바코딩을 좀더 간결하게 하기 위해 사용되는 언어,

다음과 같은 기능을 제공한다.

  • JSP 네가지 기본 객체(request, response)가 제공하는 영역의 속성 사용

  • 수치연산, 관계연산, 논리연산자 제공

  • 자바클래스, 메서드 호출기능 제공

  • 쿠키, 기본객체의 속성 등 JSP를 위한 표현언어의 기본객체 제공

  • 람다식을 이용한 함수 정의와 실행

EL은 ${ expr }형식으로 사용한다.
$달러 기호와 {}대괄호가 EL스크립트임을 표시한다.

나중에 코어 태그와 EL을 사용해 제어문 처리를 하는데 조건문을 사용할 때 변수 타입을 알아야한다.

EL에선 다음 5가지의 데이터 타입이 존재한다.
Boolean, 정수, 실수, 문자열, Null타입

EL을 java가 아닌 이상한 언어라 생각할 수 있는데

EL역시 간단히 사용하기 위한 도구일 뿐이지 사실 Java문법중 하나이다.

Boolean의 경우 true, false
실수의 경우 java.lang.Double
정수의 경우 java.lang.Long
문자열의 경우 java.lang.String 타입을 가진다.

EL 기본객체

JSP에서 바로 접근가능한 기본객체들이 있었다.
request, response, session, application 등…

재스퍼가 jsp를 servlet객체로 변환시킬 때 이런 기본객체들을 자동 생성하기에 사용할 수 있었던 것이었는데…

EL에서도 바로 접근할 수 있는 객체들이 있다.

EL 기본객체 설 명
pageContext JSP page 자체를 나타낸다. pageContext를 통해 각종 기본객체를 가져올 수 있다.
pageScope pageContext 기본 객체에 저장된 속성의 <속성, 값> 매핑을 저장한 Map 객체
requestScope request 기본 객체에 저장된 속성의 <속성, 값> 매핑을 저장한 Map 객체
sessionScope session 기본 객체에 저장된 속성의 <속성, 값> 매핑을 저장한 Map 객체
applicationScope application 기본 객체에 저장된 속성의 <속성, 값> 매핑을 저장한 Map 객체
param 요청 파라미터의 <파라미터이름, 값> 매핑을 저장한 Map 객체. 파라미터 값의 타입은 String으로서, request.getParameter(이름)의 결과와 동일하다.
paramValues 요청 파라미터의 <파라미터이름, 값배열> 매핑을 저장한 Map 객체. 값의 타입은 String[]으로서, request.getParameterValues(이름)의 결과와 동일하다.
header 요청 정보의 <헤더이름, 값> 매핑을 저장한 Map 객체, request.getHeader(이름)의 결과와 동일하다.
headerValues 요청 정보의 <헤더이름, 값 배열> 매핑을 저장한 Map 객체. request.getHeaders(이름)의 결과와 동일하다.
cookie <쿠키 이름, Cookie> 지정한 Map 객체. request.getCookies()로 구한 Cookie배열로부터 매핑을 생성한다.
initParam 초기화 파라미터의 <이름, 값> 매핑을 저장한 Map 객체, application.getInitParameter(이름)의 결과와 동일하다.

이중 위에서 소개했던 4가지 역역 pageScope, requestScope, sessionScope, applicationScope은 굳이 명시하지 않아도 가장 작은 범위인 pageScope부터 돌며 안의 속성을 접근할 수 있다.

모든 기본객체의 형식이 Map객체이며 key값을 통해 value값을 얻어올 수 있다.

${ pageScope.key }이런식으로…. pageScope는 생략가능함으로 ${ key } 키값을 통해 바로 value를 가져올 수 있다.

pageContext객체는 jsp에서 제공하는 기본객체로 다른 jsp와 1:1 매칭되는 객체로 다른 기본객체를 함수를 통해 반환할 수 있다.

EL에서 접근할 수 있는 JSP의 기본객체는 pageContext뿐이다.

만약 requestsession과 같은 기본객체에 접근하고 싶다면 pageContext를 통해 얻어올 수 있다.

request를 통해 url을 가져오는 request.getRequestURI()메서드를 EL을 통해 사용하고 싶다면

url: ${ pageContext.request.requestURI } 이런식으로 사용가능하다.
EL문법에 따라 requestURIgetRequestURI()메서드를 호출하는 것 과 같다.

application이나 session같은 기본객체를 얻어와 메서드 호출도 가능하다.

pageContext : https://kouzie.github.io/jsp/JSP-버퍼,-application,-exception,-웹-기본구조,-모듈화/#기본객체—pagecontext

또한 request.getHeader()메서드를 통해 header정보를 읽어왔었는데 EL 태그는 header라는 EL기본객체로 제공되기 때문에 바로 접근 가능하다.

name: ${ param.name }<br>
url: ${ pageContext.request.requestURI }<br>
referor: ${ header.referer }

위와 같은 EL태그는 재스퍼에 의해 java코딩으로 다음과 같이 변경된다.

out.write("\tname: ");
out.write((java.lang.String) org.apache.jasper.runtime.PageContextImpl.proprietaryEvaluate("${ param.name }", java.lang.String.class, (javax.servlet.jsp.PageContext)_jspx_page_context, null));
out.write("<br>\r\n");
out.write("\turl: ");
out.write((java.lang.String) org.apache.jasper.runtime.PageContextImpl.proprietaryEvaluate("${ pageContext.request.requestURI }", java.lang.String.class, (javax.servlet.jsp.PageContext)_jspx_page_context, null));
out.write("<br>\r\n");
out.write("\treferor: ");
out.write((java.lang.String) org.apache.jasper.runtime.PageContextImpl.proprietaryEvaluate("${ header.referer }", java.lang.String.class, (javax.servlet.jsp.PageContext)_jspx_page_context, null));
out.write("\r\n");

EL 산술 연산자

${ 3 + 5 }
${ 3 * 5 }
${ 3 / 5 }
${ 3 div 5 }
${ 3 % 5 }
${ 3 mod 5 }
${ 10/8 }
${ null + null }
${ null + 1 }

출력값

8
15
0.6
0.6
3
3
1.25
0
1

정수 실수 상관없이 사칙연산을 모두 수행한다.

결과값도 마찬가지.

참고로 ${ 0 / 10 } 0을 나누는 연산 또한 예외처리 되었는지 오류를 내보내지 않고 0.0을 출력한다.

EL empty 연산자

${ empty null } <
${ empty "" }
${ empty 0 }

출력값

true
true
false

아래와 같이 컬렉션 객체에 empty 연산자를 사용하는 경우
${ empty list }

list가 비어있어도 true반환한다.

EL 비교연산자

일반적인 비교연산자 >, <, ==, != 등을 사용할 수 도 있고 EL의 비교연산자 표현식을 사용하여 비교연산이 가능하다.

표현식 비교
a gt b a > b
a lt b a < b
a ge b a >= b
a le b a <= b
a eq b a == b
a ne b a != b

EL 세미콜론 연산자와 할당연산자

세미콜론 연산자는 EL을 사용하여 출력할 때 ; 을 사용해 여러값을 {}대괄호 안에 넣어 마지막 값만 출력한다.

${ 10; 20; 30; 40 }
출력값: 40

마지막값만 출려하기 위한 연산자로 쓸모 없어 보이지만 할당연산자와 사용하면 유용하다.

EL에서 연산 혹은 출력을 위한 변수를 만들고 싶다면 할당연산자를 사용하지 않고는 다음과 같이 request객체에 담아서 사용해야한다.

<%
	int age = 20;
	request.setAttribute("age", age);
%>
${ age }

이런식으로 사용했는데 할당 연산자를 사용하면 request에 담을 필요 없이 바로 선언, 초기화 가능하다.

${ age = 20 }<br> 
${ age }<br> 

단 이렇게 사용하면 20이 2번 출력된다….

이때 세미콜론 연산자와 같이 사용하면 간편하다.

${ age = 20; "" }
${ age }<br> 

${ name = "hong"; "" }
${ name }<br>

${ m=[1, 2, 3]; "" }
${ m[0] }<br>

출력값

20
hong
1

초기화시 뒤에 세미콜론과 함께 빈 문자열을 붙여 출력시키지 않는다.

EL - 객체의 메서드 호출하기

EL2.2버전부터 추가된 기능으로 생성된 객체의 메서드를 직접 호출 가능하다.

먼저 java Resources폴더에 다음 클래스를 정의

package days13;

public class Fruit {
	String fruit;
	
	public void setFruit(String fruit) {
		this.fruit = fruit;
	}
	
	public String getFruitName() {
		return "과일 이름은 " + fruit + "입니다.";
	}
}

위 클레스로 객체를 생성하고 EL을 사용해 getFruitName()메서드를 호출하자.

<%
	Fruit fruit = new Fruit();
	fruit.setFruit("사과");
	request.setAttribute("f", fruit);
%>
${ f.setFruit("배") }
${ f.getFruitName() }

출력값

과일 이름은 배입니다.

참고: jsp 2.1 이하버전에선 컴파일 에러난다.

EL - 정적 메서드 호출하기

정적메서드를 호출하는 방법은 2가지 있다.

  1. jsp2.1 이전 버전 호출방법
  2. jsp2.2 이후 버전 호출방법

물론 jsp2.2버전에서 jsp2.1버전의 사용방법으로 정적메서르들 호출해도 된다.

jsp2.1 이전 버전으로는 TLD(Tag Library Description)을 사용해서 정적메서드와 매칭된 파일을 별도로 작성후 web.xml에 등록해야한다.

먼저 간단한 정적메서드를 정의하자.

public class FormatUtil {
	public static String number(long number, String pattern) {
		DecimalFormat format = new DecimalFormat(pattern);
		return format.format(number);
	}
}

DecimalFormat을 사용해 원하는 포멧으로 정수를 문자열로 출력하는 static메서드이다.

number()메서드를 태그처럼 사용할 수 있도록 TLD파일을 작성하도록 하자.

확장자는 .tld이고 파일명은 el-functions.tld이다.

이 tld파일은 /WEB-INF/tlds/, /WEB-INF/jsp/ 폴더에 위치시켜야 한다.(안그러면 오류남)

<?xml version="1.0" encoding="UTF-8" ?>
<taglib 
	xmlns="http://java.sun.com/xml/ns/javaee" 
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
		http://java.sun.com/xml/ns/javaee/web-jsptaglibrary_2_1.xsd"
	version="2.1">
 
    <description>EL에서 함수실행</description>
    <tlib-version>1.0</tlib-version>
    <short-name>ELfunctions</short-name>
 
    <function>
        <description>숫자 포멧팅</description>
        <name>formatNumber</name>
        <function-class>com.util.FormatUtil</function-class>
        <function-signature>
        	java.lang.String number(long, java.lang.String)
        </function-signature>
    </function>
</taglib>

xml형식으로 작성되며 <function>태그를 통해 클래스와 메서드를 태그로 쓸 수 있도록 설정한다.

정적메서드를 사용하고 싶은 jsp페이지에 다음과 같은 tablib 디렉티브를 선언하면 된다.

<%@ taglib prefix="elfunc" uri="/WEB-INF/tlds/el-functions.tld" %>

그리고 지시자에서 정의한 접두사 elfunc 와 함께 사용하면 된다.

${ price = 12345; "" }
베추 가격은 ${ elfunc:formatNumber(price, "#,##0") } 입니다.

사실 jsp2.2 이후 버전에선 위처럼 복잡하게 정적메서드를 호출하지 않는다.

그냥 객체의 메서드 호출하듯이 사용하면 된다.

page 디렉티브로 FormatUtil클래스를 import하고

<%@page import="com.util.FormatUtil"%>

객체 메서드 호출하듯이 사용하면 된다.

${ price = 12345; "" }
베추 가격은 ${ FormatUtil.number(price, "#,##0") } 입니다.

출력값

베추 가격은 12,345 입니다.

EL 비활성화

EL을 사용하고 싶지 않다면, 물론 그럴일은 없겠지만 사용하고 싶지않다면 기능을 끌 수 있다.

${ }는 더이상 EL 문법이 아닌 문자열로 취급된다.

비활성화 하는 방법은 web.xml<jsp-config>태그안에서 설정하는 것 과
page 디렉티브에서 isELIgnored설정을 true로 세팅하는 방법이 있다.

web.xml에서 설정하는 것은 다음과 같다.

<!-- web.xml -->
<jsp-config>
	...
	...
	<jsp-property-group>
		<url-pattern>/TestELIgnore/*</url-pattern>
		<el-ignored>true</el-ignored>
	</jsp-property-group>
</jsp-config>

<jsp-confi>
<jsp-property-grou>
<url-pattern>
<el-ignored>
4개 태그를 통해 url-mapping에 해당하는 모든 파일에 EL문법을 무효화 한다.

2번째 방법인 page디렉티브를 사용하는 방법은 다음과 같다.

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

isELIgnored="true"를 통해 해당 jsp 페이지에선 더이상 EL을 사용하지 못하도록 설정한다.

JSTL

스트립트릿, 표현부를 if문, while문등 조건문과 같이 사용하면 코드가 지저분하고 유지보수하기 힘들어 진다.

이를 해결하기 위해 JSTL(표준 태그 라이브러리)을 사용한다.
직관적이고 간결한 코드를 제공한다.

EL과 JSTL을 같이 사용하면 jsp의 스크립트릿으로 코딩하는 것 보다 길이가 훨씬 짧아진다.

JSTL을 사용하기 위해 라이브러리를 받아야 하는데 아래 주소에서 다운 가능하다.

https://search.maven.org/artifact/jstl/jstl/1.2/jar

다운받아 jdbc드라이버와 같이 WEB-INF/lib/위치에 저장하면 된다.

태그의 종류

라이브러리 하위 기능 접두어 관련 URI
코어 변수 지원
흐름 제어
URL 처리
c http://java.sun.com/jsp/jstl/core
XML XML 코어
흐름제어
XML 변환
x http://java.sun.com/jsp/jstl/xml
국제화 지역
메세지 형식
숫자 및 날짜 형식
fmt http://java.sun.com/jsp/jstl/fmt
데이터베이스 SQL sql http://java.sun.com/jsp/jstl/sql
함수 콜렉션 처리
String 처리
fn http://java.sun.com/jsp/jstl/functions

JSTL에서 제공하는 태그의 종류는 코어, XML, 국제화, 데이터베이스, 함수 등의 태그가 있지만

변수지원, 흐름제어, URL처리 역할을 하는 코어태그에 대해 먼저 알아보자.

코어태그

코어태그 라이브러리를 사용하려면 html태그 위에 다음과 같은 taglib디렉티브(지시자)를 추가해야 한다.

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

prefix는 접두사로 앞으로 코어태그를 사용하려면 태그시작이 prefix로 시작해야 한다.

prefix="ctag"라면 <ctag:if>형식으로 사용.

코어 태그에서 지원하는 기능별 사용하는 태그는 아래와 같다.

기능분류 태그 설명
변수 지원 set JSP에서 사용될 변수를 설정한다.
- remove 설정한 변수를 제거한다.
흐름 제어 if 조건에 따라 내부 코드를 수행한다.
- choose 다중 조건을 처리할 때 사용한다.
- forEach 컬렉션이나 Map의 각 항목을 처리할 때 사용한다.
- forTokens 구분자로 분리된 각각의 토큰을 처리할 때 사용한다.
URL 처리 import URL을 사용하여 다른 자원의 결과를 삽입한다.
- redirect 지정한 경로로 리다이렉트 한다.
- url URL을 재작성 한다.
기타 태그 catch 익셉션 처리에 사용된다.
- out JspWriter에 내용을 알맞게 처리한 후 출력한다.

변수지원 태그 <c:set>, <c:remove>

<c:set>태그는 EL로 사용할 수 있는 변수를 만들기 위해 사용하는 태그이다.

EL로 사용할 수 있는 변수란 것은 page, request, session, application 4개 scope에 저장되어 있는 변수를 말함

지금까지 EL로 사용할 수 있는 변수를 만들기 위해서 다음과 같이 코딩하였다.

<%
String name = "admin";
request.setAttribute("name", name);
%>
name: ${ name }

<c:set>코어태그를 사용하면 아래와 같이 EL에서 사용할 수 있는 변수를 만들 수 있다.

<c:set var="name" value="홍길동"></c:set>
name: ${ name }

<c:set>코어태그에 사용되는 속성은 3가지다.
var, value, scope

<c:set var="변수명" value="값" scope="page" ></c:set>

var는 사용할 변수명, value는 변수에 해당하는 값이 들어간다.

scope속성은 위에서 말한 4가지 영역(page, request, session, application)을 지정할 수 있으며 기본값은 page이다.

<c:set>태그도 일종의 자바 라이브러리일 뿐 사실상 pageContext.setAttribute("name", "홍길동")을 실행하는 것 과 같다.

value속성 값으로 <%= %>, ${ }을 사용가능하며, 정적텍스트 값이 올 수 있다.

즉 문자열만 지정할 수 있는 것 이 아니라 Object를 변수값으로 지정 할 수 있다는 뜻.

참고: 변수값은 value속성을 사용해 초기화 할 수 있지만 <c:set>태그 사이에 값을 지정해 초기화 할 수 있다.

<c:set var="name" value="홍길동"></c:set>
<c:set var="name">홍길동</c:set>

<c:set>태그의 target, property 속성

위에서 <c:set>태그로 EL변수를 생성하는 방법을 알아보았는데

<c:set>태그로 EL변수(자바빈, Map)의 프로퍼티 값을 지정할 때에도 사용한다.

https://kouzie.github.io/jsp/JSP-포워딩-액션태그,-자바빈,-쿠키/#jspusebean-액션태그-자바빈java-beans

MemberInfo라는 자바빈 객체<c:set>태그로 초기화 해보자.

jsp 스크립트릿으로 초기화 하는 방법…

<%
MemberInfo minfo = new MemberInfo();
minfo.setId("admin");
%>
minfo: <%= minfo.getId() %> <br>

<c:set>태그로 초기화 하는 방법….

<%
MemberInfo minfo2 = new MemberInfo();
%>
<c:set target="<%= minfo2 %>" property="id" value="admin2"></c:set>
minfo2: <%= minfo2.getId() %>

출력값

minfo: admin 
minfo2: admin2

<c:set>태그를 사용해서 초기화하는 것 이기 때문에 scope에 minfo2 객체가 저장되진 않는다.


이번엔 Map객체안의 속성을 추가, 초기화 해보자.

<%
Map<String, String> prop = new HashMap<>();
prop.put("id", "admin");
%>
<c:set target="<%= prop %>" property="name">관리자</c:set>
prop.id: <%= prop.get("id") %><br>
prop.name: <%= prop.get("name") %>

출력값

prop.id: admin
prop.name: 관리자

Map객체를 초기화 할 때 <c:set>태그의 property 속성을 key값을 가리킨다.

<c:remove> 태그

<c:set var="name" value="admin" scope="page"></c:set>  
<c:set var="name" value="관리자" scope="request"></c:set>  

${ name }
<c:remove var="name"/>
${ name }

아무것도 출력되지 않는다… 4영역에서 name을 모두 삭제하기 때문.

흐름제어 태그 <c:if>, <c:choose>, <c:when> ,<c:otherwise>, <c:forEach>

if-else, switch, for문 등 을 역할을 하는 코어태그로 다음과 같은 종류가 있다.

<c:if> 태그

자바의 if 비슷한 기능을 제공,

if-else와 같은 기능을 할수 없다.(else가 없다니….)

<c:if test="조건">
...(몸체)
</c:if>

조건으로 다음과 같은 값이 들어갈 수 있다.
true: 몸체내용 수행
false: 몸체내용 수행하지 않음
some String: 몸체내용 수행하지 않음
${ expr }: EL결과값이 true일경우 몸체내용 수행
<%= expr %>: 표현식 결과값이 true인 경우 몸체내용 수행

아래의 jsp코드를 코어태그를 사용하여 간결하게 변경해보자.

<% 
if(elist == null) {
	...
	...(java)
%>
	...
	...(html)
<% 
}
%>

위의 jsp코드를 JSTL을 사용하면 아래와 같이 사용 가능.

<c:if test="${ elist == null} ">
	...
	...
</c:if>

스크립트릿의 표현식으로도 사용 가능하다. <c:if test="<%= elist.isEmpty() %>">

<c:choose>, <c:when> ,<c:otherwise> 태그

자바의 switch와 비슷한 역할을 하는 코어태그,
3개 태그를 사용해 if-else 역할을 하기도 한다.

전체적인 구조는 아래와 같다.

<c:choose>
	<c:when test="조건1">
		...
		...
	</c:when>
	<c:when test="조건2">
		...
		...
	</c:when>
	<c:when test="조건3">
		...
		...
	</c:when>
	
	<c:otherwise> 
		...(모든 조건에 부합하지 않을 때)
		...
	</c:otherwise>
</c:choose>

c:chooseswtich역할 c:choosecase역할 c:otherwisedefault역할이다.

사실 c:when에 조건식이 들억가지 때문에 switch문보다 if..else if..else구문과 더 비슷하다.

간단한 예제

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>c:choose예제</title>
</head>
<body>
<form action="">
	국어점수: <input type="text" name="kor"/>
</form>

성적: 
<c:choose>
	<c:when test="${ param.kor ge 90 && param.kor le 100  }"></c:when>
	<c:when test="${ param.kor ge 80}"></c:when>
	<c:when test="${ param.kor ge 70}"></c:when>
	<c:when test="${ param.kor ge 60}"></c:when>
	<c:when test="${ param.kor lt 0 || param.kor gt 100}">
		점수 잘못 입력
	</c:when>
	<c:when test="${ empty param.kor }">
		미입력
	</c:when>
	<c:otherwise></c:otherwise>
</c:choose>
</body>
</html>

<c:forEach> 태그

자바의 for문기능도 있으면서

iterator처리와 비슷한 기능을 하는 코어태그

<c:forEach var="i" begin="1" end="10" step="2">
	번호: ${i} <br>
</c:forEach>

출력값

번호: 1 
번호: 3 
번호: 5 
번호: 7 
번호: 9 

가장 기본적 기능을 하는 <c:forEach>,
step속성으로 증가분을 변경가능하다.(생략시 1씩 증가)

<c:forEach>는 단순한 for문기능 뿐 아니라
Collection또는 Map에 저장되어있는 요소를 순차적으로 꺼내사용 가능하다.(while문과 iterator와같이)

itemcollection을, var에 꺼내서 사용할 요소명을 지정할 수 있다.

<%
HashMap<String, String> map = new HashMap<>();
map.put("id", "admin");
map.put("name", "홍길동");
map.put("tel", "010-1111-2222");
map.put("addr", "서울");

request.setAttribute("map", map);
%>

<c:forEach items="${ map }" var="entry" >
	<li>
		${ entry.key } - ${ entry.value} 
	</li>
</c:forEach>

출력값

name - 홍길동
tel - 010-1111-2222
id - admin
addr - 서울

주의사항: EL로 map객체를 사용하고 싶다면 꼭 Scope에 속성으로 추가해주자.

의 모든 요소를 돌고싶지 않다면 begin, end, step를 추가하자

<%
HashMap<String, String> map = new HashMap<>();
map.put("id", "admin");
map.put("name", "홍길동");
map.put("tel", "010-1111-2222");
map.put("addr", "서울");

request.setAttribute("map", map);
%>

<c:forEach items="${ map }" var="entry" begin="2" end="3">
	<li>
		${ entry.key } - ${ entry.value} 
	</li>
</c:forEach>

출력값

id - admin
addr - 서울

여기서 주의할 점은 2번째, 3번째로 put()name, tel이 출력되지 않고 id, addr가 출력되었단 점이다.

Map객체는 순서유지를 하지 않기 때문…

순서유지가 되지 않는 Collection의 경우 begin, end는 무의미하다…

꼭 컬렉션 객체가 아니더라도 forEach 태그를 사용할 수 있다.

<c:set var="m" value="<%= new int[]{1, 5, 3, 2, 4} %>"/>
<c:forEach items="${ m }" var="n" varStatus="status">
	m[${ status.index }] = ${ n }<br>
</c:forEach>

출력값

m[0] = 1
m[1] = 5
m[2] = 3
m[3] = 2
m[4] = 4

참고사이트: https://www.tutorialspoint.com/jsp/jsp_standard_tag_library.htm

<c:forEach> status 속성

${status.current} 현재 for문의 해당하는 번호

${status.index} 0부터의 순서

${status.count} 1부터의 순서, 개발자 편의를 위해서 나눠놓았나?

${status.first} 첫 번째인지 여부, 처음이라면 true반환

${status.last} 마지막인지 여부, 마지막이라면 true반환

${status.begin} forEach의 begin속성값

${status.end} forEach의 end속성값

${status.step} forEach의 step속성값

<%@page import="employee.EmpDeptDTO"%>
<%@page import="java.util.ArrayList"%>
<%@page import="com.util.DBConn"%>
<%@page import="java.sql.ResultSet"%>
<%@page import="java.sql.PreparedStatement"%>
<%@page import="java.sql.Connection"%>
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%
	String sql_emp = "select * from emp ";
	Connection con = null;
	PreparedStatement pstmt = null;
	ResultSet rs_emp = null;
	try {
		con = DBConn.getConnection();
		pstmt = con.prepareStatement(sql_emp);
		rs_emp = pstmt.executeQuery();
		EmpDeptDTO dto = null;
		ArrayList<EmpDeptDTO> list = new ArrayList<>();
		while (rs_emp.next()) {
			dto = new EmpDeptDTO();
			dto.setComm(rs_emp.getInt("comm"));
			dto.setDeptno(rs_emp.getInt("deptno"));
			dto.setEmpno(rs_emp.getInt("empno"));
			dto.setEname(rs_emp.getString("ename"));
			dto.setHiredate(rs_emp.getDate("hiredate"));
			dto.setJob(rs_emp.getString("job"));
			dto.setMgr(rs_emp.getInt("mgr"));
			dto.setSal(rs_emp.getInt("sal"));
			list.add(dto);
		}
		request.setAttribute("list", list);
	} 
	catch (Exception e) {
		System.out.println("> TestEmpInfo.doGet() \n" + e.toString());
	} 
	finally {
		if (rs_emp != null) {
			try {
				rs_emp.close();
			}
			catch (Exception e) {
			}
			if (pstmt != null)
			try {
				pstmt.close();
			} 
			catch (Exception e) {
			}
			if (con != null)
			try {
				DBConn.close();
			}
			catch (Exception e) {
			}
		}
	}
%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>emp사원 목록</title>
</head>
<body>
<table border="1" width="600px">
  <tr>
		<th>deptno</th>
		<th>ename</th>
		<th>job</th>
		<th>hiredate</th>
  </tr>
<c:if test="${ empty list }">
	<tr>
		<td colspan="5">사원이 존재하지 않습니다.</td>
	</tr>   
</c:if>
<c:if test="${ not empty list }">   
   <c:forEach items="${ list }" varStatus="status">
   <c:choose>
   	<c:when test="${ status.first }"><tr style="background-color: blue"></c:when>
   	<c:when test="${ status.last }"><tr style="background-color: aqua"></c:when>
   	<c:when test="${ status.current.deptno eq 10 }"><tr style="background-color: red"></c:when>
   	<c:when test="${ status.current.deptno eq 30 }"><tr style="background-color: yellow"></c:when>
   	<c:otherwise><tr></c:otherwise>
   </c:choose>
        <td>${status.index }(${ status.count })</td>
        <td>${status.current.deptno }</td>
        <td>${status.current.ename }</td>
        <td>${status.current.job }</td>
        <td>${status.current.hiredate }</td>
      </tr>
   </c:forEach>   
</c:if>
</table>
</body>
</html>

status.first의 경우 background-color: blue

status.last의 경우 background-color: aqua

그리고 각각 deptno가 10, 30인 사원들의 배경색을 칠하였다.

<c:forEach>var 속성을 사용해서 컬렉션에서 요소를 꺼내올 수 있지만 status.current을 사용해서 현재 <c:forEach>가 가리키고 있는 객체를 가져올 수 있다.

image45

url제어 태그 <c:url>, <c:redirect>

url제어 태그를 사용해 URL생성하고 리다이렉트 할 수 있다.

코어태그를 사용해 아래 url로 리다이렉트 할 수 있도록 구현해보자.

https:/search.daum.net/search?w=blog&q=공원

먼저 뒤의 파라미터를 제거한 url을 아래와 같이 <c:url>태그로 생성.

<c:url value="https://search.daum.net/search" var="searchUrl">
</c:url>

그리고 <c:redirect url="${ searchUrl }"/> 형식으로 리다이렉트 시킬 수 있다.

<c:param>태그를 사용하면 url뒤에 파라미터 붙이는 기능을 수행 할 수 있다.

<c:redirect url="${ searchUrl }">
	<c:param name="w" value="blog"></c:param>
	<c:param name="q" value="공원"></c:param>
</c:redirect>

URL제어태그 거의 쓸일 없다….

기타 코어 태그

<c:forToken>

StringTokenizer클래스와 비슷한 역할을 하는 태그

String name = "소지섭,이동석,김동현,차인표,장동건,강동원";
StringTokenizer st = new StringTokenizer(name, ",");
while(st.hasMoreTokens())
{
	String token = st.nextToken();
	System.out.print(token);
}

출력

소지섭이동석김동현차인표장동건강동원

이런역할을 하는 클래스였는데 <c:forToken>을 사용해서 똑같이 구현해보자.

<ul>
<c:forTokens items="소지섭,이동석,김동현,차인표,장동건,강동원" delims="," var="token">
	<li>${ token }</li>
</c:forTokens>
</ul>

출력값

소지섭
이동석
김동현
차인표
장동건
강동원

보통 DB에서 문자열을 한줄형태로 받을 일이 별로 없겠지만 알아두면 좋을듯….

<c:out>

jsp의 기본객체인 out객체 역할을 하는 태그이다.
(서블릿에선 PrintWriter out = request.getWriter() 가져왔던 그 객체….)

jsp에선 기본 제공되기때문에 굳이 <c:out>태그로 사용할 일이 별로 없지만 특이한 기능이 있다.

value속성과 default속성이 있는데 value속성에 값이 있다면 value로 출력하고 value에 값이 없다면 default에 설정한 값이 출력된다.

또한 escapeXml속성을 사용하여 아래 특수문자를 html에서 사용가능한 변환된 형태로 변경한다.

문자 변환된 형태
< &lt;
> &gt;
& &amp;
' &#039;
" &#034;
<c:set var="m" value="<%= new int[]{ 1, 5, 3, 2, 4 } %>"/>
<c:forEach var="i" begin="1" end="9">
	<c:out value="${ m[i] }" escapeXml="true" default="데이터가 없습니다."/><br>
</c:forEach>

출력값

5
3
2
4
데이터가 없습니다.
데이터가 없습니다.
데이터가 없습니다.
데이터가 없습니다.
데이터가 없습니다.

그리고 FileReader 객체를 value로 설정하면 <c:out>가 알아서 파일객체안의 데이터를 읽어 출력한다.

<%
FileReader reader = null;
try {
	String path = request.getParameter("path");
	reader = new FileReader(getServletContext().getRealPath(path));
%>
<pre>
소스코드 = <%= path %>
<c:out value="<%= reader %>" escapeXml="true"></c:out>

<c:catch var="ex">태그 안에서 예외가 발생했다면 발생된 예외를 EL변수 ex에 저장한다.

<%
FileReader reader = null;
try {
	String path = request.getParameter("path");
	reader = new FileReader(getServletContext().getRealPath(path));
%>
<pre>
소스코드 = <%= path %>
<c:out value="<%= reader %>" escapeXml="true"></c:out>
</pre>
<%
}
catch(IOException ex) {
%>	
	에러: <%= ex.getMessage() %>
<%
}
finally {
	if(reader != null) {
		try {
			reader.close();
		}
		catch(IOException ex) {}
	}
}
%>

국제화 태그

국제화 태그는 특정 지역에 따라 알맞은 메시지를 출력해야 할 때 사용한다.

그뿐 아니라 원하는 날짜포멧, 숫자포멧으로 날짜데이터, 숫자데이터를 출력할 수 있다.

국제화 태그를 사용하기 위해선 아래 지시자를 추가해야 하며 fmt접두사를 사용한다.

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

<fmt:setLocale>, <fmt:requestEncoding>

<fmt:setLocale>태그는 국제화 태그들이 사용할 로케일을 지정한다.

<fmt:setLocale value="ko" scope="session"/>
<fmt:setLocale value="en" scope="session"/>

이걸 사용하기 전까지 우린 JVM기본 로케일, 혹은 web.xml파일에서 설정한 로케일을 기본 로케일로 사용하고 있었다.

사실 웹 브라우저가 설정한(알려준) 로케일에 맞게 메세지를 출력하기 때문에 <fmt:setLocale>태그 사용할 일은 거의 없다.

<fmt:requestEncoding>은 우리가 지금까지 많이 사용했던 request.setCharacterEncoding("UTF-8")을 호출하는 것과 같다.

<fmt:requestEncoding value="utf-8">

사실 필터에서 인코딩을 설정하기 때문에 <fmt:requestEncoding>태그도 거의 사용할 일 이 없다.

숫자 및 날짜 포멧팅 태그

기능 Tag 설명
숫자 → 문자열 <fmt:formatNumber> 숫자를 formatting
문자열 → 숫자 <fmt:parseNumber> 문자열로 표시된 날짜를 분석해서 숫자로 변환
날짜 → 문자열 <fmt:formatDate> Date 객체를 formatting
문자열 → 날짜 <fmt:parseDate> 문자열로 표시된 날짜를 분석해서 Date 객체로 변환

먼저 숫자 포멧팅인 <fmt:formatNumber>, <fmt:parseNumber>태그를 알아보자.

<fmt:formatNumber>, <fmt:parseNumber>

<c:set var="price" value="10000"></c:set>
숫자 : <fmt:formatNumber value="${ price }" type="number" />
통화 : <fmt:formatNumber value="${ price }" type="currency" currencySymbol="$" /> <br>
퍼센트: <fmt:formatNumber value="0.57" type="percent" groupingUsed="false"/> <br>
패턴: <fmt:formatNumber value="${ price }" type="number" pattern="0000000.00"/> <br>
반올림 가능: <fmt:formatNumber value="1.1578" pattern="#.###" />

출력값

숫자 : 10,000 
통화 : $10,000.00 
퍼센트: 54% 
패턴: 0010000.00 
반올림 가능: 1.158 

<fmt:formatNumber>속성값을 하나씩 알아가 보자.

value는 EL 변수를 사용하거나 숫자형식의 문자열이 들어간다.
type은 보다싶이 3가지 종류가 있다. number, currency, percent.

통화(currency)는 currencySymbol속성과 같이 사용하면 숫자 앞에 통화심볼을 붙일 수 있다.

퍼센트(percent)의 경우 기존 숫자에 *100을 한 후 %기호를 붙인다.

groupingUsed속성 기본값이 true인데 false로 변경할 경우 ,컴마를 찍지 않는다.

pattern속성은 DecimalFormat클래스에 정의되어 있는 패턴을 사용한다.

숫자포멧: https://gmby.tistory.com/entry/DecimalFormat-다루기

포멧을 사용해 변환된 문자열을 var속성을 사용해 저장 가능하다.

<c:set var="price" value="10000"></c:set>
<fmt:formatNumber value="${ price }" var="numberType" scope="request" />
숫자 : ${ numberType }

출력값

숫자 : 10,000 

주의사항: var 속성을 사용하면 바로 출력되지 않는다.

문자열을 숫자로 변경하는 <fmt:parseNumber>태그를 알아보자.

<c:set value="1,100.12" var="money"></c:set>
<fmt:parseNumber value="${ money }" pattern="#,###" /> <br>
<fmt:parseNumber value="${ money }" pattern="0,000.00" integerOnly="true"/> <br>
<fmt:parseNumber value="$128,398" type="currency" pattern="$#,###" /> <br>

출력값

1100.12 
1100 
128398 

integerOnly속을 true로 설정하면 소수점을 버릴 수 있다.

<fmt:formatDate>, <fmt:parseDate>

이제 문자열을 날짜로, 날짜를 문자열로 포멧팅 하는 태그를 알아보자.

포멧팅 하는 범위를 년월일과 시간을 따로 잡을 수 있고 같이 잡을 수 있다.

먼저 년 월 일만 포멧팅 하도록 typedate로 설정하자.

<fmt:formatDate value="${ now }" type="date" dateStyle="full"/><br>
<fmt:formatDate value="${ now }" type="date" dateStyle="short"/><br>
<fmt:formatDate value="${ now }" type="date" dateStyle="medium"/><br>
<fmt:formatDate value="${ now }" type="date" dateStyle="long"/><br>
<fmt:formatDate value="${ now }" type="date" dateStyle="default"/><br>

출력값

2019년 5월 15일 수요일
1.  5. 15
2.    5. 15
2019년 5월 15일 (수)
1.    5. 15

dateStyle은 5가지 종류가 있고 defaultmedium가 같은 형식임을 알 수 있다.

시간을 포멧팅하도록 typetime으로 설정

<fmt:formatDate value="${ now }" type="time" timeStyle="full"/><br>
<fmt:formatDate value="${ now }" type="time" timeStyle="short"/><br>
<fmt:formatDate value="${ now }" type="time" timeStyle="medium"/><br>
<fmt:formatDate value="${ now }" type="time" timeStyle="long"/><br>
<fmt:formatDate value="${ now }" type="time" timeStyle="default"/><br>

출력값

오후 3시 34분 08초 KST
오후 3:34
오후 3:34:08
오후 3시 34분 08초
오후 3:34:08 

timeStyle도 5가지가 있으며 mediumdefault가 같은 형식이다.

timedate를 한번에 출력하고 싶다면 typeboth로 설정

<fmt:formatDate value="${ now }" type="both" dateStyle="full" timeStyle="full"/><br>
<fmt:formatDate value="${ now }" type="both" dateStyle="short" timeStyle="short"/><br>
<fmt:formatDate value="${ now }" type="both" dateStyle="medium" timeStyle="medium"/><br>
<fmt:formatDate value="${ now }" type="both" dateStyle="long" timeStyle="long"/><br>
<fmt:formatDate value="${ now }" type="both" dateStyle="default" timeStyle="default"/><br>

출력값

2019년 5월 15일 수요일 오후 3시 35분 16초 KST
1.  5. 15 오후 3:35
2.    5. 15 오후 3:35:16
2019년 5월 15일 (수) 오후 3시 35분 16초
1.    5. 15 오후 3:35:16

이미 정해져 있는 양식 외에 pattern을 지정할 수 있다.

<fmt:formatDate value="${ now }" pattern="yyyy MM dd HH:mm:ss"/><br>

출력값

2019 05 15 23:35:16

날짜 포멧 참고: https://kouzie.github.io/java/java-제네릭,-와일드카드,-Date,-Calendar,-SimpleDateFormat!/#simpledateformat

<fmt:formatDate>도 마찬가지로 var속성과 scope속성을 사용 가능하다.

<fmt:formatDate value="${ now }" type="both" dateStyle="default" timeStyle="default" var="date" scope="request"/>

이번엔 문자열에서 날짜데이터로 바꾸는 <fmt:parseDate>을 알아보자.

<fmt:parseDate>의 속성으론 다음 속성들이 들어갈 수 있다.
속성|표현식/EL|타입|설명 :—–:|:—–:|:—–:|:—–: value|사용가능|java.util.Date|파싱할 문자열 type|사용가능|String|날짜, 시간 또는 둘 다 포맷팅 할지의 여부를 지정한다. time, date, both중 한 가지 값을 가질 수 있으며, 기본값은 date이다. dateStyle|사용가능|String|날짜에 대해 미리 정의된 포맷팅 스타일을 지정한다. default, short, medium, long, full 중 한가지 값을 가질 수 있으며, 기본값은 default이다 timeStyle|사용가능|String|시간에 대해 미리 정의된 포맷팅 스타일을 지정한다. default, short, medium, long, full 중 한가지 값을 가질 수 있으며, 기본값은 default이다. pattern|사용가능|String|직접 파싱할 때 사용할 양식을 지정한다. java.text.DateFormat에 있는 양식을 사용한다.

다음과 같이 정의된 문자열을 Date객체로 만들어보자.

<c:set value="2019년 05월 10일" var="d"></c:set>
<fmt:parseDate value="${ d }" pattern="yyyy년 mm월 dd일" var="date"/> <br>
${ date }

출력값

Thu Jan 10 00:05:00 KST 2019

DatetoString()메서드 호출값이 출력된다.

JSTL 함수태그

EL 2.3에서부터 객체의 함수를 직접 호출할 수 있게되며 효율성이 떨어졌지만 있다는 것은 알고 넘어가자.

먼저 JSTL함수태그를 사용하려면 아래 디렉티브를 추가해야 한다.

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

사용 형식을 아래와 같다 .

${ fn:함수명(매개변수) }

함수 설명
length(obj) obj가 Conllection인 경우 저장된 항목의 개수를, 문자인 경우 문자열의 길이를 반환, 배열의 길이를 반환
toUpperCase(str) str을 대문자로 변환
toLowerCase(str) str을 소문자로 변환
substring(str, idx1, idx2) str.substring(idx1, idx2)의 결과를 반환, idx2가 -1일 경우 str.substring(idx1)과 동일
substringAfter(str1, str2) str1에서 str1에 포함되어 있는 str2 이후의 문자열을 구함
substringBefore(str1, str2) str1에서 str1에 포함되어 있는 str2 이전의 문자열을 구함
trim(str) str 좌우의 공백 문자를 제거
replace(str, src, dest) str에 있는 srcdest로 변환
indexOf(str1, str2) str1에서 str2가 위치한 인덱스를 구함
startsWith(str1, str2) str1str2로 시작할 경우 true, 그렇지 않을 경우 false를 반환
endsWith(str1, str2) str1str2로 끝나는 경우 true, 그렇지 안을 경우 false를 반환
contains(str1, str2) st1이 str2를 포함하고 있을 경우 true를 반환
containslgnoreCase(str1, str2) 대소문자 구분없이 str1str2를 포함하고 있을 경우 true를 반환
split(str1, str2) str2로 명시한 글자를 기준으로 str1을 분리해서 배열로 반환
join(array, str2) array에 저장된 문자열을 합침, 각 문자열의 사이에는 str2가 붙음
escapeXml(str) XML의 객체 참조에 해당하는 특수문자를 처리함

쓸만한 함수로 ${ fn:length(..) }가 있다.

배열의 길이를 구할 때 arr.length 형식으로 length라는 public final필드를 접근하였지만 EL에선 get, set메서드가 정의되어 있지 않는 이상 필드에 접근할 수 없다.

배열에 getLength()메서드가 정의되어있을 일은 없으니
EL로 배열 길이를 구하려면 JSTL 함수중 length함수를 사용해야 한다.

<c:set var="m" value="<%= new int[]{1, 5, 3, 2, 4} %>"/>
배열 크기: ${ fn:length(m) }

배열이 scope에 저장되어 있을 경우 얻기 까다로운 배열 길이를 JSTL함수로 쉽게 얻을 수 있다.

<%
int[] m = {1, 5, 3, 2, 4};
%>
<%= m.length %>

물론 배열이 scope에 저장되어 있지 않고 위와 같은 상황이라면 그냥 스크립트릿 사용하면 된다.

카테고리:

업데이트: