스프링 부트 로드맵
https://roadmap.sh/spring-boot
스프링 부트의 로드맵에 따라서 따로 배워보겠습니다.
카테고리 : Spring Boot
스프링 코어(컨테이너) (IoC 컨테이너)
- 객체를 생성하고 관리하고 책임지고 의존성을 관리해주는 컨테이너.
- 컨테이너 : 객체의 생명주기를 관리, 생성된 인스턴스들에게 추가적인 기능을 제공하도록 하는 것.
- 인스턴스 생성부터 소멸까지의 인스턴스 생명주기 관리를 개발자가 아닌 컨테이너가 대신 해준다.
- 객체관리 주체가 프레임워크(Container)가 되기 때문에 개발자는 로직에 집중할 수 있는 장점이 있다.
- 특징
- IoC 컨테이너는 객체의 생성을 책임지고, 의존성을 관리한다.
- POJO의 생성, 초기화, 서비스, 소멸에 대한 권한을 가진다.
- POJO : 순수한 자바로 이루어진 객체 -> Bean, getter, setter 등
- 개발자들이 직접 POJO를 생성할 수 있지만 컨테이너에게 맡긴다.
- 개발자는 비즈니스 로직에 집중할 수 있다.
- 객체 생성 코드가 없으므로 TDD가 용이하다.
IoC의 분류
- DL(Dependency Lookup)
- 저장소에 저장되어 있는 Bean에 접근하기 위해 컨테이너가 제공하는 API를 이용하여 Bean을 Lockup하는 것
- DL(Dependency Injection)
- 의존성 주입
- 각 클래스간의 의존관계를 빈 설정(Bean Definition) 정보를 바탕으로 컨테이너가 자동으로 연결해주는 것
- Setter Injection (수정자 주입)
- Constructor Injection (생성자 주입)
- Method Injection (필드 주입)
- DL 사용시 컨테이너 종속이 증가하기 때문에 주로 DI를 사용한다.
스프링 컨테이너의 종류
- Bean : 스프링 컨테이너가 관리하는 객체.
- 빈들을 관리하는 의미로 컨테이너를 빈 팩토리로 부른다.
- 객체의 생성과 객체 사이의 런타임 관계를 DI 관점에서 볼 때 컨테이너를 빈 팩토리라고 한다.
- 빈 팩토리에 여러가지 컨테이너 기능을 추가한 어플리케이션컨텍스트(ApplicationContext)가 있다.
BeanFactory와 ApplicationContext
- BeanFactory
- BeanFactory 계열의 인터페이스만 구현한 클래스는 단순히 컨테이너에서 객체를 생성하고 DI를 처리하는 기능만 제공한다.
- Bean을 등록, 생성, 조회, 반환 관리를 한다.
- 팩토리 디자인 패턴을 구현한 것으로 BeanFactory는 빈을 생성하고 분배하는 책임을 지는 클래스이다.
- Bean을 조회할 수 있는 getBean() 메소드가 정의되어 있다.
- 보통은 BeanFactory를 바로 사용하지 않고, 이를 확장한 ApplicationContext를 사용한다.
- ApplicationContext
- Bean을 등록, 생성, 조회, 반환 관리하는 기능은 BeanFactory와 같다.
- 스프링의 각종 부가 기능을 추가로 제공한다.
- BeanFactory 보다 더 추가적으로 제공하는 기능
- 국제화가 지원되는 텍스트 메시지를 관리 해준다.
- 이미지같은 파일 자원을 로드할 수 있는 포괄적인 방법을 제공해준다.
- 리스너로 등록된 빈에게 이벤트 발생을 알려준다.
- 따라서 대부분의 어플리케이션에서는 빈팩토리 보다는 어플리케이션콘텍스트를 사용하는 것이 더 좋다.
Bean
- 자바 어플리케이션은 어플리케이션 동작을 제공하는 객체들로 이루어져 있다. 이때 객체들은 독립적으로 동작하는 것 보다 서로 상호작용하여 동작하는 경우가 많다. 이렇게 상호작용하는 객체를 '객체의 의존성'이라고 표현한다.
- 스프링에서 스프링 컨테이너에 객체들을 생성하면 객체끼리 의존성을 주입(DI)하는 역할을 해준다.
- 이때 스프링 컨테이너에 등록한 객체들을 빈 이라고 한다.
스프링 컨테이너에 Bean을 등록하는 두 가지 방법
1. 컴포넌트 스캔과 자동 의존관계 설정
- 스프링에서 사용자 클래스를 스프링 빈으로 등록하는 가장 쉬운 방법은 클래스 선언부 위에 @Component 어노테이션을 사용하는 것이다.
- @Controller, @Service, @Repository 세 가지 모두 @Component를 포함하고 있으며, 위에 네 가지 모두 스프링 컨테이너에 의해 자동으로 생성되어 스프링 빈으로 등록된다.
2. 자바 코드로 직접 스프링 빈 등록
- 수동으로 스프링 빈을 등록하려면 자바 설정 클래스를 만들어 사용해야 한다.
- 클래스 선언부 위에 @Configuration 어노테이션을 클래스 선언부 위에 추가하고, 특정 타입을 리턴하는 메소드를 만들고, @Bean 어노테이션을 붙여주면 자동으로 해당 타입의 빈 객체가 생성된다.
의존성 주입
1. @Autowired 어노테이션
- 스프링은 @Autowired 어노테이션을 이용한 다양한 의존성 주입 방법을 제공한다. 이를 통해 객체 간의 결합도를 줄이도 코드의 재활용성을 높일 수 있다. 이때 @Autowired는 스프링에게 의존성을 주입하는 지시자 역할로 쓰인다.
의존성 주입을 해야하는 이유
- 테스트가 용이하다.
- 코드의 재사용성을 높여준다.
- 객체 간의 의존성(종속성)을 줄이거나 없앨 수 있다.
- 객체 간의 결합도를 낮추면서 유연한 코드를 작성할 수 있다.
의존성 주입의 3가지 방법
- 생성자 주입(Constructor Injection)
@Controller
public class TestController {
//final을 붙일 수 있음
private final TestService testService;
//@Autowired
public TestController(TestService testService) {
this.testService = testService;
}
}
- 클래스의 생성자가 하나이고, 그 생성자로 주입받을 객체가 빈으로 등록되어 있자면 @Autowired를 생략할 수 있다.
- 스프링 프레임워크에서 가장 권장하는 방법.
- 필드 주입(Field Injection)
@Controller
public class TestController {
@Autowired
private TestService testService;
}
- 필드에 @Autowired 어노테이션만 붙여주면 자동으로 의존성 주입된다.
- 가장 많이 쓰인다.
- 단점
- 코드가 간결하지만, 외부에서 변경하기 힘들다.
- 프레임워크에 의존적이고 객체지향적으로 좋지 않다.
- 수정자 주입(Setter Injection)
@Controller
public class TestController {
private TestService testService;
@Autowired
public void setTestService(TestService testService) {
this.testService = testService;
}
}
- Setter 메소드에 @Autowired 어노테이션을 붙이는 방법이다.
- 단점
- 수정자 주입을 사용하면 setter를 public으로 열어두어야 하기 때문에 언제 어디서든 변경이 가능하다.
가장 좋은 주입 방식
- 스프링 프레임워크에서 권장하는 방법은 생성자 주입 방법이다.
- 아래와 같은 이유로 생성자 주입의 사용을 권장한다.
- 순환 참조를 방지할 수 있다.
- 개발을 하다 보면 여러 컴포넌트 간에 의존성이 생긴다.
- 만약, A가 B를 참조하고, B가 A를 참조한다고 가정하면, 필드 주입과 수정자 주입은 빈이 생성된 후에 참조를 하기 때문에 어플리케이션이 아무런 오류, 경고 없이 구동된다. -> 실제 코드가 호출될 때까지 문제를 알 수 없다.
- 하지만 생성자 주입은 실행하면
BeanCurrentlyInCreationException
이 발생하게 된다. - 순환 참조 뿐만 아니라 의존 관계에 내용을 외부로 노출 시킴으로서 어플리케이션을 실행하는 시점에서 오류를 체크할 수 있다.
- 불변성(Immutability)
- 생성자로 의존성을 주입할 때 final로 선언할 수 있고, 이 때문에 런타임에서 의존성을 주입받는 객체가 변할 일이 없어지게 된다.
- 하지만 수정자 주입이나 일반 메소드 주입을 이용하게 되면 불필요하게 수정의 가능성을 열어두게 되어서 OOP의 원칙 중 OCP(계방-폐쇄의 원칙)를 위반하게 된다.
- 따라서 생성자 주입을 통해 변경의 가능성을 배제하고 불변성을 보장하는 것이 좋다.
- 또한 필드 주입은 null이 만들어질 가능성이 있는데, final로 선언한 생성자 주입은 null이 불가능하다.
- 테스트에 용이하다.
- 생성자 주입을 사용하게 되면 테스트 코드를 좀 더 편리하게 작성할 수 있다.
- 독립적으로 인스턴스화가 가능한 POJO를 사용하면, DI 컨테이너 없이도 의존성을 주입하여 사용할 수 있다.
- 이를 통해 코드 가독성이 높아지며, 유지보수가 용이하고 테스트의 격리성과 예측 가능성을 높일 수 있다는 장점이 생기게 된다.
2. @RequiredArgsConstructor 어노테이션
- @Autowired 의 생성자 주입의 단점은 생성자를 만들기 번거롭다는 것이다.
- 이를 보완하기 위해 lombok을 사용하여 간단한 방법으로 생성자 주입을 할 수 있다.
- @RequiredArgsConstructor 어노테이션은 final이 붙거나 @NotNull이 붙은 필드의 생성자를 자동 생성해줘서 의존성 주입해주는 어노테이션이다.
@RequiredArgsConstructor
public class TestController {
private final TestService testService;
}
AOP (Aspect-Oriented Programming)
- 관심지향 프로그래밍으로, OOP를 돕는 보조적인 기술이며, 관심사의 분리(기능의 분리)의 문제를 해결하기 위해 만들어진 프로그래밍 패러다임이다.
- 기능을 핵심 관심 사항과 공통 관심 사항으로 분리시키고 각각을 모듈화하는 것을 의미.
- 핵심 기능 : 업무 로직을 포함하는 기능
- 부가 기능 : 핵심 기능을 도와주는 부가적인 기능
- OOP를 적용하여도 핵심 기능에서 부가 기능을 쉽게 분리된 모듈로 작성하기 어려운 문제점을 AOP가 해결해준다.
- 부가 기능을 애스펙트(Aspect)로 정의하여, 핵심 기능에서 부가 기능을 분리함으로써 핵심 기능을 설계하고 구현할 때 객체지향적인 가치를 지킬 수 있게 도와주는 개념이다.
- 필수적이지만 어쩔 수 없이 반복적으로 사용되는 코드들을 리팩토링할 수 있도록 해준다.
- 이로인해 여러 곳에서 사용될만한 코드들이 한 곳에서 유지하고 관리할 수 있는 이점을 갖게 된다.
AOP의 특징
- 프록시 패턴 기반
- 스프링은 타겟(Target) 객체에 대한 프록시를 만들어서 제공한다.
- 타겟을 감싸는 프록시는 실행시간(RunTime)에 생성된다.
- 프록시는 어드바이스(Advice)를 타겟 객체에 적용하면서 생성되는 객체
- 프록시 객체를 쓰는 이유는 접근 제어 및 부가기능을 추가하기 위함이다.
- 프록시가 호출을 가로챔(Intercept)
- 프록시는 타겟 객체에 대한 호출을 가로챈 다음 Advice의 부가기능 로직을 수행하고난 후에 타겟의 핵심기능 로직을 호출한다.(전처리 어드바이스)
- 타겟의 핵심기능 로직 메소드를 호출한 후에 부가기능을 수행하는 경우도 있다.(후처리 어드바이스)
- 메소드 JoinPoint만 지원한다.
- 스프링은 동적 프록시를 기반으로 AOP를 구현하므로 메소드 조인 포인트만 지원
- 핵심기능(타겟)의 메소드가 호출되는 런타임 시점에만 부가기능(어드바이스)를 적용할 수 있음
- 반면에 AseptJ같은 고급 AOP 프레임워크를 사용하면 객체의 생성, 필드값의 조회와 조작, static 메소드 호출 및 동기화 등의 다양한 작업에 부가기능을 적용할 수 있다.
AOP의 용어
- Target
- 부가기능을 부여할 대상 (핵심기능을 담고 있는 모듈)
- Aspect
- 부가기능 모듈을 Aspect라고 부른다. (핵심기능에 부가되어 의미를 갖는 모듈)
- 부가될 기능을 정의한 Advice와 Advice를 어디에 적용할지를 결정하는 PointCut을 함께 갖고 있다.
- 어플리케이션의 핵심적인 기능에서, 부가적인 기능을 분리해서 Aspect라는 모듈로 만들어서 설계하고 개발하는 방법
- Advice
- 실질적으로 부가기능을 담은 구현체
- 타겟 오브젝트에 종속되지 않기 때문에, 부가기능에만 집중할 수 있음
- Aspect가 무엇을 언제 할지를 정의
- PointCut
- 부가기능이 적용될 대상(Method)을 선정하는 방법
- Advice를 적용할 JoinPoint를 선별하는 기능을 정의한 모듈
- JoinPoint
- Advice가 적용될 수 있는 위치
- Spring에서는 메소드 조인포인트만 제공한다.
- 타겟 객체가 구현한 모든 메소드는 조인 포인트가 된다.
- Proxy
- Target을 감싸서 Target의 요청을 대신 받아주는 랩핑 오브젝트.
- 클라이언트에서 Target을 호출하게되면, 타겟이 아닌 타겟을 감싸고 있는 Proxy가 호출되어,
- 타겟메소드 실행 전에 선처리, 후처리를 실행한다.
- Introduction
- 타겟 클래스에 코드변경없이 신규메소드나 멤버변수를 추가하는 기능
- Weaving
- 지정된 객체에 Aspect를 적용해서, 새로운 프록시 객체를 생성하는 과정
- Spring AOP는 런타임에서 프록시 객체가 생성된다.
AOP 사용 방법
- Gradle 적용
implementation 'org.springframework.boot:spring-boot-starter-aop'
- @EnableAspectJAutoProxy 어노테이션 추가
- 클래스 선언부 이전에 @Aspect 어노테이션(AOP 클래스로 설정)과 @Conponent를 추가해준다.
추가적인 어노테이션
@RestController
- 현재 클래스를 rest api로 만드는 어노테이션이다.
- 반환값은 json 형태로 반환되게 된다.
@RequestMapping
- @RequestMapping(value = "", method = RequestMethod.GET) 형식에, value 안에 요청 url을 넣으면 매핑된다. -> GET은 POST 로 변경도 가능하다.
- @GetMappiing() : @RequestMapping(value = "", method = RequestMethod.GET) 와 같다. -> GET 방식
- @PostMapping() : @RequestMapping(value = "", method = RequestMethod.POST) 와 같다. -> POST 방식
@ResponseBody, @RequestBody
- POST방식은 데이터를 body에 담아서 보내는데 이때 body를 응답(@ResponseBody)받고, 요청(@RequestBody)할 때 사용하는 어노테이션이다.
- @ResponseBody 는 리턴 값이 View 값이 아닌, json, xml 같은 데이터를 반환한다. -> ajax에 쓰인다.
@RequestParam, @PathVariable
- @RequestParam : url
www.aaa.com?name=이름
여기서 name 값을 가져올 때 사용한다.
@GetMapping("/name")
public String getName(@RequestParam("name") String name) { // 사용 예시
}
- @PathVariable : 주로 api url
www.aaa.com/user/이름/33
여기서 name인 이름, age인 33을 가져올 때 사용한다.
@GetMapping("/user/{name}/{age}") // 형태로 매핑한다.
public String getName(@PathVariable("name") String name, @PathVariable("age") int age) { // 사용 예시
}
스프링 MVC 프레임워크
- MVC 디자인 패턴을 따르며 스프링 프레임워크의 특징도 가지고 있다.
1. DispatcherServlet(Front Controller)
- 다른 웹 프레임워크들에서 사용되는 Front Controller의 역할을 한다.
- 가장 앞단에서 클라이언트의 요청을 처리하는 Controller로서 요청부터 응답까지 전반적인 처리 과정을 통제한다.
2. Handler(Controller)
- Spring MVC에서는 Controller=Handler(핸들러)라고 생각하면 편하다. 핸들러는 DispatcherServlet가 전달해준 HTTP 요청을 처리하고 결과를 Model에 저장한다.
3. ModelAndView
- ModelAndView는 controller에 의해 반환된 Model과 View가 Wrapping 된 객체이다. 말 그대로 Model과 View가 같이 들어있는 객체라고 보면 된다.
4. ViewResolver
- ViewResolver는 ModelAndView를 처리하여 View를 그리는 역할을 한다. Model에 저장된 데이터를 사용해 View를 그리고 사용자는 View를 보게 된다.
Spring MVC 동작 순서
- 핸들러 조회 : 핸들러 매핑을 통해 요청 URL에 매핑된 핸들러(컨트롤러)를 조회
- 핸들러 어댑터 조회 : 핸들러를 실행할 수 있는 핸들러 어댑터를 조회한다.
- 핸들러 어댑터 실행 : 핸들러 어댑터를 실행한다.
- 핸들러 실행 : 핸들러 어댑터가 실제 핸들러를 실행한다.
- ModelAndView 반환 : 핸들러 어댑터는 핸들러가 요청을 처리하고 반환하는 정보를 ModelAndView로 변환해서 반환한다.
- viewResolver 호출 : viewResolver를 찾고 실행한다.
- View반환 : viewResolver는 View의 논리 이름을 물리 이름으로 바꾸고, 렌더링 역할을 담당하는 View 객체를 반환한다.
- View 렌더링 : View를 통해서 View를 렌더링한다.
핸들러 매핑 , 핸들러 어댑터
- 핸들러 매핑 : DispatcherServlet이 요청에 맞는 핸들러를 찾는 과정이다.
- 핸들러 어댑터 : 핸들러 매핑에서 찾은 핸들러 객체를 다룰 수 있는 어댑터이다.
- 다양한 핸들러의 return 타입은 String, ModelAndView 등 다양하며, Parameter로 들어오는 값도 HttpServletRequest로 받거나 HttpServletResponse로 받는 등 다양한 형식으로 받을 수 있다.
- 이처럼 다양한 핸들러 구현 방식에 맞춰 개발을 하는 것은 쉽지 않다.
- 따라서 이러한 요구 사항을 충족하기 위해 중간에 Adapter를 두는 어댑터 패턴을 사용하는 것이다.
- 만약 핸들러 어댑터를 사용하면, 핸들러에서 결괏값을 어떻게 반환하든 이를 처리해 DispatcherServlet에게 ModelAndView객체로 통일하여 응답을 전달해 줄 수 있다.
- 스프링은 이미 필요한 핸들러 매핑과 핸들러 어댑터를 대부분 구현해주어서 개발자가 직접 핸들러 매핑과 핸들러 어댑터를 구현하는 일은 거의 없다.
빈 스코프 (Bean Scope)
- 빈이 컨테이너에 의해 관리되는 범위이다.
빈 스코프 종류
- 싱글톤
- 기본 스코프, 스프링 컨테이너의 시작과 종료까지 유지되는 가장 넓은 범위의 스코프이다.
- 프로토타입
- 스프링 컨테이너는 프로토타입 빈의 생성과 의존관계 주입까지만 관여하고 더는 관리하지 않는 매우 짧은 범위의 스코프이다(따라서 빈 콜백중 종료메서드가 호출이 안된다.).
- 클래스 선언부 위에 Scope("prototype") 어노테이션을 추가하면 자동 등록된다.
- 웹 관련 스코프
- request: 웹 요청이 들어오고 나갈때 까지 유지되는 스코프이다.
- session: 웹 세션이 생성되고 종료될 때 까지 유지되는 스코프이다.
- application: 웹의 서블릿 컨텍스와 같은 범위로 유지되는 스코프이다.
다음 게시글 스프링 부트 2
참고 자료
https://dev-coco.tistory.com/80
https://dev-coco.tistory.com/69
https://dev-coco.tistory.com/70
https://dev-coco.tistory.com/81
https://chung-develop.tistory.com/56
https://code-lab1.tistory.com/258
'CS > Spring Boot' 카테고리의 다른 글
[스프링 부트] 스프링 부트 3 (0) | 2023.04.09 |
---|---|
[스프링 부트] 스프링 부트 2 (0) | 2023.04.08 |
댓글