- Spring AOP
- Spring Batch
- Spring Boot
- Spring Data
- Spring JDBC
- Spring MVC
- Spring Security
- Spring Transaction
Spring?
- 자바 언어 기반의 통합 프레임워크
- 객체 지향 앱을 개발
- di를 통해 다형성과 ocp, dip를 지킬 수 있도록 지원
- 클라이언트 코드의 변경 없이 기능을 확장
Container

- javabeans 객체의 life-cycle을 관리하고 의존성을 주입해주는 component
- ioc, di 컨테이너
- 컨테이너는 물리적인 개념이라기 보다 객체 관리의 개념 또는 시스템에 가까움
- 등록된 빈의 부모 타입으로 조회하면, 자식 타입도 함께 조회됨
- 별도 설정을 하지 않을 경우 컨테이너는 빈 객체를 싱글톤 범위로 관리
- 싱글톤을 보장하기 위해
CGLIB라이브러리를 사용해서 설정 클래스를 상속받는 프록시 객체를 생성한 후 등록
- 싱글톤을 보장하기 위해
BeanFactory
public interface BeanFactory {
String FACTORY_BEAN_PREFIX = "&";
Object getBean(String name) throws BeansException;
<T> T getBean(String name, Class<T> requiredType) throws BeansException;
Object getBean(String name, Object... args) throws BeansException;
<T> T getBean(Class<T> requiredType) throws BeansException;
<T> T getBean(Class<T> requiredType, Object... args) throws BeansException;
<T> ObjectProvider<T> getBeanProvider(Class<T> requiredType);
<T> ObjectProvider<T> getBeanProvider(ResolvableType requiredType);
boolean containsBean(String name);
boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException;
boolean isTypeMatch(String name, Class<?> typeToMatch) throws NoSuchBeanDefinitionException;
@Nullable
Class<?> getType(String name) throws NoSuchBeanDefinitionException;
@Nullable
Class<?> getType(String name, boolean allowFactoryBeanInit) throws NoSuchBeanDefinitionException;
String[] getAliases(String name);
}- 컨테이너의 최상위 인터페이스
- 빈 생성과 검색에 대한 기능을 정의
- 생성된 객체를 검색하는 데 필요한
getBean() - 빈 객체를 lazy-loading 개념에 따라 필요한 시점에 생성
- 생성된 객체를 검색하는 데 필요한
- transaction 관리, aop, 이벤트 처리 등의 고급 기능은 제공하지 않음
ApplicationContext

BeanFactory를 확장해 더 많은 기능을 제공하는 인터페이스- 이벤트 시스템, aop, transaction 관리, 국제화(i18n), 프로필/환경 변수 등
- 모든 빈 객체를 eager-loading 개념에 따라 미리 생성하여 앱 시작 시점에 로드
AnnotationConfigApplicationContext
public class AnnotationConfigApplicationContext extends GenericApplicationContext implements AnnotationConfigRegistry {
private final AnnotatedBeanDefinitionReader reader;
private final ClassPathBeanDefinitionScanner scanner;
// ...
}BeanFactory와ApplicationContext에 정의된 기능의 구현을 제공하는 클래스- 어노테이션을 이용한 클래스로부터 객체 설정 정보를 가져옴
Note
어떤 구현 클래스를 사용하든, 각 구현 클래스는 설정 정보로부터 빈이라고 불리는 객체를 생성하고 그 객체를 내부에 보관한다. 그리고
getBean()를 실행하면 해당하는 빈 객체를 제공한다.
GenericXmlApplicationContext

- 범용 xml 컨테이너로서 xml로부터 객체 설정 정보를 가져오는 클래스
- classpath 경로를 기준으로 xml 설정 파일을 찾는
ClassPathXmlApplicationContext와 전체 파일 시스템 경로를 기준으로 xml 설정 파일을 찾는FileSystemXmlApplicationContext역할을 모두 수행 가능
- classpath 경로를 기준으로 xml 설정 파일을 찾는
- 스프링 3부터 권장되는 방식이지만, 현재는 잘 사용되지 않음
BeanDefinition

- 빈의 메타 정보를 담고 있는 추상화 인터페이스

BeanDefinitionReader의 구현체에 의해BeanDefinition를 로드하고 등록- 최종적으로
BeanFactory가 이를 기반으로 빈을 생성
- 최종적으로
@Bean
@Configuration
public class AppConfig {
@Bean
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
@Bean
public MemberService memberService() {
return new MemberServiceImpl(memberRepository());
}
// ...
}- 설정 클래스 내부의
@Bean메서드가 반환하는 객체를 컨테이너가 관리하는 빈 객체로 직접 등록@Bean메서드의 이름으로 빈 객체를 식별name속성을 사용하여 빈 객체의 이름을 바꿀 수 있음
@Component
@Component // memberServiceImpl이라는 이름으로 빈 등록
public class MemberServiceImpl implements MemberService {
private final MemberRepository memberRepository; // DIP 만족
@Autowired
public MemberServiceImpl(MemberRepository memberRepository) {
super();
this.memberRepository = memberRepository;
// ...
}- 스프링이 제공하는 어노테이션을 사용하여, 컨테이너가 이를 자동으로 감지하고 빈으로 등록하도록 표시
- 빈의 기본 이름은 클래스명을 사용하되 맨 앞글자만 소문자를 사용
- 빈의 이름을 직접 지정할 수 있음
XML
<?xml version="1.0" encoding="UTF-8"?>
<beans
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="memberService" class="hello.core.member.MemberServiceImpl">
<constructor-arg name="memberRepository" ref="memberRepository" />
</bean>
<bean id="memberRepository" class="hello.core.member.MemoryMemberRepository" />
<bean id="orderService" class="hello.core.order.OrderServiceImpl">
<constructor-arg name="memberRepository" ref="memberRepository" />
<constructor-arg name="discountPolicy" ref="discountPolicy" />
</bean>
<bean id="discountPolicy" class="hello.core.discount.RateDiscountPolicy" />
</beans><constructor-arg>나<property>를 사용해서 초기화<context:component-scan>를 사용해서@ComponentScan를 대신할 수 있음
DI (Dependency Injection)
Constructor Injection
@Component
public class MemberServiceImpl implements MemberService {
private final MemberRepository memberRepository; // DIP 만족
@Autowired
public MemberServiceImpl(MemberRepository memberRepository) {
super();
this.memberRepository = memberRepository;
}@Autowired를 사용하여 의존 관계를 생성자를 통해 자동으로 주입받는 방식- 객체 변경의 유연성
- 빈 객체를 생성하는 단계에서 작동
- 생성자가 한 개만 존재한다면
@Autowired생략 가능
- 생성자 호출 시점에 딱 한 번만 호출되는 것을 보장
- 불변, 필수 관계에 주로 사용
public class MemberServiceImpl implements MemberService {
private final MemberRepository memberRepository; // DIP 만족
public MemberServiceImpl(MemberRepository memberRepository) {
super();
this.memberRepository = memberRepository;
}
}- 의존 관계를 생성자를 통해 직접 주입받을 수 있음
- 스프링 같은 di 프레임워크 없이도 순수한 객체지향 방식으로 구현 가능
Note
@Autowired를 사용할 때 동일한 타입의 빈이 여러 개 존재하면, 스프링 컨테이너는 의존 관계를 해결하기 위해@Qualifier,@Primary, 그리고 필드나 파라미터 이름 순으로 매칭을 시도한다.
Setter Injection
@Component
public class MemberServiceImpl implements MemberService {
private MemberRepository memberRepository; // DIP 만족
@Autowired
public setMemberRepository(MemberRepository memberRepository) {
super();
this.memberRepository = memberRepository;
}- setter를 통해 의존 관계를 주입하는 방식
- 선택적으로 의존 관계를 설정할 필요가 있는 경우에 사용
- 빈으로 등록되지 않은 객체 주입
Field Injection
@Component
public class MemberServiceImpl implements MemberService {
@Autowired
private MemberRepository memberRepository; // DIP 만족
}- 필드에 바로 주입하는 방식
- 외부에서 변경이 불가능하기 때문에 권장되지 않음
- 테스트 코드 작성 시 불리
Method Injection
@Component
public class MemberServiceImpl implements MemberService {
private MemberRepository memberRepository; // DIP 만족
@Autowired
public void init(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}- 일반 메서드를 통해서 주입 받는 방식
Note
@Autowired를 사용할 때@Autowired의required속성,org.springframework.lang.Nullable,java.util.Optional를 이용해서 옵션 처리를 할 수 있다. 주입할 빈이 존재하지 않을 때required속성을 사용하면 해당 메서드를 호출하지 않지만,@Nullable를 사용하면 주입할 빈이 존재하지 않더라도 해당 메서드를 호출하고NULL을 전달한다.
@Component
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Indexed
public @interface Component {
String value() default "";
}@ComponentScan의 감지 대상이 되어 빈으로 등록될 수 있도록 표시하는 클래스 레벨 어노테이션- 클래스 이름의 첫 글자를 소문자로 변환한 이름이 빈 이름이 됨
value속성을 지정하면 빈 이름을 명시적으로 설정할 수 있음
@Configuration
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
@AliasFor(annotation = Component.class)
String value() default "";
boolean proxyBeanMethods() default true;
boolean enforceUniqueMethods() default true;
}- 어노테이션 기반의 설정 클래스로 인식되도록 하는 어노테이션
CGLIB를 통해 프록시 객체로 생성되어 빈으로 등록됨@Bean메서드 간 호출 시에 싱글톤을 보장proxyBeanMethods속성 값을false로 설정할 경우 프록시 생성이 비활성화 됨
- 한 개 이상의 설정 클래스를 사용할 수 있음
@Controller
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Controller {
@AliasFor(annotation = Component.class)
String value() default "";
}- 스프링 mvc의 컨트롤러로 등록
@Service
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Service {
@AliasFor(annotation = Component.class)
String value() default "";
}- 서비스 계층 클래스를 컴포넌트로 등록
- 트랜잭션 처리, aop 적용 등 스프링의 부가 기능을 적용할 수 있는 대상임을 명시적으로 나타냄
@Repository
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Repository {
@AliasFor(annotation = Component.class)
String value() default "";
}- dao 계층을 컴포넌트로 등록
- jdbc의
Exception을 스프링의DataAccessException으로 자동 변환- 내부적으로
PersistenceExceptionTranslationPostProcessor가 처리
- 내부적으로
@ComponentScan
@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 {};
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class;
ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;
String resourcePattern() default ClassPathScanningCandidateComponentProvider.DEFAULT_RESOURCE_PATTERN;
boolean useDefaultFilters() default true;
Filter[] includeFilters() default {};
Filter[] excludeFilters() default {};
boolean lazyInit() default false;
@Retention(RetentionPolicy.RUNTIME)
@Target({})
@interface Filter {
FilterType type() default FilterType.ANNOTATION;
@AliasFor("classes")
Class<?>[] value() default {};
@AliasFor("value")
Class<?>[] classes() default {};
String[] pattern() default {};
}
}@Component가 붙은 클래스를 자동으로 탐색하여 빈으로 등록하는 어노테이션- 기본적으로
@ComponentScan이 선언된 클래스의 패키지를 기준으로 하위 패키지를 모두 탐색basePackages속성을 지정하면 특정 패키지를 탐색할 수 있음excludeFilters속성을 지정하면 탐색 대상에서 특정 클래스를 제외할 수 있음includeFilters속성을 지정하면@Component가 아닌 클래스도 빈으로 등록할 수 있음
- 기본적으로
- 수동 빈 등록과 충돌할 경우 수동 빈 등록이 우선됨
- 스프링 부트에서는 충돌 시
BeanDefinitionOverrideException발생
- 스프링 부트에서는 충돌 시
- 내부적으로
ClassPathScanningCandidateComponentProvider를 사용하여 클래스 경로를 탐색하고, 탐색된 클래스를BeanDefinition으로 변환하여 등록함 - xml 기반 설정에서
<context:component-scan>로 대체할 수 있음
Bean의 생명주기

- 빈 객체의 생성과 의존 관계 주입은 별도의 단계
- 생성자 주입 방식 제외
- 빈 객체를 생성하고, 의존 관계 주입이 다 끝난 다음에야 필요한 데이터를 사용할 수 있음
- 빈의 초기화 작업은 의존 관계 주입이 모두 완료된 후 호출해야 함
- 스프링은 모든 의존 관계가 주입되면 빈의 초기화 시점을 알리고, 컨테이너가 종료되기 직전에는 종료 시점을 알려주는 다양한 기능을 제공
InitializingBean, DisposableBean
public class NetworkClient implements InitializingBean, DisposableBean {
// ...
@Override
public void afterPropertiesSet() throws Exception {
connect();
call("초기화 연결 메시지");
}
@Override
public void destroy() throws Exception {
disconnect();
}
}- 의존 관계가 주입되고 빈이 소멸되기 전에 각각
InitializingBean,DisposableBean인터페이스의 콜백 메서드가 호출됨 - 스프링 전용 인터페이스이므로 잘 사용되지 않음
- 코드가 스프링 전용 인터페이스에 의존됨
- 초기화, 소멸 메서드의 이름을 변경할 수 없음
- 코드를 고칠 수 없는 외부 라이브러리에 적용할 수 없음
초기화, 소멸 메서드 지정
@Bean(initMethod = "init", destroyMethod = "close")
public NetworkClient networkClient() {
NetworkClient networkClient = new NetworkClient();
networkClient.setUrl("http://hello-spring.dev");
return networkClient;
}- 사용자가 정의한 초기화 및 소멸 메서드의 이름을
@Bean의initMethod와destroyMethod속성 값으로 지정- 메서드의 이름을 자유롭게 정의할 수 있으며, 스프링 빈이 스프링 코드에 의존하지 않음
- 코드가 아닌 설정 정보를 사용하기 때문에 외부 라이브러리에도 적용할 수 있음
@Bean의destroyMethod속성의 기본값은(inferred)로 등록되어 있어close,shutdown이라는 이름을 갖는 메서드를 자동으로 호출- 추론 기능을 사용하지 않을 땐 속성 값을 공백으로 지정하면 됨
@PostConstruct, @PreDestroy
@PostConstruct
public void init() {
connect();
call("초기화 연결 메시지");
}
@PreDestroy
public void close() {
disconnect();
}jakarta.annotation에서 제공- 스프링이 아닌 다른 컨테이너에서도 동작
- 외부 라이브러리에 적용할 수 없음
- 스프링에서 가장 권장하는 방식
Bean Scope
Singleton
class SingletonTest {
@Test
void singletoneBeanFind() {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SingletoneBean.class);
SingletoneBean singletonBean1 = ac.getBean(SingletoneBean.class);
SingletoneBean singletonBean2 = ac.getBean(SingletoneBean.class);
Assertions.assertThat(singletonBean1).isSameAs(singletonBean2);
ac.close();
}
@Scope("singleton")
static class SingletoneBean {
@PostConstruct
public void init() {
System.out.println("SingletonBean.init");
}
@PreDestroy
public void destroy() {
System.out.println("SingletonBean.destroy");
}
}
}- 스크링 컨테이너의 시작과 종료까지 유지되는 가장 넓은 범위의 스코프
- 빈을 조회할 때마다 스프링 컨테이너는 항상 같은 인스턴스를 반환
Prototype
class PrototypeTest {
@Test
void prototypeBeanFind() {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(PrototypeBean.class);
PrototypeBean prototypeBean1 = ac.getBean(PrototypeBean.class);
PrototypeBean prototypeBean2 = ac.getBean(PrototypeBean.class);
Assertions.assertThat(prototypeBean1).isNotSameAs(prototypeBean2);
ac.close();
}
@Scope("prototype")
static class PrototypeBean {
@PostConstruct
public void init() {
System.out.println("PrototypeBean.init");
}
@PreDestroy
public void destroy() {
System.out.println("PrototypeBean.destroy"); // 호출 안 됨
}
}
}- 빈의 생성, di, 초기화까지만 관여하고 더는 관리하지 않는 매우 좁은 범위의 스코프
- 종료 시점을 클라이언트에서 관리
- 빈을 조회할 때마다 스프링 컨테이너는 항상 새로운 인스턴스를 반환
- 싱글톤 객체에서 프로토타입 객체를 사용하면 싱글톤처럼 관리됨
Web
- 웹 환경에서만 동작
- 컨테이너가 해당 스코프의 종료 시점까지 관리
Request
@Component
@Scope(value = "request")
public class MyLogger {
// ...
}- 요청 당 하나씩 생성되고, 요청이 끝나는 시점에 소멸
- 각각의 요청마다 별도의 빈 인스턴스가 생성되고 관리됨
Session
HttpSession과 동일한 생명주기
Application
ServletContext와 동일한 생명주기
Web Socket
- 웹 소켓과 동일한 생명주기
지연 처리
ObjectProvider
@Controller
@RequiredArgsConstructor
public class LogDemoController {
// MyLogger는 request 스코프
private final ObjectProvider<MyLogger> myLoggerProvider;
@RequestMapping("log-demo")
@ResponseBody
public String logDemo(HttpServletRequest request) {
MyLogger myLogger = myLoggerProvider.getObject();
// ...
return "OK";
}
}- 지정한 빈을 컨테이너에서 대신 찾아주는 dl 서비스를 제공
- 스프링의
ObjectFactory를 상속받은 확장 인터페이스 - 조회하고자 하는 타입의 빈이 컨테이너에 등록돼 있으면,
ObjectProvider역시 빈으로 등록됨
JSR-303 Provider
@Controller
@RequiredArgsConstructor
public class LogDemoController {
// MyLogger는 request 스코프
private final Provider<MyLogger> myLoggerProvider;
@RequestMapping("log-demo")
@ResponseBody
public String logDemo(HttpServletRequest request) {
MyLogger myLogger = myLoggerProvider.get();
// ...
return "OK";
}
}- 별도의 라이브러리(
jakarta.inject.Provider)가 필요하지만 자바 표준이므로 스프링이 아닌 다른 컨테이너에서도 사용 가능
Proxy
@Component
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyLogger {
// ...
}
@Controller
@RequiredArgsConstructor
public class LogDemoController {
// MyLogger는 requset 스코프
private final MyLogger myLoggerr;
@RequestMapping("log-demo")
@ResponseBody
public String logDemo(HttpServletRequest request) {
// ...
return "OK";
}
}CGLIB라이브러리를 사용해서 프록시 객체를 생성하고 주입- 원본 객체에 대한 조회를 필요한 시점까지 지연해서 처리
- 프록시 객체는 원본 객체에 대한 위임 로직을 가지고 있으며, 싱글톤처럼 동작