Framework란?
소프트웨어의 구체적인 부분에 해당하는 설계, 구현을 재사용에 용이하게끔 일련의 협업화된 형태로 클래스를 제공하는 것
우리는 Java에서 Framework의 의미를 찾아볼 수 있다.
바로 Collections Framework인데, 이건 Map, Set, List 등의 Collection 들은 주로 데이터를 저장하기 위해 널리 알려져 있는 자료구조를 바탕으로 비슷한 유형의 데이터를 가공 및 처리하기 쉽도록 표준화된 방법을 제공하는 클래스의 집합이다.
"왜 Java의 Collection에 Framework라는 용어를 붙였을까?"
Java 클래스 유형 중, 기본적인 뼈대로만 구성되어 있는 것은 추상메서드만 정의되어 있는 인터페이스(Interface)이다.
Java에서의 Collection은 바로 Map, Set, List 같은 인터페이스와 그 인터페이스들을 구현한 구현체들의 집합인 것이다.
프로그래밍 상에서의 Framework는 기본적으로 프로그래밍을 하기 위한 어떠한 틀이나 구조를 제공한다는 것이다.
Framework 사용의 장점
- 효율적인 코드 작성이 가능하다.
아무것도 없는 상태에서 코드를 작성하는 것과 기본 틀이 만들어진 상황에서 코드를 작성하는 것은 큰 차이가 있을 것이다.
개발하고자 하는 애플리케이션을 서로 다른 애플리케이션 간의 통신이나, 데이터를 데이터 저장소에 저장하는 등의 다양한 기능을 Framework 라이브러리 형태로 제공함으로써 개발자가 애플리케이션의 핵심 로직을 개발하는 것에 집중할 수 있도록 한다.
- 정해진 규약에 애플리케이션을 효율적으로 관리할 수 있다.
Framework 규약에 맞게 코드를 작성하므로, 유지보수가 필요한 경우 더 빠르게 문제점을 파악할 수 있다. 동시에 빠르게 코드를 파악하여 수정할 수도 있다. 이는 유지보수 이외에도 비슷한 기능을 개발할 시 코드의 재상요이 용이하고, 기능이 확장이 쉬워질 수 있다.
Framework 사용의 단점
- 내가 사용하고자 하는 Framework에 대한 학습이 필요하다.
- 자유롭고 유연한 개발이 어렵다.
이미 만들어진 애플리케이션에서 Framework를 변경하거나, Framework를 사용하지 않게 변경할 경우 많은 시간과 노력이 필요하다.
Framework와 Library의 차이
Library
- 애플리케이션을 개발하는 데 사용되는 일련의 데이터 및 프로그래밍 코드
- 개발자가 짜놓은 코드 내에서 필요한 기능이 있으면 관련 라이브러리를 호출해서 사용
애플리케이션의 흐름의 주도권이 개발자에게 있다.
ex) 자동차에서 다양한 기능을 제공하는 부품들 .. (바퀴, 라이트, 와이퍼)
Framework
- 프로그래밍을 하기 위해 어떠한 틀이나 구조를 제공하는 것
애플리케이션의 흐름의 주도권이 프레임워크에 있다.
ex) 자동차의 뼈대
결론적으로 이 둘은 애플리케이션에 대한 제어권의 차이가 있다고 표현할 수 있다.
Spring Framework 배워야 하는 이유
문제가 개선되는 방향에 대하여 . .
JSP ▶ Servlet ▶ Spring MVC ▶Spring Boot
1. JSP를 이용한 애플리케이션 (Java Server Page)
초창기 Java 기반 웹 애플리케이션 개발은 JSP를 통해 만들어졌다.
JSP 개발 방식
사용자에게 보이는 View 페이지 쪽 코드와 사용자의 요청을 처리하는 서버 쪽 코드가 섞여있는 형태의 개발 방식
즉, 클라이언트 측(웹 브라우저를 통해 사용자에게 보이는) 코드와 사용자 요청을 처리하는 서버 측 코드가 섞여있음
<!-- index.jsp -->
<%@ page language="java" contentType="text/html; charset=UTF-8" %>
<!DOCTYPE html>
<html>
<head>
<title>Enter Your Name</title>
</head>
<body>
<form action="greeting.jsp" method="POST">
<label for="name">Enter your name:</label>
<input type="text" name="name" id="name">
<input type="submit" value="Submit">
</form>
<%
String name = request.getParameter("name");
if (name != null && !name.isEmpty()) {
out.println("<h1>Hello, " + name + "!</h1>");
}
%>
</body>
</html>
위의 예제 코드는 JSP 파일 내에서 Java 코드를 <% %> 태그로 감싸서 서버 측 코드를 작성하였다.
뭐가 됐던 JSP 개발 방식은 코드가 너무 길어지며 가독성이 떨어진다..
실제로 이 방식은 애플리케이션의 유지 보수 측면에서 최악이다.
따라서 이보다 더 나은 개발 방식인 서블릿(Servlet)을 이용한 개발 방식을 살펴보자.
2. 서블릿(Servlet)을 이용한 애플리케이션
Servlet
클라이언트 웹 요청 처리에 특화된 Java 클래스의 일종
JSP 방식도 내부적으로는 Servlet 방식을 사용하며,
Spring을 사용한 웹 요청을 처리할 때에도 내부적으로는 Servlet을 사용한다.
"Servlet을 사용한다."
Servlet을 위한 자바 코드가 클라이언트 측 코드에서 분리되어 별도의 Java 클래스로 관리된다는 것을 의미한다.
<!-- index.jsp -->
<!DOCTYPE html>
<html>
<head>
<title>Enter Your Name</title>
</head>
<body>
<form action="greeting" method="POST">
<label for="name">Enter your name:</label>
<input type="text" name="name" id="name">
<input type="submit" value="Submit">
</form>
</body>
</html>
<!-- greeting.jsp -->
<!DOCTYPE html>
<html>
<head>
<title>Greeting</title>
</head>
<body>
<%
String name = (String) request.getAttribute("name");
if (name != null && !name.isEmpty()) {
out.println("<h1>Hello, " + name + "!</h1>");
}
%>
</body>
</html>
// HelloServlet.java:
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class HelloServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String name = request.getParameter("name");
request.setAttribute("name", name);
request.getRequestDispatcher("greeting.jsp").forward(request, response);
}
}
클라이언트 측의 JSP 코드에서 서버 측의 Java 코드만 별도의 서블릿 클래스로 분리했다.
이렇게 어느 정도 클라이언트와 서버 간의 역할을 분리했지만 여전히.. 코드 자체가 너무 길어 보인다.
특별한 데이터 엑세스 로직이 존재하지 않는데도 불구하고 코드가 너무 길다는 단점을 보완하기 위해 Spring을 사용해보자.
3. Spring MVC 를 이용한 애플리케이션
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
@Controller
public class HomeController {
@GetMapping("/")
public String index() {
return "index";
}
@PostMapping("/greeting")
public String greeting(@RequestParam("name") String name, Model model) {
String greeting = "Hello, " + name + "!";
model.addAttribute("greeting", greeting);
return "greeting";
}
}
코드가 매우 간단해졌다!
그럼에도 불구하고 Spring 기반의 애플리케이션의 기본 구조를 잡는 설정 작업은 여전히 불편하다. .
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
id="WebApp_ID" version="3.1">
<display-name>MySpringMVCApp</display-name>
<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-mvc-config.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>
</web-app>
Spring 애플리케이션을 정상적으로 구동하기 위한 설정 파일들의 일부분인데,
해당 설정 파일 이외에도 다른 설정 파일들이 필요하다.
이러한 문제들을 해결할 수 있는 Spring Boot 를 알아보자.
4. 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)
}
}
(1) 클라이언트 측에서 전달한 요청 데이터를 Todo 클래스에 담아 한 번에 전달받을 수 있도록 하기
(2), (3) 클라이언트 측에서 전달한 요청 데이터를 데이터베이스에 저장해서 데이터 엑세스 처리까지 하도록 하기
데이터를 실제로 저장하는 기능을 추가했지만, 코드의 길이는 크게 바뀐 것이 없으며 깔끔해졌다.
spring.h2.console.enabled=true
spring.h2.console.path=/console
spring.jpa.generate-ddl=true
spring.jpa.show-sql=true
Spring MVC 방식에서의 복잡했던 설정 파일은 위와 같이 깔끔하게 처리되었다.
Spring Boot에서는 Spring의 복잡한 설정 작업마저도 Spring이 대신 처리해주기 때문에 개발자는 애플리케이션의 핵심 비즈니스 로직에만 집중할 수 있게 된다..! 굿.
POJO 프로그래밍
POJO(Plain Old Java Object)
'PO' - Java로 생성하는 순수한 객체
'JO' - 객체지향 프로그래밍
POJO 프로그래밍
POJO를 사용해서 프로그래밍 코드를 작성하는 것
POJO 프로그래밍으로 작성한 코드의 기본적인 규칙으로는 크게 두 가지가 있다.
1. Java나 Java의 스펙(사양)에 정의된 것 이외에는 다른 기술이나 규약에 얽매이지 않아야 한다.
- 특정 기술을 상속에서 코드를 작성하게 되면, 요구사항의 변경으로 애플리케이션이 다른 기술로 변경될 때 명시적으로 상속받던 클래스들은 전부 다 일일이 제거하거나 수정해야 한다.
- Java는 다중 상속을 지원하지 않으므로 'extends' 키워드를 통해 한 번 상속을 하면, 상위 클래스를 상속받아 하위 클래스를 확장하는 객체지향 설계 기법을 적용하기 어려워진다.
2. 특정 환경에 종속적이지 않아야 한다.
ex) Java로 작성한 애플리케이션 코드 내에서 Tomcat이 지원하는 API를 직접 가져다가 사용하고 있었다.
요구사항 변경(Tomcat 대신 Zetty를 사용해) => Tomcat API를 모두 걷어내고, Zetty로 수정하거나 코드를 모두 뜯어 고치기 ..
POJO 프로그래밍 필요한 이유
1. 특정 환경이나 기술에 종속적이지 않을 시에 재사용에 용이, 확장 가능한 유연한 코드를 작성 가능
2. 저수준 레벨의 기술과 환경에 종속적인 코드를 애플리케이션 코드에서 제거함으로써 코드의 간결화
3. 코드의 간결화 덕분에 디버깅하기 쉽다.
4. 테스트에 용이
5. 객체지향 설계를 제한 없이 적용할 수 있다!!
Spring은 POJO 프로그래밍을 지향하는 Framework이다.
Spring에서는 POJO 프로그래밍 코드를 작성하기 위해 세 가지 기술을 지원하고 있다.
IoC/DI, AOP, PSA
우리는 애플리케이션 코드를 작성할 때, 항상 내가 작성한 코드가 객체지향스러운가에 대한 고민을 하는 습관을 가져야 한다.
IoC(Inversion of Control)
IoC는 "제어의 역전"으로 애플리케이션 흐름의 주도권이 바뀐 것을 말한다.
여기서는 애플리케이션의 흐름의 주도권을 Spring이 갖는다고 할 수 있다.
앞서 설명했던 내용을 참고하여 설명하면 다음과 같다.
Library은 애플리케이션 흐름의 주도권이 개발자에게 있고, Framework은 애플리케이션의 흐름의 주도권이 Framework에 있다. 여기서 말하는 애플리케이션의 흐름의 주도권이 바뀌는 것을 바로 IoC(Inversion of Control) 이라고 한다.
Java 콘솔 애플리케이션을 실행하려면, 기본적으로 main() 메서드가 있어야 한다.
이렇게 개발자가 작성한 코드를 순차적으로 실행하는 것이 애플리케이션의 일반적인 제어 흐름이다.
Java 웹 애플리케이션에서 IoC가 적용되는 예
이번에는 Java 콘솔 애플리케이션이 아닌, 웹 상에서 돌아가는 Java 웹 애플리케이션의 경우를 생각해 보자.
서브릿 컨테이너에는 서블릿 사양에 맞게 작성된 서블릿 클래스만 존재하고, 별도의 main() 메서드는 존재하지 않는다.
"main() 메서드가 없는데 어떻게 애플리케이션이 실행되냐?"
서블릿 컨테이너의 경우, 클라이언트의 요청이 들어올 때마다 서블릿 컨테이너 내의 컨테이너 로직이 서블릿을 직접 실행시켜 주기 때문에 별도의 main() 메서드가 필요 없다.
DI(Dependency Injection)
DI는 "의존성 주입"으로 IoC의 개념을 더 구체화시킨 것이라고 할 수 있다.
위의 다이어그램은 A클래스가 B클래스에 의존하고 있다고 표현할 수 있다.
그니까.. A클래스의 프로그래밍 로직 완성을 위해서 B클래스에게 도움을 요청했다. 라고 해석하면 된다.
의존성 주입의 예
public class CafeClient{
public static void main(String[] args){
MenuService menuService = new MenuService();
MenuController controller = new MenuController(menuService);
List<Menu> menuList = controller.getMenus();
}
}
CafeClient가 MenuController의 생성자 파라미터로 menuService의 객체를 전달받고 있다.
public class MenuController{
private MenuService menuService;
public MenuController(MenuService menuService){
this.menuService = menuService;
}
public List<Menu> getMenus(){
return menuService.getMenuList();
}
}
public class MenuService{
public List<Menu> getMenuList(){
return null;
}
}
기억하기 ..
클래스의 생성자로 객체를 전달받는 코드가 있다면, "객체를 외부에서 주입받고 있구나. 의존성 주입이 이루어지고 있구나.." 라고 생각하자.
의존성 주입은 왜 필요한가?
애플리케이션 코드 내부에 new 키워드로 객체를 생성하게 되면, 참조할 클래스가 변경될 경우,
해당 클래스를 사용하는 모든 클래스를 수정할 수밖에 없다.
강한 결합 (Tigjt Coupling)
new 키워드를 사용하여 의존 객체를 생성할 때, 클래스들 간에 강하게 결합 되어 있다고 한다.
느슨한 결합 (Loose Coupling)
결론적으로 의존성 주입을 하더라도 혜택을 보기 위해서는 클래스들 간에 느슨한 결합을 중요시 해야 한다.
느슨한 결합은 어떻게 할까?
Java에서 클래스들 간의 관계를 느슨하게 만드는 대표적인 방법으로는 인터페이스(Interface)를 사용하는 것이다.
위의 클래스 다이어그램을 보면, MenuController가 MenuService라는 인터페이스를 의존하고 있다.
MenuController는 MenuService를 의존하고 있지만, MenuService의 구현체는 MenuServiceImpl, MenuServiceStub 인지 알지 못한다. 하지만 MenuController 입장에서는 메뉴 목록 데이터를 조회할 수 있으면 되기 때문에 알 필요가 없다..
이렇듯 어떤 클래스가 인터페이스와 같이 일반화된 구성 요소에 의존하고 있을 때, 클래스들 간에 느슨하게 결합되어 있다고 한다.
(1) MenuServiceStub 클래스의 객체를 생성해서 MenuService 인터페이스에 할당하고 있다. (업캐스팅)
MenuController가 생성자로 MenuServiceStub 클래스를 주입 받았으나, 주입받은 대상이 MenuService 인터페이스이기 때문에
MemberService 인터페이스의 구현 클래스면 어떤 클래스도 전부 주입받을 수 있다.
여기에 사용한 new 키워드도 제거하면서 의존 관계를 더 느슨하게 만들 수 없을까?
할 수 있다. 뭐로? Spring으로.
앞의 그림에서는 CafeClient 클래스에 MenuServiceStub과 MenuController 객체를 생성하기 위해 new 키워드를 사용했었는데 여기에서는 보이지 않는다.
대신에 (1)과 같이 알 수 없는 코드들이 보이는데 (1)에 해당하는 코드는 모두 Spring에서 지원하는 API 코드이다.
근데.. "다른 기술이나 규약에 얽매이지 않아야 한다."는 POJO 프로그래밍의 규칙에 어긋나게 Spring framework에 해당하는 코드가 애플리케이션 코드에 직접적으로 나와 있다. 이는 좋은 개발 방식은 아니다.
어쨌거나 당장 new 키워드를 없앨 수 있었던 이유는, 맨 아래에 있는 Config 클래스 덕분이다.
여기에서 Config 클래스의 역할은, Config 클래스에 정의해 둔 MenuController 객체를 Spring의 도움을 받아 CafeClient 클래스에게 제공하고 있다는 것이다. 근데 여기서 의문점은 Config 클래스의 new 키워드이다. 이건 문제가 없을까? 정답은 문제 없다.
Config 클래스 안에 new 키워드로 객체를 생성한 것들은 실제 애플리케이션의 핵심 로직에 영향을 주지 않는다.
Config 클래스는 Spring Framework 영역에 해당하기 때문에 온전히 Spring Framework의 영역인 것이다.
AOP(Aspect Oriented Programming)
AOP는 직역하면 "관심 지향 프로그래밍"으로
애플리케이션의 핵심 업무 로직에서 로깅, 보안, 트랜잭션같은 공통 기능 로직들을 분리하는 것을 말한다.
공통 관심 사항
애플리케이션의 공통 기능들에 대한 관심사
+ 부가적인 관심 사항이라고 표현하기도 한다.
ex) 애플리케이션에 아무나 접속하지 못하도록 제한하는 보안에 관한 부분 (전반에 걸쳐 적용되는 기능들)
핵심 관심 사항
비즈니스 로직 즉, 애플리케이션의 주목적을 달성하기 위한 핵심 로직에 대한 관심사
ex) 커피 종류 등록하기, 고객이 마시고 싶은 커피 주문하는 기능
다시 정리해보면 AOP는 애플리케이션의 핵심 업무 로직에서 로깅이나 보안, 트랜잭션 같은 공통 기능 로직들을 분리하는 것을 말한다.
AOP가 필요한 이유
- 코드의 간결성 유지
- 코드의 재사용
- 객체지향의 원칙에 맞는 코드 구현
트랜잭션
- 데이터를 처리하는 작업의 단위
ex) A작업 + B작업
커밋 (Commit)
- 모든 작업 성공
ex) A성공 + B성공
롤백 (rollback)
- 작업 실패
ex) A성공 + B실패
...
connection.setAutoCommit(false); // (1)
try {
saveMember(member); // (2)
savePoint(point); // (2)
connection.commit(); // (3)
} catch (SQLException e) {
connection.rollback(); // (4)
}
...
위는 AOP가 적용되지 않는 JDBC 트랜잭션의 예제 코드이다.
(2)는 실제 비즈니스 로직을 수행하는코드로, 회원의 포인트 정보를 저장하는 savePoint()이다.
(1), (3), (4)는 saveMember()와 savePoint() 작업을 트랜잭션으로 묶어서 처리하기 위한 기능이다.
위 코드처럼 애플리케이션 전반에 걸쳐 트랜잭션과 관련한 중복된 코드가 수도 없이 나타나면,
우리는 중복된 코드를 공통화해서 재사용이 가능하도록 만들어야 한다.
바로 .. @Transactional 애노테이션을 붙여주면 된다.
@Component
@Transactional // (1)
public class Example2_12 {
private Connection connection;
public void registerMember(Member member, Point point) throws SQLException {
saveMember(member);
savePoint(point);
}
private void saveMember(Member member) throws SQLException {
// Spring JDBC를 이용한 회원 정보 저장
}
private void savePoint(Point point) throws SQLException {
// Spring JDBC를 이용한 포인트 정보 저장
}
}
(1)의 @Transactional 애노테이션으로 Spring 내부에서 이 애노테이션 정보를 활용하여 AOP 기능을 통해 트랜잭션을 적용한다.
이렇게 AOP를 활용하면 애플리케이션 전반에 걸쳐 적용되는 공통 관심 사항(로깅, 보안, 트랜잭션, 트레이싱, 모니터링) 등을 비즈니스 로직에서 깔끔하게 분리하여 재사용이 가능한 모듈로 사용할 수 있다.
'CodeStates 45th' 카테고리의 다른 글
[Section 2] Spring Framework 핵심 개념 - DI (1) | 2023.05.30 |
---|---|
[Section 2] SQL, MySQL Command practice (0) | 2023.05.25 |
[Section 1] 회고 (3) | 2023.05.09 |
[Section 1] JAVA 객체지향 프로그래밍 기초 (0) | 2023.04.25 |
[Section 1] Git 기초 - 회고록 (0) | 2023.04.24 |