공부/전자컴퓨터공학

프레임워크(Framework)란? - Spring Framework

AhJustC 2024. 7. 23. 15:29
반응형
Framework란?

프레임워크란 소프트웨어의 구체적인 부분에 해당하는 설계와 구현을 재사용이 가능하게끔 일련의 협업화된 형태로 클래스 등을 제공하는 것이라 정의된다.

Frame이란 단어는 뼈대, 틀, 구조 라는 뜻이 있다. Frame은 어떤 대상의 큰 틀이나 외형적인 구조를 의미한다고 보면 될 것 같다. 이러한 단어의 뜻을 이해하면 소프트웨어 관점에서 Framework는 어떤 애플리케이션을 만들기 위한 틀 혹은 구조를 제공한다고 보면 된다.

 

HTML의 frame 태그의 예
<frameset cols="33%,*,33%">
    <frame name="left" src="/left_menu"/>
    <frame name="center" src="/context"/>
    <frame name="right" src="/right_menu"/>
</frameset>

웹의 초창기 시절 사용하던 frame 태그의 사용 예이다. 보이는 것처럼 frameset과 frame 태그를 이용해 HTML 문서의 틀을 구성하고 있는 것을 볼 수 있다. 

 

Framework의 장점
  • 효율적으로 코드를 작성할 수 있다.
  • 정해진 규약이 있어 애플리케이션을 효율적으로 관리할 수 있다.
Framework의 단점
  • 내가 사용하고자 하는 Framework에 대한 학습이 필요하다.
  • 자유롭고 유연한 개발이 어렵다.
Framework와 Library의 차이

사람들이 Framework와 Library를 같은 의미라고 오해한다. Library는 애플리케이션을 개발하는데 사용되는 일련의 데이터 및 프로그래밍 코드이다. 아래의 자동차 예시로 자세히 알아보자.

차체를 구성하는 Frame, 그리고 바퀴, 엔진, 핸들, 시트 등 다양한 부품들이 모여 하나의 자동차를 이루고 있다. 위의 구성요소에서 자동차의 Frame이 Framework라고 할 수 있겠다. 그리고 Library는 자동차의 다양한 기능을 제공하는 나머지 부품들을 의미한다. 실제 자동차의 경우 고장으로 인해 Frame을 교체하는건 불가능에 가깝지만 나머지 부품들은 쉽게 교체가 가능하다. 소프트웨어의 관점에서도 마찬가지로 한번 정해진 Framework를 교체하는 일은 어렵지만 Library는 쉽게 교체가 가능하며 필요한 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);
    }
}
반응형

위의 코드에서 Library를 사용하는 부분은 (1) StringUtils.upperCase(message) 이다.

StringUtils 클래스는 Apache Commons Lang3 라이브러리의 유틸리티 클래스중 하나이며 애플리케이션이 동작하는 중에 StringUtils 클래스의 upperCase() 메서드의 마라미터로 전달하는 문자열을 대문자로 변환하고 있다. 이렇듯 개발자가 짜 놓은 코드 내에서 필요한 기능이 있으면 해당 라이브러리를 호출해서 사용하는 것이 Library이다.

코드에서 사용한 애너테이션이나 main() 메서드 내의 SpringApplication.run() 메서드는 Spring Framework에서 지원하는 기능들인데 이러한 기능들은 라이브러리와는 다르게 코드 상에는 보이지 않는 상당히 많은 일들을 한다.

(2)의 getMessage() 메서드 내부의 코드처럼 개발자가 메서드 내에 코드를 작성해두면 Spring Framework에서 개발자가 작성한 코드를 사용해서 애플리케이션의 흐름을 만들어낸다. 즉 애플리캐이션의 흐름의 주도권이 개발자가 아닌 Framework에 있는 것이다. 

Spring Framework 이란?

웹 애플리케이션 개발은 위한 Framework는 Django, Express, Flask, Lalavel 등 다양한 Framework를 통해 개발이 가능하다. 각각의 Framework마다 사용하는 언어도 다르고 개발 방법도 조금씩 다르지만 Spring Framework만의 장점을 정리하자면 아래와 같다.

  • POJO(Plan Old Java Object) 기반의 구성
  • DI(Dependency Injection) 지원
  • AOP(Aspect Orientend Programming, 관점지향 프로그래밍) 지원
  • Java 언어를 사용함으로써 얻는 장점

1~3번의 경우 이후 내용에서 학습할 예정이다. 4번의 경우 Java 언어의 특징 중 어떤 점이 장점으로 다가올 수 있을까?

정적 타입 언어로서 변수의 타입, 메서드의 입력과 출력이 어떤 타입을 가져야 하느닞를 강제한다. 이는 곧 여러사람이 함께 작업할 때 다른 사람의 코드 혹은 이전에 내가 작성한 코드를 수정, 보완이 용이하고, 웹 서버를 구축하는데 있어서 런타임에 발생하는 오류를 사전에 방지할 수 있다.

그렇다면 Java 언어를 사용하는 Framework는 Spring만 있을까? Spring 이외에도 Apache Struts2나 Apache Wicket, JSF(Java Server Faces), Grails 같은 Java 또는 JVM 기반의 Web Framework들이 존재하며, 현재도 꾸준히 그 기능들이 업데이트 되고 있다.

그런데 사람들은 왜 Spring Framework에 더 열광을 하는 것일까? 

대부분의 기업들이 기업용 엔터프라이즈 시스템용 애플리케이션 개발에 있어 Framework을 선택할 때 개발 생산성을 높이고 어떻게 하면 애플리케이션의 유지보수를 좀 더 용이하게 할 것인지 많은 초점을 맞추는 것은 사실이다. Spring Framework는 개발 생산성을 향상시키고 애플리케이션의 유지보수를 용이하게 하는 목적 그 이상을 달성할 수 있다.

 

Spring Framework를 배워야 하는 이유

아래의 코드는 JSP 방식의 예제 코드이다.

<%@ 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 방식보다는 조금 나은 개발 방식인 서블릿(Servlet)을 이용한 개발방식이다. Servlet 방식은 클라이언트 웹 요청 처리에 특화된 Java 클래스의 일종이라고 보면 되는데, Spring을 사용한 웹 요청을 처리할 때에도 내부적으로는 Servlet을 사용한다.

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

위의 코드는 첫번째 jsp 코드에서 Java 코드만 별도의 서블릿 클래스로 분리된 모습니다.여전히도 코드 자체가 너무나 길다. 그렇다면 Spring에서는 이러한 서블릿 클래스의 코드들이 어떤식으로 개선되는지 보도록 하자.

아래는 Spring MVC방식 예제코드이다.

@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 MVC 방식의 코드는 눈에 보이지 않지만 Spring에서 알아서 처리해준다. 하지만 Spring 기반의 애필리케이션의 기본 구조를 잡는 설정 작업이 여전히 불편하다는 단점이 존재했다.

<?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>

위는 Spring 애플리케이션을 정상적으로 구동하기 위한 설정 파일들의 일부이다. web.xml 이라는 설정 파일 이외의 다른 설정 파일들도 필요하다. 이런 복잡한 설정때문에 개선된 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 기반의 코드를 Spring Boot 기반에서 조금 더 개선한 모습니다. 클라이언트 측에서 전달한 요청 데이터를 (1)과 같이 Todo 라는 클래스에 담아서 한 번에 전달받을 수 있도록 했다. 요청 데이터가 Todo 객체로 변경되는것은 Spring 이 알아서 해준다.그리고 이 전 방식까지는 클라이언트가 전달한 요청 데이터를 테스트 목적으로 단순히 List에 담았는데 이번에는 (2), (3) 과 같이 데이터베이스에 저장해서 데이터 액세스 처리까지 하도록 했다.

이렇게 데이터를 실제로 저장하는 기능을 추가했는데도 불구하고 코드의 길이는 크게 바뀐 것이 없고 오히려 더 깔끔해졌다.

spring.h2.console.enabled=true
spring.h2.console.path=/console
spring.jpa.generate-ddl=true
spring.jpa.show-sql=true

 위는 Spring Boot 구성파일이다. Spring의 복잡한 설정 작업도 Spring이 대신 처리를 해주기 때문에 개발자는 애플리케이션의 핵심 비즈니스 로직에만 집중할 수 있게 된다.

반응형