의존 자동 주입은 스프링 설정 파일에서 객체를 주입할 때, 명시를 하지 않아도 스프링 컨테이너가 자동으로 필요한 의존 대상 객체를 찾아서 필요한 객체에게 주입해주는 기능이다. @Autowired
@Resource
어노테이션을 이용해 구현 가능하다.
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public abstract @interface Autowired {
public abstract boolean required() default true;
}
주입하려고 하는 '객체의 타입'이 일치하는 객체를 자동으로 주입한다.
생성자나 setter를 통해 직접 의존성을 주입하고, @Bean
을 통해 Bean 등록을 했다면, @Autowired
는 의존성 주입을 자동으로 해주고, @Component
를 통해 Bean 등록을 한다.
어노테이션 정리
계속 보면서 어노테이션에 대해 헷갈려 다시 정리하고 넘어간다.
@Configuration
- 클래스단에 설정하며, 해당 클래스가 Bean 구성에 대한 정보를 담고있는 클래스임을 알린다. (싱글톤을 유지하게 해줌)@Bean
- 메소드단에 설정하며, 해당 메소드를 통해 반환되는 객체를 Bean으로 컨테이너에 등록한다. (이름은 name 속성으로 정함)@Component
- 클래스 단에 설정하며 해당 클래스로 생성되는 객체를 Bean으로 컨테이너에 등록한다. (이름은 value 속성으로 정함)@Autowired
- Component를 통해 등록한 클래스에 @Autowired로 설정한 클래스로의 의존성을 자동으로 주입해준다. (해당 클래스도 Component 설정 필요)@Controller
- 스프링 MVC 컨트롤러로 인식한다.@Repository
- 스프링 데이터 접근 계층으로 인식하고, 데이터 계층의 예외를 스프링 예외로 변환해준다.@Service
- 특별한 처리를 하지 않는다. 대신 개발자들이 비즈니스 로직이 여기에 있겠구나 라고 비즈니스 계층을 인식하는데 도움이 된다.Bean과 Component는 둘 다 빈을 등록하도록 하지만, Bean은 직접 작성하지 않은 외부 라이브러리를 등록할 때 주로 사용하고, Component는 직접 작성한 클래스를 등록할 때 사용하기 위함이라고 한다.
public class UserDao {
private ConnectionMaker connectionMaker;
public UserDao(ConnectionMaker connectionMaker) {
this.connectionMaker = connectionMaker;
}
}
public interface ConnectionMaker {
public Connection makeConnection() throws ClassNotFoundException, SQLException;
}
@Component
public class UserDao {
@Autowired
private ConnectionMaker connectionMaker;
}
@Component
public interface ConnectionMaker {
public Connection makeConnection() throws ClassNotFoundException, SQLException;
}
// 생성자 --> 생성자에서 의존성을 주입해주니 생략가능
private ConnectionMaker connectionMaker;
@Autowired
public UserDao(ConnectionMaker connectionMaker) {
this.connectionMaker = connectionMaker;
}
// 객체, 생성자 명시
@Autowired
private ConnectionMaker connectionMaker;
public UserDao() {
}
public UserDao(ConnectionMaker connectionMaker) {
this.connectionMaker = connectionMaker;
}
// Setter 사용
@Component
public class UserDao {
private ConnectionMaker connectionMaker;
@Autowired(required=false)
public void setConnectionMaker(ConnectionMaker connectionMaker) {
this.connectionMaker = connectionMaker;
}
}
// 오류나는 코드
@Autowired
private ConnectionMaker connectionMaker;
public UserDao(ConnectionMaker connectionMaker) {
this.connectionMaker = connectionMaker;
}
생성자 주입과는 다르게 setter를 이용한 주입은 필요에 의해 주입을 하기 위함으로, 대상 객체까지 Bean으로 등록하는게 필수는 아니다. 만약 required=false가 없으면 해당 코드는 빨간줄이 그어진다.
정의를 보면 required 속성은 default가 true인데, 의미는 의존성 주입을 꼭 받아야한다는 것이다. 따라서 이를 false로 해주어 빈 객체로 남겨둘 수 있다.
의존 주입 대상 객체가 여러개일 경우
만약 Autowired가 설정된 ConnectionMaker 타입이 여러개면 오류가 난다. Autowired가 빈의 타입을 보고 의존 주입을 자동으로 하기 때문이다.
일단 순서를 알아보자
Bean 검색 순서 순서
타입이 같은 빈 객체를 검색한다. 한개면 그 빈 객체를 사용한다.
@Qualifier
가 명시되어 있을 경우,@Qualifier
와 같은 값을 갖는 빈 객체여야 한다.타입이 같은 빈 객체가 두 개 이상 존재하면,
@Qualifier
로 지정한 빈 객체를 찾는다. 존재하면, 그 객체를 사용한다.타입이 같은 빈 객체가 두 개 이상 존재하고
@Qualifier
가 없을 경우, 이름이 같은 빈 객체를 찾는다. 존재하면, 그 객체를 사용한다.
@Qualifier
가 나오는데, 해결방법을 알아보면서 같이 공부한다.
공부를 하다보니 의존 주입에는 @Autowired
, @Resource
, @Inject
의 태그들이 이용된다고 한다. 내용이 끝나고 다시 공부할 것.
Bean 검색 순서
@Autowired
: 타입 => 이름 => Qualifier => Fail
@Resource
: 이름 => 타입 => Qualifier => Fail
@Inject
: 타입 => Qualifier => 이름 => Fail
해결 방법
@Primary
를 Autowired 주입 대상 중 원하는 클래스에 넣는다.(중복되는 빈 중 대표를 설정하는 것)@Component @Primary public class myConnection implements ConnectionMaker { }
@Qualifier
를 주입하는 곳에 @Querifier('bean id')로 명시한다. 대신 해당 bean id 를 가지는 한정자가 필요하다.@Component public class UserDao { @Autowired @Qualifier("yourConnection") private ConnectionMaker connectionMaker; } @Configuration public class DaoFactory { @Bean public UserDao userDao() { return new UserDao(); } @Bean( name="myConnection") public ConnectionMaker connectionMaker() { return new myConnection(); } @Bean( name="yourConnection") public ConnectionMaker connectionMakerSub() { return new yourConnection(); } }
Configuration 부분은 XML로 할 시 이렇게 가능하다.
<context:annotation-config> <bean id="con1" class="example.Connection"> <qualifier value="myConnection"/> </bean> <bean id="con2" class="example.Connection"/> <qualifier value="yourConnection"/> </bean> <context:annotation-config/>
- 모든 빈을 주입받기
public class UserDao { @Autowired private List<ConnectionMaker> connectionMaker; }
@Component
어노테이션 및 streotype(@Service, @Repository, @Controller.)
어노테이션이 부여된 Class들을 자동으로 Scan하여 Bean으로 등록해주는 역할을 하는 어노테이션
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {
@AliasFor("basePackages")
String[] value() default {};
@AliasFor("value")
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
... (매우 많음)
boolean useDefaultFilters() default true;
Filter[] includeFilters() default {};
Filter[] excludeFilters() default {};
boolean lazyInit() default false;
@interface Filter {...}
}
@Component
만 해두고 기존 Bean의 설정을 담당하던 @Configuration
부분에 @ComponentScan
만 넣으면 내용이 없어도 자동으로 된다.@SpringBootApplication
에 이미 @Configuration
과 @ComponentScan
이 붙어있다. 따라서 @SpringBootApplication
기준으로 스캐닝이 시작된다.해당 Application이 위치한 패키지 이하의 모든 클래스에 컴포넌트 스캔이 진행된다.
@ComponentScan
@Configuration
public class DaoFactory {
}
@ComponentScan
에는 중요한 속성들이 있다.
Scan 범위 지정
basePackages
정의를 보면 String 타입임을 알 수 있다.
패키지 경로를 String으로 넘겨 해당 패키지로 스캔 위치를 지정한다.
String으로 지정하여 typesafe하지 않기 때문에 2번 방법으로 지정하는 것을 추천한다.
정의를 보면 @AliasFor
이 되어있는데 이는 basePackages 를 value로 해도 상관 없다는 뜻이다.
@ComponentScan(basePackages = "com.fortice.spring")
@ComponentScan(value = "com.fortice.spring")
basePackageClasses
@ComponentScan(basePackageClasses = UserDao.class)
useDefaultFilters
defualt가 true인데, 이를 false로 바꾸면 기본적인 스캔 범위가 다 사라진다.
includeFilters
, excludeFilters
useDefaultFilters로 삭제한 뒤 다시 포함/제외시키고 싶은 빈 타입을 지정할 수 있다.
type
과 classes
or 'pattern'을 지정한다.
type
FilterType.ANNOTATION - 어노테이션 기준, classes
FilterType.ASPECTJ - AspectJ 패턴 기준, pattern (ex. "com.fortice.")
FilterType.ASSIGNABLE_TYPE - 클래스 기준, classes
FilterType.REGEX - 정규식 기준, pattern (ex. ".component.*")
FilterType.CUSTOM
@ComponentScan(
basePackages = "com",
useDefaultFilters = false,
includeFilters = {
@Filter(
type = FilterType.ANNOTATION,
classes = {Component.class, Repository.class, Service.class, Controller.class}
)
}