Spring을 하면서 꼭 알아야 한다고 생각하는 개념들에 대해 작성한 글입니다. 어떤 개념을 다루는지는 목차를 참고해 주세요! (2024.05.15 업데이트)
최근 스프링 프로젝트를 하면서 다시 개념에 대한 공부를 할 타이밍이란 것을 느끼게 되었습니다. 이유는 이론 공부한 지 너무 오래되어 개념들이 머리 속에서 정리가 안 된 기분이 들어서...
그래서! 이 글에서는 제가 작년부터 스프링을 공부하면서 정리한 개념들을 쭉 나열했습니다.
한 개념을 자세하게 설명하지 않았습니다.!
만약 어떤 개념을 자세하게 알고 싶어서 이 글을 클릭하셨다면 뒤로 가기를 추천드립니다.
여러 개념을 쓱~ 보고 싶으시다면 이 글이 도움이 될지도 모르겠습니다... ㅎ
1. 계층
스프링 부트에는 프로젠테이션, 비즈니스, 퍼시스턴스 계층이 있습니다. 이 계층들이 서로 통신하며 프로그램을 구성합니다.
- 프레젠테이션 계층: HTTP 요청을 받고 이 요청을 비즈니스 계층으로 전송하는 역할을 합니다. 컨트롤러가 바로 프레젠테이션 계층 역할을 합니다. 컨트롤러가 프레젠테이션 계층의 역할을 합니다.
- 비즈니스 계층: 모든 비즈니스 로직을 처리합니다. 비즈니스 로직이란 서비스를 만들기 위한 로직을 말합니다. 쉽게 말해 웹 사이트에서 벌어지는 모든 작업, 이를테면 주문 서비스라고 한다면 주문 개수, 가격 등의 데이터를 처리하기 위한 로직, 주문 처리를 하다가 발생하는 예외 처리 로직 등이 포함됩니다. 서비스가 비즈니스 계층의 역할을 합니다.
- 퍼시스턴스 계층: 모든 데이터베이스 관련 로직을 처리합니다. 이 과정에서 데이터베이스에 접근하는 DAO 객체를 사용할 수도 있습니다. DAO는 데이터베이스 계층과 상호작용하기 위한 객체입니다. 리포지토리가 퍼시스턴스 계층의 역할을 합니다.
스프링 부트 요청 - 응답 과정
- 클라이언트가 톰캣에 요청
- 스프링 부트의 디스패치 서블릿이 URL을 분석하고 요청을 처리할 컨트롤러를 찾아 요청 전달
- 컨트롤러 내에 있는 메서드와 요청이 매치
- 비즈니스 계층과 퍼시스턴스 계층을 통하여 필요한 작업을 함
- 뷰 리졸버가 HTML문서를 만들거나 JSON, XML등의 데이터를 생성
2. MVC
MVC는 Model-View-Controller의 약자로, 애플리케이션을 세 가지 주요 부분으로 나누어 개발하는 디자인 패턴입니다.
컨트롤러(Controller):
-
- 컨트롤러는 클라이언트의 요청을 처리하고, 적절한 비즈니스 로직을 호출하여 응답을 생성합니다.
- @Controller 애노테이션을 사용하여 클래스를 컨트롤러로 지정할 수 있습니다.
- 메서드는 @RequestMapping 애노테이션을 사용하여 특정 URL과 HTTP 메서드에 매핑됩니다.
모델(Model):
- 모델은 컨트롤러와 뷰 사이에서 데이터를 전달하는 역할을 합니다.
- 데이터와 데이터와 관련된 로직을 처리하고, 데이터를 관리하는데 사용되는 모든 주체들을 Model로 칭할 수 있습니다.
뷰(View):
- 뷰는 클라이언트에게 결과를 표시하는 역할을 합니다.
- 주로 JSP, Thymeleaf, Freemarker 등의 템플릿 엔진을 사용하여 뷰를 생성합니다.
- 뷰는 컨트롤러가 전달한 데이터를 사용하여 동적으로 페이지를 생성합니다.
3. 제어의 역전과 의존성 주입
IoC (Inversion of Control)
IoC란 Inversion of Control의 약자로 직역하면 제어의 역전입니다.
기존의 전통적인 프로그래밍 모델에서는 개발자가 코드의 흐름과 제어를 직접 관리하고 제어합니다. 그러나 제어의 역전은 이와 달리 코드의 제어 흐름을 프레임워크나 컨테이너가 가져가도록 하는 것을 의미합니다.
즉, 객체 생성을 해당 객체에 의존하는 객체가 직접 new 키워드와 생성자를 이용하여 하는 것이 아니라, 객체를 필요로 한다는 일종의 힌트를 주기 위한 어노테이션만 붙여주면 외부에서 해당 객체를 만들어 주입하여 제공하는 전략을 말합니다.
DI (Dependency Injection)
앞에서 설명한 것처럼 스프링에서는 객체들을 관리하기 위해 제어의 역전을 사용합니다. 그리고 제어의 역전을 구현하기 위해 사용하는 방법이 DI입니다. DI는 Dependency Injection의 약자로 직역하면 의존성 주입입니다. 의존성 주입은
어떤 클래스가 다른 클래스에 의존한다는 뜻입니다.
즉, 말 그대로 객체가 의존하는 특정 객체를 대신 생성하고, 주입도 해주는 디자인 패턴을 의미합니다. DI를 통해 객체간의 결합도를 줄이고 코드의 재사용성을 높일 수 있습니다.
DI를 하는 세 가지 방법
스프링에서 사용되는 DI방법에는 크게 세가지가 있습니다
생성자 주입 (Constructor Injection)
- 생성자 주입은 객체를 생성할 때 생성자를 통해 의존성을 주입하는 방식입니다.
- 스프링에서는 생성자에 @Autowired 애노테이션을 사용하여 의존성을 주입합니다. 또는 XML 설정 파일에서 <constructor-arg> 요소를 사용할 수도 있습니다.
- 생성자 주입은 필수적인 의존성을 명시적으로 표현할 수 있고, 불변성을 보장하여 안정적인 객체를 생성할 수 있습니다.
public class ExampleService {
private final Dependency dependency;
@Autowired
public ExampleService(Dependency dependency) {
this.dependency = dependency;
}
}
Setter 주입 (Setter Injection)
- Setter 주입은 객체 생성 후 Setter 메서드를 통해 의존성을 주입하는 방식입니다.
- 스프링에서는 Setter 메서드에 @Autowired 애노테이션을 사용하여 의존성을 주입합니다. 또는 XML 설정 파일에서 <property> 요소를 사용할 수도 있습니다.
- Setter 주입은 선택적인 의존성을 표현하기에 유용합니다.
public class ExampleService {
private Dependency dependency;
@Autowired
public void setDependency(Dependency dependency) {
this.dependency = dependency;
}
}
필드 주입 (Field Injection)
- 필드 주입은 필드에 바로 의존성을 주입하는 방식입니다.
- 스프링에서는 필드에 @Autowired 애노테이션을 사용하여 의존성을 주입합니다.
public class ExampleService {
@Autowired
private Dependency dependency;
}
셋 중 하나의 방법을 쓰면 됩니다만 동시에 여러 방법을 쓰는 것은 중복 주입이 이루어지므로 지양해야 합니다.
또한 보통 생성자를 통해서 의존성 주입을 진행하는 것이 가장 좋은 방법입니다. 그리고 생성자 주입을 통해서만 의존성 주입을 받는 필드를 상수로 정의할 수 있습니다. 대부분 의존성 주입을 받은 객체를 변경할 일이 없기 때문에 실수를 예방하기 위해서 상수로 지정하는 것을 선호합니다.
4. Bean과 스프링 컨테이너
스프링은 애플리케이션의 객체 생성과 관리를 담당하는 스프링 컨테이너와 이를 통해 관리되는 객체를 빈(Bean)이라고 부르는데, 이들에 대해 각각 설명해보겠습니다.
Bean
빈은 스프링 컨테이너에 의해 생성되고 관리되는 객체를 말합니다. 스프링에서는 개발자가 작성한 일반적인 자바 클래스를 빈으로 등록하여 사용할 수 있습니다.
빈은 XML 설정 파일이나 Java Config, 어노테이션을 통해 정의될 수 있습니다. 즉, 빈을 등록하는 방법에는 여러가지가 있습니다.
Bean을 등록하는 방법
XML 기반 설정 (XML Configuration): XML 파일에 <bean> 요소를 사용하여 Bean을 정의하는 방법입니다. 일반적으로 applicationContext.xml 또는 beans.xml과 같은 이름의 XML 파일을 사용합니다.
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="myBean" class="com.example.MyBean"/>
</beans>
Java 기반 설정 (Java Configuration): Java 클래스에 @Configuration 애노테이션을 사용하여 설정 클래스를 정의하고, @Bean 애노테이션을 사용하여 Bean을 정의하는 방법입니다.
@Configuration
public class AppConfig {
@Bean
public MyBean myBean() {
return new MyBean();
}
}
Component Scanning: @Component, @Service, @Repository, @Controller 등의 스프링 애노테이션을 사용하여 클래스를 표시하고, 스프링이 자동으로 Bean으로 등록되도록 하는 방법입니다. 스프링 컨테이너는 클래스 경로에서 이러한 애노테이션이 지정된 클래스를 검색하고 자동으로 Bean으로 등록합니다.
@Service
public class MyService {
//...
}
여기서 Configuration을 사용한 방법과 Component Scanning 을 통한 방법에 대해 더 자세하게 살펴보겠습니다.
- @Component:
- @Component 애노테이션은 스프링 컨테이너가 해당 클래스를 스캔하여 Bean으로 등록하도록 지시합니다. 이 애노테이션은 일반적으로 개발자가 정의한 일반적인 POJO(Plain Old Java Object) 클래스에 부여됩니다.
- @Component 애노테이션을 사용하면 해당 클래스가 스프링 컨테이너에 의해 자동으로 Bean으로 등록됩니다. 스프링은 이러한 Bean을 인스턴스화하고 관리합니다.
- @Component는 다른 세부적인 스프링 애노테이션인 @Service, @Repository, @Controller의 상위 개념입니다. 따라서 이 애노테이션들은 @Component를 확장하고 있습니다.
- @Configuration:
- @Configuration 애노테이션은 스프링의 Java 기반 설정 클래스를 나타냅니다. 이 클래스는 스프링 빈 설정을 정의하고, @Bean 애노테이션을 사용하여 빈을 생성하고 구성하는 메서드를 포함할 수 있습니다.
- @Configuration 클래스를 사용하여 스프링 빈을 설정할 때에는 해당 클래스를 구성 클래스로 지정하고, 이 클래스 내에 @Bean 메서드를 사용하여 스프링 빈을 정의합니다.
- @Configuration 클래스 내에서 @Bean으로 정의된 메서드가 스프링 빈으로 등록됩니다.
Spring IoC Container
스프링 컨테이너는 스프링 프레임워크의 핵심 부분으로, 애플리케이션의 객체를 생성, 관리, 조립하는 역할을 담당합니다.
ApplicationContext 인터페이스를 구현한 객체가 스프링 ioC 컨테이너의 역할을 수행합니다. ApplicationContext는 스프링에서 제공하는 IoC 컨테이너로, 생성된 Bean을 보관하고 있는 보관 창고같은 역할입니다.
5. 관점 지향 프로그래밍 (AOP, Aspect-Oriented Programming)
AOP란 프로그래밍에 대한 관점을 핵심 관점, 부가 관점으로 나누어서 관심 기준으로 모듈화하는 것을 의미합니다. 여기서 관점(Aspect)란 부가 기능과 그 적용처를 정의하고 합쳐서 모듈로 만든 것입니다.
AOP의 목적
관점 지향 프로그래밍은 객체 지향 프로그래밍(OOP)을 보완하기 위해 사용합니다. 기존 객체 지향은 목적에 따라 클래스, 즉 객체를 만들었습니다. 따라 핵심 비즈니스이든, 부가 비즈니스이든 객체로 분리하는데 그치고, 그 객체들을 어떻게 바라보고 나눠쓸지에 대한 정의가 부족하다는 단점이 있습니다. 이를 보완하기 위해 AOP를 사용합니다.
6. ORM
ORM(객체 관계 매핑, Object-Relational Mapping)은 객체 지향 프로그래밍 언어와 관계형 데이터베이스 간의 데이터를 변환하는 기술이며, 객체 모델과 데이터베이스 모델 간의 불일치를 해결하기 위해 사용됩니다.
일반적으로 객체는 클래스와 속성으로 구성되어 있고, 데이터베이스는 테이블과 열로 구성되어 있습니다. ORM은 이러한 객체와 데이터베이스 간의 불일치를 해결하기 위해 객체를 데이터베이스 테이블에 매핑하고, 객체 간의 관계를 데이터베이스의 관계로 매핑합니다.
ORM의 장점
- SQL을 직접 작성하지 않고 사용하는 언어로 데이터베이스에 접근할 수 있습니다.
- 객체 지향적으로 코드를 작성할 수 있기 때문에 비즈니스 로직에만 집중할 수 있습니다.
- 데이터베이스 시스템이 추상화되어 있기 때문에 MySQL에서 PostgreSQL로 전환한다고 해도 추가로 드는 작업이 거의 없습니다. 즉, 데이터베이스 시스템에 대한 종속성이 줄어들게 됩니다.
- 매핑하는 정보가 명확하기 때문에 ERD에 대한 의존도를 낮출 수 있고 유지보수할 때 유리합니다.
ORM의 단점
- 프로젝트의 복잡성이 커질수록 사용 난이도도 올라갑니다.
- 복잡하고 무거운 쿼리는 ORM으로도 해결이 불가능한 경우가 있습니다.
7. JPA (Java Persistence API)
ORM에도 여러 종류가 있습니다. 자바에서는 JPA를 표준으로 사용합니다. JPA는 자바에서 관계형 데이터베이스를 사용하는 방식을 정의한 인터페이스인데요, 인터페이스이므로 실제 사용을 위해서는 ORM 프레임워크를 추가로 선택해야 합니다. 대표적으로는 하이버네이트를 많이 사용합니다.
하이버네이트는 JPA 인터페이스를 구현한 구현체이자 자바용 ORM 프레임워크입니다. 하이버네이트 내부적으로는 JDBC API를 사용합니다. 하이버네이트의 목표는 자바 객체를 통해 데이터베이스 종류에 상관없이 데이터베이스를 자유롭게 사용할 수 있게 하는 데 있습니다.
JPA와 하이버네이트의 역할
- JPA(Java Persistence API): 자바 객체와 데이터베이스를 연결해 데이터를 관리합니다. 객체 지향 도메인 모델과 데이터베이스의 다리 역할을 합니다.
- 하이버네이트(Hibernate): JPA의 인터페이스를 구현합니다. 내부적으로는 JDBC API를 사용합니다.
엔티티 매니저
JPA의 중요한 컨셉 중 하나인 엔티티 매니저와 영속성 컨텍스트를 알아보겠습니다.
엔티티 (Entity)
엔티티는 데이터베이스의 테이블과 매핑되는 객체를 의미합니다. 엔티티는 본질적으로는 자바 객체이므로 일반 객체와 다르지 않습니다. 하지만 데이터베이스의 테이블과 직접 연결된다는 특징이 있어 구분지어 부릅니다. 즉, 엔티티는 객체이지만 데이터베이스에 영향을 미치는 쿼리를 실행하는 객체입니다.
엔티티 매니저 (Entity Manager)
엔티티 매니저는 엔티티를 관리해 데이터베이스와 애플리케이션 사이에서 객체를 생성, 수정, 삭제하는 등의 역할을 합니다. 그리고 이런 엔티티 매니저를 만드는 곳이 엔티티 매니저 팩토리입니다.
데이터베이스에는 여러 사용자가 접근할 수 있습니다. 가령 회원 2명이 동시에 회원 가입을 하려는 경우 엔티티 매니저 팩토리는 각각의 업무에 대해 엔티티 매니저를 생성합니다. 따라 업무마다 생성된 엔티티 매니저를 통해 회원의 정보가 데이터베이스에 저장됩니다. 그리고 필요한 시점에 데이터베이스와 연결한 뒤 쿼리합니다.
스프링 부트 내부에서는 엔티티 매니저 팩토리를 하나만 생성해서 관리하고 @PersistenceContext 또는 @Autowired 어노테이션을 사용해 엔티티 매니저를 사용합니다.
@PersistenceContext
EntityManager em;
그리고 스프링 부트는 기본적으로 빈을 하나만 생성해서 공유하므로 동시성 문제가 발생할 수 있습니다. 그래서 실제로는 엔티티 매니저가 아닌 실제 엔티티 매니저와 연결하는 프록시(가짜) 엔티티 매니저를 사용합니다. 필요할 때 데이터베이스 트랜잭션과 실제 엔티티 매니저를 호출하는 겁니다.
즉, 엔티티 매니저는 Spring Data JPA에서 관리하므로 개발자가 직접 생성하거나 관리할 필요가 없습니다.
영속성 컨텍스트
또한 엔티티 매니저는 엔티티를 영속성 컨텍스트에 저장한다는 특징이 있습니다. 영속성 컨텍스트는 JPA의 중요한 특징 중 하나로, 엔티티를 관리하는 가상의 공간입니다. 이것이 있기 때문에 데이터 베이스에서 효과적으로 데이터를 가져올 수 있고, 엔티티를 편하게 사용할 수 있습니다.
영속성 컨텍스트에는 1차 캐시, 쓰기 지연, 변경 감지, 지연 로딩이라는 특징이 있습니다.
1차 캐시
영속성 컨텍스트는 내부에 1차 캐시를 가지고 있습니다. 이때 캐시의 키는 엔티티의 @Id 에너테이션이 달린 기본키 역할을 하는 식별자이며 값은 엔티티입니다. 엔티티를 조회하면 1차 캐시에서 데이터를 조회하고 값이 있으면 반환합니다. 값이 없으면 데이터베이스에서 조회해 1차 캐시에 저장한 다음 반환합니다. 이를 통해 캐시된 데이터를 조회할 때에는 데이터베이스를 거치지 않아도 되므로 매우 빠르게 데이터를 조회할 수 있습니다.
쓰기 지연 (transactional write-behind)
쓰기 지연은은 트랜잭션을 커밋하기 전까지는 데이터베이스에 실제로 질의문을 보내지 않고 쿼리를 모았다가 트랜잭션을 커밋하면 모았던 쿼리를 한번에 실행하는 것을 의미합니다. 예를 들어 데이터 추가 쿼리가 3개라면 영속성 컨텍스트는 트랜잭션을 커밋하는 시점에 3개의 쿼리를 한꺼번에 쿼리를 전송합니다. 이를 통해 적당한 묶음으로 쿼리를 요청할 수 있어 데이터베이스 시스템의 부담을 줄일 수 있습니다.
변경 감지
트랜잭션을 커밋하면 1차 캐시에 저장되어 있는 엔티티의 값과 현재 엔티티의 값을 비교해서 변경된 값이 있다면 변경 사항을 감지해 변경된 값을 데이터베이스에 자동으로 반영합니다. 이를 통해 쓰기 지연과 마찬가지로 적당한 묶음으로 쿼리를 요청할 수 있고, 데이터베이스 시스템의 부담을 줄일 수 있습니다.
지연 로딩 (lazy loading)
지연 로딩은 쿼리로 요청한 데이터를 애플리케이션에 바로 로딩하는 것이 아니라 필요할 때 쿼리를 날려 데이터를 조회하는 것을 의미합니다. 반대로 조회할 때 쿼리를 보내 연관된 모든 데이터를 가져오는 즉시 로딩도 있습니다.
이 특징들은 모두 데이터베이스 접근을 최소화해 성능을 높일 수 있다는 것입니다.
엔티티의 상태
엔티티는 4가지 상태를 가집니다.
- 영속성 컨텍스트가 관리하고 있지 않는 분리(detached) 상태
- 영속성 컨텍스트가 관리하는 관리(managed) 상태
- 영속성 컨텍스트와 전혀 관계가 없는 비영속(transist) 상태
- 삭제된(removed) 상태
이 상태는 특정 메서드를 호출해 변경할 수 있고, 필요에 따라 엔티티의 상태를 조절해 데이터를 올바르게 유지하고 관리할 수 있습니다.
8. 스프링 시큐리티 (Spring Security)
스프링 시큐리티는 스프링 기반의 애플리케이션 보안(인증, 인가, 권한)을 담당하는 스프링 하위 프레임워크입니다. 스프링 시큐리티를 이해하려면 인증과 인가에 대한 개념을 알아야합니다.
인증과 인가
인증(Authentication)은 사용자의 신원을 입증하는 과정입니다. 예를 들어, 사용자가 사이트에 로그인을 할 때 누구인지 확인하는 과정을 인증이라고 합니다.
인가(Authorization)는 인증과는 달리 사이트의 특정 부분에 접근할 수 있는지 권한을 확인하는 작업입니다. 예를 들어 관리자가 아닌 유저는 관리자 페이지에 들어갈 수 없지만 관리자는 관리자 페이지에 접근할 수 있습니다. 이런 권한을 확인하는 과정이 인가입니다.
스프링 시큐리티 동작 과정
스프링 시큐리티는 필터 기반으로 동작합니다.
다음 이미지와 같이 다양한 필터들로 나누어져 있으며, 각 필터에서 인증, 인가와 관련된 작업을 처리합니다. SpringContextPersistenceFilter부터 시작해서 아래로 내려가며 FilterSecurityInterceptor까지 순서대로 필터를 거칩니다. 필터를 실행할 때는 빨간 화살표로 연결된 오른쪽 박스의 클래스를 거치며 실행됩니다. 특정 필터를 제거하거나 필터 뒤에 커스텀 필터를 넣는 등의 설정도 가능합니다.
여기서 UsernamePasswordAuthencationFilter는 아이디와 패스워드가 넘어오면 인증 요청을 위임하는 인증 관리자 역할을 합니다. FilterSecurityInterceptor는 권한 부여 처리를 위임해 접근 제어 결정을 쉽게 하는 접근 결정 관리자 역할을 합니다.
'Framework > SpringBoot' 카테고리의 다른 글
[SpringBatch] 스프링 배치란? (0) | 2024.09.08 |
---|---|
[SpringBoot] Github Action을 이용한 CI/CD (ElasticBeanstalk) (0) | 2024.04.27 |
[Spring] VO와 BO, DAO, DTO란 무엇인가 (1) | 2024.03.24 |
[SpringBoot3] 스프링부트와 Swagger 연결방법 (0) | 2024.03.21 |
[SpringBoot3] 엔티티 상속관계에서 @Builder 적용 (0) | 2024.03.20 |