[Spring] 02. 관심사의 분리와 MVC패턴/서블릿과 JSP

Hyeongmin Jung·2023년 5월 2일
0

Spring

목록 보기
2/17

🐣 관심사의 분리와 MVC패턴

관심사의 분리

하나의 관심사가 하나의 기능만 수행하도록 분리

  • OOP 5대 설계원칙(SOLID) 중 SRP(Single resposibility Principle 단일책임) 하나의 메서드는 하나의 책임(관심사)에만 집중_수정/유지보수 용이, 가독성 상승
    ✴️OCP(Open/Closed principle 개방폐쇄): 확장에는 열려있고 수정에는 닫힘
    ✴️LSP(Liskov substitution principle 리스코브 치환): 자식 클래스가 자신의 부모 클래스 대체 가능, 자식클래스는 확장만 수행
    ✴️ISP(Interface segregation principle 인터페이스 분리): 범용 인터페이스 하나보다 특정 클라이언트를 위한 여러 개의 인터페이스 나음
    ✴️DIP(Dependency Inversion principle 의존성역전): 추상화(변하기 어려운 것)에 의존, 구체화(변하기 쉬운 것)에 의존X

  • 변하지 않는 것과 변하지 않는 것의 분리

  • 공통(중복)코드의 분리

MVC패턴(Model, View, Controller)

❶ 입력(중복영역이므로 공통 코드로 분리) ➩ ❷ 처리(Controller변하지 않는 영역) ➩ ❸ 출력(View변하는 영역)

  • Model: 입력/처리/출력이 서로 다른 메서드로 분리되면서 데이터를 전달 받을 수 없으므로 model이라는 객체를 생성하여 데이터 교환

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

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");
	}
}

JSP

✔️ 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>
  • JSP의 기본객체: 생성없이 사용할 수 있는 객체, 지역변수(lv )
    request, response, pageContext, session, application, config, out, page

JSP 유효범위(scope)와 속성(attribute)

✔️ http는 상태정보를 저장하지 않음(stateless) -> 저장소 필요
✔️ 접근범위와 생존기간에 따라 4가지 저장소 분류
✔️ 저장소 map형태, 원하는 데이터 저장

  • Scope: Application( Session ( Request (Page) ) )
    Application: Most Visible
    Page: Least Visible

pageContext, application, session, request

✅ 속성 메서드:
✴️void setAttribute(String name, Object value): 값(value)-이름(name) 저장쓰기
✴️Object getAttribute(String name): 지정된 이름으로 저장된 속성값 반환
읽기
✴️void removeAttribute(String name): 지정된 이름의 속성 삭제
✴️Enumeration getAttributeNames(): 저장된 모든 속성 이름 반환


URL 패턴

✔️ ? 한글자, * 여러글자, ** 하위 경로 포함

✔️ @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는 모든 주소와 매칭되지만 우선순위가 제일 낮기 때문에 앞선 우선순위가 안될 때만 매칭)


EL(Expression Language)

<$ =값%> -> $ {값}
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>

JSTL(JSP Standard Tag Library)

<% ~ %>을 태그화하여 가동성을 높힘
<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>

Filter

공통적인 '요청 전처리'와 '응답 후처리'에 사용, 로깅, 인코딩 변환 등
✔️ 코드 간결, 중복제거

[/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() { //정리 작업 }
}

참고) 자바의 정석 | 남궁성과 끝까지 간다

0개의 댓글