다음 주제로 넘어가기전에 Spring을 배우기 위해 거쳐온 내용들을 정리해봐야겠다는 생각이 들었다. 그동안 build한 project의 구조와 사용한 도구를 review해서 각각의 project에서 MVC design pattern을 어떻게 구현해왔는지 단계별로 review 해보았다.
처음 간단한 Dynamic Web Project로 구현한 MVC pattern의 게시판 웹사이트에서부터 Oracle 또는 MariaDB와 연동된 Spring Boot 기반의 Spring Starter Project까지 여러개의 프로젝트 실습을 진행해왔다. 굉장히 많은 내용이라 중요하다고 생각하는것, 내가 헷갈렸던것들 위주로 지금 일단 정리해보고... 이 내용은 앞으로 계속 종종 다시 읽어보면서 틀린 부분이 있는지 확인해봐야겠다.
사이사이에 실습한 작은 project를 제외하고 크게 5단계로 나뉘어지는것같다.
먼저 client와 interact하고 business/module logic을 구현하는 application을 만들기위해 사용되는 주요 design pattern인 MVC의 구성에 대해 배웠다. client의 요청에서부터 돌아오는 응답까지의 전체적인 흐름을 제어하는 파트는 controller이다. Controller는 요청을 어떤 클래스/메소드를 통해 처리작업을 진행하고, DB와 소통하는 "action"으로 부터 받은 응답 데이터를 어떤 방식으로 view로 전달할지 결정하고 처리한다. 요청 처리 작업의 처음부터 마지막까지 진행을 주도하는 Controller 클래스 객체(controller 역할을 하는 Servlet 객체)는 server의 (Servlet) container에서 자동 생성되고, 사용이 완료되면 destroy()와 같은 소멸 메소드를 통해 소멸된다.
실습 환경:
STS(Spring Tool Suite 4)
jdk 1.8
Project type: Dynamic Web Project
Project name: www_mvc (w/o JDBC)
처음 Dynamic Web Project("www_mvc")를 만들어서 Servlet을 통해 MVC의 Controller 역할을 구현했다. 여기서 Servlet이 Controller 역할을 수행하기때문에 Web Content> WEB-INF> web.xml에서 Controller 클래스를 Servlet으로 지정하고, Servlet의 parameter로 요청 url(command)와 요청을 처리할 handler를 match하는 configuration.properties 파일을 지정한다.
Tomcat server의 container로 인해 Controller 객체를 자동 생성되지만, congifuration.properties에 define된 mapping은 manually map 데이터 형태로 만들서 사용해야한다. 요청 url(command)는 key값으로, handler class이름은 value값으로 구성되어있는 map이다. 이 map을 사용해서 요청 URL에서 URI만 가져와서 command 문자열을 찾고, command와 mapping되는 handler class객체를 통해 요청에 해당하는 logic을 Action 클래스에서 처리한다. Logic처리(plus 또는 service 처리) 후, 반환되는 viewpage로 응답내용을 보여줄 view(.jsp파일)가 연결된다.
Project type: Dynamic Web Project
Project name: mvc_bbs (w/ JDBC)
"www_mvc" project에 DB access를 위해 jdbc class, data source, jdbc driver가 추가되었다. Application에서부터 Database까지의 각 부분에서 data access를 위한 layer를 따져보자면 다음과 같다:
Application : DAO -> JDBC Interface : JDBC Template with DataSource(Configuration for DBMS connection) -> JDBC Implementations : JDBC Driver -> Persistence Layer : Database(Oracle, MySQL, MariaDB 등등)
요청 command를 처리하는 logic을 각 Action 클래스에서 DAO, DTO를 통해 처리한다. DAO, DTO클래스에서 직접 DBMS와 connection을 형성하고 DB를 access하여 logic처리를 위해 필요한 데이터 작업을 수행한다. java.sql package의 Connection, PreparedStatement, ResultSet, SQLException등 과 같은 클래스들을 import해서 DB와의 연결 및 SQL 쿼리문 실행과 결과 도출을 진행하는것이다. 처리된 응답은 HttpServletRequest 객체에 담겨서 view로 forward처리 된다.
여기까지는 tomcat의 container에서 servlet객체를 자동 생성되어 요청을 처리할 수 있는 환경이 구축되었었다. 다음 단계부터는 spring framework을 사용하면서, spring container(or IOC container)를 통해 component/controller 객체를 자동 생성했다.
Project type: Simple Spring Utility
Project name: HelloApp
Spring framework의 IOC container의 활용목적과 구동 방식을 확인했다. IOC(Inversion of Control)은 application이 아닌 framework에서 직접 객체를 생성하는 방식으로, 객체 생성의 주 권한이 framework를 고용하는 application이 아닌 framework에 주어지게되는것을 의미한다. 개발자가 어떻게 설정을 해야지 framework으로 부터 필요한 객체 생성을 얻을 수 있는지 실습을 통해 확인했다.
Simple Spring Utility Project("HelloApp")를 만들어서 src/main/resources/META-INF/spring/app-context.xml 설정파일을 통해 application의 context 객체를 자동 생성 했다. app-context.xml에 생성하려는 클래스 객체를 Bean요소로 설정하고, 필요한 속성들을 명시해두면, GenericXMLApplicationContext의 인스턴스를 통해 spring의 IOC container를 구통시키고 Bean으로 설정해둔 객체를 getbean() 메소드를 통해 가져올 수 있다.
collection 형태의 데이터 타입인경우에도, 그리고 해당 클래스에 존재하는 다른 맴버변수들도 함께 초기화하는 방식 또한 실습을 통해 확인해보았다. IOC기반의 DI(dependency injection)을 구현할 수 있다.
app-context.xml 설정내용에 < context: component-scan base-package= "spring.model.bbs" />와 같이 context:component-scan으로 특정 package를 지정해두면 해당 package의 클래스 객체들을 bean을 통해 자동으로 생성할 수 있다. 해당 package내 클래스들에는 대표적으로 @Component/ @Controller와 같이 annotation을 사용해서 매우 간결한 방식으로 bean을 통한 component역할의 또는 Controller 클래스 객체의 자동 생성을 설정할 수 있다.
app-context.xml 파일 단 하나를 설정해서 한번에 여러 클래스의 객체와 맴버변수를 제어할 수 있다. (Simple Spring Utility Project에는 web.xml이 생성되지 않는다. 이번 단계와는 다르게 Spring Legacy Project에서는 servlet-context와 root-context를 포함하여 web application을 전체적으로 설정할 수 있는 web.xml이 자동 생성된다.)
Project type: Spring Legacy Project (Spring MVC Project template)
Project name: spring_webtest
build tool: Maven 4.0.0
spring framework: 4.3.20.RELEASE
maven을 통해 Spring Legacy Project("spring_webtest")를 빌드했다. web.xml 설정을 기반으로 servlet-context.xml을 통해 Dispatcher Servlet을 설정하고 root-context를 통해 application 전반적으로 사용하는 resources (파일 전송처리를 위한 MultipartResolver, viewResolver 역할을 수행 할 tilesconfigurer, 등) 설정했다. (Dispatcher servlet과 root의 context내용외에도 application 전반적으로 필요한 filter 또는 listener에 대한 내용은 web.xml 설정파일에서 명시한다.)
servlet-context.xml에서 앞서 연습했던 bean 설정이 필요하다. context: component-scan을 사용해서 application context를 구성하는 여러 클래스들의 객체들이 자동으로 생성되도록 설정할 수 있고, 또한 ViewResolver와 같은 bean의 속성을 설정해서 Controller가 요청을 처리한 후 응답내용을 전송할 view를 선택하는 방식을 제어할 수 있다.
context 설정을 명시하는 파일에서는 마치 요청을 처리하고 응답을 보내는 과정이 미리 짜여진 통로를 통해 (각각의 요청에 따른 내용만 달라지고) 오고갈 수 있도록 길을 뚤어 놓는것같다.
root-context.xml에서도 application 전체가 공통적으로 사용하는 resources를 한번에 설정한다. Web Dynamic Project 실습에서는 java.sql package의 Connection 클래스를 통해 DB와의 연결을 형성하는 DBOpen.java(그리고 연결을 닫는 DBClose.java)클래스를 따로 생성해야했지만, spring framework을 사용하면 root-context.xml에서 dataSource 이름(id)으로 bean을 하나 설정해서 DB와의 연결을 형성할 수 있다. dataSource bean내에서 class를 지정하고, property(속성)으로 driverClassName, url, password를 설정할 수 있다.
또한, root-context.xml에서 persistence layer역할을 할 ORM 또는 SQL Mapper를 설정한다. ORM을 사용하는 case를 구현해보기위해 대표적인 Persistant API중 하나인 Hibernate과 JPA를 사용했다. 또 다른 persistence layer 도구를 사용해보기위해 대표적인 SQL Mapper인 MyBatis를 설정하여 DB와의 연동을 구현했다.
참고 블로그(그림과 함께 아주아주 잘 정리되어있다):
Spring과 Data access layer연동:
[spring-JDBC]
https://gmlwjd9405.github.io/2018/05/15/setting-for-db-programming.html
Spring-ORM or Spring-SQLMapper:
[spring-JPA vs. spring-MyBatis 비교]
https://gmlwjd9405.github.io/2018/12/25/difference-jdbc-jpa-mybatis.html
Spring Legacy Project에서는 더이상 (Web Dynamic project에서와 같이)요청에 대한 처리 작업을 수행하는 Action 클래스를 따로 생성하지 않는다. 대신 각 module별로 요청을 처리의 흐름을 제어할 Controller 클래스를 생성한다. (e.g., Member DB관련 요청을 처리 하는 MemberController, Reply DB관련 요청을 처리 하는 ReplyController, 등)
각 Controller 클래스에 요청 url과 mapping된 method를 구현한다. Client로부터 url 요청이 들어오면 Get/Post/RequestMapping annotation과 함께 해당 url이 묶어진 method를 찾아서 실행한다.
MyBatis SQL Mapper로 persistence layer를 형성한 경우에는 @Autowired annotation이 적용된 mapper interface를 변수로 사용해서 실제 DB access 작업을 할때에 mapper interface에 선언된 method를 mybatis mapping xml파일에 설정된 SQL문을 통해 수행한다.
MyBatis대신 JPA+Hibernate ORM을 사용하는 경우에는 해당 module의 DAO 클래스를 JPA Repository로 생성한 후, Controller 클래스에서 @Autowired annotation으로 DAO 클래스를 변수로 사용해서 각 Controller method에 해당하는 logic 처리를 수행한다.
Controller method의 매개변수와 반환형태에 따라서 사용해야하는 type이름 또는 annotation을 알고있어야한다. (자세한 내용은 이전 포스트 https://velog.io/@miscaminos/Spring-Annotation-ViewResolver 참고)
Get방식 vs. Post방식
Client의 요청을 받고 응답을 주는, client-server communication은 HTTP(Hypertext Transfer Protocol) 프로토콜(일종의 룰?)을 기반으로 진행된다. 각종 언어에 syntax와 같이 이 언어 또는 방식을 사용 하는 사람들이 함께 결정한 규정들이 있듯이.
요청을 처리하는 HTTP Methods 종류로 Get, Post, Put, Head, Delete, Patch, Options가 있다. 이중 가장! 흔하게 사용되는것이 Get과 Post이다.
Controller에서 요청을 처리할 각각의 method를 @GetMapping 또는 @PostMapping으로 annotation을 표기한다. (get/post 방식 구분하지 않고 mapping하려면 @RequestMapping을 사용)
컨트롤러가 처리할 요청이 Get방식이라는 것은 - 특정 specified resource로 부터 data를 요청하는 방식이며, URL안에 query 문자열 (name/value pairs)이 담아져서온다. URL에 그대로 담아오기때문에 보안에 취약하다. 민감한 데이터는 get방식을 피해야한다.
컨트롤러가 처리할 요청이 Post방식이라는 것은 - server가 resource를 생성하거나 update하도록 데이터를 전송하는것이며, HTTP request의 request body에 데이터를 담아서 server로 전송하는 방식이다. 그래서 로그인 처리 등, 민감한 정보가 오갈때에는 post방식을 사용해야한다.
Controller의 method는 정보를 담고있는 객체를 반환하기도하고 (ModelAndView, Model, Map, String, View등등) @ResponseBody annotation을 사용해서 바로 데이터 자체를 반환하기도 한다. 이 경우 return하는 객체를 Http응답으로 전송하는 것인데, HttpMessageConverter를 이용해서 객체를 Http응답 stream으로 변환한다. 즉, ResponseBody annotation이 있으면 실행 결과가 view를 거치지않고 HTTP Reponse Body에 직접 입력된다. 실행 결과가 응답에 작성된 상태에서 MappingJacksonHttpMessageConverter를 통해 JSON형태(데이터 자체)의 결과가 표현된다.
위에서 언급한 데이터 자체를 반환하는 방식은 ajax 통신 방식에서 JSON 형태의 데이터를 전송할때에 많이 사용된다. 그외에도 XML, HTML등 다양한 형태를 가지고 client에서 전달할 수 있다. 전달하기전에 데이터를 직렬화해서 그 시점에 데이터의 상태에 대한 표현을 해주면 된다.
RestController에서는 HttpMethod POST, GET, PUT, DELETE을 사용할 수 있다. (순서대로 C,R,U,D 기능을 구현하는 method들이다) RestController에선 요청처리 method들의 반환 타입이 ResponseEntity이다. ResponseEntity는 HttpEntity를 상속받은 클래스로 Http응답에 대한 상태값을 표현할 수 있다. RestController의 method들이 보내는 응답 header는 client 쪽에서 meta 정보로도 활용될 수 있다. ResponseEntity는 응답 몸체이기때문에, collection형태의 데이터로 담을 수 있다. DB table전체 또는 특정 조건에 해당하는 리스트를 조회하는 요청을 처리할때에도 유용하게 사용할 수 있다.
RestController에서 처리한 데이터를 .jsp(view page)에서 출력하기위해서는 ajax방식을 javascript로 구현해야한다. 아직 javascript가 익숙하지않아서 이 부분이 조금 어려운 부분이였다. 코드를 한땀한땀 설명해둔 노트로 다시 복습하고 비슷한 methods를 구현해봐야겠다.
Filter은 이름 그대로 application을 통하는 요청과 응답을 필터링한다. spring context 외부에 존재하기때문에 dispatcher servlet이전 단계에서 spring 외부의 자원에대한 처리를 하거나, 필요한 사항을 체크하거나 또는 dispatcher servlet의 응답처리가 모두 끝난 후, 응답 내용에 대해 변경처리를 할 수 있다. spring framework 실습 project들에서 가장 많이 사용된 filter는 web.xml에 설정되어 한글을 제대로 출력하도록 하는 encoding 처리기능이다.
spring MVC내에서의 filter 역할을 interceptor가 수행한다. Dispatcher servlet이 controller를 호출하기 전/ 또는 후로 이 둘 사이에 끼어들어서 controller에 대한 요청과 응답에 대한 정제 작업을 수행한다. "spring_webtest" project에서는 application의 전체 member list 조회를 하기전에, login한 user가 admin인지를 먼저 체크하고 admin인 경우에만 memebr list 조회가 가능하도록 interceptor를 적용했다.
AOP(Aspect Oriented Programming)은 application내 공통되는 업무관련 코드를 따로 관리하는 방식이다. 중복되는 코드를 방지하고, 서버 부하도 줄일 수 있으며, application의 확장을 용이하게 해준다.
Project type: Spring Starter Project (Spring Boot Project)
Project name: notice, resort
build tool: gradle 6.9
spring boot: 2.4.4
spring boot도 동일하게 spring framework를 통해 application을 개발하는 방법이지만, spring boot를 사용하면 configuration을 최소화할 수 있다. spring starter project를 새로 생성하면서 개발자가 사용하고자 하는 tool를 지정하면, spring boot가 적절하거나/일반적이라고 생각하는 default configuration을 자동으로 설정하고 third-party libraries 또한 자동으로 제공하기 때문이다. default로 초기 설정을 완료한 후, 개발자가 원하는 대로 설정내용을 override할수 있어서 spring boot는 편리함을 제공한다.
실습에서는 gradle을 통해 Spring Boot Project("notice","resort")를 build했다. 기본적으로는 maven을 사용하지만, gradle을 사용하면 maven에서 필요로하는 xml파일을 훨씬 간소화 할 수있다. gradle로 project를 build하기위해 build.gradle 파일에 project에 필요한 library와 build 명세 또는 배포 설정 내용을 명시한다.
spring boot project의 구성은 이전의 실습에서 spring framework를 사용했을때와 비슷하다. 그러나 기존 spring framework를 사용했을때에 web.xml, servlet-context.xml, root-context.xml에 각각 명시했던 내용들을 훨씬 더 간결한 방식으로 설정할 수 있다. application.properties설정 파일에 application과 spring의 설정내용을 명시하고 src/main/java의 ServletInitializer.java에는 Servlet, Filter, ServletContextInitializer bean들을 서버로 bind하기위한 설정내용을 명시한다. src/main/resources/static 폴더에는 application에 필요한 정적 resources를 모두 보관한다. 그리고 src/main/java의 ProjectApplication.java의 main method로 spring application 구동을 시작한다.
4~5단계에서 spring framework를 활용하면서 MVC pattern의 application을 구현 할때에 유용한 도구/ concept들에 대해 배웠다:
EL:
view로 넘어온 HttpServletRequest 또는 HttpSession 객체의 attribute/parameter에 담긴 데이터를 출력하기위해 사용되는 표현방식들이 있다. jsp 내부 객체를 표현하기위해 사용되는 el 객체종류가 있고 이것들은 scriptlet 표현 방식대비 더 심플하다. el 표현 방식으로 java 클래스 객체에 접근할 수도 있다. (jsp페이지내에서 해당 java 클래스의 인스턴스를 생성하거나 또는 .tld 파일을 생성해서 el방식으로 사용할 method를 설정한다)
JSTL:
JSTL은 scriptlet/el보다 더 간결한 표현방식이다. JSTL API는 많이 사용되는 사용자 정의 TAG를 모아서 규약한것으로 5가지 TAG가 있다.(core, XML, 국제화, sql, fn)
JUnit Test:
Java unit test framework를 사용해서 application내의 부분적 기능을 딱 그 기능만 test해볼 수 있다. JUnit4를 사용해서 test하려는 기능의 전단계(before)와 후단계(after)를 지정해서 flow 범위를 형성할 수 있다. DAO를 통한 DB access에 문제가 없는지, 특정 모듈내 logic 처리 또는 service 처리에 문제가 없는지 test를 통해 검증하고 소요 시간을 확인할 수 있다.
그리고 마지막으로 셀프 실습으로 spring 전에 배웠던 vue.js를 spring boot와 연동시켜서 작은 SPA를 하나 구현하고 있다. 온라인에는 resource들이 굉장히 많고 많이들 해본 실습과정인듯하다. 그치만 직접해보니 쉽지만은 않은 작업이다. 틈틈히 계속 작업해서 잘 작동하는 application을 만들어봐야겠다. +.+
그리고 느낀점 하나는 계속 에러들을 고쳐갈수록 연습을 많이 해보는것이 정말 중요하다는 것이다. (너무 당연한건가...) 6월 말까지 파이널 프로젝트에 몰입하고 7월부터는 클론 코딩으로 application을 구현하는 연습을 꾸준히 해봐야겠다.