IoC(Inversion of Control)
DI(Dependency Injection)
의존성을 생산하거나 찾지 않고 외부에서 주입받는 디자인 패턴📝 IoC와 DI를 사용하지 않은 경우
public class UserService {
private UserDao userDao;
public UserService() {
this.userDao = new UserDao();
}
// ...
}
📝 IoC와 DI를 사용한 경우
public class UserService {
private UserDao userDao;
// Constructor Injection
public UserService(UserDao userDao) {
this.userDao = userDao;
}
// ...
}
📝 강한 결합의 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("치킨을 먹는다.");
}
}
📝 약한 결합 or 약한 의존성
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("피자를 먹는다.");
}
}
eat() 메서드는 매개변수로 FOOD 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 내용 위와 동일
📝 setter를 통한 주입
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 내용 위와 동일
📝 생성자를 통한 주입
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 내용 위와 동일
결과적으로 DI 를 통해 IoC를 이룬다!
강한 결함 코드 예시의 제어의 흐름: Consumer → Food (직접 객체 생산)
약한 의존 코드 예시의 제어의 흐름 : Food → Consumer (외부를 통해 필요한 객체를 주입받음)
Bean : 스프링이 관리하는 객체
IoC Container : Bean을 모아둔 컨테이너!
일반 class를 Spring이 관리하는 Bean으로 등록하려면 어떻게 해야 할까?
Bean으로 등록하고자 하는 클래스에 @Component annotation을 주면 된다!
@Component //스프링이 관리하는 bean 으로 등록!
public class MemoService {
private final MemoRepository memoRepository;
public MemoService(MemoRepository memoRepository) {
// 생성자를 통해 생성이 될때 jdbcTemplate 을 받으면서 memoRepository 생성
this.memoRepository = memoRepository;
}
}
Spring 서버가 뜰 때 @ComponentScan에 설정해준 packages 위치와 하위 packages 들을 전부 확인하여 @Component가 설정된 클래스들을 Bean으로 등록해준다!
Spring Boot는 @SpringBootApplication에 의해 default로 설정이 되어있다!!! (프로젝트 생성시 함께 생성되는 Application.java 파일에 설정됨!)
@Autowired
스프링에서는 @Autwired라는 어노테이션(annotation)을 통해 의존성 주입을 명시할 수 있다.
스프링 4.3 이후 버전은 생성자 주입 시 @Autowired 어노테이션을 생략할 수 있다.
📝 생성자 주입
@RestController
public class DIController {
MyService myService;
@Autowired
public DIController(MyService myService) {
this.myService = myService;
}
//코드 생략
}
📝 setter를 통한 주입
@RestController
public class DIController {
MyService myService;
@Autowired
public void setMyService(MyService myService) {
this.myService = myService;
}
//코드 생략
}
📝 필드를 통한 주입
@RestController
public class DIController {
@Autowired
private MyService myService;
//코드 생략
}
스프링 공식 문서에서 권장하는 의존성 주입 방법은 생성자를 통해 의존성을 주입받는 방법이다.
다른 방식과는 다르게 레퍼런스 객체 없이는 객체를 초기화할 수 없도록 설계하기 때문이다.
스프링 4.3 이후 버전은 생성자 주입 시 @Autowired 어노테이션을 생략이 가능하기 때문에 Lombok을 통해 의존성을 주입할 수도 있다.(생성자가 하나만 있을 경우! autowired 생략가능)
📝 롬복을 통산 생성자 주입
@RestController
@RequiredArgsConstructor //final이 붙은 필드의 생성자를 자동으로 생성
public class DIController {
privite final MyService myService;
//Controller Codes
}
📝 IoC 컨테이너에 직접 접근하여 주입하는 법
그냥 알아두기!
@Component
public class MemoService {
private final MemoRepository memoRepository;
public MemoService(ApplicationContext context) { //스프링 IoC에 직접 접근하여 등록된 bean 주입하기
// 1. 'Bean' 이름으로 가져오기
// MemoRepository memoRepository = (MemoRepository) context.getBean("memoRepository");
// 2. `Bean` 클래스 형식으로 가져오기
MemoRepository memoRepository = context.getBean(MemoRepository.class);
this.memoRepository = memoRepository;
}
}
Controller, Service, Repository의 역할로 구분된 클래스들을 Bean을 등록핼때 해당 클래스의 역할을 명시하기 위해 사용한다.
1. @Controller, @RestController
2. @Service
3. @Repository