Spring
기본 개념
프레임워크와 라이브러리 차이점
- 프레임워크:
- 설명: 프레임워크는 애플리케이션 구조를 제공하며, 개발자가 작성한 코드를 호출하는 제어의 역전을 포함한다. 즉, 개발자가 작성한 코드가 프레임워크의 흐름에 따라 동작한다.
- 예시: Spring(자바), Django(파이썬), Ruby on Rails(루비)
- 라이브러리:
- 설명: 라이브러리는 특정 기능을 수행하는 코드 모음으로, 개발자가 직접 호출하여 사용하는 방식이다. 제어의 흐름이 개발자에게 있다.
- 예시: NumPy(파이썬), React(자바스크립트), STL(C++)
DI (Dependency Injection)
의존성 주입(Dependency Injection, DI)은 스프링 프레임워크의 핵심 개념 중 하나로, 객체의 생성과 의존성 관리를 컨테이너가 담당하는 디자인 패턴입니다. 스프링은 주로 생성자 주입, 세터 주입, 필드 주입 등의 방법을 통해 DI를 지원합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
@Component
public class MyService {
private final MyRepository myRepository;
@Autowired
public MyService(MyRepository myRepository) {
this.myRepository = myRepository;
}
public void performAction() {
// ...
}
}
위 코드에서는 MyRepository
객체가 MyService
객체에 주입(Inject)되고 있습니다. 스프링이 MyService
객체를 생성할 때 MyRepository
객체를 자동으로 주입합니다.
IoC (Inversion of Control)
제어의 역전(Inversion of Control, IoC)은 객체의 생성과 제어 권한을 개발자가 아닌 프레임워크나 컨테이너가 담당하는 디자인 원칙입니다. 스프링 컨테이너는 IoC 원칙을 따르며, 객체의 생성과 생명주기 관리를 대신해 줍니다. 이를 통해 객체 간의 의존성을 쉽게 관리하고 결합도를 낮출 수 있습니다.
스프링 컨테이너는 애플리케이션 시작 시 config
파일이나 어노테이션을 통해 정의된 객체를 생성하고 필요한 의존성을 주입하며, 애플리케이션 종료 시 객체를 소멸시키는 역할을 합니다. 이로 인해 개발자는 객체의 생성과 관리에 신경 쓰지 않고 비즈니스 로직에 집중할 수 있습니다.
컨테이너 (Container)
컨테이너(Container)는 스프링 프레임워크의 핵심 컴포넌트로, 애플리케이션에서 사용하는 객체를 관리하는 역할을 합니다. 스프링 컨테이너는 BeanFactory와 ApplicationContext 두 가지 주요 인터페이스로 제공되며, 이들은 스프링 빈(Bean)의 생성, 초기화, 주입, 소멸 등을 담당합니다.
- BeanFactory: 가장 기본적인 컨테이너로, 빈의 생성과 의존성 주입을 담당합니다. 경량 컨테이너로 메모리 사용이 적습니다.
- ApplicationContext: BeanFactory를 확장한 인터페이스로, 추가적인 기능(예: 이벤트 발행, 메시지 소스 처리 등)을 제공합니다. 대부분의 스프링 애플리케이션은 ApplicationContext를 사용합니다.
스프링 컨테이너는 설정 파일(XML)이나 어노테이션(@Configuration, @Component 등)을 통해 구성할 수 있으며, 컨테이너는 이를 기반으로 애플리케이션에서 사용할 객체들을 관리합니다.
설정 클래스 예시
1
2
3
4
5
6
7
8
9
10
11
12
13
@Configuration
public class AppConfig {
@Bean
public MyService myService() {
return new MyService(myRepository());
}
@Bean
public MyRepository myRepository() {
return new MyRepository();
}
}
위 예시에서는 AppConfig
클래스가 스프링 컨테이너에 의해 관리되는 설정 클래스입니다. @Bean
어노테이션을 통해 MyService
와 MyRepository
빈을 정의하고 있습니다. 스프링 컨테이너는 이 설정을 기반으로 객체를 생성하고 주입합니다.
컴포넌트 스캔 어노테이션 사용 방법
스프링에서는 @ComponentScan
어노테이션을 사용하여 지정된 패키지에서 자동으로 빈을 검색하고 등록할 수 있습니다. @Component
, @Service
, @Repository
, @Controller
등의 어노테이션을 사용하여 빈을 정의하고, @ComponentScan
을 통해 이를 스캔합니다.
컴포넌트 클래스 정의
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import org.springframework.stereotype.Component;
@Component
public class MyRepository {
public void doSomething() {
System.out.println("Doing something in MyRepository");
}
}
@Component
public class MyService {
private final MyRepository myRepository;
public MyService(MyRepository myRepository) {
this.myRepository = myRepository;
}
public void performAction() {
myRepository.doSomething();
}
}
설정 클래스에 컴포넌트 스캔 추가
1
2
3
4
5
6
7
8
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan(basePackages = "com.example")
public class AppConfig {
// 빈 정의가 필요 없다.
}
위 예시에서는 @ComponentScan
어노테이션을 사용하여 com.example
패키지를 스캔하고, @Component
어노테이션이 붙은 클래스들을 자동으로 빈으로 등록합니다.
SpringBoot
에서는 @SpringBootApplication 어노테이션을 사용하면, 이 어노테이션이 @Configuration, @EnableAutoConfiguration, @ComponentScan을 포함하고 있기 때문에 별도의 @ComponentScan 설정이 필요 없습니다.
메인 클래스에서 컨테이너 사용
1
2
3
4
5
6
7
8
9
10
11
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Main {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
MyService myService = context.getBean(MyService.class);
myService.performAction();
}
}
Bean
스프링 빈(Spring Bean)은 스프링 프레임워크에 의해 관리되는 객체를 의미합니다. 스프링 빈은 애플리케이션의 구성 요소로, 스프링 IoC(Inversion of Control) 컨테이너가 생성, 관리, 주입 및 소멸을 담당합니다. 빈은 보통 애플리케이션의 비즈니스 로직이나 데이터 액세스 로직을 담고 있는 객체입니다.
- 관리: 스프링 컨테이너가 객체의 생성, 초기화, 주입, 소멸을 관리합니다.
- 의존성 주입: DI(Dependency Injection)를 통해 빈 간의 의존성을 설정하고 관리합니다.
- 라이프사이클: 빈의 생명주기를 컨테이너가 제어합니다.
스프링 빈 설정 예시들
1. 빈의 구현체가 여러 개일 경우 주입 받는 방법
스프링 애플리케이션에서 같은 타입의 여러 빈이 존재할 때 특정 빈을 주입하는 방법은 다음과 같습니다:
- @Primary: 해당 빈을 최우선으로 주입합니다.
- @Qualifier(“beanName”):
beanName
으로 지정된 빈을 주입합니다. - Set 또는 List로 모두 받기: 모든 빈을 컬렉션으로 주입받습니다.
- 프로퍼티 이름을 빈과 동일하게 하기: 가장 흔하게 사용하는 방법으로, 필드나 프로퍼티 이름을 빈 이름과 동일하게 맞추는 것입니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
public interface MyService {
void performAction();
}
@Component
@Primary
class MyServiceImpl1 implements MyService {
@Override
public void performAction() {
System.out.println("MyServiceImpl1 performing action");
}
}
@Component
class MyServiceImpl2 implements MyService {
@Override
public void performAction() {
System.out.println("MyServiceImpl2 performing action");
}
}
@Component
class ClientComponent {
// @Primary를 사용한 빈이 주입됩니다.
@Autowired
private MyService myService;
// @Qualifier를 사용하여 특정 빈을 주입합니다.
@Autowired
@Qualifier("myServiceImpl2")
private MyService myService2;
// 모든 빈을 리스트로 주입받습니다.
@Autowired
private List<MyService> myServices;
public void doSomething() {
myService.performAction();
myService2.performAction();
myServices.forEach(MyService::performAction);
}
}
2. 빈의 스코프(Scope)
스프링 빈의 스코프는 빈의 생명주기를 정의합니다. 주요 스코프는 다음과 같습니다:
- 싱글톤: (기본값) 하나만 만들어서 계속 재활용.
- 프로토타입: 요청할 때마다 새로 생성.
- Request: HTTP 요청마다 새로 생성.
- Session: HTTP 세션마다 새로 생성.
- WebSocket: WebSocket 세션마다 새로 생성.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import org.springframework.web.context.annotation.RequestScope;
import org.springframework.web.context.annotation.SessionScope;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
@Configuration
public class BeanScopeConfig {
@Bean
public MySingletonBean mySingletonBean() {
return new MySingletonBean();
}
@Bean
@Scope("prototype")
public MyPrototypeBean myPrototypeBean() {
return new MyPrototypeBean();
}
@Bean
@RequestScope
public MyRequestBean myRequestBean() {
return new MyRequestBean();
}
@Bean
@SessionScope
public MySessionBean mySessionBean() {
return new MySessionBean();
}
@Bean
@Scope("websocket")
public MyWebSocketBean myWebSocketBean() {
return new MyWebSocketBean();
}
}
class MySingletonBean { }
class MyPrototypeBean { }
class MyRequestBean { }
class MySessionBean { }
class MyWebSocketBean { }
3. 스프링의 환경 설정: 프로파일(Profile)
스프링 프로파일(Profile)을 사용하면 다양한 환경(예: 개발, 테스트, 프로덕션)에서 특정 빈이 활성화되도록 설정할 수 있습니다. 프로파일은 클래스 단위와 메서드 단위에 적용할 수 있습니다.
클래스 단위 프로파일 적용
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;
@Configuration
@Profile("test")
class TestConfig {
@Bean
public MyTestService myTestService() {
return new MyTestService();
}
}
@Component
@Profile("test")
class MyTestComponent {
// Test 환경에서만 활성화
}
class MyTestService { }
메서드 단위 프로파일 적용
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
@Configuration
public class AppConfig {
@Bean
@Profile("dev")
public MyService devService() {
return new MyServiceImpl1();
}
@Bean
@Profile("prod")
public MyService prodService() {
return new MyServiceImpl2();
}
}
Rsource
스프링에서 “리소스(Resource)”는 다양한 형태의 외부 자원을 추상화한 개념입니다. 스프링은 파일 시스템, 클래스패스, URL 등 다양한 위치에 있는 리소스를 통합적으로 접근할 수 있도록 org.springframework.core.io.Resource
인터페이스와 그 구현체를 제공합니다. 이를 통해 애플리케이션에서 외부 리소스를 쉽게 읽고 쓸 수 있습니다.
- ClasspathResource: 클래스패스에 있는 리소스.
- FileSystemResource: 파일 시스템에 있는 리소스.
- UrlResource: URL로 접근 가능한 리소스.
- ByteArrayResource: 바이트 배열을 리소스로 사용.
- InputStreamResource:
InputStream
을 리소스로 사용.
스프링에서 리소스를 접근할 때는 Resource
인터페이스를 사용하며, ResourceLoader
를 통해 리소스를 로드할 수 있습니다. 스프링의 ApplicationContext
는 ResourceLoader
를 구현하고 있으므로, ApplicationContext
를 사용하여 리소스를 로드할 수 있습니다.
클래스패스 리소스 읽기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import java.io.BufferedReader;
import java.io.InputStreamReader;
public class ResourceExample {
public static void main(String[] args) {
try {
Resource resource = new ClassPathResource("data/example.txt");
BufferedReader reader = new BufferedReader(new InputStreamReader(resource.getInputStream()));
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
reader.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
파일 시스템 리소스 읽기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import java.io.BufferedReader;
import java.io.InputStreamReader;
public class FileSystemResourceExample {
public static void main(String[] args) {
try {
Resource resource = new FileSystemResource("C:/path/to/your/file.txt");
BufferedReader reader = new BufferedReader(new InputStreamReader(resource.getInputStream()));
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
reader.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
ApplicationContext를 통한 리소스 로딩
스프링의 ApplicationContext
를 통해 리소스를 로드할 수도 있습니다. 이 방법은 리소스 타입을 자동으로 인식하여 적절한 Resource
구현체를 사용합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.core.io.Resource;
import java.io.BufferedReader;
import java.io.InputStreamReader;
public class ApplicationContextResourceExample {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext();
Resource resource = context.getResource("classpath:data/example.txt");
try (BufferedReader reader = new BufferedReader(new InputStreamReader(resource.getInputStream()))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
@Transactional
@Transactional
어노테이션은 Spring 프레임워크에서 트랜잭션 관리를 선언적으로 처리할 수 있게 해줍니다. 이 어노테이션을 사용하면 메서드나 클래스에서 트랜잭션 경계를 설정할 수 있습니다. 트랜잭션은 일련의 작업이 모두 성공적으로 완료되거나 모두 실패해야 함을 보장하는 메커니즘입니다.
- 트랜잭션 경계 설정:
@Transactional
어노테이션을 메서드나 클래스에 적용하면, 해당 메서드나 클래스의 모든 메서드가 트랜잭션 내에서 실행됩니다.
1 2 3 4 5 6 7 8
@Service public class UserService { @Transactional public void createUser(User user) { // 사용자 생성 로직 } }
- 속성 설정:
propagation
: 트랜잭션의 전파 방식을 설정합니다 (예: REQUIRED, REQUIRES_NEW, MANDATORY 등).isolation
: 트랜잭션의 고립 수준을 설정합니다 (예: READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE).timeout
: 트랜잭션이 완료되지 않고 대기할 수 있는 최대 시간을 설정합니다.readOnly
: 읽기 전용 트랜잭션으로 설정할 수 있습니다.rollbackFor
,noRollbackFor
: 트랜잭션을 롤백할 예외 타입을 지정합니다.
1 2 3 4
@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.SERIALIZABLE, timeout = 5, readOnly = true) public void processOrder(Order order) { // 주문 처리 로직 }
- 트랜잭션 롤백:
@Transactional
을 사용하면 예외가 발생했을 때 트랜잭션을 자동으로 롤백할 수 있습니다. 기본적으로RuntimeException
및 그 하위 클래스가 발생하면 롤백됩니다.
1 2 3 4 5 6 7 8
@Transactional public void transferMoney(Account from, Account to, BigDecimal amount) { // 송금 로직 if (from.getBalance().compareTo(amount) < 0) { throw new InsufficientFundsException(); } // 금액 전송 로직 }
Spring Cron 스케줄링
Spring에서는 @Scheduled
어노테이션을 사용하여 메서드를 주기적으로 실행할 수 있습니다. cron 표현식을 사용하면 정교한 스케줄링을 설정할 수 있습니다.
주요 기능과 특징:
- 기본 스케줄링:
@Scheduled
어노테이션을 사용하여 메서드를 일정 간격으로 실행할 수 있습니다.
1 2 3 4
@Scheduled(fixedRate = 5000) public void fixedRateTask() { System.out.println("5초마다 실행"); }
- cron 표현식:
- cron 표현식을 사용하면 특정 시간, 일, 주, 월, 년 단위로 스케줄링을 설정할 수 있습니다.
- cron 표현식 형식:
seconds minutes hours dayOfMonth month dayOfWeek [year]
1 2 3 4
@Scheduled(cron = "0 0 12 * * ?") public void cronTask() { System.out.println("매일 12시에 실행"); }
- 스케줄링 설정:
fixedRate
: 이전 작업이 시작된 시간으로부터 고정된 시간 간격으로 실행됩니다.fixedDelay
: 이전 작업이 완료된 시간으로부터 고정된 시간 간격으로 실행됩니다.initialDelay
: 애플리케이션 시작 후 첫 작업이 실행되기 전의 지연 시간을 설정합니다.
1 2 3 4
@Scheduled(fixedRate = 5000, initialDelay = 10000) public void scheduledTaskWithInitialDelay() { System.out.println("애플리케이션 시작 후 10초 후에 첫 실행, 이후 5초마다 실행"); }
스케줄링 활성화
스케줄링을 활성화하려면 Spring 부트 애플리케이션 클래스 또는 구성 클래스에 @EnableScheduling
어노테이션을 추가해야 합니다.
1
2
3
4
5
6
7
8
9
10
11
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication
@EnableScheduling
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
부가 기능들
스프링 프레임워크는 다양한 기능을 제공하여 개발자의 생산성을 높이고 애플리케이션의 유지보수성을 개선합니다. 여기서는 AOP, Validation, Data Binding, SpEL에 대해 간략히 소개하겠습니다.
1. AOP (Aspect-Oriented Programming)
AOP는 관심사 분리(Separation of Concerns)를 통해 애플리케이션의 횡단 관심사(예: 로깅, 트랜잭션 관리, 보안)를 분리하여 모듈화하는 프로그래밍 패러다임입니다.
- Aspect: 횡단 관심사를 모듈화한 단위.
- Advice: 실제로 수행되는 작업. (Before, After, Around 등)
- Pointcut: Advice가 적용될 위치를 정의.
- Join Point: Advice가 적용될 수 있는 위치 (메서드 호출 등).
- Weaving: Aspect와 실제 코드가 결합되는 과정.
1
2
3
4
5
6
7
8
9
@Aspect
@Component
public class LoggingAspect {
@Before("execution(* com.example.service.*.*(..))")
public void logBefore(JoinPoint joinPoint) {
System.out.println("Executing: " + joinPoint.getSignature().getName());
}
}
2. Validation (검증)
스프링은 Bean Validation API (JSR-380)를 통해 객체의 필드 값을 검증하는 기능을 제공합니다.
@NotNull
: null이 아닌지 검증.@Size
: 문자열, 배열, 컬렉션 등의 크기를 검증.@Min
,@Max
: 숫자의 최소/최대 값을 검증.@Pattern
: 정규 표현식을 사용하여 문자열을 검증.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
public class User {
@NotNull
@Size(min = 2, max = 30)
private String name;
@NotNull
private String email;
// getters and setters
}
컨트롤러에서 검증 적용:
1
2
3
4
5
6
7
@PostMapping("/users")
public ResponseEntity<String> createUser(@Valid @RequestBody User user, BindingResult result) {
if (result.hasErrors()) {
return new ResponseEntity<>("Validation errors", HttpStatus.BAD_REQUEST);
}
return new ResponseEntity<>("User created", HttpStatus.OK);
}
3. Data Binding (데이터 바인딩)
스프링의 데이터 바인딩은 요청 파라미터를 자바 객체로 변환하고, 자바 객체를 응답으로 변환하는 기능을 제공합니다. 이를 통해 HTTP 요청 파라미터를 간편하게 객체에 매핑할 수 있습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class User {
private String name;
private String email;
// getters and setters
}
@RestController
public class UserController {
@PostMapping("/users")
public ResponseEntity<String> createUser(@RequestBody User user) {
// user 객체에 요청 파라미터가 바인딩됨
return new ResponseEntity<>("User created: " + user.getName(), HttpStatus.OK);
}
}
4. SpEL (Spring Expression Language)
SpEL은 스프링 애플리케이션에서 객체 그래프를 조회하고 조작할 수 있는 표현 언어입니다. XML 및 어노테이션 기반 설정에서 사용되며, 런타임에 동적으로 값을 계산할 수 있습니다.
- 속성 접근:
#{bean.property}
- 메서드 호출:
#{bean.method()}
- 산술 연산:
#{1 + 1}
- 조건식:
#{bean.property == 'value' ? 'yes' : 'no'}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Component
public class MyBean {
private String name = "Spring";
public String getName() {
return name;
}
}
@Configuration
public class AppConfig {
@Value("#{myBean.name}")
private String beanName;
@Bean
public MyBean myBean() {
return new MyBean();
}
}
요약
- AOP: 횡단 관심사를 모듈화하여 코드의 중복을 줄이고 유지보수성을 높임.
- Validation: 객체의 필드 값을 검증하여 데이터의 무결성을 보장.
- Data Binding: HTTP 요청 파라미터를 자바 객체로 변환하고 응답으로 변환.
- SpEL: 표현 언어를 통해 런타임에 동적으로 값을 계산하고 설정에 활용.
###