public class Main {
public static void main(String[] args) {
// (1) 스프링 컨테이너 생성
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfigurer.class);
// (2) 빈 조회
ProductRepository productRepository = applicationContext.getBean("productRepository", ProductRepository.class);
Menu menu = applicationContext.getBean("menu", Menu.class);
Cart cart = applicationContext.getBean("cart", Cart.class);
Order order = applicationContext.getBean("order", Order.class);
// (3) 의존성 주입
OrderApp orderApp = new OrderApp(
productRepository,
menu,
cart,
order
);
// (4) 프로그램 실행
orderApp.start();
}
}
ApplicationContext 인터페이스를 일반적으로 스프링 컨테이너라 부른다. 정확하게는 ApplicationContext가 상속하고 있는 BeanFactory와 구분해 사용하지만 BeanFactory를 직접 사용하는 경우는 거의 없기 때문에 ApplicationContext를 스프링 컨테이너라 한다. 그리고 ApplicationContext는 BeanFactory의 빈을 관리하고 조회하는 기능뿐만 아니라 웹 애플리케이션을 개발하는 데 필요한 다양한 부가 기능을 함께 제공한다.
다음으로 AnnotationConfigApplicationContext는 그 구현 객체로 AppConfigurer.class를 넘겨주고 스프링 컨테이너는 이렇게 넘겨받은 구성 정보를 가지고 호출되는 메서드의 이름을 기준으로 빈을 등록한다.
: 클래스의 인스턴스가 단 한번만 생성되게 만들어 객체의 참조값을 공유할 수 있도록 하는 것
public class AppConfigurer {
private Cart cart = new Cart(productRepository(), menu());
// 최초로 생성된 Cart 인스턴스를 private로 정의된 cart 변수에 할당해, 외부에서 추가적으로 인스턴스가 생성되는 것을 방지하고, cart() 메서드를 통해 항상 같은 인스턴스가 반환되도록 함
public Cart cart() {
return cart;
}
...
}
스프링 컨테이너는 내부적으로 객체들을 싱글톤으로 관리하며 싱글톤 컨테이너 역할을 수행한다. 그리고 이렇게 싱글톤으로 객체를 생성 및 관리하는 기능을 싱글톤 레지스트리라 부른다.
public class Main {
public static void main(String[] args) {
// 스프링 컨테이너는 크게 초기화와 종료라는 생명 주기를 가지고 있다.
// (1) 컨테이너 초기화
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(TestConfigurer.class);
// (2) 컨테이너 사용
Cart cart = applicationContext.getBean("cart", Cart.class);
...
// (3) 컨테이너 종료
applicationContext.close();
}
}
: AnnotationConfigApplicationContext를 통해 객체를 생성함과 동시에 스프링 컨테이너를 초기화한다. 이때 스프링 컨테이너는 구성 정보를 기반으로 빈 객체를 생성, 의존 관계를 연결한다.
: 초기화 작업이 완료된, 스프링 컨테이너의 관리하에 있는 빈 객체들을 getBean()을 통해 조회하여 사용할 수 있다.
: 컨테이너 사용이 모두 끝나면 close()를 통해 종료시킬 수 있다.
: 스프링 컨테이너의 생명 주기와 비슷하게 (1) 빈 객체 생성 -> (2) 의존 관계 주입 -> (3) 초기화 -> (4) 소멸 의 생명 주기를 가진다.
: AppConfigurer 클래스와 같은 외부 구성 정보에 @Bean을 통해 수동으로 빈을 생성하고 의존 관계를 설정하는 번거로움을 해결하기 위해 @ComponentScan을 통해 자동으로 빈을 등록하고, @Component와 @Autowired를 통해 자동으로 의존 관계를 설정할 수 있다.
: 의존 관계 자동 주입을 위한 @Autowired 애너테이션은 기본적으로 타입으로 빈을 조회한다. 그래서 해당 빈의 타입이 2개 이상인 경우 스프링은 어떤 구현 객체를 선택해야 할지 알 수 없어 오류가 발생한다.
AOP란, 애플리케이션 개발의 과정에서 여러 객체에 공통적으로 적용할 수 있는 공통 관심 사항과 핵심 로직에 관련된 핵심 관심 사항을 분리시키는 프로그래밍
: Spring Framework가 제공하는 AOP방식은 런타임 시에 프록시 객체를 생성해 공통 관심 기능을 적용하는 방식
public interface Gugudan {
// 추상 메서드 정의
void calculate(int level, int count);
}
public class GugudanProxy implements Gugudan {
private Gugudan delegator;
public GugudanProxy(Gugudan delegator) {
this.delegator = delegator;
}
@Override
public void calculate(int level, int count) {
long start = System.nanoTime();
delegator.calculate(2,1); // 핵심 기능
long end = System.nanoTime();
System.out.printf("클래스명: = %s\n", delegator.getClass().getSimpleName());
System.out.printf("실행 시간 = %d ms\n", (end - start));
System.out.println("-------------------------------");
}
}
GugudanProxy 클래스가 핵심 기능 로직을 가지고 있는 것이 아니라, 생성자로 받은 delegator 객체에게 핵심 기능을 위임한다. 그리고 시간 측정같은 공통 기능을 GugudanProxy 클래스에서 실행한다.
AOP란 공통 관심 사항과 핵심 관심 사항을 분리시켜 코드 중복을 제거하고, 코드 재사용성을 높이는 프로그래밍 방법론
AOP는 핵심 기능에 공통 기능을 삽입하는 것으로, 핵심 관심 사항 코드의 변경없이 공통 기능 구현을 추가 또는 변경 가능
개념 | 설명 |
---|---|
어드바이스(Advice) | 공통 관심 사항과 적용 시점을 정의 |
조인포인트(Joinpoint) | 어드바이스가 적용될 수 있는 위치 |
포인트컷(Pointcut) | 조인포인트의 부분 집합으로 공통 기능이 적용될 대상을 선정하는 방법 |
위빙(Weaving) | 어드바이스를 핵심 기능 코드에 적용하는 것 |
어드바이스 종류 | 설명 |
---|---|
@Before | 타깃 객체의 메서드 호출 전에 공통 기능 실행 |
@After | 예외 발생 여부에 관계없이 타깃 객체의 메서드 실행 후 공통 기능 실행 |
@AfterReturning | 타깃 객체의 메서드가 예외 없이 실행되어 값을 반환한 경우 공통 기능 실행 |
@AfterThrowing | 타깃 객체의 메서드 실행 중 예외가 발생한 경우 공통 기능 실행 |
@Around | 타깃 객체의 메서드 실행 전과 후 또는 예외 발생 시 공통 기능 실행 |
@Aspect
public class GugudanAspect {
@Pointcut("execution(public void cal*(..))")
private void targetMethod() {}
@Around("targetMethod()")
public Object measureTime(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.nanoTime();
try {
Object result = joinPoint.proceed();
return result;
} finally {
long end = System.nanoTime();
Signature signature = joinPoint.getSignature();
System.out.printf("%s.%s 메서드 호출!\n", joinPoint.getTarget().getClass().getSimpleName(), signatuer.getName());
System.out.printf("실행 시간: %d ns", end - start);
}
}
}
@Configuration
@EnableAspectJAutoProxy
public class GugudanConfig {
@Bean
public GugudanAspect gugudanAspect() {
return new GugudanAspect();
}
@Bean
public Gugudan gugudan() {
return new GugudanByForLoop();
}
}