📌 프레임워크란 무엇일까?
"프레임워크란 소프트웨어의 구체적인 부분에 해당하는 설계와 구현을 재사용 가능하게끔 일련의 협업화된 형태로 클래스들을 제공하는 것이다." -Ralph Johnson-
우리의 일상 속에서 Frame이라는 단어의 의미는 "틀", "구조"를 의미한다. 프로그래밍 세계에서의 Frame 역시 그 의미와 별반 다르지 않다. 프로그래밍에서의 프레임워크는 기본적으로 프로그래밍을 하기 위한 어떤 틀이나 구조를 제공한다고 말할 수 있다.
개발하고자 하는 애플리케이션을 그 밑바닥부터 일일이 전부 개발하는 것이 아니라 서로 다른 애플리케이션 간의 통신이나, 데이터를 데이터 저장소에 저장하는 등의 다양한 기능들 역시 프레임워크가 라이브러리 형태로 제공함으로써 개발자가 애플리케이션의 핵심 로직을 개발하는 것에 집중할 수 있도록 해준다.
프레임워크의 예시로 Java의 Collection Framework를 예로 들 수 있다.
Collection 프레임워크는 Java에서 자주 사용하는 Map이나 Set, List 등의 Collection들은 데이터를 저장하기 위해 널리 알려져 있는 자료구조를 바탕으로 비슷한 유형의 데이터들을 가공 및 처리하기 쉽도록 표준화된 방법을 제공하는 클래스의 집합이다.
그런데 왜 Collection에 하필 "틀"이나 "뼈대"같은 의미의 프레임워크라는 용어를 붙였을까?
이는 Collection의 구성을 보면 알 수 있다. Java 클래스의 유형 중에서 기본적으로 뼈대로만 구성되어 있는 것이 바로 추상 메서드만 정의되어 있는 인터페이스(Interface)이다. 그리고 Collection은 바로 Map, Set, List 같은 인터페이스와 그 인터페이스들을 구현한 구현체들의 집합이다.
결론적으로 프로그래밍 상에서의 프레임워크는 기본적으로 프로그래밍을 하기 위한 어떤 틀이나 구조를 제공한다는 것을 알 수 있다.
그런데 이와 비슷한 개념인 라이브러리(Library)가 있다. 프레임워크와 라이브러리는 어떤 차이가 있을까?
📌 프레임워크와 라이브러리의 차이
프레임워크(Framework)와 라이브러리(Library)의 가장 큰 차이점은 애플리케이션에 대한 제어권에 있다.
애플리케이션의 구현을 위해 필요한 여러가지 기능들을 제공한다는 의미에서 프레임워크와 라이브러리는 유사하다고 할 수 있지만 애플리케이션에 대한 제어권 관점에서 프레임워크와 라이브러리는 분명한 차이점이 있다.
@SpringBootApplication
@RestController
@RequestMapping(path = "/v1/message")
public class SampleApplication {
@GetMapping
public String getMessage() { // (2)
String message = "hello world";
return StringUtils.upperCase(message); // (1)
}
public static void main(String[] args) {
SpringApplication.run(SampleApplication.class, args);
}
}
<스프링 프레임워크 예시 코드>
먼저 코드에서 명확하게 라이브러리를 사용하는 부분은 (1)의 'StringUtils.upperCase(message)'이다.
StringUtils 클래스의 upperCase 메서드를 사용하여 파라미터로 전달하는 문자열(message)를 대문자로 변환하고 있다. 이처럼 개발자가 짜 놓은 코드 내에서 필요한 기능이 있으면 해당 라이브러리를 호출해서 사용하는 것이 바로 라이브러리이다.
즉, 애플리케이션의 흐름의 주도권이 개발자에게 있는 것이다.
다음으로 코드에서 프레임워크를 사용하고 있는 부분은 @SpringBootApplication .... @GetMapping과 같은 애노테이션이다.
해당 애노테이션들을 메서드나 클래스에 작성해두면, 스프링 프레임워크에서 개발자가 작성한 애노테이션을 사용해서 애플리케이션의 흐름을 만들어낸다.
즉, 애플리케이션의 흐름의 주도권이 개발자가 아닌 프레임워크에 있는 것이다.
이번에는 자바의 대표적인 프레임워크인 스프링 프레임 워크에 대해 알아보자.
📌 스프링 프레임워크
자바 엔터프라이즈 개발을 편하게 해주는 오픈 소스 경량급 애플리케이션 프레임워크이다.
설명이 다소 난해한데 천천히 살펴보면 쉽게 이해할 수 있다.
먼저 라이브러리나 프레임워크는 특정 업무 분야나 한 가지 기술에 특화된 목표를 가지고 만들어진다. 예를 들면 Collection 프레임워크는 데이터들을 효과적으로 가공하고 저장하기 위해 사용하는 효율적인 자료구조의 모음이다. 이처럼 프레임워크는 특정한 목표를 수행하는데 존재의 목적을 두고 있다.
스프링 프레임워크는 자바 엔터프라이즈 개발에 특화된 프레임워크이다.
※ 자바 엔터프라이즈(Java Enterprise) : 자바 엔터프라이즈란 자바를 사용하여 기업 환경의 애플리케이션을 만드는데 필요한 스펙들을 모아둔 집합
즉 스프링 프레임워크는 기업용 애플리케이션을 만드는데 특화된 프레임워크이다. 대부분의 기업들이 기업용 엔터프라이즈 시스템 애플리케이션 개발에 있어 프레임워크를 선택할 때, 개발의 생산성을 높이고 어떻게 하면 유지 보수를 조금 더 용이하게 할 것인가에 많은 고민을 한다.
엔터프라이즈 시스템은 대량의 사용자 요청을 처리해야 하기 때문에 서버의 자원 효율성, 보안성, 시스템의 안전성이나 확장성 등을 충분히 고려해서 시스템을 구축하는 것이 일반적이다. 스프링 프레임워크는 이러한 엔터프라이즈 시스템의 니즈(Needs)에 의해 탄생되었다.
📌 스프링 프레임워크는 뭐가 좋지?
스프링 프레임워크를 사용하는 이유를 알기 위해서는 스프링 프레임워크 이전에 웹 개발에서 어떤 기술을 사용해왔는지를 알 필요가 있다.
다양한 기술들이 있겠지만 주로 언급되는 것이 바로 JSP(Java Server Pages)와 서블릿(Servlet)이 있다.
✔️ 그 시절 기술 1 - JSP
JSP는 Java Server Pages의 약자로 HTML 코드에 JAVA 코드를 넣어 동적 웹페이지를 생성하는 웹 애플리케이션 도구이다.
JSP는 정적으로 돌아가는 웹사이트를 동적으로 처리하기 위해 탄생했다.
클라이언트의 요청으로부터 동적 컨텐츠를 생성하여 응답해주는 역할을 한다. 이로써 클라이언트의 요청에 대해 좀 더 발빠르게 응답할 수 있는 페이지를 만들 수 있었다.
JSP 개발 방식은 사용자에게 보여지는 View 페이지쪽 코드와 사용자의 요청을 처리하는 서버쪽 코드가 섞여있는 형태의 개발 방식이다. 쉽게 말하자면 웹 브라우저를 통해서 사용자에게 보여지는 클라이언트 측 html/Javascript 코드와 사용자의 요청을 처리하는 서버 측 Java 코드가 뒤섞여 있는 방식이다.
아래는 JSP로 작성된 TO-DO List 코드 로직이다. (펼쳐서 보세요 ㅎ)
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>
<!-- (1) 시작 -->
<%
request.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
System.out.println("Hello Servlet doPost!");
String todoName = request.getParameter("todoName");
String todoDate = request.getParameter("todoDate");
ToDo.todoList.add(new ToDo(todoName, todoDate));
RequestDispatcher dispatcher = request.getRequestDispatcher("/todo_model1.jsp");
request.setAttribute("todoList", ToDo.todoList);
dispatcher.forward(request, response);
%>
<!-- (1) 끝 -->
<html>
<head>
<meta http-equiv="Content-Language" content="ko"/>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<title>TODO 등록</title>
<style>
#todoList {
border: 1px solid #8F8F8F;
width: 500px;
border-collapse: collapse;
}
th, td {
padding: 5px;
border: 1px solid #8F8F8F;
}
</style>
<script>
function registerTodo(){
var todoName = document.getElementById("todoName").value;
var todoDate = document.getElementById("todoDate").value;
if(!todoName){
alert("할일을 입력해주세요..");
return false;
}
if(!todoDate){
alert("날짜를 입력해주세요.");
return false;
}
var form = document.getElementById("todoForm");
form.submit();
}
</script>
</head>
<body>
<h3>TO DO 등록</h3>
<div>
<form id="todoForm" method="POST" action="/todo_model1.jsp">
<input type="text" name="todoName" id="todoName" value=""/>
<input type="date" name="todoDate" id="todoDate" value=""/>
<input type="button" id="btnReg" value="등록" onclick="registerTodo()"/>
</form>
</div>
<div>
<h4>TO DO List</h4>
<table id="todoList">
<thead>
<tr>
<td align="center">todo name</td><td align="center">todo date</td>
</tr>
</thead>
<!-- (2) 시작 --->
<tbody>
<c:choose>
<c:when test="${fn:length(todoList) == 0}">
<tr>
<td align="center" colspan="2">할 일이 없습니다.</td>
</tr>
</c:when>
<c:otherwise>
<c:forEach items="${todoList}" var="todo">
<tr>
<td>${todo.todoName}</td><td align="center">${todo.todoDate}</td>
</tr>
</c:forEach>
</c:otherwise>
</c:choose>
</tbody>
<!-- (2) 끝 -->
</table>
</div>
</body>
</html>
먼저 코드 상단의 (1) 영역은 클라이언트의 요청을 처리하는 서버쪽 코드이다.
다음으로는 코드 아래 쪽의 (2) 영역은 서버로부터 전달 받은 응답을 화면에 표시하기 위한 JSP에서 지원하는 jstl 태그 영역이다.
마지막으로 (1), (2)의 영역이외의 나머지 구현 코드들은 사용자에게 보여지는 화면을 구성하는 html 태그 및 css 스타일 코드와 할일 등록 시 유효성 검사를 실시하는 Javascript 코드 즉, 클라이언트 측에서 사용하는 기술들에 해당하는 코드이다.
❗️JSP의 단점
기술의 구현 방법을 잘 모르긴해도 코드 자체가 너무 길어서 가독성도 떨어지고 복잡해보인다.
실제로 이 방식은 애플리케이션의 유지 보수 측면에서 최악의 방식이다.
웹 디자이너와 html 퍼블리셔 그리고 자바스크립트 개발자 및 자바 개발자 간에 효율적으로 협업하는 것이 거의 불가능한 수준이다. 때문에 JSP 방식으로 개발할 때에는 프론트엔드/백엔드 영역을 구분하지 않고 양쪽 모두를 개발하는 개발자들이 대부분이다.
✔️ 그 시절 기술 2 - 서블릿
서블릿(Servlet)은 Server Application Let의 합성어로 서버 애플리케이션의 조각 모음을 의미한다.
서블릿은 클라이언트의 다양한 요청을 동적으로 처리하기 위해 서버 애플리케이션을 다양한 조각 모음 클래스로 관리한다. 일반적인 자바 독립 실핼프로그램과 달리 main 메서드가 없고, 서블릿 컨테이너에 등록된 후 서블릿 컨테이너에 의해 생성, 호출, 소멸이 이루어진다.
※ 서블릿 컨테이너(Servlet Container) : 네트워크 통신, 서블릿의 생명주기 관리, 스레드 기반의 병렬처리 등을 대행한다.
예전에는 html 코드 조각 또한 서블릿 클래스 안에서 보관되기도 했는데 JSP가 등장하면서 View 부분은 JSP가 Model과 Controller 부분을 서블릿이 관리하는 형태가 되었다.
다음은 JSP의 서버 측 Java 코드인 서블릿 클래스 코드의 예제이다.
@WebServlet(name = "TodoServlet")
public class TodoServlet extends HttpServlet {
// (1) Database를 대신한다.
private List<ToDo> todoList;
@Override
public void init() throws ServletException {
super.init();
this.todoList = new ArrayList<>();
}
// (2)
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
String todoName = request.getParameter("todoName");
String todoDate = request.getParameter("todoDate");
todoList.add(new ToDo(todoName, todoDate));
RequestDispatcher dispatcher =
request.getRequestDispatcher("/todo.jsp");
request.setAttribute("todoList", todoList);
dispatcher.forward(request, response);
}
// (3)
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
System.out.println("Hello Servlet doGet!");
RequestDispatcher dispatcher =
request.getRequestDispatcher("/todo.jsp");
dispatcher.forward(request, response);
}
}
(1)과 같이 별도의 데이터 저장소를 사용하지 않고 Todo 객체를 담을 수 있는 List를 추가해줬다.
(2)의 경우, 클라이언트 측에서 등록할 할일 데이터를 전송하면 이 요청을 받아서 데이터 저장소에 등록해주는 역할을 한다.
클라이언트 측의 JSP 코드에서 서버 측 Java 코드는 서블릿 클래스로 분리했기 때문에 클라이언트와 서버 간에 어느 정도 역할이 분리되었다고 볼 수 있다.
그러나 뭔가 많은 일을 할 것 같지만 데이터를 가공하는 비즈니스 로직이 있는 것도 아니고 가공된 데이터를 데이터베이스에 저장하는 등의 데이터 엑세스 로직 역시 존재하지 않음에도 코드가 너무 길어보인다.
그렇다면 Spring에서는 이러한 서블릿 클래스의 코드들이 어떤식으로 개선되는지 살펴보자.
✔️ Spring MVC를 이용한 애플리케이션
다음은 Spring MVC 방식으로 구현한 Controller 부의 예제코드이다.
@Controller
public class ToDoController {
@RequestMapping(value = "/todo", method = RequestMethod.POST)
@ResponseBody
public List<ToDo> todo(@RequestParam("todoName")String todoName,
@RequestParam("todoDate")String todoDate) {
ToDo.todoList.add(new ToDo(todoName, todoDate));
return ToDo.todoList;
}
@RequestMapping(value = "/todo", method = RequestMethod.GET)
@ResponseBody
public List<ToDo> todoList() {
return ToDo.todoList;
}
}
일단 한눈에 봐도 코드가 훨씬 간결해졌다.
서블릿 방식의 코드에서는 클라이언트의 요청에 담긴 데이터를 꺼내오는 작업을 개발자가 직접 코드로 작성 해야되고, 캐릭터셋도 지정 해주어야 했지만 Spring MVC 방식의 코드에서는 눈에 보이지 않지만 그런 작업들을 Spring에서 알아서 처리해준다.
그런데 Spring의 이런 편리함과 간결함에도 불구하고 Spring 기반의 애플리케이션의 기본 구조를 잡는 설정 작업이 여전히 불편하다는 단점이 존재했다.
❗️Spring MVC 설정 파일 예제 코드(web.xml)
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring-config/applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring-config/dispatcher-servlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<filter>
<filter-name>CORSFilter</filter-name>
<filter-class>com.codestates.filter.CORSFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>CORSFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
해당 설정 파일은 위의 MVC 형식의 컨트롤러가 정상적으로 구동되기 위해 필요한 설정들이다. 또한 web.xml이라는 설정 파일 외에 다른 설정 파일들도 필요하다.
실제로 Spring MVC의 이런 복잡한 설정때문에 Spring 학습을 포기하는 입문자들도 많이 있었고, Spring의 핵심 원리를 학습하고, 이해하기도 모자란 시간에 Spring의 설정 문제로 애플리케이션을 띄워보지도 못한채 며칠을 끙끙 앓는 그런 상황들이 많았다.
그렇지만 다행히도 이러한 문제가 대부분 개선된 Spring Boot가 탄생하게 되었다.
✔️ Spring Boot를 이용한 애플리케이션
다음은 Spring Boot 기반의 예제 코드이다.
@RestController
public class TodoController {
private TodoRepository todoRepository;
@Autowired
TodoController(TodoRepository todoRepository) {
this.todoRepository = todoRepository;
}
@PostMapping(value = "/todo/register")
@ResponseBody
public Todo register(Todo todo){ // (1)
todoRepository.save(todo); // (2)
return todo;
}
@GetMapping(value = "/todo/list")
@ResponseBody
public List<Todo> getTodoList(){
return todoRepository.findAll(); // (3)
}
}
Spring MVC 기반의 코드를 조금 더 개선한 모습이 보인다.
먼저, 클라이언트 측에서 전달한 요청 데이터를 (1)과 같이 Todo라는 클래스에 담아서 한번에 전달 받을 수 있도록 변경되었다. 요청 데이터가 Todo 객체로 변경되는 것은 Spring이 알아서 한다.
그리고 이 전 방식까지는 클라이언트가 전달한 요청 데이터를 테스트 목적으로 단순히 List에 담았는데 이번에는 (2), (3)과 같이 데이터베이스에 저장해서 데이터 액세스 처리까지 하도록 변경되었다. 이렇게 데이터를 실제로 저장하는 기능을 추가했는데도 불구하고 코드의 길이는 크게 바뀐 것이 없고, 오히려 더 깔끔해졌다.
❗️Spring MVC 방식에서의 복잡한 설정 파일들은 어떻게 되었을까?(application.properties)
spring.h2.console.enabled=true
spring.h2.console.path=/console
spring.jpa.generate-ddl=true
spring.jpa.show-sql=true
Spring Boot을 사용하지 않는 Spring MVC 방식에서 겪어야 했던 설정의 복잡함을 Spring Boot에서는 찾아볼 수 없다. 심지어 데이터베이스를 연동하지 않았다면 위에 입력한 4줄의 코드도 필요하지 않다.
그 많던 설정 파일의 내용들은 어디에 갔을까?
이제는 Spring의 복잡한 설정 작업마저도 Spring이 대신 처리를 해주기 때문에 개발자는 애플리케이션의 핵심 비즈니스 로직에만 집중할 수 있게 되었다. (밥 줘)
✔️ 알아두어야할 점
앞서 언급한 JSP와 서블릿은 현재 사용하지 않는 기술들이 아니다.
JSP 같은 경우 여전히 많은 웹페이지가 JSP로 구현되어 있는 경우가 많고 공공기관에서도 많이 사용하고 있다. (신차 나왔다고 구형차들을 다 폐차하진 않는다) 요즘은 JSP를 걷어내고 javascript 기반의 View를 구현하는 추세이다.
서블릿 같은 경우 서블릿 자체를 사용하는 기술은 현재 거의 사용하고 있지 않지만 Spring MVC와 같은 Java 기반의 웹 애플리케이션 내부에서는 여전히 사용되고 있다. Spring MVC에서는 아파치 톰캣(Apache Tomcat)이라는 서블릿 컨테이너가 사용되고 있다. 서블릿 컨테이너를 통해 서블릿의 생명 주기를 관리하며, 쓰레드 폴(Thread Pool)을 생성해서 서블릿과 쓰레드를 매핑시켜주기도 한다.
댓글