[패스트캠퍼스: Java&Spring 웹 개발] Week 4 - Spring MVC

2023. 6. 28. 22:54Learn

728x90

본 게시글은 내일배움카드로 신청한 패스트캠퍼스의 국비지원교육 강의: Java&Spring 웹 개발입니다.

본 게시글은 패스트캠퍼스의 열공 챌린지 형식을 준수하며, 개인적인 정리 목적으로 학습 내용을 정리한 글입니다.

- 제목, 본문, 해쉬태그 키워드 포함

- 사진 1장 이상, 글자수 500자 이상 (공백 포함, 코드 제외)

 

https://github.com/castello/spring_basic/tree/main/ch2


원격 프로그램의 실행

  • static vs instance
    static 메소드는 static 변수만 사용 가능, instance 메소드는 instance 변수도 사용 가능.
    단순히 좋다/나쁘다의 차이가 아니고, 설계 원칙 상 구분하여 사용하자
  • @requestMapping("/dir")
    Annotation으로 인해 적용된 Reflection API(java.lang.reflect)는 Class.forName 메소드를 통해 생성된 클래스 파일을 직접 활용하기에 private method도 외부 접근이 가능하다.
  • Reflection API 예제
package com.fastcampus.app

Class Hello {
	private void main(){ ... }
}

Class helloClass = Class.forName("com.fastcampus.app.Hello");

Method mainMethod = helloClass.getDeclaredMethod("main");
mainMethod.setAccessible(true);
mainMethod.invoke( (Hello)helloClass.newInstance() );

AWS에 배포하기

  • 동일한 Tomcat 설치 후, WAR file로 export한 패키지를 webapps 폴더에 압축 해제
  • Tomcat start.sh 실행 / shutdown.sh 종료
  • AWS 및 EC2 instance TCP/IP 방화벽 설정
  • URL: instance public Ip:port/package/dir

HTTP 요청과 응답

  • package com.fastcampus.app에 존재하는 Class Hello의 메인 메소드 public static void main( String[] args )를 콘솔에서 호출할 때, java com.fastcampus.app 2021 11 12 와 같이 매개변수를 전달하는 일반적인 호출 방법과 동일한 원리로..
  • Apache Tomcat을 통해 HttpServletRequest request를 매개변수로 설정한 메인 메소드는 browser로부터 요청을 받을 때, URL을 기반으로 HttpServletRequest form에 따라 내용을 자동으로 채워서 전달해준다. 개발자는 request 변수를 통해 채워진 내용을 바로 사용할 수 있다.
  • HttpServletRequest
    http://52.78.79.190:8080/ch2/requestInfo?year=2021&month=10&day=1
    getScheme(), getServletName(), getServerPort(), getContextPath(), getServletPath(), getQueryString()
    getRequestURL(), getRequestURI(), getParameter() ... https://github.com/castello/spring_basic/blob/main/ch2/RequestInfo.java
  • main method arguments에 따라 호출 가능한 메소드 형태로 변형하기
    1. PrivateMethodCall (java.lang.reflection을 통한 호출 API 구현)
    2. public void main(HttpServletRequest request, HttpServletResponse response) throws Exception
    3. main 메소드 내 parameters는 request.getParameter("...")로 얻어 사용하기
    4-1. response.setContentType("text/html")
    4-2. response.setCharacterEncoding("utf-8")
    4-3. PrintWriter out = response.getWriter()
    5. out.println("...")
  • 동적 리소스 vs 정적 리소스: 결과 기준, 변동 여부

클라이언트와 서버

  • browser의 URL을 통한 request를 해당 서버의 Tomcat이 받아 저장하고, 호출한 method 매개변수의 형태에 따라 전달한다.
  • request.getParameter 또는 getParameterValues 또는 getParameterMap
  • client/server: side vs application vs computer
    요청하는 쪽
    - client side: 일반적으로 client application은 browser, client computer는 구동에 사용된 컴퓨터
    다양한 서버가 존재할 수 있으므로 요청자는 반드시 포트 번호를 명시해야함 (단, default 80은 생략 가능)
    응답하는 쪽 - server side: 어떤 서비스를 제공하는지에 따라 달리 불림 (SMTP, FTP, HTTP)
    Server listening on a single port, cannot use predetermined ports (#0 - #1023)
    WAS( Web Application Service ): Web을 통해 외부에서 프로그램을 실행할 수 있도록 하는 서비스
    Client의 접근성 및 Update 편의성 향상
  • Tomcat 구조
    Thread Pool on request
    Server(Tomcat) > Service > Connector(HTTP 1.1/2.0, AJP): Request/Response instances, 최초 생성 후 전달 > Engine(Catalina) > Host(s) > Context:Web App(s) > filter:servlet 전처리 > Dispatcher Serv-let: 작은 서버 프로그램 > Connector 

설정 파일 - server.xml, web.xml

  • [tomcat dir] / conf / server.xml & web.xml : 공통 설정
  • src / .. / webapp/ WEB-INF/ ... : 각 프로젝트 내 개별 설정; 공통 설정 overriding
  • 구성: Server > Connector > Service > Engine > Host > Context
  • 기존에는 web.xml에서 원격 프로그램 등록 및 URL 연결을 진행했으나,
    Spring에서는 Annotation(@Controller, @RequestMspping)으로 편의성 개선
    단, Servlet에서는 기존 방식 유지

HTTP 요청과 응답 - 이론

  • Protocol - 통신을 위한 약속 규칙
  • HTTP: Hyper Text Transfer Protocol
    human readable
    Stateless (상태를 저장하지 않음; 동일 클라이언트 구분 불가; 쿠키, 세션을 통해 보완)
    custom header: server/client 약속만 되어 있다면 기능 확장
  • 요청/응답 & 헤더/바디

    Request (GET- read/ POST- write)
    request-line (GET: queryString in URL, POST: queryString in body)

    Response

    HTTP/1.1 200 OK
    Content-Length: 44
    Content-Type: text/html
    Data: Sat, 20 Oct 2021 19:03:38 GMT
Status Code Meaning
1xx Informational
2xx Success
3xx Redirect
4xx Client Error
5xx Server Error
  • GET: 서버의 리소스를 가져오기 위해 설계
    보안에 취약하나, URL을 통해 타인과의 공유에 유리
    POST: 서버에 데이터를 올리기 위해 설계
    HTTP + TLS로 암호화하면 보안에 유리 (SSL upgraded to TLS)

텍스트와 바이너리,  MIME, Base64

  • Binary: 이진파일 (0, 1): 문자와 숫자를 바이트 코드로 쓰고 읽는다.
    'A': 41 (char), 12:00 00 00 0C (int), 12.625f: 41 4A 00 00 (float)
    Text: 문자와 숫자 모두 "문자"로 인코딩/ 디코딩한다.
    'A': 41 (char), 12: 31, 32 (char, char)
  • MIME: Multipurpose Internet Mail Extensions
    전송할 데이터의 타입을 명시
Type Description MIME type / subtype
text documents include texts text/plain, text/html, text/css, text/javascript
image images image/bmp, image/webp
audio audio files audio/midi, audio/mpeg, audio/webm, audio/ogg, audio/wav
video audio files video/webm, video/ogg
application binary datas application/octetstream, pkcs12, vnd.mspowerpoint, xhtml+xml, xml, pdf
  • Base64: 64진법, 서로 다른 OS 및 Encoding 관계없이 호환되는 방식의 인코딩 방법
    파일에 대한 링크를 이용하지 않고도, html 문서에 정적으로 로드할 수 있다

  • 요약: 텍스트가 아닌 데이터를 전송할 때, MIME로 타입을 명시하거나, 호환되는 방식으로 바이너리 파일을 인코딩하자.
<img src="data:image/[extension];base64, [encoded source]">

 


관심사의 분리와 MVC 패턴

  • 관심사 (concern; 책임; 역할; 기능)
  • OOP: SOLID
    SRP 단일책임원칙: 하나의 메서드는 하나의 책임(관심사)만 진다,
    common/ uncommon code
    code duplicate
  • Spring 기능을 활용하여 Servlet 클래스를 강제 사용하지 않더라도, 자동 변환된 인자를 활용할 수 있다.
  • Controller: 작업 처리, View: 출력, Model: VC 간 데이터 중개

Input > Model > Controller > Model > View > Output
현재까지 배운 내용을 토대로 그려본, MVC 모델의 자연스러운 흐름

  • WEB-INF/spring/appServlet/servlet-context.xml에 존재하는 prefix와 suffix의 정의를 토대로 controller가 반환하는 String과 같은 이름의 View가 매핑된다. void형으로 반환하는 경우, @RequestMapping으로 설정한 URL 이름에 대한 매핑을 시도한다. (명시적으로 사용하는 경우가 많지만, 만약 URL과 View 이름이 동일하다면 관리 포인트를 줄일 수 있다는 장점이 있다.)
  • 기본적인 Controller 작성법
    1. 매개변수 정의 (..., Model m)
    2. 유효성 검사
    3. Model 작업 결과 저장: m.addParameter( String key, Any value)
    4. View 반환 return String viewName
  • ModelAndView 클래스 활용
    1. mv.addObject( String key, Any value)
    2. mv.setViewName( String viewName )
    3. return mv
  • Model vs ModelAndView
    DispatcherServlet에 의해 생성된 Model 객체를 사용하는가 또는
    개발자가 직접 Controller 내에서 생성한 ModelAndView 객체를 사용하는가
    (대부분 전자를 활용한다고 하며, 후자는 View에 대한 일시적인 수정을 반영해야 하는 경우에 사용할 수 있을 것 같다.)

관심사의 분리와 MVC 패턴 - 원리

 

  • java.lang.reflection API를 활용하여 특정 소스에 포함된 Methods 배열을 얻어오고, 적절한 format으로 출력한다.
    기본적으로 compiler는 각 매개변수의 정확한 이름에는 관심이 없기 때문에, argN 형태로 출력되는 것을 볼 수 있다.
    만약 정확한 이름을 얻을 수 있다면, 내부 private 프로그램으로 디버깅에 도움이 될 것 같다.
boolean isValid(int arg0, int arg1, int arg2)
char getYoil(int arg0, int arg1, int arg2)
java.lang.String main(int arg0, int arg1, int arg2, org.springframework.ui.Model arg3)
더보기
package com.fastcampus.example;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.StringJoiner;

public class MethodInfo {
	public static void main(String[] args) throws Exception{

		Class clazz = Class.forName("com.fastcampus.example.YoilTellerMVC");
		Object obj = clazz.newInstance();
		
		Method[] methodArr = clazz.getDeclaredMethods();
		
		for(Method m : methodArr) {
			String name = m.getName();
			Parameter[] paramArr = m.getParameters();
//			Class[] paramTypeArr = m.getParameterTypes();
			Class returnType = m.getReturnType();
			
			StringJoiner paramList = new StringJoiner(", ", "(", ")");
			
			for(Parameter param : paramArr) {
				String paramName = param.getName();
				Class  paramType = param.getType();
				
				paramList.add(paramType.getName() + " " + paramName);
			}
			
			System.out.printf("%s %s%s%n", returnType.getName(), name, paramList);
		}
	} // main
}

/* result
 * boolean isValid(int arg0, int arg1, int arg2)
 * char getYoil(int arg0, int arg1, int arg2)
 * java.lang.String main(int arg0, int arg1, int arg2, org.springframework.ui.Model arg3)
 * */

 

  • javac -parameters 옵션을 부여하면 매개변수 이름을 저장하게 된다.
    eclipse에서는 project properties > Java Compiler > Store information about method parameters 옵션을 체크하면 된다.
    Spring은 Maven을 통해 Project를 관리하기 때문에 /pom.xml 파일도 수정해야 한다.
더보기
project properties > Java Compiler > Store information about method parameters: checked
properties > java-version: 11
plugin > groupId: ...maven > configuration > source/target: ${java-version}
  • .class File
    Eclipse > Window > Show View > Other > Navigator
    [project] > target > classes > [package] > .class file로 클래스 파일을 직접 볼 수도 있다.
더보기
Local variable table: (this), year, month, day
  • 보통 reflection API를 활용하지만, ModelController 클래스를 직접 생성해서 사용할 수도 있다.

java.lang.reflect를 활용한 Controller 호출

목표: reflection API를 활용하여 구현된 controller를 내부적으로 호출(invoke)하자.

가정: controller: [package] / [className] / method: [ foo( int, Model ) / return String viewName ]

 

1. 클래스 정보 가져오기
Class class = Class.forName(String [package] + [className] )

2. 해당 클래스의 메소드 정보 가져오기: API 확인: 1️⃣메소드명, 2️⃣매개변수, 3️⃣반환타입
Method foo = class.getDeclaredMethod( "1️⃣foo", 2️⃣int.class, 2️⃣Model.class )
3️⃣String viewName = (3️⃣String)foo.invoke( class.newInstance(), new Object[] { 2️⃣[params...] } )

* 모델 생성: Model은 인터페이스이고, 결국 구현된 특정 모델로 생성해야 한다. Model이라 쓰고, 데이터라 읽는다.
기존 Controller method에서 요청한다면 반드시 생성해서 넘겨주고,
요청하지 않더라도 가변 데이터를 반영하는 View를 생성하려면 render 과정에 넘겨준다.
Model model = new BindingAwareModelMap()

3. pars[], HashMap, args[]
Parameter[] pars = foo.getParameters()
Map map = new HashMap() //  dict: { key: value, ... }
Object[] args = new Object[ foo.getParameterCount() ]  // init

for(int i=0; i<foo.getParameterCount; ++i) args[i] = map.get( key )
** pars의 정보를 토대로 처리하는 코드 기반을 만드는 것이 자동화 코드의 핵심!


Tomcat Library Import

  • Tomcat의 @WebServlet annotation 사용을 위한 Library Import
  • Spring vs Servlet
    Spring: Class 앞에는 @Controller, 개별 Method 앞에 @RequestMapping(" .. ")
    Servlet:
    > 클래스 앞에서만 @WebServlet(" .. ") 가능
    > Class extends HttpServlet
    > Method name: service
    > 1st/2nd 매개변수: HttpServletRequest, HttpServletResponse

** 동일한 서비스에 기능을 추가하며 확장하는 방식으로 개발하는 과정이 소스로 보인다. 이렇게 기능이 추가/변형되는 부분들을 잘 기억해두었다가, 각각을 인터페이스로 정의하여 필요에 따라 선택할 수 있도록 만들어두면 보다 재사용성이 높은 코드가 될 것 같다.


서블릿과 컨트롤러의 비교

  • Servlet: Spring은 Servlet을 이용하여 구현되었다.
    Servlet은 HttpServlet을 상속받아야 하며, Method Overriding을 통해 기능을 재정의한다.
    init(), service(HttpServletRequest, HttpServletResponse), destroy() 순으로 생명 주기에 따라 호출된다.
    각각 생성자, 기능, 소멸자 느낌이다. init을 통해 생성된 Servlet Instance는 싱글톤이다. 
    생성 시 제약이 많아서 상속 관계를 갖는 복잡한 애플리케이션을 만들 때에는 매우 불편할 것 같지만,
    단순한 애플리케이션에 대한 유지/보수 측면에서는 오히려 나을 수도 있을 것 같다.
  • JSP: Java Server Pages; JSP는 Servlet과 유사하다.
    기본적인 웹 문서가 HTML + script:JavaScript로 구성되는 것과 유사하게
    JSP는 HTML + script:Java로 생각하면 되고, 그 format은 Servlet으로 변환되어 사용된다.
    • JSP 내 변수 선언
      <%! .. %> iv / cv: instance / class(static) variables
      <% .. %> lv : local variables
    • JSP to Servlet
      Servlet으로 변환이라고 해봤자 거창한 것이 아니고
      변수 선언부는 문법에 맞게 iv/cv , lv 구분하여 선언하고, HTML은 out.write 형식으로 출력한다.
      default: lazy initialization ( Spring은 early init이 기본값 )
    • JSP 기본 객체: pageContext, request, response, out, exception, session, application, config, ...
      _jspService의 지역 변수로 선언되어 있는 것들

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

  • 4개의 저장소가 각각 1️⃣접근 범위, 2️⃣생존 기간에 따라 구분되어 존재한다.
    상황에 따라 알맞은 저장소(자료구조)를 사용하면 됨.
    Map( key, value ): set/remove/getAttribute( Names )
  1. pageContext
    기본 객체 및 일반적인 EL 문법에 활용.
    해당 jsp 파일(페이지)에 한해 존재하는 lv(지역 변수; 단일 요청 처리 간 유효)의 읽기 / 쓰기
  2. application: per-application; for-all-clients; in-context
  3. session(쿠키 사용): per-client
    ★ 유저 수에 비례하여 객체가 증가하므로, 가능한 한 사용하지 않거나 최소한의 데이터만 저장... 또는 일시적 생성/사용 후 삭제
  4. request: per-request: JSP forwarding( redirected to another .jsp)
    ★ 가능한 한 request를 통해 jsp 간 데이터 교환을 수행하고, 불가피한 경우에만 session

URL mapping in Servlet

  • WEB-INF / web.xml
  • @WebServlet으로 Servlet URL Mapping
    4가지 우선 순위: 1. 정확히 일치 2. path 3. extension 4. default
  • Servlet은 Servlet context에 따라 각 priority rule에 대해 Servlet을 대응되지만,
    Spring은 Singleton DispatcherServlet이 @RequestMapping 내용을 통해 내부적으로 연결해준다.
  • Servlet은 상기한 우선 순위에 따른 단계적인 대응 끝에 최종적으로는 default servlet이 모든 요청을 수용하 도록 설계되었으나, Spring은 singleton인 DispatcherServlet이 모든 요청을 수용한다. 이후 내장된 정보를 토대로 매핑 진행할 뿐.

EL

  • EL을 사용할 때, getter를 attributeName만으로 사용할 수 있다.
    String color라는 멤버 변수에 대해 String getColor()라는 Getter가 정의되어 있다면, obj.color 만으로 obj.getColor()와 동일한 결과를 반환한다.
  • EL을 사용할 때, 변수명만 전달하더라도 4개의 저장소를 가까운 scope에서부터 찾아본다.
    단, request 객체는 사용자가 정의한 변수명이 아니므로, 저장소를 생략하면 사용할 수 없다.
    컴파일 과정에서 삽입되는 lv instance는 모두 pageContext에 존재하니, 해당 저장소를 명시하여 사용하자.
  • EL은 null을 출력하지 않는다. (JSP는 null 출력)
  • EL에서 String concat 연산자는 += 이다.

JSTL (JSP Standard Tag Library)

  • 문법에 따라 Java 코드가 들어가면 <% .. %> 괄호가 가독성을 해친다.
    JSTL은 c:set, c:forEach, c:if, c:choose, fmt 등 간단한 Java 코드를 한 줄로 변환시켜준다.

Filter

  • 전/후처리
  • @WebFilter(urlPatterns="/*")
  • extends Filter

UTF-8 forceEncoding filter: web.xml에 삽입

<!-- 한글 변환 필터 시작 -->
<filter>
    <filter-name>encodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
        <param-name>encoding</param-name>
        <param-value>UTF-8</param-value>
    </init-param>
    <init-param>
        <param-name>forceEncoding</param-name>
        <param-value>true</param-value>
    </init-param>
</filter>

<filter-mapping>
    <filter-name>encodingFilter</filter-name>
    <url-pattern>/*</url-pattern>	
</filter-mapping>
<!-- 한글 변환 필터 끝 -->

@RequestParam(required=true/false, defaultValue= ..)

  • 각 매개변수에 대한 옵션처럼 생각하면 된다.
  • 반드시 입력해야 하는 매개변수에 대해서는 적절한 입력이 이루어졌는가에 대한 유효성 검증이 필요하다.
    적절하지 않은 입력에 대비한 적절한 수준의 피드백이 예외처리를 통해 구현되어야 한다.
  • resources / log4j.xml 에서 로깅 수준을 변경할 수 있다.
  • @ExceptionHandler(Exception.class)

@ModelAttribute(String key)

  • 사용 대상
    1️⃣ 컨트롤러 메서드의 매개 변수
    2️⃣ 반환 타입
  • model.addAttribute 대체
    annotation의 문자열 인자로 key를 명시하거나 기본값으로 첫 문자만 소문자로 변환한 데이터 타입을 사용한다.
    즉, 일반적으로 메소드에 1회 등장하는 Object object, Class class 느낌의 일회성 인스턴스들을 깔끔하게 처리하기 위한 annotation인 것 같다.

@RequestParam vs @ModelAttribute 기본 설정

  • method parameters
    primitive type: @RequestParam(required=true)
    reference type: @ModelAttribute("object")

WebDataBinder: return BindingResult

  • 반드시 바인딩할 파라미터의 바로 뒤에 위치해야 한다.
  • 1️⃣ Type Casting
    2️⃣ Data Validation

마치며..

거의 모든 Framework는 Native 언어를 기반으로 구현한 결과물을 개선하기 위한 시도로부터 파생되었을 것이다.
그렇게 새로운 방식의 시스템을 제안한다면, 이름과 용법이 변경되는 것은 불가피하다.
물론 Native 언어로 구현한 결과물에 익숙하고, 기존 코드의 불편함을 통감하는 사용자일수록 이해는 빠를 것이다.

그러나 결국 기본은 암기이다. 기존에 annotation이라고는 @override, @TODO, Lombok의 @Getter, @Setter 정도 밖에 사용하지 않았던 나로서는 적응할 시간이 필요하다. 스스로 사용해보는 시간을 충분히 가져봐야겠다.

728x90