들어가며
항해99 3주차도 이제 마무리되어가고 있다.
회고에 앞서 Java Spring Boot를 이용하며 끊임없이 접하게 될 DI, IoC, Bean에 대한 개념정리를 해보고자 한다.
강한 결합의 문제점
강한 결합은 어떠한 객체가 다른 객체에 강한 의존성을 가지고 있음을 뜻한다.
public class Controller1 {
private final Service1 service1;
public Controller1() {
this.service1 = new Service1(); //new 생성자를 통한 객체 생성이 강한 결합을 부여
}
}
위 코드를 예시로, Controller1은 service1라는 멤버 타입 변수에 강한 의지를 하게된다.
즉 Controller1은 service1이 없으면 정의할 수도 없고, B 클래스의 생성자를 포함한 메서드 등이 바뀌면
이에 요구되는 매게변수의 값도 일일이 추가해줘야한다.
위와 같은 코드의 형태로 Controller1부터 Service1 그리고 Repository까지 생성된다면 다음과 같은 형태가 구축된다.
위 이미지처럼 Controller들이 Service에, 또 다시 Service가 Repository에 강한 의존을 하는 결합 형태를 가지고 있을 경우,만약 repository 단에서 id와 pw를 요구하는 형태로 변화하게 되었을경우, Service와 Controller 모두 에러가 나고해결을 위해서 일일히 코드를 수정해줘야하는 문제가 발생되게된다.
👉그렇다면 "강한 결합"을 해결할 방법이 없을까?
1. 각 객체에 대한 객체 생성은 딱 1번만 한다.
2. 생성된 객체를 모든 곳에서 재사용한다.
위 해결 방법을 통해서 다시 한 번 Controller, Service, Reposoitory를 생성하면 아래와 같다.
1. Repository1 클래스 선언 및 객체(repository1) 생성
public class Repository1 { ... }
// 객체 생성
Repository1 repository1 = new Repository1();
2. Service1 클래스 선언 및 객체 생성 (repostiroy1 사용) → service1
Class Service1 {
private final Repository1 repitory1;
// repository1 객체 사용
public Service1(Repository1 repository1) {
this.repository1 = new Repository1();
this.repository1 = repository1;
}
}
// 객체 생성
Service1 service1 = new Service1(repository1);
강한 결합 때의 코드와 다르게 이번에는 Service 단에서 repository1 객체를 new 연산자를 통해 생성, 사용하는게 아닌
기존의 Repository1 클래스에서 만들어진 객체를 가져와 사용하고 있다.
핵심이 바로 이것 👉 매번 new 연산자를 통해 새로 객체를 생성하는 것이 아닌 기존의 것을 가져와 사용!
이렇게 코드가 작성된다면 이전 강한 결합에서의 유지 보수 상황 발생 시 우려됐던 문제가 개선된다.
repository1이 id와 pw를 요구하도록 변경하여도 연결되어있는 Service나 Controller 클래스의 코드를 수정할 필요가 없다.
IoC(Inversion of Control) - 제어의 역전
강한 결합 때의 예와 느슨한 결합 때의 예를 비교하자면 다음과 같다.
강한 결합 : 사용자가 자신이 필요한 객체를 생성해서 사용
느슨한 결합 : 용도에 맞게 필요한 객체를 가져다 사용 👉 DI(Dependency Injection) - 의존성 주입
이처럼 프로그램의 제어 흐름이 뒤바뀌는 것을 제어의 역전(IOC)이라고 한다.
DI(Dependency Injection) - 의존성 주입
느스한 결합 때의 예와 같이 용도에 맞게 필요한 객체를 가져다 사용하는 것을 DI, 의존성 주입이라고 한다.
정확히 말하자면 이미 정의된 객체를 사용한다는 것은 외부에서 의존관계를 결정하고 주입한다는 것이다.
DI는 의존 관계를 외부에서 결정하는 것이기 때문에, 클래스 변수를 결정하는 방법들이 곧 DI를 구현하는 방법이다.
//아래 @Autowired가 붙은 생성자의 역할은
//클래스 위 @RequiredArgsConstructor에 의해 완벽히 대체된다. 따라서 같이 쓰면 오류 발생
// productService라는 Bean을 넣어줌 (DI: 의존성 주입)
@Autowired
public ProductController(ProductService productService){
this.productService = productService;
}
Bean, IoC Container란?
위 코드를 보면 기존에 작성했던 코드와 다른 부분이 보여 "넣어줄 객체를 어디서 구현했는가?"라는 의문이 생긴다.
기존의 new 연산자를 통해 객체를 생성한 것이 아닌 일반 객체를 넣어줬기 때문
바로 이러한 부분이 Spring 프레임 워크에서 객체를 생성, 저장해주는 역할을 하기에 가능한 것이다.
- Bean: 스프링이 관리하는 객체
- Spring IoC Container: Bean을 모아둔 통
Bean 등록 방법
1) @Component
- 스프링 서버가 뜰 때 스프링 IoC 에 '빈' 저장
- @Component 클래스에 대해서 스프링이 해 주는 일
// 1. ProductService 객체 생성
ProductService productService = new ProductService();
// 2. 스프링 IoC 컨테이너에 빈 (productService) 저장
// productService -> 스프링 IoC 컨테이너
- 스프링 '빈' 이름: 클래스의 앞글자만 소문자로 변경
- public class ProductServcie → productServcie
'빈' 아이콘 확인 → 스프링 IoC 에서 관리할 '빈' 클래스라는 표시
- @Component 적용 조건
- @ComponentScan 에 설정해 준 packages 위치와 하위 packages 들까지만 스캔 범위에 속한다. (패키지 외부에 @Component는 등록 안됨)
@Configuration
@ComponentScan(basePackages = "com.sparta.springcore")
class BeanConfig { ... }
- @SpringBootApplication 에 의해 default 설정이 되어 있어서 따로 설정하지 않아도 된다.
2) @Bean
- 직접 객체를 생성하여 빈으로 등록 요청
- 아래 코드는 메서드에 @Bean을 붙여 return된 값을 빈으로 등록하는 것
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class BeanConfiguration {
@Bean
public ProductRepository productRepository() {
String dbUrl = "jdbc:h2:mem:springcoredb";
String dbId = "sa";
String dbPassword = "";
return new ProductRepository(dbUrl, dbId, dbPassword);
}
}
- 스프링 서버가 뜰 때 스프링 IoC에 빈을 저장한다.
// 1. @Bean 설정된 함수 호출
ProductRepository productRepository = beanConfiguration.productRepository();
// 2. 스프링 IoC 컨테이너에 빈 (productRepository) 저장
// productRepository -> 스프링 IoC 컨테이너
- 스프링 '빈' 이름: @Bean 이 설정된 함수명
- public ProductRepository productRepository() {..} → productRepository
- 위 아이콘은 스프링 IoC의 빈에 등록될 것이라는 표시이다.
Bean 사용 방법
- 사용 방법도 아주 간단하다.
- 멤버 변수 선언 위에 @Autowired만 붙여주면 끝이다.
- (혹은 빈을 사용할 메서드 위에 붙여도 된다.)
- 즉, @Autowired를 붙여두면 스프링에 의해 DI(의존 관계 주입)이 되는 것이다.
- 위의 설명에서 외부란 스프링이 되는 것이다. ( 외부인 스프링이 의존 관계를 주입한다)
@Component
public class ProductService {
@Autowired
private ProductRepository productRepository;
// ...
}
- 단, @Autowired는 스프링 IoC 컨테이너에 의해 관리되는 클래스에서만 가능하다.스프링 입문 강의에서 무지성으로 사용했던 @RequiredArgsConstructor는 final로 선언된 멤버 변수 (빈)을 담은 생성자를 자동으로 만들어주며 @Autowired 까지 붙여준다.
+) ApplicationContext -> 스프링 IoC 컨테이너에서 빈을 수동적으로 가져오는 방법
@Component
public class ProductService {
private final ProductRepository productRepository;
@Autowired
public ProductService(ApplicationContext context) {
// 1.'빈' 이름으로 가져오기
ProductRepository productRepository = (ProductRepository) context.getBean("productRepository");
// 2.'빈' 클래스 형식으로 가져오기
// ProductRepository productRepository = context.getBean(ProductRepository.class);
this.productRepository = productRepository;
}
// ...
}
추가적으로 스프링 3계층 annotation은 모두 @Component를 상속받는다.
- @Controller, @RestController
- @Service
- @Repository
- 참고/출처
🤜금주목표(7/25~7/31)
- 항해99 중도하차 하지 않기
- 내가 작성하는 코드의 의미는 알고가기(무지성 Copy Paste는 더이상 그만..)
- 현 시점에서 자바에 대해 이해한 만큼은 Spring Boot에 대해서 이해하기
🤜차주목표(8/1~8/7)
- 항해99 중도하차 하지 않기
- 코드 작성뿐만 아니라 개념적인 부분들에 대해서도 이해하려하기(TIL 작성 시 하루 1 개념 포스팅)
- 마인드셋 다잡기
문득 그간 너무 안일한 생각을 가지고 항해에 임하지는 않았나라는 생각이 들었다.
잘하고 있지는 않지만 그렇다고 못하지는 않고 있지만은 않다.
99일, 항해 일정은 내가 버티기만하면 무사히 지나갈 것이며 수료 후에는 내게 선택지가 주어질 것이다.
이런 생각 때문에 지금보다 성장할 수 있었음에도 지금 수준에 머물러있는 것은 아니었나 싶다.
오는 차주부터는 또 다시 지금보다 성장을 이루기위해서 집중해보자
부족한 개념적인 부분들을 매일 하나씩이라도 보다 확실하게하여 기본기를 다져보고 꾸준히 노력해나가자
그러면 분명 수료 후에는 내가 기대했던 개발자로서의 모습이 조금은 보이지 않을까?
'WIL👨🏫' 카테고리의 다른 글
20220821_WIL(프로젝트 협업) (0) | 2022.08.21 |
---|---|
20220814_WIL(Spring Security CORS) (0) | 2022.08.14 |
20220807_WIL(feat.항해 4주차 회고) / ORM, SQL, MVC (0) | 2022.08.07 |
20220724_WIL(feat.항해99 2주차 회고) (0) | 2022.07.24 |
20220717_WIL(feat.항해99 1주차 회고) (0) | 2022.07.17 |