**스프링의 주요 특징**
의존관계를 주입하는 기능으로, 객체를 직접 생성하지 않고 외부에서 생성한 후 주입시키는 방식
결합도가 낮아지고 유연성이 높아진다.
위에 설명한 스프링의 주요 특징을 IoC(Inversion of Control)이라고 한다.
public class MemberController {
private MemberService memberService = new MemberService();
}
public class MemberController{
private MemberService memberService;
public MemberController(MemberService memberService){
this.memberService = memberservice;
}
}
위 처럼 객체를 직접 생성하지 않고, 외부(스프링 컨테이너)에서 의존성을 가져오는 경우인데. 이렇게 의존성을 주입하는 것을 DI(Dependency Injection)이라고 한다. DI가 IoC의 일부라고 생각하면 된다.
그리고 의존성 주입은 반드시 Bean으로 등록된 객체만 가능하다.
스프링 컨테이너가 관리하는 객체들을 Bean이라고 한다. 자바 빈과 똑같다.
class Data {
private int age;
public void setAge(int age) {
this.age = age;
}
public int getAge() {
return age;
}
}
@Component
public class OrderServiceImpl implements OrderService{
...
}
이렇게 선언 하면 @SpringBootApplication이 선언된 main클래스에서 실행시
@ComponentScan이 동작되어 @ComponentScan이 선언된 클래스의 동일 및 하위 패키지에 선언된 @Component들을 모두 스프링 빈으로 등록해준다.
자세한 내용은 https://velog.io/@tjddnths0223/ComponentScan
@SpringBootApplication
public class Week01Application {
public static void main(String[] args) {
SpringApplication.run(Week01Application.class, args);
}
}
@Component대신 @Service, @Controller, @Repository를 선언했을 경우도 자동으로 빈으로 등록이 되는 것을 볼 수 있는데 그 이유는 위 어노테이션 안에 @Component가 선언되었기 때문이다.
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Service {
/**
* The value may indicate a suggestion for a logical component name,
* to be turned into a Spring bean in case of an autodetected component.
* @return the suggested component name, if any (or empty String otherwise)
*/
@AliasFor(annotation = Component.class)
String value() default "";
}
@Configuration
public class AppConfig {
@Bean
public JPAQueryFactory jpaQueryFactory() {
return new JPAQueryFactory(entityManager);
}
}
@Component
public class OrderServiceImpl implements OrderService {
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
@Autowired
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
}
참고 : @Autowired 의 기본 동작은 주입할 대상이 없으면 오류가 발생한다. 주입할 대상이 없어도 동작하게 하려면 @Autowired(required = false) 로 지정하면 된다.
참고:
@Component
public class OrderServiceImpl implements OrderService {
private MemberRepository memberRepository;
private DiscountPolicy discountPolicy;
@Autowired
public void setMemberRepository(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
@Autowired
public void setDiscountPolicy(DiscountPolicy discountPolicy) {
this.discountPolicy = discountPolicy;
}
}
public으로 열려있어서 잘못 건드리면 에러가 날 수 있다.
@Component
public class OrderServiceImpl implements OrderService {
@Autowired
private MemberRepository memberRepository;
@Autowired
private DiscountPolicy discountPolicy;
}
참고: 순수한 자바 테스트 코드에는 당연히 @Autowired가 동작하지 않는다. @SpringBootTest 처럼 스프링 컨테이너를 테스트에 통합한 경우에만 가능하다.
@Component
public class OrderServiceImpl implements OrderService {
private MemberRepository memberRepository;
private DiscountPolicy discountPolicy;
@Autowired
public void init(MemberRepository memberRepository, DiscountPolicy
discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
}
추가 구분자를 붙여주는 방법. (빈 이름을 변경하는 것이 아니다.)
@Component
@Qualifier("mainDiscountPolicy")
public class RateDiscountPolicy implements DiscountPolicy {}
@Component
@Qualifier("fixDiscountPolicy")
public class FixDiscountPolicy implements DiscountPolicy {}
@Autowired
public OrderServiceImpl(MemberRepository memberRepository,
@Qualifier("mainDiscountPolicy") DiscountPolicy
discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
만약 @Qualifier("mainDiscountPolicy")을 못찾으면 mainDiscountPolicy라는 스프링 빈을 추가로 찾는다. 하지만, @Qualifier는 찾는 용도로만 사용하는 것이 좋다.
@Component
@Primary
public class RateDiscountPolicy implements DiscountPolicy {}
@Component
public class FixDiscountPolicy implements DiscountPolicy {}
코드에서 자주 사용하는 스프링 빈에 @Primary를 선언하고, 특별한 곳에서 가끔 사용하는 곳은 @Qualifier를 선언해서 필요한 곳에 직접 지정해서 사용한다.
@Qulifier("") 이렇게 적으면 문자라서 컴파일시 타입 체크가 안되는데 이는 애노테이션을 직접 만들면 해결 할 수 있다.
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Qualifier("mainDiscountPolicy")
public @interface MainDiscountPolicy {
}
@MainDiscountPolicy
public class RateDiscountPolicy implements DiscountPolicy {}
@Autowired
public OrderServiceImpl(MemberRepository memberRepository,
@MainDiscountPolicy DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
애노테이션에는 상속이라는 개념이 없다. 이렇게 여러 애노테이션을 모아서 사용하는 기능은 스프링에서 지원하는 기능이다.
주의 @Autowired를 통한 DI는 스프링이 관리하는 객체에서만 동작한다.
스프링 빈으로 등록하지 않고 내가 직접 생성한 객체에서는 동작하지 않는다.
출처 : 김영한의 스프링 핵심 원리 - 기본편