하나의 관심사가 하나의 기능만 수행하도록 분리
OOP 5대 설계원칙(SOLID) 중 SRP(Single resposibility Principle 단일책임) 하나의 메서드는 하나의 책임(관심사)에만 집중_수정/유지보수 용이, 가독성 상승
✴️OCP(Open/Closed principle 개방폐쇄): 확장에는 열려있고 수정에는 닫힘
✴️LSP(Liskov substitution principle 리스코브 치환): 자식 클래스가 자신의 부모 클래스 대체 가능, 자식클래스는 확장만 수행
✴️ISP(Interface segregation principle 인터페이스 분리): 범용 인터페이스 하나보다 특정 클라이언트를 위한 여러 개의 인터페이스 나음
✴️DIP(Dependency Inversion principle 의존성역전): 추상화(변하기 어려운 것)에 의존, 구체화(변하기 쉬운 것)에 의존X
변하지 않는 것과 변하지 않는 것의 분리
공통(중복)코드의 분리
❶ 입력(중복영역이므로 공통 코드로 분리) ➩ ❷ 처리(Controller변하지 않는 영역) ➩ ❸ 출력(View변하는 영역)
1) 브라우저(사용자) 요청: 브우저가 서버에 요청하면서 여러 메타 데이터(HttpServletRequest)를 DispatherServlet에 넘겨줌
2) DispatherServlet: 요청 데이터를 입력하고 입력 받은 데이터 타입 전환, model 객체 생성
3) Controller: 데이터 처리, model에 결과 저장
4) 컨트롤러에 저장한 model 객체를 DispatherServlet에 다시 반환 (model에 저장된 데이터는 key, value 형태로 저장)
5) view 페이지에 model 객체에 담은 데이터를 넘겨줌
@Controller
public class YoilTellerMVC {
// http://localhost:8080/ch2/getYoilMVC?year=2023&month=6&day=13
@RequestMapping("/getYoilMVC")
public String main(int year, int month, int day, Model model) {
// 1. 유효성 검사
if(!isValid(year, month, day))
return "yoilError"; // 유효하지 않으면, /WEB-INF/views/yoilError.jsp로 이동
// 2. 처리
char yoil = getYoil(year, month, day);
// 3. Model에 작업 결과 저장
model.addAttribute("year", year);
model.addAttribute("month", month);
model.addAttribute("day", day);
model.addAttribute("yoil", yoil);
// 4. 출력, 작업 결과를 보여줄 View의 이름을 반환
return "yoil"; // /WEB-INF/views/yoil.jsp
}
private char getYoil(int year, int month, int day) {
Calendar cal = Calendar.getInstance();
cal.set(year, month - 1, day);
int dayOfWeek = cal.get(Calendar.DAY_OF_WEEK);
return " 일월화수목금토".charAt(dayOfWeek);
}
private boolean isValid(int year, int month, int day) {
if(year==-1 || month==-1 || day==-1)
return false;
return (1<=month && month<=12) && (1<=day && day<=31); // 간단히 체크
}
}
// yoil.jsp
< h1>${year}년 ${month}월 ${day}일은 ${yoil}요일입니다.< /h1>
JSP ≒ 서블릿 -> Spring
✔️ 서블릿: lazy-init, 요청이 왔을 때 객체 만들고 초기화
✔️ Spring: early-init, 요청이 오지 않아도 미리 객체를 만들어 놓고 초기화
✅ 서블릿:
✔️ @WebServlet = @Controller + @RequestMapping
✔️ url매핑은 클래스 하나만 사용가능(1클래스-1서블릿)
✔️ 단일상속, HttpServlet 상속
@WebServlet("/rollDice2")
public class TwoDiceServelet extends HttpServlet{
@Override
public void service(HttpServletResponse response, HttpServletResponse response) throws IOException { 생략 }
}
✅ Spring: @RequestMapping로 메서드마다 각각의 url을 매핑해줄 수 있음(1클래스-n개의 url 매핑)
@Controller
public class TwoDice {
@RequestMapping("/rollDice")
public void main(HttpServletResponse response) throws IOException { 생략 }
}
init(), service(), destroy()
✔️ 서블릿 인스턴스 無: 인스턴스 생성➩ 초기화init() ➩ service() 호출
✔️ 서블릿 인스턴스 有: service() 호출
@WebServlet("/hello")
public class HelloServlet extends HttpServlet{
@Override
public void init() throws ServletException {
// 서블릿이 초기화될 때 자동 호출되는 메서드
// 1. 서블릿 초기화 작업 담당
System.out.println("[HelloServlet] init() is called");
}
@Override // 호출될 때마다 반복적으로 수행
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//입력
//처리
//출력
System.out.println("[HelloServlet] service() is called");
}
@Override
public void destroy() {
// 뒷정리 - 서블릿이 메모리에서 제거(갱신_reload)될 때 서블릿 컨테이너에 의해서 자동호출
System.out.println("[HelloServlet] destroy() is called");
}
}
✔️ html안에 java코드
✔️ 자동으로 매핑
✔️ 컴파일 시 서블릿으로 변환됨
✅ <%! ~ %> : 클래스영역, 클래스 변수, 인스턴스 변수
✅ <% ~ %> : 메소드 영역, service() 내부, 지역변수
✅ <%=값%> : 값 출력
// src/main/webapp/twoDice.jsp
<%@ page contentType="text/html;charset=utf-8"%>
<%@ page import="java.util.Random" %>
<%-- <%! 클래스 영역 %> --%>
<%!
int getRandomInt(int range){
return new Random().nextInt(range)+1;
}
%>
<%-- <% 메서드 영역 - service()의 내부 %> --%>
<%
int idx1 = getRandomInt(6);
int idx2 = getRandomInt(6);
%>
<html>
<head>
<title>twoDice.jsp</title>
</head>
<body>
<img src='resources/img/dice<%=idx1%>.jpg'>
<img src='resources/img/dice<%=idx2%>.jpg'>
</body>
</html>
✔️ http는 상태정보를 저장하지 않음(stateless) -> 저장소 필요
✔️ 접근범위와 생존기간에 따라 4가지 저장소 분류
✔️ 저장소 map형태, 원하는 데이터 저장
✅ pageContext, application, session, request
✅ 속성 메서드:
✴️void setAttribute(String name, Object value): 값(value)-이름(name) 저장쓰기
✴️Object getAttribute(String name): 지정된 이름으로 저장된 속성값 반환읽기
✴️void removeAttribute(String name): 지정된 이름의 속성 삭제
✴️Enumeration getAttributeNames(): 저장된 모든 속성 이름 반환
✔️ ? 한글자, * 여러글자, ** 하위 경로 포함
✔️ @WebServlet(urlPatterns={"/hello","/hello/*"},loadOnStartup=1)
public class HelloServlet extends HttpServlet
서블릿은 늦은 초기화지만 loadOnStartup을 사용하면 미리 초기화 가능
✔️ 우선순위: exact>path>extenstion>default
1. exact mapping: /login/hello/do
2. path(경로) mapping:/login/
http://localhost/ch2/login/
http://localhost/ch2/login/hello.do
3. extension(확장자) mapping: .do
http://localhost/ch2/hi.do
http://localhost/ch2/login/hello.do
4. default mapping: /
http://localhost/ch2/
(default는 모든 주소와 매칭되지만 우선순위가 제일 낮기 때문에 앞선 우선순위가 안될 때만 매칭)
<$ =값%> -> $ {값}
ex. <$ =person.getCar().getColor()%>
${person.car.color}
person.getCar().getColor()=red
person.getCar().getColor()=red
person.getCar().getColor()=red
name=남궁성
name=남궁성
name=남궁성
id=null
id=
id=
"1"+1 = 2
"1"+="1" = 11
"2">1 = true
null =
null+1 = 1
null+null = 0
"" + null = 0
""-1 = -1
empty null=true
empty list=true
null==0 = false
null eq 0 = false
name == "남궁성"=true
name != "남궁성"=false
name eq "남궁성"=true
name ne "남궁성"=false
name.equals("남궁성")=true
<%
Person person = new Person();
request.setAttribute("person", person);
request.setAttribute("name", "남궁성");
request.setAttribute("list", new java.util.ArrayList());
%>
<html>
<head>
<title>EL</title>
</head>
<body>
person.getCar().getColor()=<%=person.getCar().getColor()%> <br>
person.getCar().getColor()=${person.getCar().getColor()} <br>
person.getCar().getColor()=${person.car.color} <br><br>
name=<%=request.getAttribute("name")%> <br>
name=${requestScope.name} <br>
name=${name} <br><br>
id=<%=request.getParameter("id")%> <br>
<!-- el은 lv를 못쓰므로 pageContext 사용 -->
id=${pageContext.request.getParameter("id")} <br>
id=${param.id} <br><br>
"1"+1 = ${"1"+1} <br> <!-- 2 -->
"1"+="1" = ${"1"+="1"} <br> <!-- 11 -->
"2">1 = ${"2">1} <br><br> <!-- true -->
null = ${null}<br> <!-- null -->
null+1 = ${null+1} <br> <!-- 1 -->
null+null = ${null+null} <br> <!-- 0 -->
"" + null = ${""+null} <br> <!-- 0 -->
""-1 = ${""-1} <br><br> <!-- -1 -->
empty null=${empty null} <br> <!-- true -->
empty list=${empty list} <br> <!-- true -->
null==0 = ${null==0} <br> <!-- false -->
null eq 0 = ${null eq 0} <br><br> <!-- false -->
<!-- eq=equal, nq=not equal -->
name == "남궁성"=${name=="남궁성"} <br> <!-- true -->
name != "남궁성"=${name!="남궁성"} <br> <!-- false -->
name eq "남궁성"=${name eq "남궁성"} <br> <!-- true -->
name ne "남궁성"=${name ne "남궁성"} <br> <!-- false -->
name.equals("남궁성")=${name.equals("남궁성")} <br> <!-- true -->
</body>
</html>
<% ~ %>을 태그화하여 가동성을 높힘
<c:set>, <c:if>
1 2 3 4 5 6 7 8 9 10
1. arr[0]=10
2. arr[1]=20
3. arr[2]=30
4. arr[3]=40
5. arr[4]=50
6. arr[5]=60
7. arr[6]=70
msg=
hello
msg=< p>hello< /p>
값이 유효하지 않습니다.
Server time is 2023/06/26 15:02:51
<%@ page contentType="text/html;charset=utf-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<html>
<head>
<title>JSTL</title>
</head>
<body>
<!-- EL이 lv를 사용하지 못하므로 저장소에 저장 -->
<c:set var="to" value="10" scope="page"/>
<c:set var="arr" value="10,20,30,40,50,60,70"/>
<c:forEach var="i" begin="1" end="${to}">
${i}
</c:forEach>
<br>
<c:if test="${not empty arr}">
<!-- status | count: 1~, index: 0~ -->
<!-- 1. arr[0]=10 -->
<c:forEach var="elem" items="${arr}" varStatus="status">
${status.count}. arr[${status.index}]=${elem}<BR>
</c:forEach>
</c:if>
<br>
<c:if test="${param.msg != null}">
msg=${param.msg}
<!-- out | tag해석 안함 -->
msg=<c:out value="${param.msg}"/>
</c:if>
<c:if test="${param.msg == null}">메시지가 없습니다.</c:if>
<br>
<c:set var="age" value="${param.age}"/>
<c:choose>
<c:when test="${age >= 19}">성인입니다.</c:when>
<c:when test="${0 <= age && age < 19}">성인이 아닙니다.</c:when>
<c:otherwise>값이 유효하지 않습니다.</c:otherwise>
</c:choose>
<br>
<c:set var="now" value="<%=new java.util.Date() %>"/>
Server time is <fmt:formatDate value="${now}" type="both" pattern="yyyy/MM/dd HH:mm:ss"/>
</body>
</html>
공통적인 '요청 전처리'와 '응답 후처리'에 사용, 로깅, 인코딩 변환 등
✔️ 코드 간결, 중복제거
[/ch2/el.jsp] 소요시간=1211ms
[/ch2/el.jsp] 소요시간=4ms
[/ch2/el.jsp] 소요시간=1ms
[/ch2/el.jsp] 소요시간=3ms
@WebFilter(urlPatterns="/*")
public class PerformanceFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException { //초기화 작업 }
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
// 1. 전처리 작업
long startTime = System.currentTimeMillis();
// 2. 서블릿 또는 다음 필터를 호출
chain.doFilter(request, response);
// 3. 후처리 작업
System.out.print("["+((HttpServletRequest)request).getRequestURI()+"]");
System.out.println(" 소요시간="+(System.currentTimeMillis()-startTime)+"ms");
}
@Override
public void destroy() { //정리 작업 }
}
참고) 자바의 정석 | 남궁성과 끝까지 간다