환경
윈도우즈 10 / 이클립스 2022-06 (4.24.0) / Java SE 18 / 메이븐 3.8.6 / spring-context 5.3.22
교재
책 : 초보 웹 개발자를 위한 스프링5 프로그래밍 입문 챕터 4
영상 : 예제로 배우는 스프링 입문(개정판)
설정코드(e.g. @Configure가 포함된 AppCtx.java)에서 각 Bean객체에 직접 의존주입 하는 대신, 스프링이 의존객체에 맞는 타입의 Bean객체를 찾아 자동으로 주입
구현에는 아래 요소 사용
자동 주입이 없는 경우 아래와 같이 작성할 수 있음 (모두 같은 패키지라고 가정)
say()메소드를 가진 사람Person상속. say()메소드를 재정의하는 고라니 관리직원makeSound(String)메소드를 가진 사람의 입(mouse)Person, Staff 모두 할 수 있는 일 : GoraniDao를 통한 Gorani의 구매/판매 + Mouse를 통한 발화Staff만 할 수 있는 일 : GoraniDao를 통한 목록 확인Person, Staff가 GoraniDao, Mouse에 의존// AppCtx.java
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Bean;
@Configuration
public class AppCtx {
@Bean
public GoraniDao goraniDao() {
return new GoraniDao();
}
@Bean
public Mouse mouse() {
return new Mouse();
}
@Bean
public Person person() {
Person person;
person = new Person(mouse()); // 생성자로 의존주입
person.setGoraniDao(goraniDao()); // setter로 의존주입
return person;
}
@Bean
public Staff staff() {
Staff staff = new Staff(mouse()); // 생성자로 의존주입
staff.setGoraniDao(goraniDao()); // setter로 의존주입
return staff;
}
}
// Main.java
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Main {
public static ApplicationContext ctx;
public static void main(String[] args) {
ctx = new AnnotationConfigApplicationContext(AppCtx.class);
Person person = ctx.getBean("person", Person.class);
person.buyGorani(1, "wonjin");
person.buyGorani(2, "hyungyu");
person.buyGorani(3, "comgong");
person.sellGorani(2);
person.say(); // I want to go home
Staff staff = ctx.getBean("staff", Staff.class);
staff.say();
// I have these goranis
// [Gorani] #1 : wonjin
// [Gorani] #3 : comgong
staff.sellGorani(3);
staff.say();
// I have these goranis
// [Gorani] #1 : wonjin
}
}
// Gorani.java
public class Gorani {
private int id;
private String name;
public Gorani(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() { return id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
@Override
public String toString() {
return "[Gorani] #" + id + " : " + name;
}
}
// GoraniDao.java
import java.util.HashMap;
public class GoraniDao {
private HashMap<Integer, Gorani> list = new HashMap<Integer, Gorani>(); // Database
public void add(Gorani gorani) {
list.put(gorani.getId(), gorani);
}
public void remove(int id) {
list.remove(id);
}
public void printAll() {
(list.values()).forEach(item -> System.out.println(item));
}
}
// Person.java
public class Person {
GoraniDao goraniDao;
Mouse mouse;
// 생성자 DI
public Person(Mouse mouse) { this.mouse = mouse; }
// setter DI
public void setGoraniDao(GoraniDao goraniDao) { this.goraniDao = goraniDao; }
// 메소드 (buy, sell, say)
public void buyGorani(int id, String name) {
goraniDao.add(new Gorani(id, name));
}
public void sellGorani(int id) {
goraniDao.remove(id);
}
public void say() {
mouse.makeSound("I want to go home");
}
}
// Staff.java
public class Staff extends Person {
public Staff(Mouse mouse) {
super(mouse);
}
@Override
public void say() {
mouse.makeSound("I have these goranis");
goraniDao.printAll();
}
}
// Mouse.java
public class Mouse {
public void makeSound(String str) {
System.out.println(str);
}
}
AppCtx.java에서 의존주입 문장 삭제
Person.java에서 @Autowired 두 가지 방법으로 사용
Staff.java에서 super()문장 삭제
// AppCtx.java
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Bean;
@Configuration
public class AppCtx {
@Bean
public GoraniDao goraniDao() {
return new GoraniDao();
}
@Bean
public Mouse mouse() {
return new Mouse();
}
@Bean
public Person person() {
return new Person();
}
@Bean
public Staff staff() {
return new Staff();
}
}
// Person.java
import org.springframework.beans.factory.annotation.Autowired;
public class Person {
// [ 방법 1 ] 필드에 @Autowired 사용
// 필드에 직접 자동주입 하는 경우. 자동으로 Bean객체 Mouse가 등록됨
@Autowired
Mouse mouse;
// [ 방법 2 ] setter에 @Autowired 사용
// setter를 거쳐 자동주입 되는 경우. setter의 매개변수에 자동으로 Bean객체 GoraniDao가 넘어옴
GoraniDao goraniDao;
@Autowired
public void setGoraniDao(GoraniDao goraniDao) { this.goraniDao = goraniDao; }
// 메소드 (buy, sell, say)
public void buyGorani(int id, String name) {
goraniDao.add(new Gorani(id, name));
}
public void sellGorani(int id) {
goraniDao.remove(id);
}
public void say() {
mouse.makeSound("I want to go home");
}
}
// Staff.java
public class Staff extends Person {
@Override
public void say() {
mouse.makeSound("I have these goranis");
goraniDao.printAll();
}
}
Bean객체는 자신을 식별할 수 있는 수단으로 Bean이름에 더불어 한정자를 가짐
자동주입 수행 시 타입과 한정자 사용
한정자를 별도로 지정하지 않으면 아래와 같이 기본 한정자 적용
상기 <@Autowired> 섹션의 코드 에서 자동주입 가능 여부를 살펴보면 다음과 같음
| 파일 | 대상(식별자) | 타입 | 한정자 |
|---|---|---|---|
| AppCtx.java | Bean객체 mouse | Mouse | mouse |
| Person.java | 필드 mouse | Mouse | mouse |
기본 한정자만으로 자동주입을 수행할 수 없는 경우 @Qualifier어노테이션으로 한정자를 변경/지정
// 설정파일에서의 사용 예
@Configuration
public class AppCtx {
@Bean
@Qualifier("IamDao")
public GoraniDao goraniDao() {
return new GoraniDao();
} // goraniDao Bean객체의 한정자는 IamDao
}
// 의존객체를 주입받는 Bean객체에서의 사용 예
public class Person {
@Autowired
@Qualifier("specialMouse")
Mouse mouse; // 한정자가 specialMouse, 타입이 Mouse인 객체를 주입받음
GoraniDao goraniDao;
@Autowired
@Qualifier("IamDao")
public void setGoraniDao(GoraniDao goraniDao) {
this.goraniDao = goraniDao;
} // 한정자가 IamDao, 타입이 GoraniDao인 객체를 주입받음
}
상기 <@Autowired> 섹션의 코드 를 아래와 같이 변경한 뒤 자동주입 가능 여부를 살펴보면 다음과 같음
Mouse mouse를 자동주입 받음mouse_1과 mouse_2가 주입 후보WARNING: Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'person':
Unsatisfied dependency expressed through field 'mouse'; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'chap04practice.Mouse' available:
expected single matching bean but found 2: mouse_1,mouse_2
// AppCtx.java
... 생략 ...
public class AppCtx {
@Bean
public GoraniDao goraniDao() { 생략 }
@Bean
public Mouse mouse_1() { return new Mouse(); }
@Bean
public Mouse mouse_2() { return new Mouse(); }
@Bean
public Person person() { 생략 }
@Bean
public Staff staff() { 생략 }
}
// Person.java
(수정없음)
정상적으로 자동주입 되도록 수정하는 방법 중 세가지를 뽑아보면 아래와 같음
---------- 방법 1 : 설정파일의 Bean과 주입받는 필드 양쪽에 한정자 지정
// 설정파일
@Configuration
public class AppCtx {
@Bean
@Qualifier("wonjin")
public Mouse mouse_1() { return new Mouse(); }
@Bean
public Mouse mouse_2() { return new Mouse(); }
}
// 의존객체를 주입받는 클래스
public class Person {
@Autowired
@Qualifier("wonjin")
Mouse mouse;
}
---------- 방법 2 : 주입받는 필드의 한정자를 설정파일Bean의 기본한정자로 지정
// 설정파일
@Configuration
public class AppCtx {
@Bean
public Mouse mouse_1() { return new Mouse(); }
@Bean
public Mouse mouse_2() { return new Mouse(); }
}
// 의존객체를 주입받는 클래스
public class Person {
@Autowired
@Qualifier("mouse_1")
Mouse mouse;
}
---------- 방법 3 : 주입받는 필드의 식별자를 설정파일Bean의 기본한정자로 변경
// 설정파일
@Configuration
public class AppCtx {
@Bean
public Mouse mouse_1() { return new Mouse(); }
@Bean
public Mouse mouse_2() { return new Mouse(); }
}
// 의존객체를 주입받는 클래스
public class Person {
@Autowired
Mouse mouse_1;
}
어떤 필드가 의존객체를 반드시 넘겨받지 않아도 되도록 설정 가능
아래 코드에서 Person은 의존객체 GoraniDao를 필수적으로 요구.
이를 주입받아도, 받지 않아도 되도록 처리하기 위해 세 가지 기법 사용가능
(아래 코드는 goraniDao가 Bean객체로 등록되지 않았으므로 런타임예외 발생)
// AppCtx.java
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Bean;
@Configuration
public class AppCtx {
@Bean
public Mouse mouse() {
return new Mouse();
}
@Bean
public Person person() {
return new Person();
}
}
// Main.java
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Main {
public static ApplicationContext ctx;
public static void main(String[] args) {
ctx = new AnnotationConfigApplicationContext(AppCtx.class);
Person person = ctx.getBean("person", Person.class);
person.buyGorani(1, "wonjin");
person.buyGorani(2, "hyungyu");
person.buyGorani(3, "comgong");
person.sellGorani(2);
person.say(); // I want to go home
}
}
// Person.java
import org.springframework.beans.factory.annotation.Autowired;
public class Person {
@Autowired
Mouse mouse;
@Autowired
GoraniDao goraniDao;
// 메소드 (buy, sell, say)
public void buyGorani(int id, String name) {
if(hasDao())
goraniDao.add(new Gorani(id, name));
else
System.out.println("I have no DAO");
}
public void sellGorani(int id) {
if(hasDao())
goraniDao.remove(id);
else
System.out.println("I have no DAO");
}
public void say() {
mouse.makeSound("I want to go home");
}
public boolean hasDao() {
return goraniDao != null;
}
}
// Gorani.java
public class Gorani {
private int id;
private String name;
public Gorani(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() { return id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
@Override
public String toString() {
return "[Gorani] #" + id + " : " + name;
}
}
// Mouse.java
public class Mouse {
public void makeSound(String str) {
System.out.println(str);
}
}
기존에 사용하던 @Autowired에 인수로 required = false를 줄 수 있음
GoraniDao객체에 대해
goraniDao에 대입 자체를 하지 않음)// Person.java
import org.springframework.beans.factory.annotation.Autowired;
public class Person {
@Autowired
Mouse mouse;
@Autowired(required = false)
GoraniDao goraniDao;
... 생략 ...
}
의존주입 대상에 null을 허용함을 뜻하는 @Nullable 어노테이션을 붙임
GoraniDao객체에 대해
// Person.java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.lang.Nullable;
public class Person {
@Autowired
Mouse mouse;
@Autowired
@Nullable
GoraniDao goraniDao;
... 생략 ...
}
java기본 패키지에서 제공하는 Optional로 인수를 받아
의존객체 또는 null 대입
// Person.java
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired;
public class Person {
GoraniDao goraniDao;
@Autowired
Mouse mouse;
@Autowired
public void setGoraniDao(Optional<GoraniDao> opt) {
this.goraniDao = (opt.isPresent())
? opt.get()
: null;
}
// 메소드 (buy, sell, say)
public void buyGorani(int id, String name) {
... 생략 ...
}