IoC (제어의 역전) → 객체지향의 SOLID 설계원칙
DI (의존성 주입) → GoF 디자인 패턴
김치 볶음밥 맛있게 만드는 방법 (설계 원칙)
김치 볶음밥 레시피 (디자인 패턴)
좋은 코드를 위한 Spring의 IoC와 DI
- 논리가 간단해야 한다.
- 중복을 제거하고 표현을 명확하게 한다.
- 코드를 처음 보는 사람도 쉽게 이해하고 수정할 수 있어야 한다.
- 의존성을 최소화해야 한다.
- 새로운 기능을 추가 하더라도 크게 구조의 변경이 없어야 한다.
→ DI 패턴을 사용하여 IoC 설계 원칙을 구현하여 Spring이 개발자가 Java를 사용하여 쉽게 좋은 코드를 작성할 수 있도록 도와줌
의존성
강하게 결합되어있는 Consumer 와 Chicken
public class Consumer {
void eat() {
Chicken chicken = new Chicken();
chicken.eat();
}
public static void main(String[] args) {
Consumer consumer = new Consumer();
consumer.eat();
}
}
class Chicken {
public void eat() {
System.out.println("치킨을 먹는다.");
}
}
Consumer 가 치킨이 아니라 피자를 먹고 싶어 하는 등
변경이 필요할 때
많은 수의 코드를 변경해야 함
→ 약한 결합, 약한 의존성으로 만들어봄
public class Consumer {
void eat(Food food) {
food.eat();
}
public static void main(String[] args) {
Consumer consumer = new Consumer();
consumer.eat(new Chicken());
consumer.eat(new Pizza());
}
}
interface Food {
void eat();
}
class Chicken implements Food{
@Override
public void eat() {
System.out.println("치킨을 먹는다.");
}
}
class Pizza implements Food{
@Override
public void eat() {
System.out.println("피자를 먹는다.");
}
}
Interface 다형성의 원리를 사용하여 구현하면 고객이 어떠한 음식을 요구하더라도 쉽게 대처
주입
필요로 하는 객체를 해당 객체에 전달하는 것
필드에 직접 주입
public class Consumer {
Food food;
void eat() {
this.food.eat();
}
public static void main(String[] args) {
Consumer consumer = new Consumer();
consumer.food = new Chicken();
consumer.eat();
consumer.food = new Pizza();
consumer.eat();
}
}
interface Food {
void eat();
}
class Chicken implements Food{
@Override
public void eat() {
System.out.println("치킨을 먹는다.");
}
}
class Pizza implements Food{
@Override
public void eat() {
System.out.println("피자를 먹는다.");
}
}
Food를 Consumer에 포함 시키고 Food에 필요한 객체를 주입받아 사용
메서드를 통한 주입
public class Consumer {
Food food;
void eat() {
this.food.eat();
}
public void setFood(Food food) {
this.food = food;
}
public static void main(String[] args) {
Consumer consumer = new Consumer();
consumer.setFood(new Chicken());
consumer.eat();
consumer.setFood(new Pizza());
consumer.eat();
}
}
interface Food {
void eat();
}
class Chicken implements Food{
@Override
public void eat() {
System.out.println("치킨을 먹는다.");
}
}
class Pizza implements Food{
@Override
public void eat() {
System.out.println("피자를 먹는다.");
}
}
set 메서드를 사용하여 필요한 객체를 주입받아 사용
생성자를 통한 주입
public class Consumer {
Food food;
public Consumer(Food food) {
this.food = food;
}
void eat() {
this.food.eat();
}
public static void main(String[] args) {
Consumer consumer = new Consumer(new Chicken());
consumer.eat();
consumer = new Consumer(new Pizza());
consumer.eat();
}
}
interface Food {
void eat();
}
class Chicken implements Food{
@Override
public void eat() {
System.out.println("치킨을 먹는다.");
}
}
class Pizza implements Food{
@Override
public void eat() {
System.out.println("피자를 먹는다.");
}
}
생성자를 사용하여 필요한 객체를 주입받아 사용
제어의 역전
이전에는 Consumer가 직접 Food를 만들어 먹었기 때문에
새로운 Food를 먹으려면 추가적인 코드변경이 필요했음
→
Food를 Consumer에게 주입해서 코드 변경 없이 어느 Food든 먹을 수 있게 됨
이를 제어의 흐름이 Food → Consumer 로 역전되었다고 한다
Consumer는 가만히 있고 외부에서 Food를 만들어서 주입하니까
메모장 프로젝트의 IoC & DI
하기 전에
! 잠깐 !
지금 메모장 프로젝트 코드가 가지고 있는 객체 중복 생성 문제를 먼저 해결
new MemoRepository(jdbcTemplate); 코드가 중복되고 있음
public class MemoService {
private final JdbcTemplate jdbcTemplate;
public MemoService(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public MemoResponseDto createMemo(MemoRequestDto requestDto) {
// DB 저장
MemoRepository memoRepository = new MemoRepository(jdbcTemplate);
...
}
public List<MemoResponseDto> getMemos() {
// DB 조회
MemoRepository memoRepository = new MemoRepository(jdbcTemplate);
...
}
public Long updateMemo(Long id, MemoRequestDto requestDto) {
MemoRepository memoRepository = new MemoRepository(jdbcTemplate);
...
}
public Long deleteMemo(Long id) {
MemoRepository memoRepository = new MemoRepository(jdbcTemplate);
...
}
}
→ 객체가 생성될때 한번만 만들어서 같이 쓰면 훨씬 효율적
기존 코드
private final JdbcTemplate jdbcTemplate;
public MemoService(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public MemoResponseDto createMemo(MemoRequestDto requestDto) {
// DB 저장
**MemoRepository memoRepository = new MemoRepository(jdbcTemplate);
Memo saveMemo = memoRepository.save(memo);**
...
}
→
바뀐 코드
public class MemoService {
private final MemoRepository memoRepository;
public MemoService(JdbcTemplate jdbcTemplate) {
this.memoRepository = new MemoRepository(jdbcTemplate);
}
public MemoResponseDto createMemo(MemoRequestDto requestDto) {
// DB 저장
**Memo saveMemo = memoRepository.save(requestDto);
...**
}
...
}
MemoController의 이 부분도 개선
@RestController
@RequestMapping("/api")
public class MemoController {
private final JdbcTemplate jdbcTemplate;
public MemoController(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
@PostMapping("/memos")
public MemoResponseDto createMemo(@RequestBody MemoRequestDto requestDto) {
MemoService memoService = new MemoService(jdbcTemplate);
return memoService.createMemo(requestDto);//클라이언트에서 전달받은 requestDto도 Service에게 사용하라고 보내줘야 함
}
@GetMapping("/memos")
public List<MemoResponseDto> getMemos() {
MemoService memoService = new MemoService(jdbcTemplate);
return memoService.getMemos();
}
@PutMapping("/memos/{id}")
public Long updateMemo(@PathVariable Long id, @RequestBody MemoRequestDto requestDto) {
MemoService memoService = new MemoService(jdbcTemplate);
return memoService.updateMemo(id,requestDto);
}
@DeleteMapping("/memos/{id}")
public Long deleteMemo(@PathVariable Long id) {
MemoService memoService = new MemoService(jdbcTemplate);
return memoService.deleteMemo(id);
}
}
→
public class MemoController {
private final MemoService memoService;
public MemoController(JdbcTemplate jdbcTemplate) {
memoService = new MemoService(jdbcTemplate);
}
@PostMapping("/memos")
public MemoResponseDto createMemo(@RequestBody MemoRequestDto requestDto) {
return memoService.createMemo(requestDto);//클라이언트에서 전달받은 requestDto도 Service에게 사용하라고 보내줘야 함
}
@GetMapping("/memos")
public List<MemoResponseDto> getMemos() {
return memoService.getMemos();
}
@PutMapping("/memos/{id}")
public Long updateMemo(@PathVariable Long id, @RequestBody MemoRequestDto requestDto) {
return memoService.updateMemo(id,requestDto);
}
@DeleteMapping("/memos/{id}")
public Long deleteMemo(@PathVariable Long id) {
return memoService.deleteMemo(id);
}
}
메모장 프로젝트의 강한 결합상태 개선
강한 결합 문제상황(예시)
- Contoller1 이 Service1 객체를 생성하여 사용
public class Controller1 {
private final Service1 service1;
public Controller1() {
this.service1 = new Service1();
}
}
- Service1 이 Repostiroy1 객체를 생성하여 사용
public class Service1 {
private final Repository1 repository1;
public Service1() {
this.repository1 = new Repository1();
}
}
- Repostiroy1 객체 선언
public class Repository1 { ... }
이 상황에서 다음과 같이 변경된다면 어떨까?
Repository1 객체 생성 시 DB 접속 id, pw 를 받아서 DB 접속 시 사용
(생성자에 DB 접속 id, pw 를 추가)
public class Repository1 {
public Repository1(String id, String pw) {
// DB 연결
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/memo", id, pw);
}
}
강한 결합의 문제점
Controller 5 개가 각각 Service1 을 생성하여 사용 중일 때,
Repository1 생성자가 변경되면
모든 Contoller 와 모든 Service 의 코드 변경이 필요함
강한 결합 해결방법
- 각 객체에 대한 객체 생성은 딱 1번만
- 생성된 객체를 모든 곳에서 재사용
- 필요한 객체에 생성자 주입으로 해당 객체 주입
- Repository1 클래스 선언 및 객체 생성 → repository1
public class Repository1 { ... }
// 객체 생성
**Repository1 repository1 = new Repository1();**
- Service1 클래스 선언 및 객체 생성 (repostiroy1 사용) → service1
Class Service1 {
private final Repository1 repository1;
// repository1 객체 사용
public Service1(Repository1 repository1) {
~~this.repository1 = new Repository1();~~
this.repository1 = repository1;
}
}
// 객체 생성
**Service1 service1 = new Service1(repository1);**
- Contoller1 선언 ( service1 사용)
Class Controller1 {
private final Service1 service1;
// service1 객체 사용
public Controller1(Service1 service1) {
~~this.service1 = new Service1();~~
this.service1 = service1;
}
}
다음과 같이 변경됨 →
Repository1 객체 생성 시 DB 접속 id, pw 를 받아서 DB 접속 시 사용
생성자에 DB 접속 id, pw 를 추가
public class Repository1 {
public Repository1(**String id, String pw**) {
// DB 연결
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/memo", **id, pw**);
}
}
// 객체 생성
**String id = "root";
String pw = "1234";**
Repository1 repository1 = new Repository1(**id, pw**);
[개선 결과]
Repository1 생성자 변경이 적은 코드 수정만으로 ok
Service1 생성자가 변경되면? 모든 Controller 변경 필요 X
문제 상황
!메모장 프로젝트에서도 동일한 문제 발생중이었음!
public MemoController(JdbcTemplate jdbcTemplate) {
this.memoService = new MemoService(jdbcTemplate);
}
...
public MemoService(JdbcTemplate jdbcTemplate) {
this.memoRepository = new MemoRepository(jdbcTemplate);
}
문제 상황
- MemoService에서 자신은 사용하지 않지만 MemoRepository를 사용하기 위해 MemoRepository의 생성자에 JdbcTemplate을 넣어주고 있다.
- MemoController에서도 MemoService를 사용해야하기 때문에 생성자에 JdbcTemplate을 넣어주고 있다.
코드 따라 짜면서 왜 jdbcTemplate넘기고 또 넘기는 형식으로 하는지 그러는 이유가 있었는지 궁금했었는데 여기서 풀리네…
문제 해결
public class MemoController {
private final MemoService memoService;
public MemoController(JdbcTemplate jdbcTemplate) {
memoService = new MemoService(jdbcTemplate);
}
→
public class MemoController {
private final MemoService memoService;
public MemoController(MemoService memoService) {
this.memoService = memoService;
}
public class MemoService {
private final JdbcTemplate jdbcTemplate;
private final MemoRepository memoRepository;
public MemoService(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
this.memoRepository = new MemoRepository(jdbcTemplate);
}
→
public class MemoService {
private final MemoRepository memoRepository;
public MemoService(MemoRepository memoRepository) {
this.memoRepository = memoRepository;
}
이제는 더 이상 MemoController와 MemoService에서 사용하지도 않는 JdbcTemplate을 주입 받아와 MemoRepository애 주입해줄 필요가 없어졌다.
코드 수정하면서 제어의 흐름이 반대가 된다는게 이런 뜻인거 같다고 어렴풋이 느꼈다:
예전에는
Controller만들면 그 안에서 Service만들고 Service안에서 Repository만들었었는데
이제 Repository를 만들어서 Service생성자에 넣고 그걸 또 Controller생성자에 넣음
반대가 됨
근데 Repository를 만들어서 Service생성자에 넣고 그걸 또 Controller생성자에 넣는 걸
누가 언제 어디서 해주는거지? → 다음 강의
**IoC: Inversion of Control(**제어의 역전)
프로그램의 제어 흐름이 뒤바뀜
제어의 흐름 of 강한 결합 상태의 메모장 프로젝트
Controller ⇒ Service ⇒ Repository
Controller에서 Service를 만들고 Service에서 Repository를 만듦
Consumer가 직접 음식을 만들어 먹었을 때와 같다
제어의 흐름 of DI(의존성 주입)후 메모장 프로젝트
Repository ⇒ Service ⇒ Controller
Food가 만들어져서 Consumer에게 주입당했을때랑 같음
Spring IoC 컨테이너
DI를 사용하기 위해서는 객체를 우선 만들어야 함
그러면 누가 그걸 해줌?
Spring 프레임워크가 필요한 객체를 생성하고 관리하는 역할을 대신 해줌
Bean :
Spring이 관리하는 객체
Spring IoC 컨테이너 :
Bean을 모아둔 컨테이너
Spring Bean 등록 방법
@Component
‘Bean’으로 등록하고자하는 클래스 위에 설정
→Spring 서버가 뜰 때 IoC 컨테이너에 'Bean'을 저장
@Component
public class MemoService { ... }
@Component
public class MemoRepository {...}
MemoRepository에 @Component다니까 빨간 줄 사라짐
MemoApplication → @SpringBootApplication → ctrl + click → SpringBootApplication.java
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
@ComponentScan : Component를 찾아주는 annotation
이게 달린 패키지와 하위 패키지를 전부 확인한다고 함
Component annotation이 설정되어있는 클래스들을 전부 찾아서 Bean으로 등록을 한다고 함
@SpringBootApplication에 의해 default 설정 되어있습니다.
com.sparta.memo/MemoApplication.java
Spring 'Bean' 이름: 클래스의 앞글자만 소문자로 변경
public class MemoService → memoService
'Bean' 아이콘 → 스프링 IoC 에서 관리할 'Bean' 클래스라는 표시
// 1. MemoService 객체 생성
MemoService memoService = new MemoService();
// 2. Spring IoC 컨테이너에 Bean (memoService) 저장
// memoService -> Spring IoC 컨테이너
Spring 'Bean' 사용 방법
@Autowired
필드 위에 @Autowired
@Component
public class MemoService {
**@Autowired**
private MemoRepository memoRepository;
// ...
}
Spring에서 IoC 컨테이너에 저장된 memoRepository ‘Bean’을 해당 필드에 DI (의존성 주입)
메서드 위에 @Autowired
@Component
public class MemoService {
private final MemoRepository memoRepository;
**@Autowired**
public MemoService(MemoRepository memoRepository) {
this.memoRepository = memoRepository;
}
// ...
}
객체의 불변성을 확보하기 위해 → final로
생성자에 DI(@Autowired를 적용)하는 것이 좋다.
Setter로 DI(@Autowired) 할 시 final 못 씀
private에도 autowired 달면 Spring이 알아서 주입해줌, 추천하지 않는 방법
이유는 나중에 알게된다 함
강의에서는 그냥 생성자로 주입
@Autowired 적용 조건
Spring IoC 컨테이너에 의해 관리되는 클래스에서만 가능
Spring IoC 컨테이너에 의해 관리되는 ‘Bean’객체만 DI에 사용가능
@Autowired 생략 조건
Spring 4.3 버젼 부터 생성자 선언이 1개 일 때 @Autowired 생략가능
public class A {
//생략 가능
public A(B b) { ... }
}
오버로딩된 생성자들
public class A {
@Autowired // 생략 불가
public A(B b) { ... }
@Autowired // 생략 불가
public A(B b, C c) { ... }
}
Lombok 의 @RequiredArgsConstructor를 사용하면 다음과 같이 코딩 가능
@Component
**@RequiredArgsConstructor** // final로 선언된 멤버 변수를 파라미터로 사용하여 생성자를 자동으로 생성합니다.
public class MemoService {
private final MemoRepository memoRepository;
// public MemoService(MemoRepository memoRepository) {
// this.memoRepository = memoRepository;
// }
...
}
- ApplicationContextBeanFactory : ‘Bean’ 의 생성, 관계설정등의 제어를 담당하는 IoC 객체
- 스프링 IoC 컨테이너에서 ‘Bean’을 수동으로 가져오는 방법
- ApplicationContext는 BeanFactory등을 상속하여 기능을 확장한 Container
public MemoService(ApplicationContext context) {
//1. Bean 이름으로 가져오기
MemoRepository memoRepository = (MemoRepository) context.getBean("memoRepository");
//2. Bean 클래스 형식으로 가져오기
MemoRepository memoRepository = (MemoRepository) context.getBean(MemoRepository.class);
this.memoRepository=memoRepository;
}
3 Layer Annotation
- Spring 3 Layer Annotation은 Controller, Service, Repository의 역할로 구분된 클래스들을 ‘Bean’으로 등록할 때 해당 ‘Bean’ 클래스의 역할을 명시하기위해 사용됩니다.
- @Controller, @RestController
- @Service
- @Repository
- 앞으로는 @Component가 아닌 3 Layer Annotation을 사용해서 ‘Bean’으로 등록하겠습니다.
<aside> 💡 Spring 3 Layer Annotation은 모두 @Component가 추가되어있습니다
</aside>
IoC (제어의 역전) → 객체지향의 SOLID 설계원칙
DI (의존성 주입) → GoF 디자인 패턴
김치 볶음밥 맛있게 만드는 방법 (설계 원칙)
김치 볶음밥 레시피 (디자인 패턴)
좋은 코드를 위한 Spring의 IoC와 DI
- 논리가 간단해야 한다.
- 중복을 제거하고 표현을 명확하게 한다.
- 코드를 처음 보는 사람도 쉽게 이해하고 수정할 수 있어야 한다.
- 의존성을 최소화해야 한다.
- 새로운 기능을 추가 하더라도 크게 구조의 변경이 없어야 한다.
→ DI 패턴을 사용하여 IoC 설계 원칙을 구현하여 Spring이 개발자가 Java를 사용하여 쉽게 좋은 코드를 작성할 수 있도록 도와줌
의존성
강하게 결합되어있는 Consumer 와 Chicken
public class Consumer {
void eat() {
Chicken chicken = new Chicken();
chicken.eat();
}
public static void main(String[] args) {
Consumer consumer = new Consumer();
consumer.eat();
}
}
class Chicken {
public void eat() {
System.out.println("치킨을 먹는다.");
}
}
Consumer 가 치킨이 아니라 피자를 먹고 싶어 하는 등
변경이 필요할 때
많은 수의 코드를 변경해야 함
→ 약한 결합, 약한 의존성으로 만들어봄
public class Consumer {
void eat(Food food) {
food.eat();
}
public static void main(String[] args) {
Consumer consumer = new Consumer();
consumer.eat(new Chicken());
consumer.eat(new Pizza());
}
}
interface Food {
void eat();
}
class Chicken implements Food{
@Override
public void eat() {
System.out.println("치킨을 먹는다.");
}
}
class Pizza implements Food{
@Override
public void eat() {
System.out.println("피자를 먹는다.");
}
}
Interface 다형성의 원리를 사용하여 구현하면 고객이 어떠한 음식을 요구하더라도 쉽게 대처
주입
필요로 하는 객체를 해당 객체에 전달하는 것
필드에 직접 주입
public class Consumer {
Food food;
void eat() {
this.food.eat();
}
public static void main(String[] args) {
Consumer consumer = new Consumer();
consumer.food = new Chicken();
consumer.eat();
consumer.food = new Pizza();
consumer.eat();
}
}
interface Food {
void eat();
}
class Chicken implements Food{
@Override
public void eat() {
System.out.println("치킨을 먹는다.");
}
}
class Pizza implements Food{
@Override
public void eat() {
System.out.println("피자를 먹는다.");
}
}
Food를 Consumer에 포함 시키고 Food에 필요한 객체를 주입받아 사용
메서드를 통한 주입
public class Consumer {
Food food;
void eat() {
this.food.eat();
}
public void setFood(Food food) {
this.food = food;
}
public static void main(String[] args) {
Consumer consumer = new Consumer();
consumer.setFood(new Chicken());
consumer.eat();
consumer.setFood(new Pizza());
consumer.eat();
}
}
interface Food {
void eat();
}
class Chicken implements Food{
@Override
public void eat() {
System.out.println("치킨을 먹는다.");
}
}
class Pizza implements Food{
@Override
public void eat() {
System.out.println("피자를 먹는다.");
}
}
set 메서드를 사용하여 필요한 객체를 주입받아 사용
생성자를 통한 주입
public class Consumer {
Food food;
public Consumer(Food food) {
this.food = food;
}
void eat() {
this.food.eat();
}
public static void main(String[] args) {
Consumer consumer = new Consumer(new Chicken());
consumer.eat();
consumer = new Consumer(new Pizza());
consumer.eat();
}
}
interface Food {
void eat();
}
class Chicken implements Food{
@Override
public void eat() {
System.out.println("치킨을 먹는다.");
}
}
class Pizza implements Food{
@Override
public void eat() {
System.out.println("피자를 먹는다.");
}
}
생성자를 사용하여 필요한 객체를 주입받아 사용
제어의 역전
이전에는 Consumer가 직접 Food를 만들어 먹었기 때문에
새로운 Food를 먹으려면 추가적인 코드변경이 필요했음
→
Food를 Consumer에게 주입해서 코드 변경 없이 어느 Food든 먹을 수 있게 됨
이를 제어의 흐름이 Food → Consumer 로 역전되었다고 한다
Consumer는 가만히 있고 외부에서 Food를 만들어서 주입하니까
메모장 프로젝트의 IoC & DI
하기 전에
! 잠깐 !
지금 메모장 프로젝트 코드가 가지고 있는 객체 중복 생성 문제를 먼저 해결
new MemoRepository(jdbcTemplate); 코드가 중복되고 있음
public class MemoService {
private final JdbcTemplate jdbcTemplate;
public MemoService(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public MemoResponseDto createMemo(MemoRequestDto requestDto) {
// DB 저장
MemoRepository memoRepository = new MemoRepository(jdbcTemplate);
...
}
public List<MemoResponseDto> getMemos() {
// DB 조회
MemoRepository memoRepository = new MemoRepository(jdbcTemplate);
...
}
public Long updateMemo(Long id, MemoRequestDto requestDto) {
MemoRepository memoRepository = new MemoRepository(jdbcTemplate);
...
}
public Long deleteMemo(Long id) {
MemoRepository memoRepository = new MemoRepository(jdbcTemplate);
...
}
}
→ 객체가 생성될때 한번만 만들어서 같이 쓰면 훨씬 효율적
기존 코드
private final JdbcTemplate jdbcTemplate;
public MemoService(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public MemoResponseDto createMemo(MemoRequestDto requestDto) {
// DB 저장
**MemoRepository memoRepository = new MemoRepository(jdbcTemplate);
Memo saveMemo = memoRepository.save(memo);**
...
}
→
바뀐 코드
public class MemoService {
private final MemoRepository memoRepository;
public MemoService(JdbcTemplate jdbcTemplate) {
this.memoRepository = new MemoRepository(jdbcTemplate);
}
public MemoResponseDto createMemo(MemoRequestDto requestDto) {
// DB 저장
**Memo saveMemo = memoRepository.save(requestDto);
...**
}
...
}
MemoController의 이 부분도 개선
@RestController
@RequestMapping("/api")
public class MemoController {
private final JdbcTemplate jdbcTemplate;
public MemoController(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
@PostMapping("/memos")
public MemoResponseDto createMemo(@RequestBody MemoRequestDto requestDto) {
MemoService memoService = new MemoService(jdbcTemplate);
return memoService.createMemo(requestDto);//클라이언트에서 전달받은 requestDto도 Service에게 사용하라고 보내줘야 함
}
@GetMapping("/memos")
public List<MemoResponseDto> getMemos() {
MemoService memoService = new MemoService(jdbcTemplate);
return memoService.getMemos();
}
@PutMapping("/memos/{id}")
public Long updateMemo(@PathVariable Long id, @RequestBody MemoRequestDto requestDto) {
MemoService memoService = new MemoService(jdbcTemplate);
return memoService.updateMemo(id,requestDto);
}
@DeleteMapping("/memos/{id}")
public Long deleteMemo(@PathVariable Long id) {
MemoService memoService = new MemoService(jdbcTemplate);
return memoService.deleteMemo(id);
}
}
→
public class MemoController {
private final MemoService memoService;
public MemoController(JdbcTemplate jdbcTemplate) {
memoService = new MemoService(jdbcTemplate);
}
@PostMapping("/memos")
public MemoResponseDto createMemo(@RequestBody MemoRequestDto requestDto) {
return memoService.createMemo(requestDto);//클라이언트에서 전달받은 requestDto도 Service에게 사용하라고 보내줘야 함
}
@GetMapping("/memos")
public List<MemoResponseDto> getMemos() {
return memoService.getMemos();
}
@PutMapping("/memos/{id}")
public Long updateMemo(@PathVariable Long id, @RequestBody MemoRequestDto requestDto) {
return memoService.updateMemo(id,requestDto);
}
@DeleteMapping("/memos/{id}")
public Long deleteMemo(@PathVariable Long id) {
return memoService.deleteMemo(id);
}
}
메모장 프로젝트의 강한 결합상태 개선
강한 결합 문제상황(예시)
- Contoller1 이 Service1 객체를 생성하여 사용
public class Controller1 {
private final Service1 service1;
public Controller1() {
this.service1 = new Service1();
}
}
- Service1 이 Repostiroy1 객체를 생성하여 사용
public class Service1 {
private final Repository1 repository1;
public Service1() {
this.repository1 = new Repository1();
}
}
- Repostiroy1 객체 선언
public class Repository1 { ... }
이 상황에서 다음과 같이 변경된다면 어떨까?
Repository1 객체 생성 시 DB 접속 id, pw 를 받아서 DB 접속 시 사용
(생성자에 DB 접속 id, pw 를 추가)
public class Repository1 {
public Repository1(String id, String pw) {
// DB 연결
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/memo", id, pw);
}
}
강한 결합의 문제점
Controller 5 개가 각각 Service1 을 생성하여 사용 중일 때,
Repository1 생성자가 변경되면
모든 Contoller 와 모든 Service 의 코드 변경이 필요함
강한 결합 해결방법
- 각 객체에 대한 객체 생성은 딱 1번만
- 생성된 객체를 모든 곳에서 재사용
- 필요한 객체에 생성자 주입으로 해당 객체 주입
- Repository1 클래스 선언 및 객체 생성 → repository1
public class Repository1 { ... }
// 객체 생성
**Repository1 repository1 = new Repository1();**
- Service1 클래스 선언 및 객체 생성 (repostiroy1 사용) → service1
Class Service1 {
private final Repository1 repository1;
// repository1 객체 사용
public Service1(Repository1 repository1) {
~~this.repository1 = new Repository1();~~
this.repository1 = repository1;
}
}
// 객체 생성
**Service1 service1 = new Service1(repository1);**
- Contoller1 선언 ( service1 사용)
Class Controller1 {
private final Service1 service1;
// service1 객체 사용
public Controller1(Service1 service1) {
~~this.service1 = new Service1();~~
this.service1 = service1;
}
}
다음과 같이 변경됨 →
Repository1 객체 생성 시 DB 접속 id, pw 를 받아서 DB 접속 시 사용
생성자에 DB 접속 id, pw 를 추가
public class Repository1 {
public Repository1(**String id, String pw**) {
// DB 연결
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/memo", **id, pw**);
}
}
// 객체 생성
**String id = "root";
String pw = "1234";**
Repository1 repository1 = new Repository1(**id, pw**);
[개선 결과]
Repository1 생성자 변경이 적은 코드 수정만으로 ok
Service1 생성자가 변경되면? 모든 Controller 변경 필요 X
문제 상황
!메모장 프로젝트에서도 동일한 문제 발생중이었음!
public MemoController(JdbcTemplate jdbcTemplate) {
this.memoService = new MemoService(jdbcTemplate);
}
...
public MemoService(JdbcTemplate jdbcTemplate) {
this.memoRepository = new MemoRepository(jdbcTemplate);
}
문제 상황
- MemoService에서 자신은 사용하지 않지만 MemoRepository를 사용하기 위해 MemoRepository의 생성자에 JdbcTemplate을 넣어주고 있다.
- MemoController에서도 MemoService를 사용해야하기 때문에 생성자에 JdbcTemplate을 넣어주고 있다.
코드 따라 짜면서 왜 jdbcTemplate넘기고 또 넘기는 형식으로 하는지 그러는 이유가 있었는지 궁금했었는데 여기서 풀리네…
문제 해결
public class MemoController {
private final MemoService memoService;
public MemoController(JdbcTemplate jdbcTemplate) {
memoService = new MemoService(jdbcTemplate);
}
→
public class MemoController {
private final MemoService memoService;
public MemoController(MemoService memoService) {
this.memoService = memoService;
}
public class MemoService {
private final JdbcTemplate jdbcTemplate;
private final MemoRepository memoRepository;
public MemoService(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
this.memoRepository = new MemoRepository(jdbcTemplate);
}
→
public class MemoService {
private final MemoRepository memoRepository;
public MemoService(MemoRepository memoRepository) {
this.memoRepository = memoRepository;
}
이제는 더 이상 MemoController와 MemoService에서 사용하지도 않는 JdbcTemplate을 주입 받아와 MemoRepository애 주입해줄 필요가 없어졌다.
코드 수정하면서 제어의 흐름이 반대가 된다는게 이런 뜻인거 같다고 어렴풋이 느꼈다:
예전에는
Controller만들면 그 안에서 Service만들고 Service안에서 Repository만들었었는데
이제 Repository를 만들어서 Service생성자에 넣고 그걸 또 Controller생성자에 넣음
반대가 됨
근데 Repository를 만들어서 Service생성자에 넣고 그걸 또 Controller생성자에 넣는 걸
누가 언제 어디서 해주는거지? → 다음 강의
**IoC: Inversion of Control(**제어의 역전)
프로그램의 제어 흐름이 뒤바뀜
제어의 흐름 of 강한 결합 상태의 메모장 프로젝트
Controller ⇒ Service ⇒ Repository
Controller에서 Service를 만들고 Service에서 Repository를 만듦
Consumer가 직접 음식을 만들어 먹었을 때와 같다
제어의 흐름 of DI(의존성 주입)후 메모장 프로젝트
Repository ⇒ Service ⇒ Controller
Food가 만들어져서 Consumer에게 주입당했을때랑 같음
Spring IoC 컨테이너
DI를 사용하기 위해서는 객체를 우선 만들어야 함
그러면 누가 그걸 해줌?
Spring 프레임워크가 필요한 객체를 생성하고 관리하는 역할을 대신 해줌
Bean :
Spring이 관리하는 객체
Spring IoC 컨테이너 :
Bean을 모아둔 컨테이너
Spring Bean 등록 방법
@Component
‘Bean’으로 등록하고자하는 클래스 위에 설정
→Spring 서버가 뜰 때 IoC 컨테이너에 'Bean'을 저장
@Component
public class MemoService { ... }
@Component
public class MemoRepository {...}
MemoRepository에 @Component다니까 빨간 줄 사라짐
MemoApplication → @SpringBootApplication → ctrl + click → SpringBootApplication.java
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
@ComponentScan : Component를 찾아주는 annotation
이게 달린 패키지와 하위 패키지를 전부 확인한다고 함
Component annotation이 설정되어있는 클래스들을 전부 찾아서 Bean으로 등록을 한다고 함
@SpringBootApplication에 의해 default 설정 되어있습니다.
com.sparta.memo/MemoApplication.java
Spring 'Bean' 이름: 클래스의 앞글자만 소문자로 변경
public class MemoService → memoService
'Bean' 아이콘 → 스프링 IoC 에서 관리할 'Bean' 클래스라는 표시
// 1. MemoService 객체 생성
MemoService memoService = new MemoService();
// 2. Spring IoC 컨테이너에 Bean (memoService) 저장
// memoService -> Spring IoC 컨테이너
Spring 'Bean' 사용 방법
@Autowired
필드 위에 @Autowired
@Component
public class MemoService {
**@Autowired**
private MemoRepository memoRepository;
// ...
}
Spring에서 IoC 컨테이너에 저장된 memoRepository ‘Bean’을 해당 필드에 DI (의존성 주입)
메서드 위에 @Autowired
@Component
public class MemoService {
private final MemoRepository memoRepository;
**@Autowired**
public MemoService(MemoRepository memoRepository) {
this.memoRepository = memoRepository;
}
// ...
}
객체의 불변성을 확보하기 위해 → final로
생성자에 DI(@Autowired를 적용)하는 것이 좋다.
Setter로 DI(@Autowired) 할 시 final 못 씀
private에도 autowired 달면 Spring이 알아서 주입해줌, 추천하지 않는 방법
이유는 나중에 알게된다 함
강의에서는 그냥 생성자로 주입
@Autowired 적용 조건
Spring IoC 컨테이너에 의해 관리되는 클래스에서만 가능
Spring IoC 컨테이너에 의해 관리되는 ‘Bean’객체만 DI에 사용가능
@Autowired 생략 조건
Spring 4.3 버젼 부터 생성자 선언이 1개 일 때 @Autowired 생략가능
public class A {
//생략 가능
public A(B b) { ... }
}
오버로딩된 생성자들
public class A {
@Autowired // 생략 불가
public A(B b) { ... }
@Autowired // 생략 불가
public A(B b, C c) { ... }
}
Lombok 의 @RequiredArgsConstructor를 사용하면 다음과 같이 코딩 가능
@Component
**@RequiredArgsConstructor** // final로 선언된 멤버 변수를 파라미터로 사용하여 생성자를 자동으로 생성합니다.
public class MemoService {
private final MemoRepository memoRepository;
// public MemoService(MemoRepository memoRepository) {
// this.memoRepository = memoRepository;
// }
...
}
- ApplicationContextBeanFactory : ‘Bean’ 의 생성, 관계설정등의 제어를 담당하는 IoC 객체
- 스프링 IoC 컨테이너에서 ‘Bean’을 수동으로 가져오는 방법
- ApplicationContext는 BeanFactory등을 상속하여 기능을 확장한 Container
public MemoService(ApplicationContext context) {
//1. Bean 이름으로 가져오기
MemoRepository memoRepository = (MemoRepository) context.getBean("memoRepository");
//2. Bean 클래스 형식으로 가져오기
MemoRepository memoRepository = (MemoRepository) context.getBean(MemoRepository.class);
this.memoRepository=memoRepository;
}
3 Layer Annotation
- Spring 3 Layer Annotation은 Controller, Service, Repository의 역할로 구분된 클래스들을 ‘Bean’으로 등록할 때 해당 ‘Bean’ 클래스의 역할을 명시하기위해 사용됩니다.
- @Controller, @RestController
- @Service
- @Repository
- 앞으로는 @Component가 아닌 3 Layer Annotation을 사용해서 ‘Bean’으로 등록하겠습니다.
<aside> 💡 Spring 3 Layer Annotation은 모두 @Component가 추가되어있습니다
</aside>
IoC (제어의 역전) → 객체지향의 SOLID 설계원칙
DI (의존성 주입) → GoF 디자인 패턴
김치 볶음밥 맛있게 만드는 방법 (설계 원칙)
김치 볶음밥 레시피 (디자인 패턴)
좋은 코드를 위한 Spring의 IoC와 DI
- 논리가 간단해야 한다.
- 중복을 제거하고 표현을 명확하게 한다.
- 코드를 처음 보는 사람도 쉽게 이해하고 수정할 수 있어야 한다.
- 의존성을 최소화해야 한다.
- 새로운 기능을 추가 하더라도 크게 구조의 변경이 없어야 한다.
→ DI 패턴을 사용하여 IoC 설계 원칙을 구현하여 Spring이 개발자가 Java를 사용하여 쉽게 좋은 코드를 작성할 수 있도록 도와줌
의존성
강하게 결합되어있는 Consumer 와 Chicken
public class Consumer {
void eat() {
Chicken chicken = new Chicken();
chicken.eat();
}
public static void main(String[] args) {
Consumer consumer = new Consumer();
consumer.eat();
}
}
class Chicken {
public void eat() {
System.out.println("치킨을 먹는다.");
}
}
Consumer 가 치킨이 아니라 피자를 먹고 싶어 하는 등
변경이 필요할 때
많은 수의 코드를 변경해야 함
→ 약한 결합, 약한 의존성으로 만들어봄
public class Consumer {
void eat(Food food) {
food.eat();
}
public static void main(String[] args) {
Consumer consumer = new Consumer();
consumer.eat(new Chicken());
consumer.eat(new Pizza());
}
}
interface Food {
void eat();
}
class Chicken implements Food{
@Override
public void eat() {
System.out.println("치킨을 먹는다.");
}
}
class Pizza implements Food{
@Override
public void eat() {
System.out.println("피자를 먹는다.");
}
}
Interface 다형성의 원리를 사용하여 구현하면 고객이 어떠한 음식을 요구하더라도 쉽게 대처
주입
필요로 하는 객체를 해당 객체에 전달하는 것
필드에 직접 주입
public class Consumer {
Food food;
void eat() {
this.food.eat();
}
public static void main(String[] args) {
Consumer consumer = new Consumer();
consumer.food = new Chicken();
consumer.eat();
consumer.food = new Pizza();
consumer.eat();
}
}
interface Food {
void eat();
}
class Chicken implements Food{
@Override
public void eat() {
System.out.println("치킨을 먹는다.");
}
}
class Pizza implements Food{
@Override
public void eat() {
System.out.println("피자를 먹는다.");
}
}
Food를 Consumer에 포함 시키고 Food에 필요한 객체를 주입받아 사용
메서드를 통한 주입
public class Consumer {
Food food;
void eat() {
this.food.eat();
}
public void setFood(Food food) {
this.food = food;
}
public static void main(String[] args) {
Consumer consumer = new Consumer();
consumer.setFood(new Chicken());
consumer.eat();
consumer.setFood(new Pizza());
consumer.eat();
}
}
interface Food {
void eat();
}
class Chicken implements Food{
@Override
public void eat() {
System.out.println("치킨을 먹는다.");
}
}
class Pizza implements Food{
@Override
public void eat() {
System.out.println("피자를 먹는다.");
}
}
set 메서드를 사용하여 필요한 객체를 주입받아 사용
생성자를 통한 주입
public class Consumer {
Food food;
public Consumer(Food food) {
this.food = food;
}
void eat() {
this.food.eat();
}
public static void main(String[] args) {
Consumer consumer = new Consumer(new Chicken());
consumer.eat();
consumer = new Consumer(new Pizza());
consumer.eat();
}
}
interface Food {
void eat();
}
class Chicken implements Food{
@Override
public void eat() {
System.out.println("치킨을 먹는다.");
}
}
class Pizza implements Food{
@Override
public void eat() {
System.out.println("피자를 먹는다.");
}
}
생성자를 사용하여 필요한 객체를 주입받아 사용
제어의 역전
이전에는 Consumer가 직접 Food를 만들어 먹었기 때문에
새로운 Food를 먹으려면 추가적인 코드변경이 필요했음
→
Food를 Consumer에게 주입해서 코드 변경 없이 어느 Food든 먹을 수 있게 됨
이를 제어의 흐름이 Food → Consumer 로 역전되었다고 한다
Consumer는 가만히 있고 외부에서 Food를 만들어서 주입하니까
메모장 프로젝트의 IoC & DI
하기 전에
! 잠깐 !
지금 메모장 프로젝트 코드가 가지고 있는 객체 중복 생성 문제를 먼저 해결
new MemoRepository(jdbcTemplate); 코드가 중복되고 있음
public class MemoService {
private final JdbcTemplate jdbcTemplate;
public MemoService(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public MemoResponseDto createMemo(MemoRequestDto requestDto) {
// DB 저장
MemoRepository memoRepository = new MemoRepository(jdbcTemplate);
...
}
public List<MemoResponseDto> getMemos() {
// DB 조회
MemoRepository memoRepository = new MemoRepository(jdbcTemplate);
...
}
public Long updateMemo(Long id, MemoRequestDto requestDto) {
MemoRepository memoRepository = new MemoRepository(jdbcTemplate);
...
}
public Long deleteMemo(Long id) {
MemoRepository memoRepository = new MemoRepository(jdbcTemplate);
...
}
}
→ 객체가 생성될때 한번만 만들어서 같이 쓰면 훨씬 효율적
기존 코드
private final JdbcTemplate jdbcTemplate;
public MemoService(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public MemoResponseDto createMemo(MemoRequestDto requestDto) {
// DB 저장
**MemoRepository memoRepository = new MemoRepository(jdbcTemplate);
Memo saveMemo = memoRepository.save(memo);**
...
}
→
바뀐 코드
public class MemoService {
private final MemoRepository memoRepository;
public MemoService(JdbcTemplate jdbcTemplate) {
this.memoRepository = new MemoRepository(jdbcTemplate);
}
public MemoResponseDto createMemo(MemoRequestDto requestDto) {
// DB 저장
**Memo saveMemo = memoRepository.save(requestDto);
...**
}
...
}
MemoController의 이 부분도 개선
@RestController
@RequestMapping("/api")
public class MemoController {
private final JdbcTemplate jdbcTemplate;
public MemoController(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
@PostMapping("/memos")
public MemoResponseDto createMemo(@RequestBody MemoRequestDto requestDto) {
MemoService memoService = new MemoService(jdbcTemplate);
return memoService.createMemo(requestDto);//클라이언트에서 전달받은 requestDto도 Service에게 사용하라고 보내줘야 함
}
@GetMapping("/memos")
public List<MemoResponseDto> getMemos() {
MemoService memoService = new MemoService(jdbcTemplate);
return memoService.getMemos();
}
@PutMapping("/memos/{id}")
public Long updateMemo(@PathVariable Long id, @RequestBody MemoRequestDto requestDto) {
MemoService memoService = new MemoService(jdbcTemplate);
return memoService.updateMemo(id,requestDto);
}
@DeleteMapping("/memos/{id}")
public Long deleteMemo(@PathVariable Long id) {
MemoService memoService = new MemoService(jdbcTemplate);
return memoService.deleteMemo(id);
}
}
→
public class MemoController {
private final MemoService memoService;
public MemoController(JdbcTemplate jdbcTemplate) {
memoService = new MemoService(jdbcTemplate);
}
@PostMapping("/memos")
public MemoResponseDto createMemo(@RequestBody MemoRequestDto requestDto) {
return memoService.createMemo(requestDto);//클라이언트에서 전달받은 requestDto도 Service에게 사용하라고 보내줘야 함
}
@GetMapping("/memos")
public List<MemoResponseDto> getMemos() {
return memoService.getMemos();
}
@PutMapping("/memos/{id}")
public Long updateMemo(@PathVariable Long id, @RequestBody MemoRequestDto requestDto) {
return memoService.updateMemo(id,requestDto);
}
@DeleteMapping("/memos/{id}")
public Long deleteMemo(@PathVariable Long id) {
return memoService.deleteMemo(id);
}
}
메모장 프로젝트의 강한 결합상태 개선
강한 결합 문제상황(예시)
- Contoller1 이 Service1 객체를 생성하여 사용
public class Controller1 {
private final Service1 service1;
public Controller1() {
this.service1 = new Service1();
}
}
- Service1 이 Repostiroy1 객체를 생성하여 사용
public class Service1 {
private final Repository1 repository1;
public Service1() {
this.repository1 = new Repository1();
}
}
- Repostiroy1 객체 선언
public class Repository1 { ... }
이 상황에서 다음과 같이 변경된다면 어떨까?
Repository1 객체 생성 시 DB 접속 id, pw 를 받아서 DB 접속 시 사용
(생성자에 DB 접속 id, pw 를 추가)
public class Repository1 {
public Repository1(String id, String pw) {
// DB 연결
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/memo", id, pw);
}
}
강한 결합의 문제점
Controller 5 개가 각각 Service1 을 생성하여 사용 중일 때,
Repository1 생성자가 변경되면
모든 Contoller 와 모든 Service 의 코드 변경이 필요함
강한 결합 해결방법
- 각 객체에 대한 객체 생성은 딱 1번만
- 생성된 객체를 모든 곳에서 재사용
- 필요한 객체에 생성자 주입으로 해당 객체 주입
- Repository1 클래스 선언 및 객체 생성 → repository1
public class Repository1 { ... }
// 객체 생성
**Repository1 repository1 = new Repository1();**
- Service1 클래스 선언 및 객체 생성 (repostiroy1 사용) → service1
Class Service1 {
private final Repository1 repository1;
// repository1 객체 사용
public Service1(Repository1 repository1) {
~~this.repository1 = new Repository1();~~
this.repository1 = repository1;
}
}
// 객체 생성
**Service1 service1 = new Service1(repository1);**
- Contoller1 선언 ( service1 사용)
Class Controller1 {
private final Service1 service1;
// service1 객체 사용
public Controller1(Service1 service1) {
~~this.service1 = new Service1();~~
this.service1 = service1;
}
}
다음과 같이 변경됨 →
Repository1 객체 생성 시 DB 접속 id, pw 를 받아서 DB 접속 시 사용
생성자에 DB 접속 id, pw 를 추가
public class Repository1 {
public Repository1(**String id, String pw**) {
// DB 연결
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/memo", **id, pw**);
}
}
// 객체 생성
**String id = "root";
String pw = "1234";**
Repository1 repository1 = new Repository1(**id, pw**);
[개선 결과]
Repository1 생성자 변경이 적은 코드 수정만으로 ok
Service1 생성자가 변경되면? 모든 Controller 변경 필요 X
문제 상황
!메모장 프로젝트에서도 동일한 문제 발생중이었음!
public MemoController(JdbcTemplate jdbcTemplate) {
this.memoService = new MemoService(jdbcTemplate);
}
...
public MemoService(JdbcTemplate jdbcTemplate) {
this.memoRepository = new MemoRepository(jdbcTemplate);
}
문제 상황
- MemoService에서 자신은 사용하지 않지만 MemoRepository를 사용하기 위해 MemoRepository의 생성자에 JdbcTemplate을 넣어주고 있다.
- MemoController에서도 MemoService를 사용해야하기 때문에 생성자에 JdbcTemplate을 넣어주고 있다.
코드 따라 짜면서 왜 jdbcTemplate넘기고 또 넘기는 형식으로 하는지 그러는 이유가 있었는지 궁금했었는데 여기서 풀리네…
문제 해결
public class MemoController {
private final MemoService memoService;
public MemoController(JdbcTemplate jdbcTemplate) {
memoService = new MemoService(jdbcTemplate);
}
→
public class MemoController {
private final MemoService memoService;
public MemoController(MemoService memoService) {
this.memoService = memoService;
}
public class MemoService {
private final JdbcTemplate jdbcTemplate;
private final MemoRepository memoRepository;
public MemoService(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
this.memoRepository = new MemoRepository(jdbcTemplate);
}
→
public class MemoService {
private final MemoRepository memoRepository;
public MemoService(MemoRepository memoRepository) {
this.memoRepository = memoRepository;
}
이제는 더 이상 MemoController와 MemoService에서 사용하지도 않는 JdbcTemplate을 주입 받아와 MemoRepository애 주입해줄 필요가 없어졌다.
코드 수정하면서 제어의 흐름이 반대가 된다는게 이런 뜻인거 같다고 어렴풋이 느꼈다:
예전에는
Controller만들면 그 안에서 Service만들고 Service안에서 Repository만들었었는데
이제 Repository를 만들어서 Service생성자에 넣고 그걸 또 Controller생성자에 넣음
반대가 됨
근데 Repository를 만들어서 Service생성자에 넣고 그걸 또 Controller생성자에 넣는 걸
누가 언제 어디서 해주는거지? → 다음 강의
IoC: Inversion of Control(제어의 역전)
프로그램의 제어 흐름이 뒤바뀜
제어의 흐름 of 강한 결합 상태의 메모장 프로젝트
Controller ⇒ Service ⇒ Repository
Controller에서 Service를 만들고 Service에서 Repository를 만듦
Consumer가 직접 음식을 만들어 먹었을 때와 같다
제어의 흐름 of DI(의존성 주입)후 메모장 프로젝트
Repository ⇒ Service ⇒ Controller
Food가 만들어져서 Consumer에게 주입당했을때랑 같음
Spring IoC 컨테이너
DI를 사용하기 위해서는 객체를 우선 만들어야 함
그러면 누가 그걸 해줌?
Spring 프레임워크가 필요한 객체를 생성하고 관리하는 역할을 대신 해줌
Bean :
Spring이 관리하는 객체
Spring IoC 컨테이너 :
Bean을 모아둔 컨테이너
Spring Bean 등록 방법
@Component
‘Bean’으로 등록하고자하는 클래스 위에 설정
→Spring 서버가 뜰 때 IoC 컨테이너에 'Bean'을 저장
@Component
public class MemoService { ... }
@Component
public class MemoRepository {...}
MemoRepository에 @Component다니까 빨간 줄 사라짐
MemoApplication → @SpringBootApplication → ctrl + click → SpringBootApplication.java
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
@ComponentScan : Component를 찾아주는 annotation
이게 달린 패키지와 하위 패키지를 전부 확인한다고 함
Component annotation이 설정되어있는 클래스들을 전부 찾아서 Bean으로 등록을 한다고 함
@SpringBootApplication에 의해 default 설정 되어있습니다.
com.sparta.memo/MemoApplication.java
Spring 'Bean' 이름: 클래스의 앞글자만 소문자로 변경
public class MemoService → memoService
'Bean' 아이콘 → 스프링 IoC 에서 관리할 'Bean' 클래스라는 표시
// 1. MemoService 객체 생성
MemoService memoService = new MemoService();
// 2. Spring IoC 컨테이너에 Bean (memoService) 저장
// memoService -> Spring IoC 컨테이너
Spring 'Bean' 사용 방법
@Autowired
필드 위에 @Autowired
@Component
public class MemoService {
**@Autowired**
private MemoRepository memoRepository;
// ...
}
Spring에서 IoC 컨테이너에 저장된 memoRepository ‘Bean’을 해당 필드에 DI (의존성 주입)
메서드 위에 @Autowired
@Component
public class MemoService {
private final MemoRepository memoRepository;
**@Autowired**
public MemoService(MemoRepository memoRepository) {
this.memoRepository = memoRepository;
}
// ...
}
객체의 불변성을 확보하기 위해 → final로
생성자에 DI(@Autowired를 적용)하는 것이 좋다.
Setter로 DI(@Autowired) 할 시 final 못 씀
private에도 autowired 달면 Spring이 알아서 주입해줌, 추천하지 않는 방법
이유는 나중에 알게된다 함
강의에서는 그냥 생성자로 주입
@Autowired 적용 조건
Spring IoC 컨테이너에 의해 관리되는 클래스에서만 가능
Spring IoC 컨테이너에 의해 관리되는 ‘Bean’객체만 DI에 사용가능
@Autowired 생략 조건
Spring 4.3 버젼 부터 생성자 선언이 1개 일 때 @Autowired 생략가능
public class A {
//생략 가능
public A(B b) { ... }
}
오버로딩된 생성자들
public class A {
@Autowired // 생략 불가
public A(B b) { ... }
@Autowired // 생략 불가
public A(B b, C c) { ... }
}
Lombok 의 @RequiredArgsConstructor를 사용하면 다음과 같이 코딩 가능
@Component
**@RequiredArgsConstructor** // final로 선언된 멤버 변수를 파라미터로 사용하여 생성자를 자동으로 생성합니다.
public class MemoService {
private final MemoRepository memoRepository;
// public MemoService(MemoRepository memoRepository) {
// this.memoRepository = memoRepository;
// }
...
}
- ApplicationContextBeanFactory : ‘Bean’ 의 생성, 관계설정등의 제어를 담당하는 IoC 객체
- 스프링 IoC 컨테이너에서 ‘Bean’을 수동으로 가져오는 방법
- ApplicationContext는 BeanFactory등을 상속하여 기능을 확장한 Container
public MemoService(ApplicationContext context) {
//1. Bean 이름으로 가져오기
MemoRepository memoRepository = (MemoRepository) context.getBean("memoRepository");
//2. Bean 클래스 형식으로 가져오기
MemoRepository memoRepository = (MemoRepository) context.getBean(MemoRepository.class);
this.memoRepository=memoRepository;
}
3 Layer Annotation
- Spring 3 Layer Annotation은 Controller, Service, Repository의 역할로 구분된 클래스들을 ‘Bean’으로 등록할 때 해당 ‘Bean’ 클래스의 역할을 명시하기위해 사용됩니다.
- @Controller, @RestController
- @Service
- @Repository
- 앞으로는 @Component가 아닌 3 Layer Annotation을 사용해서 ‘Bean’으로 등록하겠습니다.
💡 Spring 3 Layer Annotation은 모두 @Component가 추가되어있습니다
'TIL(Develop)' 카테고리의 다른 글
JPA in Spring Boot (0) | 2023.11.15 |
---|---|
[JPA Core] (0) | 2023.11.15 |
3 Layer Architecture 역할 분리 (1) | 2023.11.09 |
Database&SQL&JDBC (0) | 2023.11.07 |
HTTP 데이터를 객체로 처리하는 방법 (0) | 2023.11.07 |